blob: 5861786482b955b7918d4efd0bb9488726010060 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.beans.beaninfo;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyEditorSupport;
import java.beans.PropertyVetoException;
import java.beans.VetoableChangeListener;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
import javax.imageio.ImageIO;
import javax.swing.ButtonGroup;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.border.EtchedBorder;
import javax.swing.border.TitledBorder;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.queries.SourceForBinaryQuery;
import org.openide.DialogDescriptor;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.explorer.ExplorerManager;
import org.openide.explorer.propertysheet.ExPropertyEditor;
import org.openide.explorer.propertysheet.PropertyEnv;
import org.openide.explorer.view.BeanTreeView;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileStateInvalidException;
import org.openide.filesystems.FileUtil;
import org.openide.loaders.DataObject;
import org.openide.loaders.DataObjectNotFoundException;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.FilterNode;
import org.openide.nodes.Node;
import org.openide.util.Exceptions;
import org.openide.util.HelpCtx;
import org.openide.util.NbBundle;
/**
* PropertyEditor for Icons. Depends on existing DataObject for images.
* Images must be represented by some DataObject which returns itselv
* as cookie, and has image file as a primary file. File extensions
* for images is specified in isImage method.
*
* @author Jan Jancura
*/
final class BiIconEditor extends PropertyEditorSupport implements ExPropertyEditor {
private static final String BEAN_ICONEDITOR_HELP = "beans.icon"; // NOI18N
private FileObject sourceFileObject;
private PropertyEnv env;
/** Standard variable for localization. */
static java.util.ResourceBundle bundle = org.openide.util.NbBundle.getBundle(
BiIconEditor.class);
public static boolean isImage(String s) {
s = s.toLowerCase();
return s.endsWith(".jpg") || s.endsWith(".gif") || // NOI18N
s.endsWith(".jpeg") || s.endsWith(".jpe") || // NOI18N
s.equals("jpg") || s.equals("gif") || // NOI18N
s.equals("jpeg") || s.equals("jpe"); // NOI18N
}
// variables .................................................................................
//private Icon icon;
// init .......................................................................................
public BiIconEditor( FileObject sourceFileObject ) {
this.sourceFileObject = sourceFileObject;
}
// Special access methods......................................................................
/** @return the name of image's source - depending on the type it can be a URL, file name or
* resource path to the image on classpath */
public String getSourceName() {
if (getValue() instanceof BiImageIcon)
return getValue().getName();
else
return null;
}
@Override
public void setValue(Object value) {
BiImageIcon old = getValue();
if (old == value || old != null && old.equals(value)) {
return;
}
if (env != null) {
BiImageIcon newval = (BiImageIcon) value;
env.setState(newval != null && (newval.url == null || newval.getIcon() == null) ? PropertyEnv.STATE_INVALID : PropertyEnv.STATE_VALID);
}
super.setValue(value);
}
@Override
public BiImageIcon getValue() {
return (BiImageIcon) super.getValue();
}
/**
* @return The property value as a human editable string.
* <p> Returns null if the value can't be expressed as an editable string.
* <p> If a non-null value is returned, then the PropertyEditor should
* be prepared to parse that string back in setAsText().
*/
@Override
public String getAsText() {
Object val = getValue();
return String.valueOf(textFromIcon((BiImageIcon) val));
}
/**
* Set the property value by parsing a given String. May raise
* java.lang.IllegalArgumentException if either the String is
* badly formatted or if this kind of property can't be expressed
* as text.
* @param text The string to be parsed.
*/
@Override
public void setAsText(String string) throws IllegalArgumentException {
try {
BiImageIcon iconFromText = iconFromText(string);
if (iconFromText == null || iconFromText.url != null) {
setValue(iconFromText);
} else {
String msg = NbBundle.getMessage(IconPanel.class, "CTL_Icon_not_exists", string);
DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message(msg));
}
}
catch ( IllegalArgumentException e ) {
// User inserted incorrect path either report or
// do nothing
// For now choosing doing nothing
}
}
/** translates icon object to text representation; null in case of undefined icon */
String textFromIcon(BiImageIcon icon) {
return icon == null
? null
: icon.getName();
}
BiImageIcon iconFromText(String string) throws IllegalArgumentException {
BiImageIcon ii;
try {
if (string.length() == 0 || string.equals("null")) { // NOI18N
ii = null;
}
else {
URL res = resolveIconPath(string, sourceFileObject);
ii = new BiImageIcon(res, string);
}
} catch (IOException ex) {
ii = new BiImageIcon(null, string);
}
return ii;
}
/**
* translates resource path defined in {@link java.beans.BeanInfo}'s subclass
* that complies with {@link Class#getResource(java.lang.String) Class.getResource} format
* to format complying with {@link ClassPath#getResourceName(org.openide.filesystems.FileObject) ClassPath.getResourceName}
* @param resourcePath absolute path or path relative to package of BeanInfo's subclass
* @param beanInfo BeanInfo's subclass
* @return path as URL
* @throws FileStateInvalidException invalid FileObject
* @throws FileNotFoundException resource cannot be found
*/
private static URL resolveIconPath(String resourcePath, FileObject beanInfo)
throws FileStateInvalidException, FileNotFoundException {
ClassPath cp = ClassPath.getClassPath(beanInfo, ClassPath.SOURCE);
String path = resourcePath.charAt(0) != '/'
? '/' + cp.getResourceName(beanInfo.getParent()) + '/' + resourcePath
: resourcePath;
FileObject res = cp.findResource(path);
if (res != null && res.canRead() && res.isData()) {
return res.getURL();
} else {
throw new FileNotFoundException(path);
}
}
/**
* @return True if the class will honor the paintValue method.
*/
@Override
public boolean isPaintable() {
return false;
}
/**
* @return True if the propertyEditor can provide a custom editor.
*/
@Override
public boolean supportsCustomEditor() {
return true;
}
/**
* A PropertyEditor may choose to make available a full custom Component
* that edits its property value. It is the responsibility of the
* PropertyEditor to hook itself up to its editor Component itself and
* to report property value changes by firing a PropertyChange event.
* <P>
* The higher-level code that calls getCustomEditor may either embed
* the Component in some larger property sheet, or it may put it in
* its own individual dialog, or ...
*
* @return A java.awt.Component that will allow a human to directly
* edit the current property value. May be null if this is
* not supported.
*/
@Override
public java.awt.Component getCustomEditor() {
return new IconPanel(this, env);
}
public void attachEnv(PropertyEnv env) {
this.env = env;
BiImageIcon val = getValue();
if (val != null && (val.url == null || val.getIcon() == null)) {
env.setState(PropertyEnv.STATE_INVALID);
}
}
public static final class BiImageIcon {
private String name;
private URL url;
private Icon icon;
public BiImageIcon() {
}
BiImageIcon(URL url, String name) {
this.url = url;
this.name = name;
}
String getName() {
return name;
}
public Icon getIcon() {
if (icon == null) {
if (url == null) {
return icon;
}
try {
Image image = ImageIO.read(url);
if (image == null) {
return null;
}
icon = new ImageIcon(image);
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
}
return icon;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final BiImageIcon other = (BiImageIcon) obj;
if (this.name != other.name && (this.name == null || !this.name.equals(other.name))) {
return false;
}
return true;
}
}
private static final class IconPanel extends JPanel implements VetoableChangeListener {
JRadioButton rbClasspath, rbNoPicture;
JTextField tfName;
JButton bSelect;
JScrollPane spImage;
private final PropertyEnv env;
private BiImageIcon value;
private BiIconEditor editor;
IconPanel(BiIconEditor editor, PropertyEnv env) {
this.env = env;
this.editor = editor;
// visual components .............................................
JLabel lab;
setLayout(new BorderLayout(6, 6));
setBorder(new EmptyBorder(6, 6, 6, 6));
getAccessibleContext().setAccessibleName(bundle.getString("ACS_IconPanelA11yName")); // NOI18N
getAccessibleContext().setAccessibleDescription(bundle.getString("ACS_IconPanelA11yDesc")); // NOI18N
JPanel p = new JPanel(new BorderLayout(3, 3));
JPanel p1 = new JPanel(new BorderLayout());
p1.setBorder(new TitledBorder(new EtchedBorder(), bundle.getString("CTL_ImageSourceType")));
JPanel p2 = new JPanel();
p2.setBorder(new EmptyBorder(0, 3, 0, 3));
GridBagLayout l = new GridBagLayout();
GridBagConstraints c = new GridBagConstraints();
p2.setLayout(l);
c.anchor = GridBagConstraints.WEST;
p2.add(rbClasspath = new JRadioButton(bundle.getString("CTL_Classpath")));
rbClasspath.setToolTipText(bundle.getString("ACS_ClasspathA11yDesc"));
rbClasspath.setMnemonic(bundle.getString("CTL_Classpath_Mnemonic").charAt(0));
c.gridwidth = 1;
l.setConstraints(rbClasspath, c);
p2.add(lab = new JLabel(bundle.getString("CTL_ClasspathExample")));
lab.getAccessibleContext().setAccessibleDescription(bundle.getString("ACS_ClasspathExampleA11yDesc"));
c.gridwidth = GridBagConstraints.REMAINDER;
l.setConstraints(lab, c);
p2.add(rbNoPicture = new JRadioButton(bundle.getString("CTL_NoPicture")));
rbNoPicture.setToolTipText(bundle.getString("ACS_NoPictureA11yDesc"));
rbNoPicture.setMnemonic(bundle.getString("CTL_NoPicture_Mnemonic").charAt(0));
c.gridwidth = 1;
l.setConstraints(rbNoPicture, c);
p2.add(lab = new JLabel(bundle.getString("CTL_Null")));
lab.getAccessibleContext().setAccessibleDescription(bundle.getString("ACS_NullA11yDesc"));
c.gridwidth = GridBagConstraints.REMAINDER;
l.setConstraints(lab, c);
ButtonGroup bg = new ButtonGroup();
bg.add(rbClasspath);
bg.add(rbNoPicture);
rbClasspath.setSelected(true);
p1.add(p2, "West"); // NOI18N
p.add(p1, "North"); // NOI18N
p1 = new JPanel(new BorderLayout(6, 6));
JLabel nameLabel = new JLabel(bundle.getString("CTL_ImageSourceName"));
nameLabel.getAccessibleContext().setAccessibleDescription(bundle.getString("ACS_ImageSourceNameA11yDesc"));
nameLabel.setDisplayedMnemonic(bundle.getString("CTL_ImageSourceName_Mnemonic").charAt(0));
p1.add(nameLabel, "West"); // NOI18N
p1.add(tfName = new JTextField(), "Center"); // NOI18N
nameLabel.setLabelFor(tfName);
tfName.getAccessibleContext().setAccessibleName(bundle.getString("ACS_ImageSourceNameTextFieldA11yName"));
tfName.setToolTipText(bundle.getString("ACS_ImageSourceNameTextFieldA11yDesc"));
p1.add(bSelect = new JButton("..."), "East"); // NOI18N
bSelect.getAccessibleContext().setAccessibleName(bundle.getString("ACS_ImageSourceNameBrowseButtonA11yName"));
bSelect.setToolTipText(bundle.getString("ACS_ImageSourceNameBrowseButtonA11yDesc"));
bSelect.setEnabled(false);
p.add(p1, "South"); // NOI18N
add(p, "North"); // NOI18N
spImage = new JScrollPane() {
@Override
public Dimension getPreferredSize() {
return new Dimension(60, 60);
}
};
add(spImage, "Center"); // NOI18N
// listeners .................................................
tfName.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
setValue();
}
});
rbClasspath.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
bSelect.setEnabled(true);
tfName.setEnabled(true);
setValue();
}
});
rbNoPicture.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
bSelect.setEnabled(false);
tfName.setEnabled(false);
setValue(null);
updateIcon();
}
});
bSelect.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (rbClasspath.isSelected()) {
String name = selectResource();
if (name != null) {
tfName.setText("/" + name); // NOI18N
setValue();
}
}
}
});
// initialization ......................................
env.setState(PropertyEnv.STATE_NEEDS_VALIDATION);
env.addVetoableChangeListener(this);
setValue(editor.getValue());
updateIcon();
HelpCtx.setHelpIDString(this, BEAN_ICONEDITOR_HELP);
BiImageIcon i = getValue();
if (i == null) {
rbNoPicture.setSelected(true);
bSelect.setEnabled(false);
tfName.setEnabled(false);
return;
}
rbClasspath.setSelected(true);
bSelect.setEnabled(true);
tfName.setText((i).getName());
}
void updateIcon() {
BiImageIcon bii = getValue();
Icon i = bii == null? null: bii.getIcon();
spImage.setViewportView((i == null) ? new JLabel() : new JLabel(i));
// repaint();
validate();
}
void setValue() {
String val = tfName.getText();
val.trim();
if ("".equals(val)) { // NOI18N
setValue(null);
return;
}
try {
setValue(editor.iconFromText(val));
} catch (IllegalArgumentException ee) {
// Reporting the exception is maybe too much let's do nothing
// instead
// org.openide.ErrorManager.getDefault().notify(org.openide.ErrorManager.INFORMATIONAL, ee);
}
updateIcon();
}
private void setValue(BiImageIcon icon) {
this.value = icon;
}
private BiImageIcon getValue() {
return this.value;
}
private Object getPropertyValue(PropertyChangeEvent evt) throws PropertyVetoException {
BiImageIcon ii = null;
String s = tfName.getText().trim();
if (rbClasspath.isSelected() && s.length() != 0) {
try{
URL res = resolveIconPath(s, editor.sourceFileObject);
ii = new BiImageIcon(res, s);
} catch (FileStateInvalidException ex) {
throw new PropertyVetoException(
NbBundle.getMessage(IconPanel.class, "CTL_Icon_not_exists", ex.getFileSystemName()), //NOI18N
evt);
} catch (FileNotFoundException ex) {
throw new PropertyVetoException(
NbBundle.getMessage(IconPanel.class, "CTL_Icon_not_exists", ex.getMessage()), //NOI18N
evt);
}
}
return ii;
}
public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
if (PropertyEnv.PROP_STATE == evt.getPropertyName()) {
BiImageIcon ii = (BiImageIcon) getPropertyValue(evt);
editor.setValue(ii);
}
}
private List<FileObject> getRoots(ClassPath cp) {
List<FileObject> list = new ArrayList<FileObject>(cp.entries().size());
for (ClassPath.Entry e : cp.entries()) {
// try to map it to sources
URL url = e.getURL();
SourceForBinaryQuery.Result r = SourceForBinaryQuery.findSourceRoots(url);
FileObject [] fos = r.getRoots();
if (fos.length > 0) {
for (int i = 0 ; i < fos.length; i++) list.add(fos[i]);
} else {
if (e.getRoot()!=null)
list.add(e.getRoot()); // add the class-path location directly
}
}
return list;
}
private String rootDisplayName(FileObject fo) {
return FileUtil.getFileDisplayName(fo);
}
/**
* Obtains icon resource from the user.
*
* @returns name of the selected resource or <code>null</code>.
*/
private String selectResource() {
ClassPath executionClassPath = ClassPath.getClassPath(editor.sourceFileObject, ClassPath.EXECUTE);
List<FileObject> roots = (executionClassPath == null)
? Collections.<FileObject>emptyList()
: getRoots(executionClassPath);
Node nodes[] = new Node[roots.size()];
int selRoot = -1;
try {
ListIterator<FileObject> iter = roots.listIterator();
while (iter.hasNext()) {
FileObject root = iter.next();
DataObject dob = DataObject.find(root);
final String displayName = rootDisplayName(root);
nodes[iter.previousIndex()] = new RootNode(dob.getNodeDelegate(), displayName);
}
} catch (DataObjectNotFoundException donfex) {
Exceptions.printStackTrace(donfex);
return null;
}
Children children = new Children.Array();
children.add(nodes);
final AbstractNode root = new AbstractNode(children);
root.setIconBaseWithExtension("org/netbeans/modules/beans/resources/iconResourceRoot.gif"); // NOI18N
root.setDisplayName(bundle.getString("CTL_ClassPathName")); // NOI18N
ResourceSelector selector = new ResourceSelector(root);
DialogDescriptor dd = new DialogDescriptor(selector, bundle.getString("CTL_OpenDialogName")); // NOI18N
Object res = DialogDisplayer.getDefault().notify(dd);
nodes = (res == DialogDescriptor.OK_OPTION) ? selector.getNodes() : null;
String name = null;
if ((nodes != null) && (nodes.length == 1)) {
DataObject dob = nodes[0].getCookie(DataObject.class);
if (dob != null) {
FileObject fob = dob.getPrimaryFile();
if (fob != null) {
if (executionClassPath.contains(fob)) {
name = executionClassPath.getResourceName(fob);
} else {
ClassPath srcClassPath = ClassPath.getClassPath(fob, ClassPath.SOURCE);
name = srcClassPath.getResourceName(fob);
}
}
}
}
return name;
}
} // end of IconPanel
private static final class RootNode extends FilterNode {
RootNode(Node node, String displayName) {
super(node);
if (displayName != null) {
disableDelegation(DELEGATE_GET_DISPLAY_NAME | DELEGATE_SET_DISPLAY_NAME);
setDisplayName(displayName);
}
}
} // RootNode
private static final class ResourceSelector extends JPanel implements ExplorerManager.Provider {
/** Manages the tree. */
private ExplorerManager manager = new ExplorerManager();
public ResourceSelector(Node root) {
setLayout(new BorderLayout(0, 5));
setBorder(new EmptyBorder(12, 12, 0, 11));
getAccessibleContext().setAccessibleDescription(bundle.getString("ACSD_ResourceSelector")); // NOI18N
getAccessibleContext().setAccessibleName(bundle.getString("ACSN_ResourceSelector")); // NOI18N
manager.setRootContext(root);
BeanTreeView tree = new BeanTreeView();
tree.setPopupAllowed(false);
tree.setDefaultActionAllowed(false);
// install proper border for tree
tree.setBorder((Border)UIManager.get("Nb.ScrollPane.border")); // NOI18N
tree.getAccessibleContext().setAccessibleName(bundle.getString("ACSN_ResourceSelectorView")); // NOI18N
tree.getAccessibleContext().setAccessibleDescription(bundle.getString("ACSD_ResourceSelectorView")); // NOI18N
add(tree, BorderLayout.CENTER);
}
/**
* Gets preferred size. Overrides superclass method.
* Height is adjusted to 1/2 screen.
*/
@Override
public Dimension getPreferredSize() {
Dimension dim = super.getPreferredSize();
dim.height = Math.max(dim.height, org.openide.util.Utilities.getUsableScreenBounds().height / 2);
return dim;
}
/**
* @return selected nodes
*/
public Node[] getNodes() {
return manager.getSelectedNodes();
}
public ExplorerManager getExplorerManager() {
return manager;
}
} // ResourceSelector
}