/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun * Microsystems, Inc. All Rights Reserved. */ package org.openide.explorer.propertysheet; import java.awt.AWTEvent; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Cursor; import java.awt.Font; import java.awt.Toolkit; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.AWTEventListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.beans.FeatureDescriptor; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.beans.PropertyEditor; import java.beans.PropertyEditorManager; import java.beans.PropertyVetoException; import java.beans.VetoableChangeListener; import java.lang.reflect.InvocationTargetException; import java.util.WeakHashMap; import javax.swing.DefaultComboBoxModel; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.SwingUtilities; import javax.swing.UIManager; import org.openide.ErrorManager; import org.openide.explorer.propertysheet.editors.EnhancedCustomPropertyEditor; import org.openide.explorer.propertysheet.editors.EnhancedPropertyEditor; import org.openide.nodes.Node; import org.openide.util.Mutex; import org.openide.util.NbBundle; import org.openide.util.WeakListener; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import javax.swing.text.Document; /** Visual Java Bean for editing of properties. It takes the model * and represents the property editor for it. * * @author Jaroslav Tulach, Petr Hamernik, Jan Jancura, David Strupl */ public class PropertyPanel extends JComponent implements javax.accessibility.Accessible { /** * Constant defining preferences in displaying of value. * Value should be displayed in read-only mode. */ public static final int PREF_READ_ONLY = 0x0001; /** * Constant defining preferences in displaying of value. * Value should be displayed in custom editor. */ public static final int PREF_CUSTOM_EDITOR = 0x0002; /** * Constant defining preferences in displaying of value. * Value should be displayed in editor only. */ public static final int PREF_INPUT_STATE = 0x0004; /** Name of the 'preferences' property. */ public static final String PROP_PREFERENCES = "preferences"; // NOI18N /** Name of the 'model' property. */ public static final String PROP_MODEL = "model"; // NOI18N /** Name of the read-only property 'propertyEditor'. */ public static final String PROP_PROPERTY_EDITOR = "propertyEditor"; // NOI18N /** Name of property 'state' that describes the state of the embeded PropertyEditor. * @since 2.20 */ public static final String PROP_STATE = PropertyEnv.PROP_STATE; /** Name of 'canEditAsText' property. */ private static final String PROP_CAN_EDIT_AS_TEXT = "canEditAsText"; // NOI18N /** Static instance of empty PropertyModel. */ private static final PropertyModel EMPTY_MODEL = new EmptyModel(); /** Holds value of property preferences. */ private int preferences; /** Holds value of property model. */ private PropertyModel model; /** Current property editor. */ private PropertyEditor editor; /** Environment passed to the property editor. */ private PropertyEnv env; /** Descriptor for the property. */ private FeatureDescriptor descriptor = new FeatureDescriptor(); /** Holds painting style. */ private int paintingStyle; /** Foreground color of values. */ private Color foregroundColor; /** Foreground color of disabled values. */ private Color disabledColor; /** Is plastic property value. */ private boolean plastic; /** */ static ThreadLocal current = new ThreadLocal(); /** Indicates whether the property is writable. */ private boolean canWrite = true; /** In the (rare) case when the property is not readable value * of this variable is set to false. */ private boolean canRead = true; // private variables for visual controls ........................................... /** Component for showing property value is stored here. */ private SheetButton readComponent; /** Component cache. */ private SheetButton textView; /** Component cache. */ private PropertyShow propertyShow; /** Component cache. */ private SheetButton paintView; /** Component cache. */ private SheetButton customizeButton; /** If this is true the read component is visible and we are not * performing any component switch. */ private boolean isReadState; /** If this is true the write component is visible and we are not * performing any component switch. */ private boolean isWriteState; /** Prevents firing back to ourselves. */ private boolean ignoreEvents; /** Set to <code>true</code> when the custom dialog is showing. */ private boolean customDialogShown; /** <code>True</code> if we represent more values in this panel. */ private boolean differentValues; /** TextField used for editing the property value as text. */ private JTextField textField; /** Combo used for editing the property value as tags. */ private JComboBox comboBox; /** Listener capable of switching to the writing state. */ private ReadComponentListener readComponentListener; /** Listener on textField and comboBox - it allows * to switch back to the reading state. */ private WriteComponentListener writeComponentListener; /** Listens on changes in the model. */ private PropertyChangeListener modelListener; /** Listens on changes in the editor. */ private PropertyChangeListener editorListener; /** Weak wrapper for the <code>editorListener</code>. */ private PropertyChangeListener weakEditorListener; /** If this is not <code>null</code> the listener is added to * all newly created Sheetbuttons. */ private SheetButtonListener sheetButtonListener; /** * A listener weakly attached to the env to capture * updates when the env changes. The reference is stored * here in order for the listener not to be garbage collected * before PropertyPanel. */ private VetoableChangeListener envListener; /** * Maps ExPropertyEditor --> Reference<PropertyEnv>. * We remember instances of ExPropertyEditor to be * able to pass the same env to the same instance. * If we would not cache them we could end up in * situation where env from second PropertyPanel * hides the instance from a first panel. */ private static final WeakHashMap envCache = new WeakHashMap(); /** * If this is <code>true</code> the changes made in the property editor * are immediately propagated to the value of the property * (to the property model). */ private boolean changeImmediate = true; // constructors ------------------------------------------------------- /** Creates new PropertyPanel with the empty DefaultPropertyModel */ public PropertyPanel () { this (EMPTY_MODEL, 0); } /** Creates new PropertyPanel with DefaultPropertyModel * @param bean The instance of bean * @param propertyName The name of the property to be displayed */ public PropertyPanel ( Object bean, String propertyName, int preferences ) { this ( new DefaultPropertyModel (bean, propertyName), preferences ); } /** Creates new PropertyPanel * @param model The model for displaying */ public PropertyPanel ( PropertyModel model, int preferences ) { this.model = model; this.preferences = preferences; setLayout (new BorderLayout ()); boolean problem = false; try { Class c = Class.forName("org.openide.explorer.propertysheet.PropertyPanel$PropertySheetSettingsInvoker"); // NOI18N Runnable r = (Runnable)c.newInstance(); current.set(this); r.run(); } catch (Exception e) { problem = true; } catch (LinkageError e) { problem = true; } if (problem) { // set defaults without P ropertySheetSettings paintingStyle = PropertySheet.PAINTING_PREFERRED; plastic = false; disabledColor = UIManager.getColor("textInactiveText"); foregroundColor = new Color (0, 0, 128); } model.addPropertyChangeListener (getModelListener()); updateEditor (); reset(); } /** Uses an instance of SimpleModel as model.*/ PropertyPanel(Node.Property p, Object []beans) { this(new SimpleModel(p, beans), 0); } /** Reference to PropertySheetSettings are separated here.*/ static class PropertySheetSettingsInvoker implements Runnable { public void run() { PropertyPanel instance = (PropertyPanel)current.get(); current.set(null); if (instance == null) { throw new IllegalStateException(); } PropertySheetSettings pss = PropertySheetSettings.getDefault(); instance.paintingStyle = pss.getPropertyPaintingStyle(); instance.plastic = pss.getPlastic(); instance.disabledColor = pss.getDisabledPropertyColor(); instance.foregroundColor = pss.getValueColor(); } } // public methods ------------------------------------------------------- /** Getter for property preferences. * @return Value of property preferences. */ public int getPreferences () { return preferences; } /** Setter for visual preferences in displaying * of the value of the property. * @param preferences PREF_XXXX constants */ public void setPreferences (int preferences) { int oldPreferences = this.preferences; this.preferences = preferences; readComponent = null; reset(); firePropertyChange( PROP_PREFERENCES, new Integer (oldPreferences), new Integer (preferences) ); } /** Getter for property model. * @return Value of property model. */ public PropertyModel getModel() { return model; } /** Setter for property model. *@param model New value of property model. */ public void setModel(PropertyModel model) { PropertyModel oldModel = this.model; this.model = model; oldModel.removePropertyChangeListener(getModelListener()); model.addPropertyChangeListener(getModelListener()); updateEditor(); reset(); firePropertyChange (PROP_MODEL, oldModel, model); } /** Getter for the state of the property editor. The editor can be in * not valid states just if it implements the <link>ExPropertyEditor</link> * and changes state by the <code>setState</code> method of the <link>PropertyEnv</link> * environment. * <P> * @return <code>PropertyEnv.STATE_VALID</code> if the editor is not the <code>ExPropertyEditor</code> * one or other constant from <code>PropertyEnv.STATE_*</code> that was assigned to <code>PropertyEnv</code> * @since 2.20 */ public final Object getState () { PropertyEnv e = env; return e == null ? PropertyEnv.STATE_VALID : e.getState(); } /** If the editor is <link>ExPropertyEditor</link> it tries to change the * <code>getState</code> property to <code>PropertyEnv.STATE_VALID</code> * state. This may be vetoed, in such case a warning is presented to the user * and the <code>getState</code> will still return the original value * (different from STATE_VALID). * <P> * Also updates the value if * <code>org.openide.explorer.propertysheet.editors.EnhancedCustomPropertyEditor</code> * is used. */ public void updateValue() { if (editor == null) return; PropertyEnv e = env; if (e != null && e.getState () == PropertyEnv.STATE_NEEDS_VALIDATION) { e.setState (PropertyEnv.STATE_VALID); } if (editor.supportsCustomEditor()) { Component customEditor = editor.getCustomEditor(); if (customEditor instanceof EnhancedCustomPropertyEditor) { try { Object value = ((EnhancedCustomPropertyEditor)customEditor).getPropertyValue(); editor.setValue(value); } catch (IllegalStateException ise) { PropertyDialogManager.notify(ise); } } } } /** * Getter for current property editor depending on the model. * It could be <CODE>null</CODE> if there is not possible * to obtain property editor for the current model. * * @return the property editor or <CODE>null</CODE> */ public PropertyEditor getPropertyEditor() { return editor; } // bugfix# 10171 added setEnabled() and setComponentEnabled() methods /** Sets whether or not this component is enabled. * * all panel components gets disabled when enabled parameter is set false * @param enabled flag defining the action. */ public void setEnabled(boolean enabled) { Component[] comp = getComponents(); if(comp!=null){ for(int i=0; i<comp.length; i++){ setComponentEnabled(comp[i], enabled); } } super.setEnabled(enabled); } /** Getter for property changeImmediate. * IF this is true the changes made in the property editor * are immediately propagated to the value of the property * (to the property model). * * @return Value of property changeImmediate. */ public boolean isChangeImmediate() { return changeImmediate; } /** Setter for property changeImmediate. * IF this is true the changes made in the property editor * are immediately propagated to the value of the property * (to the property model). * @param changeImmediate New value of property changeImmediate. */ public void setChangeImmediate(boolean changeImmediate) { if (this.changeImmediate == changeImmediate) { return; } this.changeImmediate = changeImmediate; if (env != null) { env.setChangeImmediate(changeImmediate); } firePropertyChange(PropertyEnv.PROP_CHANGE_IMMEDIATE, changeImmediate?Boolean.FALSE:Boolean.TRUE, changeImmediate?Boolean.TRUE:Boolean.FALSE); } // package private methods ---------------------------------------------- /** Return a shett button serving as readComponent if it has been initailize.*/ SheetButton getReadComponent() { return readComponent; } /** * Switches from reading component to writing one. */ void setWriteState () { if (isWriteState) { return; } if ((preferences & PREF_READ_ONLY) != 0) { return; } if ((preferences & PREF_CUSTOM_EDITOR) != 0) { return; } isReadState = false; isWriteState = false; removeAll (); JComponent c = getWriterComponent (); c.setToolTipText(getPanelToolTipText()); add (c, BorderLayout.CENTER); updateNeighbourPanels(); revalidate(); repaint (); Component focused = getDefaultFocusComponent(c); if(focused != null) { WriteComponentListener l = getWriteComponentListener(); focused.requestFocus(); focused.removeFocusListener(l); focused.removeKeyListener(l); focused.addFocusListener(l); focused.addKeyListener(l); } isReadState = false; isWriteState = true; } /** Gets default focus component from the JComponent container hierarchy, * i.e. component which calling on container requestDefaultComponent * should get the focus - it differs from SwingUtilities.findFocusOwner. * @return <code>Component</code> which should get the focus as default * or <code>null</code> if there is no such one. */ private static Component getDefaultFocusComponent(JComponent container) { Component[] ca = container.getComponents(); for(int i = 0; i < ca.length; i++) { if(ca[i].isFocusTraversable()) { return ca[i]; } if(ca[i] instanceof JComponent && !((JComponent)ca[i]).isManagingFocus()) { Component res = getDefaultFocusComponent((JComponent)ca[i]); if(res != null) { return res; } } } return null; } /** * Set whether buttons in sheet should be plastic. * @param plastic true if so */ void setPlastic (boolean plastic) { this.plastic = plastic; reset(); } /** * Test whether buttons in sheet are plastic. * @return <code>true</code> if so */ boolean getPlastic () { return plastic; } /** * Set the foreground color of values. * @param color the new color */ void setForegroundColor (Color color) { this.foregroundColor = color; reset(); } /** * Set the foreground color of disabled properties. * @param color the new color */ void setDisabledColor (Color color) { disabledColor = color; reset(); } /** Sets painting style. */ void setPaintingStyle(int style) { paintingStyle = style; reset(); } /** Adds sheet button listener to the <code>readComponent</code>. */ void addSheetButtonListener(SheetButtonListener list) { this.sheetButtonListener = list; if (readComponent != null) { readComponent.addSheetButtonListener(list); } } /** Getter for <code>isWriteState</code> property. */ boolean isWriteState() { return isWriteState; } // private methods ------------------------------------------------------- /** Updates all neighbour panels. */ private void updateNeighbourPanels() { Component c = getParent(); while ((c != null) && (!(c instanceof NamesPanel))) { c = c.getParent(); } if (c instanceof NamesPanel) { NamesPanel np = (NamesPanel)c; np.reset(); } } /** * Update the current property editor depending on the model. */ private void updateEditor() { if (model == EMPTY_MODEL) { return; } PropertyEditor oldEditor = editor; if (editor != null) { editor.removePropertyChangeListener(getEditorListener()); } // find new editor editor = null; if (env != null) { env.removePropertyChangeListener(getEditorListener ()); } env = null; if (model instanceof ExPropertyModel) { descriptor = ((ExPropertyModel)model).getFeatureDescriptor(); if (descriptor instanceof Node.Property) { canWrite = ((Node.Property)descriptor).canWrite(); canRead = ((Node.Property)descriptor).canRead(); editor = ((Node.Property)descriptor).getPropertyEditor(); if ((editor == null) && (descriptor instanceof Node.IndexedProperty)) { editor = new IndexedPropertyEditor(); // indexed property editor does not want to fire immediately descriptor.setValue(PropertyEnv.PROP_CHANGE_IMMEDIATE, Boolean.FALSE); } } } // -------------------------------- if (editor == null) { Class editorClass = model.getPropertyEditorClass(); if (editorClass != null) { try { java.lang.reflect.Constructor c = editorClass.getConstructor(new Class[0]); c.setAccessible (true); editor = (PropertyEditor) c.newInstance(new Object[0]); } catch (Exception e) { PropertyDialogManager.notify(e); } } } if (editor == null) { Class propertyTypeClass = model.getPropertyType(); if (propertyTypeClass != null) { editor = PropertyEditorManager.findEditor(propertyTypeClass); } } if (editor != null) { if (editor instanceof ExPropertyEditor) { Reference ref = (Reference)envCache.get(editor); if (ref != null) env = (PropertyEnv)ref.get(); if (env == null) { env = new PropertyEnv(); envCache.put(editor, new WeakReference(env)); } setChangeImmediate(true); if (model instanceof ExPropertyModel) { ExPropertyModel epm = (ExPropertyModel) model; env.setFeatureDescriptor(epm.getFeatureDescriptor()); env.setBeans(epm.getBeans()); } envListener = new VetoableChangeListener() { public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException { refresh(); } }; env.addVetoableChangeListener( WeakListener.vetoableChange(envListener, env)); env.addPropertyChangeListener (getEditorListener ()); ((ExPropertyEditor)editor).attachEnv(env); } try { if (canRead) { editor.setValue(model.getValue()); } } catch (ProxyNode.DifferentValuesException dve) { differentValues = true; } catch (Exception e) { processThrowable(e); } if (canWrite) { editor.addPropertyChangeListener(getEditorListener()); } } // fire the change firePropertyChange(PROP_PROPERTY_EDITOR, oldEditor, editor); } /** Set the panel to the read state with fresh * read component. */ private void reset() { isWriteState = false; isReadState = false; if ((preferences & PREF_INPUT_STATE) != 0) { setWriteState(); return; } if (((preferences & PREF_CUSTOM_EDITOR) != 0) && (editor != null)){ Component c = editor.getCustomEditor(); if ((c != null) && (c.getParent() != this)) { removeAll(); add (c, BorderLayout.CENTER); validate(); return; } } setReadState(); } /** Refreshes the view after the property has changed. */ void refresh() { if (customDialogShown) { // if custom dialog is currently shown the // refresh will happen immediately after it is closed return; } if (isReadState) { // [PENDING] anything more reasonable than reset here? } if(isWriteState && ((preferences & PREF_INPUT_STATE) == 0) ) { // When leaving 'write state' setReadState will be called directly, // see listeners at the bottom. return; } reset(); } /** * Switches from writing component to reading one. */ void setReadState () { if (isReadState) { return; } if ((preferences & PREF_INPUT_STATE) != 0) { return; } if ((preferences & PREF_CUSTOM_EDITOR) != 0) { return; } isWriteState = false; isReadState = false; removeAll (); readComponent = getReaderComponent(); readComponent.addSheetButtonListener(getReadComponentListener()); // bugfix# 10171 setComponentEnabled() call before add()... setComponentEnabled(readComponent, isEnabled()); readComponent.setToolTipText(getPanelToolTipText()); updateSheetButtonVisually(); add (readComponent, BorderLayout.CENTER); revalidate(); repaint (); // Bug fix #13933 needles requesting of focus for cases when only refresh // needed. In case of problems some better solution is needed. // Sure is only the thing, focus may not be requested when coming from refresh(). //requestFocus(); isReadState = true; isWriteState = false; } /** Lazy init of the <code>readComponentListener</code>. */ private ReadComponentListener getReadComponentListener() { if (readComponentListener == null) { readComponentListener = new ReadComponentListener(); } return readComponentListener; } /** Lazy init of the <code>writeComponentListener</code>. */ private WriteComponentListener getWriteComponentListener() { if (writeComponentListener == null) { writeComponentListener = new WriteComponentListener(); } return writeComponentListener; } /** Lazy init of the <code>modelListener</code>. */ private PropertyChangeListener getModelListener() { if (modelListener == null) { modelListener = new ModelListener(); } return modelListener; } /** Lazy init of the <code>weakEditorListener</code>. */ private PropertyChangeListener getEditorListener() { if (editorListener == null) { editorListener = new EditorListener(); weakEditorListener = WeakListener.propertyChange( editorListener, editor ); } return weakEditorListener; } public void setBackground(Color bg) { super.setBackground(bg); updateSheetButtonVisually(); } // reader component ........................ /** * Creates Reader component. */ private SheetButton getReaderComponent() { String stringValue = null; SheetButton c = null; if (editor == null) { //return getTextView(getTypeString(model.getPropertyType())); // display description in place of class name return getTextView (NbBundle.getMessage (PropertyPanel.class, "CTL_No_property_editor")); //NOI18N } try { if ((!differentValues) && (canRead)){ stringValue = editor.getAsText (); } if ( editor.isPaintable () && ( (paintingStyle == PropertySheet.PAINTING_PREFERRED) || ( (paintingStyle == PropertySheet.STRING_PREFERRED) && (stringValue == null) ) ) ) { if (differentValues) c = getTextView (NbBundle.getMessage (PropertyPanel.class, "CTL_Different_Values")); //NOI18N else c = getPaintView (); } else { if (stringValue == null) { // display info the values are different in place of class name c = (differentValues)? getTextView (NbBundle.getMessage (PropertyPanel.class, "CTL_Different_Values")): //NOI18N getTextView(getTypeString(model.getPropertyType())); } else { c = getTextView(stringValue); } } } catch (Exception e) { //exception while getAsText () | isPaintable () ErrorManager.getDefault().annotate( e, getString("PS_ExcIn") + " " + editor.getClass ().getName () + // NOI18N " " + getString ("PS_Editor") + "." // NOI18N ); c = getTextView(getExceptionString(e)); } // we should never return null (dstrupl) if (c == null) { c = getTextView("null"); // NOI18N } return c; } /** * Creates SheetButton with text representing current value of property. */ private SheetButton getTextView (String str) { textView = new PropertySheetButton(str, plastic, plastic); textView.setFocusTraversable(true); if (sheetButtonListener != null) { textView.addSheetButtonListener(sheetButtonListener); } if ((env != null) && (env.getState() != PropertyEnv.STATE_NEEDS_VALIDATION) && (!customDialogShown)) { textView.setInvalidMark(env.getState() == PropertyEnv.STATE_INVALID); if (descriptor instanceof Node.Property) { Node.Property np = (Node.Property)descriptor; if (np.supportsDefaultValue()) { textView.setModifiedMark(! np.isDefaultValue()); } } } // XXX Read-only should be handled via enabling/disabling component // not via settin 'active' foreground color. if(canWrite) { textView.setActiveForeground(foregroundColor); } else { textView.setActiveForeground(disabledColor); } return textView; } /** * Creates SheetButton with PropertyShow representing current value of property. */ private SheetButton getPaintView () { if (propertyShow == null) { propertyShow = new PropertyShow (editor); } else { propertyShow.setEditor(editor); } // bugfix #26886, don't create new button for each call this method if (paintView == null) { paintView = new PropertySheetButton (); paintView.add (propertyShow); } if (sheetButtonListener != null) { paintView.addSheetButtonListener (sheetButtonListener); } // bugfix #26340, set label for paint view paintView.setLabel (editor.getAsText ()); paintView.setFocusTraversable(true); // XXX Read-only should be handled via enabling/disabling component // not via settin 'active' foreground color. if(canWrite) { paintView.setActiveForeground(foregroundColor); propertyShow.setForeground(foregroundColor); } else { paintView.setActiveForeground(disabledColor); propertyShow.setForeground(disabledColor); } if ((env != null) && (env.getState() != PropertyEnv.STATE_NEEDS_VALIDATION) && (!customDialogShown)) { paintView.setInvalidMark(env.getState() == PropertyEnv.STATE_INVALID); if (descriptor instanceof Node.Property) { Node.Property np = (Node.Property)descriptor; if (np.supportsDefaultValue()) { paintView.setModifiedMark(! np.isDefaultValue()); } } } paintView.setPlastic(plastic); paintView.setToolTipText(getPanelToolTipText()); return paintView; } // writer component ........................ /** * This method returns property value editor Component like input line (if property supports * setAsText (String string) method) or some others. * * @return property value editor Component */ private JComponent getWriterComponent () { if (editor == null) return getDisabledWriterComponent(); String stringValue = null; boolean canEditAsText = true; Object customEditAsText = descriptor.getValue(PROP_CAN_EDIT_AS_TEXT); if (customEditAsText instanceof Boolean) { canEditAsText = ((Boolean)customEditAsText).booleanValue(); } try { if (canRead) { if (!differentValues) { stringValue = editor.getAsText (); // bugfix #22357 editing value in-place is enabled if there are different values } else { stringValue = NbBundle.getMessage (PropertyPanel.class, "CTL_Different_Values"); //NOI18N } } } catch (Exception x) { processThrowable(x); } if ((stringValue == null) && (customEditAsText == null)){ canEditAsText = false; } getWriteComponentListener().setOldValue(editor.getValue()); boolean existsCustomEditor = editor.supportsCustomEditor (); if ((editor instanceof EnhancedPropertyEditor) && (((EnhancedPropertyEditor)editor).hasInPlaceCustomEditor())) { return getInput (getInPlace (), existsCustomEditor); } if (descriptor instanceof Node.Property) { Node.Property np = (Node.Property)descriptor; if (!np.canWrite ()) { if (existsCustomEditor) { return getInput (getDisabledWriterComponent(), true);// read-only } else { return getDisabledWriterComponent(); } } } boolean editable = (editor instanceof EnhancedPropertyEditor) && (((EnhancedPropertyEditor)editor).supportsEditingTaggedValues ()); String[] tags; // Tags if(((tags = editor.getTags()) != null)) { return getInput( getInputTag( tags, stringValue, editable), existsCustomEditor ); } if (canEditAsText) { return getInput( getInputLine((stringValue == null) ? "???" : stringValue, true), // NOI18N existsCustomEditor); } if (existsCustomEditor) { return getInput (getDisabledWriterComponent(), true); } return getDisabledWriterComponent(); } /** */ private JComponent getDisabledWriterComponent() { SheetButton c = getReaderComponent(); if (descriptor instanceof Node.Property) { if (!((Node.Property)descriptor).canWrite()) { c.setActiveForeground(disabledColor); } } c.addFocusListener(getWriteComponentListener()); return c; } /** * This is helper method for method getWriterComponent () which returns Panel with Choice * in the "Center" and enhanced property editor open button on the "East". This Panel * is then returned as property value editor Component. * * @param tags There are lines for Choice stored. * @param selected Line to be selected. * * @return Choice Component */ private JComponent getInputTag (String[] tags, final String selected, boolean editable) { comboBox = new PropertyComboBox(); comboBox.setModel(new DefaultComboBoxModel(tags)); comboBox.setMaximumRowCount(tags.length <= 12 ? tags.length : 8); if(selected != null) { for(int i = 0; i < tags.length; i++) { if(tags [i].equals(selected)) { comboBox.setSelectedIndex(i); break; } } } if (editable) { comboBox.setEditable (true); comboBox.setSelectedItem (selected); comboBox.getEditor().setItem (selected); comboBox.getEditor().getEditorComponent(). addFocusListener(getWriteComponentListener()); comboBox.getEditor().getEditorComponent(). addKeyListener(getWriteComponentListener()); } comboBox.addActionListener (getWriteComponentListener()); comboBox.setToolTipText(getPanelToolTipText()); return comboBox; } /** Attempts to advance to next item in comboBox. */ void tryToSelectNextTag() { if(comboBox == null) { return; } setWriteState(); int index = comboBox.getSelectedIndex(); index++; if(index >= comboBox.getItemCount()) { index = 0; } comboBox.setSelectedIndex(index); } /** Gets 'in-place' custom editor component. */ private Component getInPlace () { Component c = ((EnhancedPropertyEditor) editor). getInPlaceCustomEditor (); c.addFocusListener (getWriteComponentListener()); c.addKeyListener(getWriteComponentListener()); if (c instanceof JComponent) { ((JComponent)c).setToolTipText(getPanelToolTipText()); } return c; } /** * This is helper method for method getWriterComponent () which returns Panel with TextField * in the "Center" and enhanced property editor open button on the "East". This Panel * is then returned as property value editor Component. * * @param String propertyStringValue initial property value. * @param boolean editable is true if string editing should be allowed. * @param boolean existsCustomEditor is true if enhanced property editor open button * should be showen. * * @return Panel Component */ private JComponent getInputLine (final String propertyStringValue, boolean editable) { textField = new PropertyTextField(); textField.addActionListener (getWriteComponentListener()); textField.addKeyListener(getWriteComponentListener()); textField.addFocusListener (getWriteComponentListener()); textField.setText(propertyStringValue); textField.setEditable (editable); textField.setToolTipText(getPanelToolTipText()); if (!isWriteState) { textField.selectAll(); } return textField; } /** */ private String getExceptionString (Throwable exception) { if (exception instanceof InvocationTargetException) exception = ((InvocationTargetException) exception). getTargetException (); return "<" + exception.getClass().getName() + ">"; // NOI18N } /** */ private String getTypeString (Class clazz) { if (clazz == null) { return getString("CTL_NoPropertyEditor"); } if (clazz.isArray()) { return "[" + getString ("PS_ArrayOf") +" " + // NOI18N getTypeString(clazz.getComponentType()) + "]"; // NOI18N } return "[" + clazz.getName() + "]"; // NOI18N } /** * This is helper method for method getInput () and getInputTag () which returns Panel * with enhanced property editor open button on the "East". * * @param Component leftComponent this component will be added to the "Center" of this panel * @param boolean existsCustomEditor is true if enhanced property editor open button * should be shown. * * @return <code>JPanel</code> component */ private JComponent getInput(Component leftComponent, boolean existsCustomEditor) { JPanel panel; if ( (leftComponent == null) && (editor != null) && (editor.isPaintable ()) && (paintingStyle != PropertySheet.ALWAYS_AS_STRING) ) { panel = new PropertyShow(editor); } else { panel = new JPanel (); } panel.setLayout (new BorderLayout()); if (leftComponent != null) { panel.add (leftComponent, BorderLayout.CENTER); } if (existsCustomEditor) { panel.add(getCustomizeButton(), BorderLayout.EAST); } panel.setToolTipText(getPanelToolTipText()); panel.addFocusListener(getWriteComponentListener()); panel.addKeyListener(getWriteComponentListener()); return panel; } /** Gets <code>customizedButton</code>, so called 'three-dot-button'. */ private SheetButton getCustomizeButton() { if (customizeButton == null) { customizeButton = new SheetButton("...", plastic, plastic); // NOI18N customizeButton.setFocusTraversable(true); Font currentFont = customizeButton.getFont (); customizeButton.setFont ( new Font ( currentFont.getName (), currentFont.getStyle () | Font.BOLD, currentFont.getSize () ) ); customizeButton.addFocusListener(getWriteComponentListener()); customizeButton.addSheetButtonListener(new CustomizeListener()); customizeButton.setToolTipText(getString("CTL_ElipsisHint")); } // XXX Read-only should be handled via enabling/disabling component // not via settin 'active' foreground color. if(canWrite) { customizeButton.setActiveForeground(foregroundColor); } else { customizeButton.setActiveForeground(disabledColor); } return customizeButton; } /** Processes <code>Exception</code> thrown from * <code>setAsText</code> or <code>setValue</code> call * on <code>editor</code>. Helper method. */ private void notifyExceptionWhileSettingProperty(Exception iae) { // partly bugfix #10791, notify exception to an user if PREF_INPUT_STATE is set if (getPreferences () == 0 || getPreferences () == PREF_INPUT_STATE){ PropertyPanel.notifyUser (iae, descriptor.getDisplayName ()); } else { ErrorManager.getDefault ().notify (ErrorManager.INFORMATIONAL, iae); } } /** * Tries to find a localized message in the exception annotation * or directly in the exception. If the message is found it is * notified with user severity. If the message is not found the * exception is notified with informational severity. * @param e exception to notify * @param propertyName display name of property */ static void notifyUser(Exception e, String propertyName) { String userMessage = extractLocalizedMessage (e); if ((userMessage == null) && (e instanceof InvocationTargetException)) { userMessage = extractLocalizedMessage( ((InvocationTargetException)e).getTargetException()); } ErrorManager em = ErrorManager.getDefault (); if ((userMessage != null)){ em.annotate (e, NbBundle.getMessage(PropertyPanel.class, "FMT_ErrorSettingProperty", userMessage, propertyName )); em.notify (ErrorManager.USER, e); } else { em.notify (ErrorManager.INFORMATIONAL, e); } } /** * Scans the annotations list for a localized message. If there * is none tries to get the localized message from the Throwable itself. */ private static String extractLocalizedMessage(Throwable t) { ErrorManager em = ErrorManager.getDefault(); ErrorManager.Annotation[] an = em.findAnnotations(t); String message = null; if (an != null) { for (int i = 0; i < an.length; i++) { String msg = an[i].getLocalizedMessage(); if (msg != null) { message = msg; break; } } } if (message == null) { message = t.getLocalizedMessage(); } return message; } /** Processes <code>Throwable</code> thrown from <code>setAsText</code> * or <code>setValue</code> call on <code>editor</code>. Helper method. */ private void processThrowable(Throwable throwable) { if(throwable instanceof ThreadDeath) { throw (ThreadDeath)throwable; } ErrorManager em = ErrorManager.getDefault(); em.annotate(throwable, NbBundle.getMessage(PropertyPanel.class, "FMT_ErrorSettingProperty", throwable.getLocalizedMessage(), descriptor.getDisplayName() )); em.notify(throwable); } /** Sets whether or not all components within selected component * will be enabled. * * @param cmp component to be enabled/disabled * @param enabled flag defining the action. */ private void setComponentEnabled(Component cmp, boolean enabled){ cmp.setEnabled(enabled); if (Container.class.isAssignableFrom(cmp.getClass()) ){ Container cont = (Container)cmp; Component[] comp = cont.getComponents(); for(int i=0; i<comp.length; i++){ comp[i].setEnabled(enabled); setComponentEnabled(comp[i], enabled); } } } /** Gets tooltip for this <code>PropertyPanel</code>. * @return tooltip retrieved from getToolTipText method * or property class type name if the former is <code>null</code> * or <code>null</code> if the model returns null as its class type */ private String getPanelToolTipText() { String toolTip = getToolTipText(); if(toolTip != null) { return toolTip; } if (editor == null) return null; // bugfix #24021, replace class name on value with property value if (differentValues) return NbBundle.getMessage (PropertyPanel.class, "CTL_Desc_Different_Values"); //NOI18N if (canRead) return editor.getAsText (); else return null; } /** Paint sheet button with PropertyPanel backround and without border when * used as cell renderer. */ private void updateSheetButtonVisually() { boolean flat = false; Object f = getClientProperty("flat"); // NOI18N if (f instanceof Boolean) flat = ((Boolean)f).booleanValue(); if (readComponent != null) { readComponent.setFlat(flat); readComponent.setBackground(getBackground()); } if (propertyShow != null) propertyShow.setBackground(getBackground()); //if (customizeButton != null) // customizeButton.setFlat(flat); } /** Gets localized string from specified key. */ private static String getString(String key) { return NbBundle.getMessage(PropertyPanel.class, key); } // innerclasses .............................................................. /** Empty implementation of the <code>PropertyModel</code> interface. */ private static class EmptyModel implements PropertyModel { EmptyModel() {} /** @return <code>null</code> */ public Object getValue() throws InvocationTargetException { return null; } /** Dummy implementation. Does nothing. */ public void setValue(Object v) throws InvocationTargetException { } /** @return <code>Object.class</code> */ public Class getPropertyType() { return Object.class; } /** @return <code>null</code> */ public Class getPropertyEditorClass() { return null; } /** Dummy implementation. Does nothing. */ public void addPropertyChangeListener(PropertyChangeListener l) { } /** Dummy implementation. Does nothing. */ public void removePropertyChangeListener(PropertyChangeListener l) { } } // End of class EmptyModel. /** Implementation of the <code>PropertyModel</code> interface keeping * a <code>Node.Property</code>. */ static class SimpleModel implements ExPropertyModel { /** Property to work with. */ private Node.Property prop; /** Array of beans(nodes) to which belong the property. */ private Object []beans; /** Property change support. */ private PropertyChangeSupport sup = new PropertyChangeSupport(this); /** Construct simple model instance. * @param property proeprty to work with * @param beans array of beans(nodes) to which belong the property */ public SimpleModel(Node.Property property, Object[] beans) { this.prop = property; this.beans = beans; } /** Implements <code>PropertyModel</code> interface. */ public Object getValue() throws InvocationTargetException { try { return prop.getValue(); } catch(IllegalAccessException iae) { throw annotateException(iae); } catch(InvocationTargetException ite) { throw annotateException(ite); } } /** Implements <code>PropertyModel</code> interface. */ public void setValue(Object v) throws InvocationTargetException { try { prop.setValue(v); sup.firePropertyChange(PropertyModel.PROP_VALUE, null, null); } catch(IllegalAccessException iae) { throw annotateException(iae); } catch(IllegalArgumentException iaae) { throw annotateException(iaae); } catch(InvocationTargetException ite) { throw annotateException(ite); } } /** Annotates specified exception. Helper method. * @param exception original exception to annotate * @return <code>IvocationTargetException</code> which annotates the * original exception */ private InvocationTargetException annotateException(Exception exception) { if(exception instanceof InvocationTargetException) { return (InvocationTargetException)exception; } else { return new InvocationTargetException(exception); } } /** Implements <code>PropertyModel</code> interface. */ public Class getPropertyType() { return prop.getValueType(); } /** Implements <code>PropertyModel</code> interface. */ public Class getPropertyEditorClass() { Object ed = prop.getPropertyEditor(); if (ed != null) { return ed.getClass(); } return null; } /** Implements <code>PropertyModel</code> interface. */ public void addPropertyChangeListener(PropertyChangeListener l) { sup.addPropertyChangeListener(l); } /** Implements <code>PropertyModel</code> interface. */ public void removePropertyChangeListener(PropertyChangeListener l) { sup.removePropertyChangeListener(l); } /** Implements <code>ExPropertyModel</code> interface. */ public Object[] getBeans() { return beans; } /** Implements <code>ExPropertyModel</code> interface. */ public FeatureDescriptor getFeatureDescriptor() { return prop; } void fireValueChanged() { sup.firePropertyChange(PropertyModel.PROP_VALUE, null, null); } } // End of class SimpleModel. /** Listener on textField(<code>JTextField</code>) or comboBox(<code>JComboBox</code>) * - input line components controlled by this panel. */ private final class WriteComponentListener extends KeyAdapter implements ActionListener, FocusListener { WriteComponentListener() {} /** Holds old value. */ private Object oldValue; /** Task which handles action performed from combo box. */ private Runnable comboActionTask; /** Task which requests default focus for enclosing class * when setting to 'readState'. */ private Runnable requestFocusTask; /** Sets <code>oldValue</code>. */ private void setOldValue(Object oldValue) { this.oldValue = oldValue; } /** Implements <code>ActionListener</code> interface. */ public void actionPerformed (ActionEvent e) { if (!isWriteState) return; if(e.getSource() == comboBox) { // XXX #16101 - There is a problem with actions fired // from combo, we can't see the diff if it was caused by mouse // selection in popup or key navigation in popup, thus we plan // to handle this event later so we will know to decide what // was the cause of the action depening on it if popup is // visible or not (yes->mouse, no->key navigation). SwingUtilities.invokeLater(getComboActionTask()); } else { if(e.getSource() == textField) { String val = textField.getText(); changeValue(val); } prepareReadState(); } } /** Overrides <code>KeyAdapter</code> superclass method. */ public void keyPressed(KeyEvent e) { Object source = e.getSource(); if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { if(// XXX ESC during navigation in popup resets the old value. comboBox != null && comboBox.isPopupVisible() && source instanceof Component && SwingUtilities.isDescendingFrom((Component)source, comboBox)) { resetOldValue(); } else if(source == textField) { resetOldValue(); e.consume(); } prepareReadState(); } else if(e.getKeyCode() == KeyEvent.VK_ENTER && comboBox != null && source instanceof Component && SwingUtilities.isDescendingFrom((Component)source, comboBox) && !comboBox.isEditable() && ((preferences & PREF_INPUT_STATE) == 0)) { changeValue((String) comboBox.getSelectedItem ()); prepareReadState(); } } /** Implements <code>FocusListener</code> interface. */ public void focusLost (final FocusEvent e) { if (!isWriteState) { return; } if ((comboBox != null) && (e.isTemporary())) { return; } // help flag, don't set a value if no change final boolean differentValuesNoChange = differentValues && (e.getSource().equals(textField)) && NbBundle.getMessage (PropertyPanel.class, "CTL_Different_Values").equals( //NOI18N textField.getText ()); boolean supportsCustom = (editor != null && editor.supportsCustomEditor()); if(supportsCustom || comboBox != null) { // XXX // In this case we need to find out if the focus was // moved to component inside this PropetyPanel hierarchy, // currently to "..." customize button. // To figure out the new focus owner we have to skip the current // task. SwingUtilities.invokeLater(new Runnable() { public void run() { // bugfix #18326 set value to editor w/o "read state" is reset if (e.getSource().equals(textField)) { if (!differentValuesNoChange) { changeValue(textField.getText()); if(editor!=null && !editor.getAsText().equals(textField.getText())) { // if a value is invalid then old value is set back textField.setText(editor.getAsText()); } } } // Reset 'read state' only in case the focus was lost // from this PropertyPanel hierarchy. if(SwingUtilities.findFocusOwner(PropertyPanel.this) == null && isWriteState) { if (!differentValuesNoChange) { resetReadState(e); } else { setReadState (); } } } }); } else { if (!differentValuesNoChange) { resetReadState(e); } else { setReadState (); } } } /** Implements <code>FocusListener</code> interface. */ public void focusGained(FocusEvent e) { if(e.getSource() == textField) { textField.selectAll(); } else if(comboBox != null && comboBox.isEditable()) { comboBox.getEditor ().selectAll (); } } /** Changes value if needed. */ public void changeValue(String s) { if(s != null && (editor.getValue() == null // Fix #13339 || !(s.equals(editor.getAsText())) ) ) { if(!setAsText(s)) { resetOldValue(); } } } /** Gets <code>comboActionTask</code>. */ private synchronized Runnable getComboActionTask() { if(comboActionTask == null) { comboActionTask = new Runnable() { public void run() { // XXX For combos allow key navigation in popup. if(comboBox.isPopupVisible() || !isWriteState) { return; } changeValue((String) comboBox.getSelectedItem ()); prepareReadState(); } }; } return comboActionTask; } /** Gets <code>requestFocusTask</code>. */ private synchronized Runnable getRequestFocusTask() { if(requestFocusTask == null) { requestFocusTask = new Runnable() { public void run() { Component focused = getDefaultFocusComponent(PropertyPanel.this); if(focused != null) focused.requestFocus(); } }; } return requestFocusTask; } /** Prepares 'readState'. Sets 'readState' and requests default focus * for changed component tree of enclosing <code>PropertyPanel</code>. */ private void prepareReadState() { boolean hasFocus = SwingUtilities.findFocusOwner(PropertyPanel.this) != null; setReadState(); if (hasFocus) { // XXX direct call doesn't work - #16052. SwingUtilities.invokeLater(getRequestFocusTask()); } } /** Resets 'read state' on focus lost change. Helper method. */ private void resetReadState(FocusEvent e) { // bugfix #18326 change value if "..." customize button is left if(e.getSource() == customizeButton && textField!=null) { changeValue(textField.getText ()); } else if(e.getSource() == textField) { changeValue(textField.getText ()); } else if((comboBox != null) && (comboBox.isEditable())) { changeValue((String) comboBox.getEditor().getItem()); } setReadState(); } /** Resets specified value to <code>editor</code>. */ private void resetOldValue() { try { // don't set old value if there are different values if (!differentValues) editor.setValue(oldValue); } catch(IllegalArgumentException iae) { notifyExceptionWhileSettingProperty(iae); } catch(RuntimeException throwable) { processThrowable(throwable); } } /** * Sets as text. * @value <code>String</code> value which is possible to convert to * the type of property. * @returns <code>true</code> if succesfull, <code>false</code> otherwise */ private boolean setAsText (String value) { try { editor.setAsText (value); return true; } catch (IllegalArgumentException iae) { notifyExceptionWhileSettingProperty(iae); } catch (RuntimeException throwable) { processThrowable(throwable); } return false; } } // End of class WriteComponentListener. /** Listener on <code>readComponent</code>. */ private final class ReadComponentListener implements SheetButtonListener { ReadComponentListener() {} /** * Invoked when the mouse exits a component. */ public void sheetButtonExited(ActionEvent e) { } /** * Invoked when the mouse has been clicked on a component. */ public void sheetButtonClicked(ActionEvent e) { if(SheetButton.RIGHT_MOUSE_COMMAND.equals(e.getActionCommand()) || isWriteState) { return; } SwingUtilities.invokeLater(new Runnable() { public void run() { setWriteState(); } }); } /** * Invoked when the mouse enters a component. */ public void sheetButtonEntered(ActionEvent e) { } } // End of class ReadComponentListener. /** Listener on <code>customizeButton</code>. */ private final class CustomizeListener implements SheetButtonListener { CustomizeListener() {} /** * Invoked when the mouse exits a component. Dummy implementation. * Does nothing. */ public void sheetButtonExited(ActionEvent e) { } /** * Invoked when the mouse has been clicked on a component. */ public void sheetButtonClicked(ActionEvent e) { String title = descriptor.getDisplayName(); customDialogShown = true; // bugfix #18326 editor's value is taken from textField if (textField != null) { try { if(editor.getValue() == null // Fix #13339 || !(textField.getText().equals(editor.getAsText())) ) { editor.setAsText(textField.getText()); } } catch (ProxyNode.DifferentValuesException dve) { // old value back will be set back } catch (Exception ite) { // old value back will be set back } } //set the cursor *before* causing the property editor to //create the component final Container w = getTopLevelAncestor(); w.setCursor (Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); //Strange but true, this is the safest way to do this; //there is no guarantee that what is shown by Window.show() will //be the window instance you called show() on. AWTEventListener listener = new AWTEventListener () { public void eventDispatched (AWTEvent ae) { Toolkit.getDefaultToolkit().removeAWTEventListener (this); w.setCursor (Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } }; try { PropertyDialogManager pdm = new PropertyDialogManager( NbBundle.getMessage(PropertyPanel.class, "PS_EditorTitle", title == null ? "" : title, // NOI18N model.getPropertyType()), true, editor, model, env); final Window wd = pdm.getDialog(); //Component event seems to be the most reliable; sometimes window //events are being consumed by something or never fired Toolkit.getDefaultToolkit().addAWTEventListener(listener, AWTEvent.COMPONENT_EVENT_MASK); wd.show(); } finally { Toolkit.getDefaultToolkit().removeAWTEventListener (listener); w.setCursor (Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } // update editor from the model if (canRead) { try { ignoreEvents = true; Object newValue = model.getValue(); Object oldValue = editor.getValue(); // test if newValue is not equal to oldValue if ((newValue != null && !newValue.equals(oldValue)) || (newValue == null && oldValue != null)) { editor.setValue(newValue); } } catch (ProxyNode.DifferentValuesException dve) { differentValues = true; } catch (Exception ite) { processThrowable(ite); } finally { ignoreEvents = false; } } reset(); requestFocus(); customDialogShown = false; } /** * Invoked when the mouse enters a component. Dummy implementation. * Does nothing. */ public void sheetButtonEntered(ActionEvent e) { } } // End of class CustomizeListener. /** Property change listener for the model. */ private class ModelListener implements PropertyChangeListener { ModelListener() {} /** Property was changed. */ public void propertyChange(PropertyChangeEvent evt) { if (PropertyModel.PROP_VALUE.equals(evt.getPropertyName())) { if (editor != null) { Mutex.EVENT.readAccess(new Runnable() { public void run() { try { ignoreEvents = true; differentValues = false; Object newValue = model.getValue(); Object oldValue = editor.getValue(); // test if newValue is not equal to oldValue if ((newValue != null && !newValue.equals(oldValue)) || (newValue == null && oldValue != null)) { editor.setValue(newValue); } refresh(); } catch (ProxyNode.DifferentValuesException dve) { differentValues = true; } catch (InvocationTargetException e) { notifyExceptionWhileSettingProperty(e); } finally { ignoreEvents = false; } } }); } } } } // End of class ModelListener. /** Property change listener for the editor. */ private class EditorListener implements PropertyChangeListener { EditorListener() {} /** Property was changed. */ public void propertyChange(PropertyChangeEvent evt) { if (evt.getSource () instanceof PropertyEnv) { // we are listening also on PropertyEnv if (PropertyEnv.PROP_STATE.equals (evt.getPropertyName ())) { PropertyPanel.this.firePropertyChange (PropertyEnv.PROP_STATE, evt.getOldValue(), evt.getNewValue()); } return; } if (ignoreEvents) return; if ((!isWriteState) && (!customDialogShown) && (preferences < 2)) { return; } if ((env != null) && (!env.isChangeImmediate()) && (customDialogShown)) { return; } // ExPropertyEditor can fire PROP_VALUE_VALID property change, which // doesn't mean the change of edited property. if (ExPropertyEditor.PROP_VALUE_VALID.equals (evt.getPropertyName ())) { return; } if (editor != null) { try { ignoreEvents = true; Object newValue = editor.getValue(); Object oldValue = null; try { if (canRead) { oldValue = model.getValue(); } } catch (ProxyNode.DifferentValuesException dve) { // ok - no problem here, it was just the old value } // test if newValue is not equal to oldValue if ((newValue != null && !newValue.equals(oldValue)) || (newValue == null && oldValue != null)) { model.setValue(newValue); } } catch (InvocationTargetException e) { notifyExceptionWhileSettingProperty(e); try { if (canRead) { editor.setValue(model.getValue()); } } catch (ProxyNode.DifferentValuesException dve) { // ok - no problem here, it was just the old value } catch (Exception ex) { PropertyDialogManager.notify(ex); } } finally { ignoreEvents = false; } } } } // End of class EditorListener. /* Overriden to forward focus request to focus traversable subcomponent. */ public void requestFocus() { requestDefaultFocus(); } ////////////////// Accessibility support /////////////////////////////////// public javax.accessibility.AccessibleContext getAccessibleContext() { if (accessibleContext == null) { accessibleContext = new AccessiblePropertyPanel(); } return accessibleContext; } private class AccessiblePropertyPanel extends AccessibleJComponent { AccessiblePropertyPanel() {} public javax.accessibility.AccessibleRole getAccessibleRole() { return javax.accessibility.AccessibleRole.PANEL; } public String getAccessibleName() { String name = super.getAccessibleName(); if (name == null && model instanceof ExPropertyModel) { FeatureDescriptor fd = ((ExPropertyModel)model).getFeatureDescriptor(); name = NbBundle.getMessage(PropertyPanel.class, "ACS_PropertyPanel", fd.getDisplayName()); } return name; } public String getAccessibleDescription() { String description = super.getAccessibleDescription(); if (description == null && model instanceof ExPropertyModel) { FeatureDescriptor fd = ((ExPropertyModel)model).getFeatureDescriptor(); description = NbBundle.getMessage(PropertyPanel.class, "ACSD_PropertyPanel", fd.getShortDescription()); } return description; } } private String getWriteComponentAccessibleName() { String name = getAccessibleContext().getAccessibleName(); if (name == null) return null; return NbBundle.getMessage(PropertyPanel.class, "ACS_PropertyPanelWriteComponent", name, (editor == null) ? getString("CTL_No_value") : editor.getAsText()); } private String getWriteComponentAccessibleDescription() { String description = getAccessibleContext().getAccessibleDescription(); if (description == null) return null; return NbBundle.getMessage(PropertyPanel.class, "ACSD_PropertyPanelWriteComponent", description, getPanelToolTipText()); } private class PropertyTextField extends JTextField { PropertyTextField() {} //XXX workaround of jdkbug #4670767 in jdk1.4 // JTextField filters the new lines if the property filterNewlines is TRUE // which is TRUE as default // there is a problem when in PropertyTextFiled is set a multi-line string // then single-line string is get back, see issue 22450 public void setDocument(Document doc) { super.setDocument(doc); if (doc != null) { doc.putProperty("filterNewlines", null); } } public javax.accessibility.AccessibleContext getAccessibleContext() { if (accessibleContext == null) { accessibleContext = new AccessiblePropertyTextField(); } return accessibleContext; } private class AccessiblePropertyTextField extends AccessibleJTextField { public String getAccessibleName() { return getWriteComponentAccessibleName(); } public String getAccessibleDescription() { return getWriteComponentAccessibleDescription(); } } } private class PropertyComboBox extends JComboBox { PropertyComboBox() {} public javax.accessibility.AccessibleContext getAccessibleContext() { if (accessibleContext == null) { accessibleContext = new AccessiblePropertyComboBox(); } return accessibleContext; } private class AccessiblePropertyComboBox extends AccessibleJComboBox { AccessiblePropertyComboBox() {} public String getAccessibleName() { return getWriteComponentAccessibleName(); } public String getAccessibleDescription() { return getWriteComponentAccessibleDescription(); } } } private class PropertySheetButton extends SheetButton { PropertySheetButton() { } PropertySheetButton (String aLabel, boolean isPlasticButton, boolean plasticActionNotify) { super(aLabel, isPlasticButton, plasticActionNotify); } public javax.accessibility.AccessibleContext getAccessibleContext() { if (accessibleContext == null) { accessibleContext = new AccessiblePropertySheetButton(); } return accessibleContext; } private class AccessiblePropertySheetButton extends AccessibleSheetButton { AccessiblePropertySheetButton() {} public String getAccessibleName() { return getReadComponentAccessibleName(); } public String getAccessibleDescription() { return getReadComponentAccessibleDescription(); } private String getReadComponentAccessibleName() { String name = PropertyPanel.this.getAccessibleContext().getAccessibleName(); if (name == null) return null; return NbBundle.getMessage(PropertyPanel.class, "ACS_PropertyPanelReadComponent", name, super.getAccessibleName()); } private String getReadComponentAccessibleDescription() { String description = PropertyPanel.this.getAccessibleContext().getAccessibleDescription(); if (description == null) return null; String beansList = null; int j = 0; if (model instanceof ExPropertyModel) { Object[] beans = ((ExPropertyModel)model).getBeans(); String delimiter = getString("ACSD_BeanListDelimiter"); // NOI18N for (int i = 0; i < beans.length; i++) { if (beans[i] instanceof Node) { Node n = ((Node)beans[i]); beansList = ((beansList == null) ? "" : beansList + delimiter) + n.getDisplayName(); // NOI18N j++; } } } Class clazz = model.getPropertyType(); return NbBundle.getMessage(PropertyPanel.class, "ACSD_PropertyPanelReadComponent", new Object[] { description, clazz == null ? getString("CTL_No_type") : clazz.getName(), new Integer(j), beansList }); } } } }