KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > openide > explorer > propertysheet > PropertyPanel


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19 package org.openide.explorer.propertysheet;
20
21 import org.openide.nodes.Node;
22 import org.openide.util.NbBundle;
23
24 import java.awt.Color JavaDoc;
25 import java.awt.Component JavaDoc;
26 import java.awt.Container JavaDoc;
27 import java.awt.Dimension JavaDoc;
28 import java.awt.Graphics JavaDoc;
29 import java.awt.KeyboardFocusManager JavaDoc;
30 import java.awt.LayoutManager JavaDoc;
31 import java.awt.Toolkit JavaDoc;
32 import java.awt.event.ActionEvent JavaDoc;
33 import java.awt.event.FocusEvent JavaDoc;
34 import java.awt.event.KeyEvent JavaDoc;
35
36 import java.beans.FeatureDescriptor JavaDoc;
37 import java.beans.PropertyChangeEvent JavaDoc;
38 import java.beans.PropertyChangeListener JavaDoc;
39 import java.beans.PropertyEditor JavaDoc;
40 import java.beans.PropertyVetoException JavaDoc;
41 import java.beans.VetoableChangeListener JavaDoc;
42
43 import javax.swing.*;
44 import javax.swing.event.ChangeListener JavaDoc;
45
46 import org.netbeans.modules.openide.explorer.TTVEnvBridge;
47
48
49 /** <p>PropertyPanel is a generic GUI component for displaying and editing a JavaBeans&trade;
50  * property or any compatible getter/setter pair for which there is a property editor
51  * available, in accordance with the JavaBeans specification. It makes it possible to
52  * instantiate an appropriate GUI component for a property and provides the plumbing
53  * between user interation with the gui component and calls to the getter/setter pair
54  * to update the value.</p>
55  *
56  * <p>The simplest way to use PropertyPanel is by driving it from an instance of
57  * <code>PropertySupport.Reflection</code>. To do that, simply pass the name of the
58  * property and an object with a getter/setter pair matching that property to the
59  * PropertySupport.Reflection's constructor, and pass the resulting instance of
60  * PropertySupport.Reflection to the PropertyPanel constructor.</p>
61  *
62  * <p>A more efficient approach is to implement Node.Property or pass an existing Node.Property
63  * object to the PropertyPanel's constructor or PropertyPanel.setProperty - thus
64  * bypassing the use of reflection to locate the getter and setter.</p>
65  *
66  * <p><b>A note on uses of Node.Property and PropertyModel</b>: PropertyPanel was
67  * originally designed to work with instances of PropertyModel, and has since been
68  * rewritten to be driven by instances of Node.Property. The main reason for this
69  * is simplification - there is considerable overlap between PropertyModel and
70  * Node.Property; particularly, DefaultPropertyModel and PropertySupport.Reflection
71  * effectively are two ways of doing exactly the same thing.</p>
72  *
73  * <p>Use of PropertyModel is still supported, but discouraged except under special
74  * circumstances. The one significant difference between <code>Node.Property</code>
75  * and PropertyModel is that PropertyModel permits listening for changes.</p>
76  * <p>It is generally accepted that GUI components whose contents unexpectedly change
77  * due to events beyond their control does not tend to lead to quality, usable user
78  * interfaces. However, there are cases where a UI will, for example, contain several
79  * components and modification to one should immediately be reflected in the other.
80  * For such a case, use of PropertyModel is still supported. For other cases,
81  * it makes more sense to use <code>BeanNode</code> and for the designer of the UI
82  * to make a design choice as to how to handle (if at all) unexpected changes happening to
83  * properties it is displaying. If all you need to do is display or edit a
84  * property, use one of the constructors that takes a Node.Property object or
85  * use <code>setProperty</code>. PropertyModel will be deprecated at some point
86  * in the future, when a suitable replacement more consistent with
87  * <code>Node.Property</code> is created.</p>
88  *
89  * PropertyModel and displays an editor component for it.
90  * @author Jaroslav Tulach, Petr Hamernik, Jan Jancura, David Strupl, Tim Boudreau
91  */

92 public class PropertyPanel extends JComponent implements javax.accessibility.Accessible JavaDoc {
93     /** Constant defining a preference for rendering the value.
94      * Value should be displayed in read-only mode.
95      */

96     public static final int PREF_READ_ONLY = 0x0001;
97
98     /** Constant defining a preference for rendering the value.
99      * Value should be displayed in custom editor.
100      */

101     public static final int PREF_CUSTOM_EDITOR = 0x0002;
102
103     /** Constant defining a preference for rendering the value.
104      * Value should be displayed in editor only.
105      */

106     public static final int PREF_INPUT_STATE = 0x0004;
107
108     /** Constant defining a preference for a borderless UI suitable for
109      * use in a table */

110     public static final int PREF_TABLEUI = 0x0008;
111
112     /** Name of the 'preferences' property. */
113     public static final String JavaDoc PROP_PREFERENCES = "preferences"; // NOI18N
114

115     /** Name of the 'model' property. */
116     public static final String JavaDoc PROP_MODEL = "model"; // NOI18N
117

118     /** Name of the read-only property 'propertyEditor'.
119      * @deprecated - the property editor is re-fetched from the underlying
120      * property object as needed. It is up to the property object to
121      * cache or not cache the property editor. This property will no longer
122      * be fired. */

123     public static final @Deprecated JavaDoc String JavaDoc PROP_PROPERTY_EDITOR = "propertyEditor"; // NOI18N
124

125     /** Name of property 'state' that describes the state of the embeded PropertyEditor.
126     * @see PropertyEnv#getState
127     * @since 2.20 */

128     public static final String JavaDoc PROP_STATE = PropertyEnv.PROP_STATE;
129
130     /** Holds value of property preferences. */
131     private int preferences;
132
133     /** Holds value of property model. */
134     private PropertyModel model;
135
136     /**
137      * If this is <code>true</code> the changes made in the property editor
138      * are immediately propagated to the value of the property
139      * (to the property model). */

140     private boolean changeImmediate = true;
141
142     /** The inner component, either a custom property editor, an
143      * InplaceEditor's component or null, depending on the mode and state */

144     Component JavaDoc inner = null;
145
146     /** Listener that will listen for changes in model, editor, env */
147     private Listener JavaDoc listener = null;
148
149     /** The property which will drive the PropertyPanel */
150     private Node.Property prop;
151
152     /** Flag to avoid an endless loop when setProperty is called by setModel */
153     private boolean settingModel = false;
154     private boolean initializing = false;
155     private PropertyDisplayer displayer = null;
156     Object JavaDoc[] beans = null;
157     private ReusablePropertyEnv reusableEnv = new ReusablePropertyEnv();
158     private ReusablePropertyModel reusableModel = new ReusablePropertyModel(reusableEnv);
159     private final boolean isGtk = "GTK".equals(UIManager.getLookAndFeel().getID()) || //NOI18N
160
(UIManager.getLookAndFeel().getClass().getSuperclass().getName().indexOf("Synth") != -1); //NOI18N
161

162     /** Creates new PropertyPanel backed by a dummy property */
163     public PropertyPanel() {
164         this(ModelProperty.toProperty(null), 0, null);
165     }
166
167     /**
168      * Creates new PropertyPanel with DefaultPropertyModel
169      * @param preferences the preferences that affect how this propertypanel
170      * will operate
171      * @param bean The instance of bean
172      * @param propertyName The name of the property to be displayed
173      */

174     public PropertyPanel(Object JavaDoc bean, String JavaDoc propertyName, int preferences) {
175         //XXX inefficient, get DefaultPropertyModel out of the loop
176
this(
177             ModelProperty.toProperty(new DefaultPropertyModel(bean, propertyName)), preferences,
178             
179         //XXX can probably subst null for below
180
new DefaultPropertyModel(bean, propertyName)
181         );
182     }
183
184     /** Creates a new PropertyPanel. While not quite deprecated, do not
185      * use this constructor if your intention is to display a Node.Property
186      * object; use the constructor that takes a Node.Property object directly
187      * instead.
188      * @param model The model to display
189      * @see org.openide.explorer.propertysheet.PropertyModel
190      */

191     public PropertyPanel(PropertyModel model, int preferences) {
192         this(null, preferences, model);
193     }
194
195     /**
196      * Create a new property panel for the specified property with the
197      * specified preferences.
198      * @param p
199      * @param preferences
200      */

201     public PropertyPanel(Node.Property p, int preferences) {
202         this(p, preferences, null);
203     }
204
205     /**
206      * Create a new property panel for displaying/editing the specified
207      * property
208      * @param p A Property object for this node to represent
209      * @see org.openide.nodes.Node.Property
210      */

211     public PropertyPanel(Node.Property p) {
212         this(p, 0, null);
213     }
214
215     /** Create a property panel that displays a property belonging to several
216      * nodes. This is useful e.g. for TreeTableView. The property panel will
217      * display the standard &quot;different values&quot; condition for cases
218      * where the value of various properties does not match.
219      * <p>
220      * Note that this method assumes that none of the nodes will have two
221      * different property sets each containing a property with the requested
222      * name. The behavior of this constructor is undefined in this case,
223      * and will likely result in a ClassCastException.
224      * <p>
225      * This constructor is fail-fast, and will preemptively check that the
226      * properties to be proxied indeed exist and are of the same type.
227      *
228      * @param nodes The nodes that will supply properties
229      * @param propertyName The name of the property to look for
230      * @throws ClassCastException if the named property for one of the nodes
231      * has a different value type than for others
232      * @throws NullPointerException if even one of the nodes does not have
233      * a property of the given name.
234      */

235     PropertyPanel(Node[] nodes, String JavaDoc propertyName) throws ClassCastException JavaDoc, NullPointerException JavaDoc {
236         //Protected for now - TreeTableView can use it
237
this(
238             (nodes.length == 1) ? ModelProperty.findProperty(nodes[0], propertyName)
239                                 : ModelProperty.toProperty(nodes, propertyName)
240         );
241     }
242
243     /** The constructor all the other constructors call */
244     private PropertyPanel(Node.Property p, int preferences, PropertyModel mdl) {
245         if (p == null) {
246             prop = ModelProperty.toProperty(mdl);
247         } else {
248             prop = p;
249         }
250
251         this.preferences = preferences;
252         initializing = true;
253         setModel(mdl);
254         initializing = false;
255         setOpaque(true);
256
257         //for debugging, allow CTRL-. to dump the state to stderr
258
getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
259             KeyStroke.getKeyStroke(KeyEvent.VK_PERIOD, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), "dump"
260         );
261         getActionMap().put(
262             "dump",
263             new AbstractAction() { //NOI18N
264
public void actionPerformed(ActionEvent JavaDoc ae) {
265                     System.err.println(""); //NOI18N
266
System.err.println(PropertyPanel.this);
267                     System.err.println(""); //NOI18N
268
}
269             }
270         );
271
272         //#44226 - Unpretty, but this allows the TreeTableView to invoke a custom editor dialog when
273
//necessary - with the TTV rewrite, all cell editor infrastructure will be moved to
274
//org.netbeans.modules.openide.explorer, and they will simply share editor classes. Since that
275
//involves an API change (some package private methods of PropertyEnv need to be accessible to
276
//the editor classes), this will have to wait for after 4.0 - Tim
277
getActionMap().put("invokeCustomEditor", new CustomEditorProxyAction()); //NOI18N
278
}
279
280     public void setBackground(Color JavaDoc c) {
281         if (inner != null) {
282             inner.setBackground(c);
283         }
284
285         super.setBackground(c);
286     }
287
288     public void setForeground(Color JavaDoc c) {
289         if (inner != null) {
290             inner.setForeground(c);
291         }
292
293         super.setForeground(c);
294     }
295
296     /** Returns an appropriate property displayer instance depending on the
297      * preferences. For non-editable modes, will use a lightweight, near-stateless
298      * RendererPropertyDisplayer component */

299     private PropertyDisplayer findPropertyDisplayer() {
300         PropertyDisplayer result;
301         Node.Property prop = getProperty();
302
303         if (((preferences & PREF_CUSTOM_EDITOR) == 0) && (((preferences & PREF_READ_ONLY) != 0) || !isEnabled())) {
304             //Always use a renderer if we're inline and non-editable
305
return getRendererComponent(prop);
306         }
307
308         switch (preferences) {
309         case 9:
310         case 1: //PREF_READ_ONLY
311
result = getRendererComponent(prop);
312
313             break;
314
315         case 10:
316         case 2: //PREF_CUSTOM_EDITOR
317
result = new CustomEditorDisplayer(prop, model);
318
319             break;
320
321         case 11:
322         case 3: //PREF_CUSTOM_EDITOR & PREF_READ_ONLY
323
result = new CustomEditorDisplayer(prop, model);
324
325             //XXX remember to set enabled on the components
326
break;
327
328         case 12:
329         case 4: //PREF_INPUT_STATE
330
result = new EditablePropertyDisplayer(prop, model);
331
332             break;
333
334         case 13:
335         case 5: //PREF_INPUT_STATE & PREF_READ_ONLY
336
result = getRendererComponent(prop);
337
338             break;
339
340         case 14:
341         case 6: //PREF_INPUT_STATE & PREF_CUSTOM_EDIITOR
342
result = new CustomEditorDisplayer(prop, model);
343
344             //Only difference with this combination is it should display
345
//an error dialog on commit if the entered value is bad
346
break;
347
348         case 15:
349         case 7: //PREF_INPUT_STATE & PREF_CUSTOM_EDITOR & PREF_READ_ONLY
350
result = new CustomEditorDisplayer(prop, model);
351
352             break;
353
354         case 0:
355         case 8:default:
356             result = new EditablePropertyDisplayer(prop, model);
357
358             break;
359         }
360
361         if (result instanceof PropertyDisplayer_Inline) {
362             PropertyDisplayer_Inline inline = (PropertyDisplayer_Inline) result;
363             boolean tableUI = ((preferences & PREF_TABLEUI) != 0) || Boolean.TRUE.equals(getClientProperty("flat")); //NOI18N
364
inline.setTableUI(tableUI); //NOI18N
365

366             if (inline.isTableUI()) {
367                 inline.setUseLabels(!tableUI);
368             }
369         }
370
371         boolean isTableUI = (preferences & PREF_TABLEUI) != 0;
372
373         if (result instanceof CustomEditorDisplayer) {
374             ((PropertyDisplayer_Editable) result).setUpdatePolicy(
375                 changeImmediate ? PropertyDisplayer.UPDATE_ON_FOCUS_LOST : PropertyDisplayer.UPDATE_ON_EXPLICIT_REQUEST
376             );
377         } else if (result instanceof PropertyDisplayer_Editable) {
378             ((PropertyDisplayer_Editable) result).setUpdatePolicy(
379                 isTableUI ? PropertyDisplayer.UPDATE_ON_CONFIRMATION : PropertyDisplayer.UPDATE_ON_FOCUS_LOST
380             );
381         }
382
383         if (((preferences & PREF_READ_ONLY) != 0) && result instanceof CustomEditorDisplayer) {
384             ((CustomEditorDisplayer) result).setEnabled(false);
385         } else if (result instanceof PropertyDisplayer_Editable) {
386             if (!isEnabled()) {
387                 ((PropertyDisplayer_Editable) result).setEnabled(isEnabled());
388             }
389         }
390
391         return result;
392     }
393
394     /** Convenience method to allow reuse of mutable renderer components -
395      * will either create a renderer or reuse the current displayer if there
396      * is one and it's a renderer */

397     private RendererPropertyDisplayer getRendererComponent(Node.Property prop) {
398         RendererPropertyDisplayer result;
399
400         if (inner instanceof RendererPropertyDisplayer) {
401             //re-use the one we already have if possible
402
((RendererPropertyDisplayer) inner).setProperty(prop);
403             result = (RendererPropertyDisplayer) inner;
404         } else {
405             result = new RendererPropertyDisplayer(prop);
406         }
407
408         return result;
409     }
410
411     /** Fetch the PropertyDisplayer which will do the actual work of displaying
412      * the property. It will be created if necessary */

413     private PropertyDisplayer getPropertyDisplayer() {
414         if (displayer == null) {
415             setDisplayer(findPropertyDisplayer());
416         }
417
418         return displayer;
419     }
420
421     /** Installs the component we will embed to display the property */
422     private void installDisplayerComponent() {
423         //Fetch or instantiate the component we will embed to display the
424
//property. Depending on the prefs, it may be a RendererPropertyDisplayer,
425
//an EditablePropertyDisplayer or a CustomPropertyDisplayer.
426
PropertyDisplayer displayer = getPropertyDisplayer();
427
428         //Find who has focus now, so if we have focus, focus won't end up set
429
//to null
430
Component JavaDoc focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getPermanentFocusOwner();
431
432         boolean hadFocus = (focusOwner == this) || isAncestorOf(focusOwner);
433
434         if (hadFocus) {
435             //If we had focus, clear the global focus owner for now, so that
436
//when the existing component is removed, it does not cause
437
//focus to get briefly set to a random component
438
KeyboardFocusManager.getCurrentKeyboardFocusManager().clearGlobalFocusOwner();
439         }
440
441         //Fetch the new inner component (the custom editor or Inplace editor)
442
Component JavaDoc newInner = displayer.getComponent();
443
444         //Set the enabled state appropriately. For implementations of
445
//PropertyDisplayer_Editable, this will already be handled; for render-
446
//only cases, it should be handled explicitly
447
if (!(displayer instanceof PropertyDisplayer_Editable)) {
448             //only for renderers
449
newInner.setEnabled(isEnabled() && getProperty().canWrite());
450         }
451
452         newInner.setForeground(getForeground());
453         newInner.setBackground(getBackground());
454
455         //Make sure the inner component has really changed
456
if (newInner != inner) {
457             synchronized (getTreeLock()) {
458                 //remove the odl component
459
if (inner != null) {
460                     remove(inner);
461                 }
462
463                 //and add the new one (if any)
464
if (newInner != null) {
465                     add(newInner);
466
467                     //invalidate its layout so it will be re-laid out
468
newInner.invalidate();
469                     inner = newInner;
470                 }
471             }
472         }
473
474         //Force a re-layout immediately if visible
475
if (isShowing() && !(getParent() instanceof javax.swing.CellRendererPane JavaDoc)) {
476             validate();
477         }
478
479         //Restore focus if necessary
480
if (hadFocus && isEnabled() && ((preferences & PREF_READ_ONLY) == 0)) {
481             requestFocus();
482         }
483
484         //Simply adding a component to a container can sometimes cause it to be
485
//given focus even though it's not focusable. If this has happened,
486
//find the next component in the focus cycle root and force focus to that.
487
//Mainly a problem with JFileChooser, but we also have a few property
488
//editors that force focus on addNotify which should be fixed
489
if (!isEnabled() || ((preferences & PREF_READ_ONLY) != 0)) {
490             Component JavaDoc focus = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
491
492             if ((focus == inner) || ((inner instanceof Container JavaDoc) && ((Container JavaDoc) inner).isAncestorOf(focus))) {
493                 this.transferFocusUpCycle();
494             }
495         }
496     }
497
498     public void doLayout() {
499         layout();
500     }
501
502     @SuppressWarnings JavaDoc("deprecation")
503     public void layout() {
504         if (inner != null) {
505             inner.setBounds(0, 0, getWidth(), getHeight());
506         }
507     }
508
509     public Dimension JavaDoc getMinimumSize() {
510         return getPreferredSize();
511     }
512
513     public Dimension JavaDoc getPreferredSize() {
514         Dimension JavaDoc result;
515
516         if (!isDisplayable() && ((preferences & PREF_CUSTOM_EDITOR) == 0)) {
517             //XXX use rendererfactory to make this more efficient and just
518
//configure a shared renderer instacne with the property & fetch size
519
result = getRendererComponent(getProperty()).getComponent().getPreferredSize();
520         } else if (inner != null) {
521             result = inner.getPreferredSize();
522         } else {
523             result = PropUtils.getMinimumPanelSize();
524         }
525
526         return result;
527     }
528
529     /** Sets the property displayer we are using to display the property,
530      * detaching listeners, etc */

531     private void setDisplayer(PropertyDisplayer nue) {
532         if (displayer != null) {
533             detachFromDisplayer(displayer);
534         }
535
536         displayer = nue;
537
538         if (nue != null) {
539             attachToDisplayer(displayer);
540         }
541     }
542
543     /** Attach any necessary listeners to the property displayer to be used */
544     private void attachToDisplayer(PropertyDisplayer displayer) {
545         if (displayer instanceof PropertyDisplayer_Inline) {
546             updateDisplayerFromClientProps();
547         }
548
549         if (displayer instanceof CustomEditorDisplayer) {
550             ((CustomEditorDisplayer) displayer).setRemoteEnvListener(getListener());
551             ((CustomEditorDisplayer) displayer).setRemoteEnvVetoListener(getListener());
552         }
553
554         if (displayer instanceof EditablePropertyDisplayer) {
555             ((EditablePropertyDisplayer) displayer).setRemoteEnvListener(getListener());
556             ((EditablePropertyDisplayer) displayer).setRemoteEnvVetoListener(getListener());
557
558             PropertyEnv env = ((EditablePropertyDisplayer) displayer).getPropertyEnv();
559
560             if (env != null) {
561                 env.setFeatureDescriptor(getProperty());
562             }
563         }
564     }
565
566     /** Remove any listeners and dispose any state relating to a displayer
567      * we are no longer interested in */

568     private void detachFromDisplayer(PropertyDisplayer displayer) {
569         if (displayer instanceof CustomEditorDisplayer) {
570             ((CustomEditorDisplayer) displayer).setRemoteEnvVetoListener(null);
571         }
572
573         if (displayer instanceof EditablePropertyDisplayer) {
574             ((EditablePropertyDisplayer) displayer).setRemoteEnvVetoListener(null);
575         }
576     }
577
578     /** Overridden to catch changes in those client properties that are
579      * relevant to PropertyPanel */

580     protected void firePropertyChange(String JavaDoc nm, Object JavaDoc old, Object JavaDoc nue) {
581         if (
582             ("flat".equals(nm) || "radioButtonMax".equals(nm) || "suppressCustomEditor".equals(nm) ||
583                 "useLabels".equals(nm)) && (displayer != null) && displayer instanceof PropertyDisplayer_Inline
584         ) { //NOI18N
585
updateDisplayerFromClientProp(nm, nue);
586         }
587
588         super.firePropertyChange(nm, old, nue);
589     }
590
591     /** Update the current property displayer based on previously set client
592      * properties */

593     private void updateDisplayerFromClientProp(String JavaDoc nm, Object JavaDoc val) {
594         PropertyDisplayer displayer = getPropertyDisplayer();
595
596         if (displayer instanceof PropertyDisplayer_Inline) {
597             PropertyDisplayer_Inline inline = (PropertyDisplayer_Inline) displayer;
598
599             if ("flat".equals(nm)) { //NOI18N
600
inline.setTableUI(Boolean.TRUE.equals(val));
601
602                 if (Boolean.TRUE.equals(val)) {
603                     inline.setUseLabels(false);
604                 } else if (Boolean.FALSE.equals(val) && (getClientProperty("useLabels") == null)) { //NOI18N
605
inline.setUseLabels(true);
606                 }
607             } else if ("radioButtonMax".equals(nm)) { //NOI18N
608

609                 int max = (val instanceof Integer JavaDoc) ? ((Integer JavaDoc) val).intValue() : 0;
610                 inline.setRadioButtonMax(max);
611             } else if ("suppressCustomEditor".equals(nm)) { //NOI18N
612
inline.setShowCustomEditorButton(!Boolean.TRUE.equals(val));
613             } else if ("useLabels".equals(nm)) { //NOI18N
614
inline.setUseLabels(Boolean.TRUE.equals(val));
615             }
616         }
617     }
618
619     /** Overridden to return false in cases that the preferences specify a
620      * read-only state */

621     public boolean isFocusable() {
622         return super.isFocusable() && isEnabled() && ((preferences & PREF_READ_ONLY) == 0);
623     }
624
625     /** Overridden to do
626      * nothing in a read only state, since some custom property editors (File
627      * chooser) are capable of receiving focus even if they are disabled,
628      * effectively making focus disappear */

629     public void requestFocus() {
630         //Do this because even if everything is disabled, JFileChooser's UI
631
//*does* supply some focusable components
632
if (!isEnabled() || ((preferences & PREF_READ_ONLY) != 0)) {
633             return;
634         } else if ((inner != null) && inner.isEnabled()) {
635             super.requestFocus();
636             inner.requestFocus();
637         }
638     }
639
640     /** In the case that some client properties may have been set before a
641      * PropertyRenderer was added, set up its values accordingly. */

642     private void updateDisplayerFromClientProps() {
643         String JavaDoc[] props = new String JavaDoc[] { "flat", "radioButtonMax", "suppressCustomEditor", "useLabels" }; //NOI18N
644

645         for (int i = 0; i < props.length; i++) {
646             Object JavaDoc o = getClientProperty(props[i]);
647
648             if (o != null) {
649                 updateDisplayerFromClientProp(props[i], o);
650             }
651         }
652     }
653
654     protected void processFocusEvent(FocusEvent JavaDoc fe) {
655         super.processFocusEvent(fe);
656
657         if (fe.getID() == fe.FOCUS_GAINED) {
658             if ((inner != null) && inner.isEnabled() && inner.isFocusTraversable()) {
659                 inner.requestFocus();
660             }
661         }
662     }
663
664     /** Lazily create the listener for listening to the property editor, env
665      * and model */

666     private Listener JavaDoc getListener() {
667         if (listener == null) {
668             listener = new Listener JavaDoc();
669         }
670
671         return listener;
672     }
673
674     /** Overridden to install the inner component that will display the property*/
675     public void addNotify() {
676         attachToModel();
677
678         if (displayer != null) {
679             attachToDisplayer(displayer);
680         }
681
682         if (inner == null) {
683             installDisplayerComponent();
684         }
685
686         super.addNotify();
687     }
688
689     /** Overridden to dispose the component that actually displays the property
690      * and any state information associated with it */

691     public void removeNotify() {
692         super.removeNotify();
693         detachFromModel();
694
695         if ((displayer != null) && (!(displayer instanceof RendererPropertyDisplayer))) {
696             detachFromDisplayer(displayer);
697             displayer = null;
698         }
699
700         if (null != inner && !(inner instanceof RendererPropertyDisplayer)) {
701             //Renderers hold no references the property panel doesn't, so avoid
702
//creating a new one for performance reasons in TTV - PropertyPanel
703
//will be repeatedly added to and removed from a CellRendererPane
704
remove(inner);
705             inner = null;
706         }
707     }
708
709     /*
710     public Dimension getPreferredSize() {
711         Dimension result;
712         if (!isDisplayable() && (preferences & PREF_CUSTOM_EDITOR) == 0) {
713             //XXX use rendererfactory to make this more efficient and just
714             //configure a shared renderer instacne with the property & fetch size
715             result = getRendererComponent(
716                 getProperty()).getComponent().getPreferredSize();
717         } else if (inner != null) {
718             result = inner.getPreferredSize();
719         } else {
720             result = super.getPreferredSize();
721         }
722         return result;
723     }
724
725     public Dimension getMinimumSize() {
726         return getPreferredSize();
727     }
728      */

729
730     /** Returns the preferences set for this property panel. The preferences
731      * determine such things as read-only mode and whether an inline or custom
732      * editor is displayed
733      * @return The preferences
734      */

735     public int getPreferences() {
736         return preferences;
737     }
738
739     /** Setter for visual preferences in displaying
740      * of the value of the property.
741      * @param preferences PREF_XXXX constants
742      */

743     public void setPreferences(int preferences) {
744         if (preferences != this.preferences) {
745             int oldPreferences = this.preferences;
746             this.preferences = preferences;
747             hardReset();
748             firePropertyChange(PROP_PREFERENCES, oldPreferences, preferences);
749         }
750     }
751
752     /** Get the property model associated with this property panel. Note that
753      * while the PropertyModel usages of PropertyPanel are not yet deprecated,
754      * the preferred and more efficient use of PropertyPanel is directly with
755      * a Node.Property instance rather than a PropertyModel. <p><strong><b>Note:</b>
756      * This method is primarily here for backward compatibility, and the single
757      * use case where it is truly desirable to have a GUI component which reflects
758      * changes made by some source other than itself. If you have used one
759      * of the constructors which takes a <code>Node.Property</code> instance
760      * or the <code>setProperty</code> method, this method will return a
761      * PropertyModel instance generated for the underlying Node.Property object.
762      * In such a case, the PropertyModel instance thus obtained <u>will not
763      * fire changes</u> due to changes made by calling <code>setValue</code>
764      * on the <code>Node.Property</code>. For details on why this is the case,
765      * see <a HREF="http://www.netbeans.org/issues/show_bug.cgi?id=37779">issue
766      * 37779</code>.</strong>
767      *
768      * @return Either the PropertyModel set in the constructor or via <code>setModel</code>,
769      * or a generated instance of PropertyModel which wraps the <code>Node.Property</code>
770      * which was set in the constructor or via <code>setProperty</code>.
771      */

772     public PropertyModel getModel() {
773         if (model == null) {
774             return new NodePropertyModel(getProperty(), null);
775         }
776
777         return model;
778     }
779
780     /** Setter for property model.
781      * Note that while the PropertyModel usages of PropertyPanel are not yet deprecated,
782      * the preferred and more efficient use of PropertyPanel is directly with
783      * a Node.Property instance rather than a PropertyModel. The PropertyPanel
784      * will either construct a wrapper Node.Property instance or find the
785      * underlying Node.Property instance, and use that to drive its infrastructure.
786      * The only remaining use case for PropertyModel here is if the component
787      * needs to listen for changes in the underlying value <i>which do not
788      * originate in this PropertyPanel</i>.
789      *
790      *@param model New model.
791      */

792     public void setModel(PropertyModel model) {
793         if (model != this.model) {
794             settingModel = true;
795
796             if ((this.model != null) && (listener != null)) {
797                 detachFromModel();
798             }
799
800             try {
801                 if (!initializing) {
802                     setProperty(ModelProperty.toProperty(model));
803                     this.model = model;
804
805                     if (model != null) {
806                         if (isDisplayable()) {
807                             attachToModel();
808                         }
809                     }
810                 } else {
811                     this.model = model;
812                     attachToModel();
813                 }
814             } finally {
815                 settingModel = false;
816             }
817         }
818     }
819
820     /** Attach listeners to an instance of PropertyModel */
821     private final void attachToModel() {
822         if (model != null) {
823             model.addPropertyChangeListener(getListener());
824         }
825     }
826
827     /** Detach listeners from an instance of PropertyModel */
828     private final void detachFromModel() {
829         if (model != null) {
830             model.removePropertyChangeListener(getListener());
831         }
832     }
833
834     Object JavaDoc[] getBeans() {
835         return beans;
836     }
837
838     /** Set or change the property this PropertyPanel will display
839      * @param p the Property
840      */

841     public final void setProperty(Node.Property p) {
842         Object JavaDoc bridgeID = getClientProperty("beanBridgeIdentifier");
843
844         if (bridgeID != null) {
845             TTVEnvBridge bridge = TTVEnvBridge.findInstance(bridgeID);
846
847             if (bridge != null) {
848                 beans = bridge.getCurrentBeans();
849                 bridge.clear();
850             }
851         }
852
853         if (p != prop) {
854             prop = p;
855
856             if (!settingModel) {
857                 //model will be recreated dynamically
858
model = null;
859             }
860
861             if (displayer != null) {
862                 if (displayer instanceof PropertyDisplayer_Mutable) {
863                     ((PropertyDisplayer_Mutable) displayer).setProperty(p);
864                 } else {
865                     hardReset();
866                 }
867             }
868         }
869     }
870
871     /** Reset any edits in progress and restore the property's value to the
872      * component displaying it. In custom editor mode, this may cause the
873      * entire inner component to be replaced with a new instance of the same
874      * thing */

875     final void reset() {
876         if ((preferences & PREF_CUSTOM_EDITOR) != 0) {
877             getPropertyDisplayer().refresh();
878         } else {
879             hardReset();
880         }
881     }
882
883     /** Do a full reset, replacing the inner component */
884     final void hardReset() {
885         setDisplayer(findPropertyDisplayer());
886
887         if (isDisplayable()) { //XXX maybe check for isShowing, and if not just clear the inner component?
888
installDisplayerComponent();
889         }
890     }
891
892     /**
893      * Fetch the property that this PropertyPanel displays. For cases where
894      * the PropertyPanel was initialized with an instance of PropertyModel,
895      * the return value of this method is not officially defined.
896      * @return the property
897      */

898     public final Node.Property getProperty() {
899         if ((prop == null) && (model != null)) {
900             prop = ModelProperty.toProperty(model);
901         }
902
903         return prop;
904     }
905
906     /** Getter for the state of the property editor. The editor can be in
907      * not valid states just if it implements the <link>ExPropertyEditor</link>
908      * and changes state by the <code>setState</code> method of the <link>PropertyEnv</link>
909      * environment.
910      * <P>
911      * @return <code>PropertyEnv.STATE_VALID</code> if the editor is not the <code>ExPropertyEditor</code>
912      * one or other constant from <code>PropertyEnv.STATE_*</code> that was assigned to <code>PropertyEnv</code>
913      * @since 2.20
914      */

915     public final Object JavaDoc getState() {
916         if ((displayer != null) && displayer instanceof PropertyDisplayer_Editable) {
917             return ((PropertyDisplayer_Editable) displayer).getPropertyEnv().getState();
918         } else {
919             PropertyEditor JavaDoc ed = propertyEditor();
920
921             if (ed instanceof ExPropertyEditor) {
922                 //XXX until we kill ReusablePropertyModel, anyway
923
ReusablePropertyEnv env = reusableEnv;
924                 reusableModel.setProperty(prop);
925                 ((ExPropertyEditor) ed).attachEnv(env);
926
927                 return env.getState();
928             }
929         }
930
931         return PropertyEnv.STATE_VALID;
932     }
933
934     /** If the editor is <link>ExPropertyEditor</link> it tries to change the
935      * <code>getState</code> property to <code>PropertyEnv.STATE_VALID</code>
936      * state. This may be vetoed, in such case a warning is presented to the user
937      * and the <code>getState</code> will still return the original value
938      * (different from STATE_VALID).
939      * <P>
940      * Also updates the value if
941      * <code>org.openide.explorer.propertysheet.editors.EnhancedCustomPropertyEditor</code>
942      * is used.
943      */

944     public void updateValue() {
945         if ((displayer != null) && displayer instanceof PropertyDisplayer_Editable) {
946             PropertyEnv env = ((PropertyDisplayer_Editable) displayer).getPropertyEnv();
947
948             if (PropertyEnv.STATE_NEEDS_VALIDATION.equals(env.getState())) {
949                 env.setState(PropertyEnv.STATE_VALID);
950             }
951
952             if (!changeImmediate) {
953                 try {
954                     ((PropertyDisplayer_Editable) displayer).commit();
955                 } catch (IllegalArgumentException JavaDoc iae) {
956                     PropertyDialogManager.notify(iae);
957                 }
958             }
959         }
960     }
961
962     /**
963      * Getter for current property editor depending on the model.
964      * It may be <CODE>null</CODE> if it is not possible
965      * to obtain a property editor.<p>
966      * <strong>Note: When not in custom editor mode, PropertyPanel does
967      * not cache the editor supplied by the property. If the PropertyPanel
968      * was initialized from an instance of (deprecated) DefaultPropertyModel,
969      * a different instance of the property editor may be constructed and
970      * returned for each call. Client code must take this into account.
971      * </strong>. For cases of initialization via a Node.Property object,
972      * it is up to the supplied Property to cache or not cache the property
973      * editor returned from <code>getPropertyEditor()</code> as suits its
974      * needs.
975      * @deprecated The property panel does not cache the property editor, and
976      * depending on its state, it may not consistently return the same property
977      * editor instance on repeated calls. The current implementation will
978      * do so for editable states, but there is no guarantee this will remain
979      * so in the future.
980      * @return the property editor or <CODE>null</CODE>
981      */

982     public @Deprecated JavaDoc PropertyEditor JavaDoc getPropertyEditor() {
983         return propertyEditor();
984     }
985
986     /** Internal implementation of getPropertyEditor(). */
987     private PropertyEditor JavaDoc propertyEditor() {
988         PropertyEditor JavaDoc result = null;
989
990         if (displayer != null) {
991             //Use the package private methods to fetch the same editor
992
//being used by the displayer, so the state will be appropriate
993
if (displayer instanceof CustomEditorDisplayer) {
994                 result = ((CustomEditorDisplayer) displayer).getPropertyEditor();
995             } else if (displayer instanceof EditablePropertyDisplayer) {
996                 result = ((EditablePropertyDisplayer) displayer).getPropertyEditor();
997             }
998         }
999
1000        if (result == null) {
1001            //Fetch the property editor using the utility method (which will
1002
//handle no-property editor and multiple selection states
1003
//appropriately)
1004
result = PropUtils.getPropertyEditor(getProperty());
1005        }
1006
1007        return result;
1008    }
1009
1010    /** Sets whether or not this component is enabled.
1011     *
1012     * all panel components gets disabled when enabled parameter is set false
1013     * @param enabled flag defining the action.
1014     */

1015    public void setEnabled(boolean enabled) {
1016        // bugfix# 10171, explicitly disable components inside the custom editor
1017
super.setEnabled(enabled);
1018
1019        if (inner != null) {
1020            PropertyDisplayer displayer = getPropertyDisplayer();
1021
1022            if (displayer instanceof PropertyDisplayer_Editable) {
1023                //Make sure we iterate all components in the custom editor
1024
//by calling setEnabled on the PropertyDisplayer interface,
1025
//not the component
1026
((PropertyDisplayer_Editable) displayer).setEnabled(enabled);
1027            } else {
1028                //We're probably using a renderer, we now need an editable
1029
//component
1030
hardReset();
1031            }
1032        }
1033    }
1034
1035    /** Getter for property changeImmediate.
1036     * If this is true the changes made in the property editor
1037     * are immediately propagated to the value of the property
1038     * (to the property model).
1039     *
1040     * @return Value of property changeImmediate.
1041     */

1042    public boolean isChangeImmediate() {
1043        return changeImmediate;
1044    }
1045
1046    /** Setter for property changeImmediate.
1047     * IF this is true the changes made in the property editor
1048     * are immediately propagated to the value of the property
1049     * (to the property model).
1050     * @param changeImmediate New value of property changeImmediate.
1051     */

1052    public void setChangeImmediate(boolean changeImmediate) {
1053        if (this.changeImmediate == changeImmediate) {
1054            return;
1055        }
1056
1057        this.changeImmediate = changeImmediate;
1058
1059        if (isShowing()) {
1060            PropertyDisplayer displayer = getPropertyDisplayer();
1061
1062            if (displayer instanceof PropertyDisplayer_Editable) {
1063                ((PropertyDisplayer_Editable) displayer).setUpdatePolicy(
1064                    changeImmediate ? PropertyDisplayer.UPDATE_ON_FOCUS_LOST
1065                                    : PropertyDisplayer.UPDATE_ON_EXPLICIT_REQUEST
1066                );
1067            }
1068        }
1069
1070        firePropertyChange(
1071            PropertyEnv.PROP_CHANGE_IMMEDIATE, changeImmediate ? Boolean.FALSE : Boolean.TRUE,
1072            changeImmediate ? Boolean.TRUE : Boolean.FALSE
1073        );
1074    }
1075
1076    /** Overridden to provide information from the embedded property renderer
1077     * if not in custom editor mode */

1078    public String JavaDoc toString() {
1079        if ((preferences & PREF_CUSTOM_EDITOR) != 0) {
1080            //custom editor mode, not much useful to say here
1081
return super.toString() + " - " + prefsToString(getPreferences()); //NOI18N
1082
} else {
1083            return getClass().getName() + System.identityHashCode(this) + prefsToString(getPreferences()) +
1084            " propertyRenderer: " + ((inner == null) ? " null " : inner.toString()); //NOI18N
1085
}
1086    }
1087
1088    /** Utility method to convert PropertyPanel preferences to human-readable
1089     * form */

1090    private static String JavaDoc prefsToString(int prefs) {
1091        StringBuffer JavaDoc sb = new StringBuffer JavaDoc(" prefs:"); //NOI18N
1092
int[] vals = new int[] { PREF_CUSTOM_EDITOR, PREF_INPUT_STATE, PREF_READ_ONLY };
1093        String JavaDoc[] s = new String JavaDoc[] { "PREF_CUSTOM_EDITOR", "PREF_INPUT_STATE", "PREF_READ_ONLY" }; //NOI18N
1094
boolean found = false;
1095
1096        for (int i = 0; i < vals.length; i++) {
1097            if ((vals[i] & prefs) != 0) {
1098                sb.append(s[i]);
1099            }
1100
1101            if (found && (i != (vals.length - 1))) {
1102                sb.append(","); //NOI18N
1103
}
1104
1105            found = true;
1106        }
1107
1108        return sb.toString();
1109    }
1110
1111    /*
1112     * Overridden to fill in the background color, since Synth/GTKLookAndFeel ignores
1113     * setOpaque(true).
1114     * @see http://www.netbeans.org/issues/show_bug.cgi?id=43024
1115     */

1116    public void paint(Graphics JavaDoc g) {
1117        if (isGtk) {
1118            //Presumably we can get this fixed for JDK 1.5.1
1119
Color JavaDoc c = getBackground();
1120
1121            if (c == null) {
1122                c = UIManager.getColor("control"); //NOI18N
1123
}
1124
1125            if (c == null) {
1126                c = Color.LIGHT_GRAY;
1127            }
1128
1129            g.setColor(c);
1130            g.fillRect(0, 0, getWidth(), getHeight());
1131        }
1132
1133        super.paint(g);
1134    }
1135
1136    ////////////////// Accessibility support ///////////////////////////////////
1137
public javax.accessibility.AccessibleContext JavaDoc getAccessibleContext() {
1138        if (accessibleContext == null) {
1139            accessibleContext = new AccessiblePropertyPanel();
1140        }
1141
1142        return accessibleContext;
1143    }
1144
1145    /**
1146     * Held in action map to allow TreeTableView to invoke the custom editor over read-only cells
1147     */

1148    private class CustomEditorProxyAction extends AbstractAction {
1149        public void actionPerformed(ActionEvent JavaDoc e) {
1150            Action wrapped = getWrapped();
1151
1152            if (wrapped != null) {
1153                wrapped.actionPerformed(e);
1154            } else {
1155                Toolkit.getDefaultToolkit().beep();
1156            }
1157        }
1158
1159        private Action getWrapped() {
1160            Node.Property p = getProperty();
1161            EditablePropertyDisplayer pd = (getPropertyDisplayer() instanceof EditablePropertyDisplayer)
1162                ? (EditablePropertyDisplayer) getPropertyDisplayer() : new EditablePropertyDisplayer(p);
1163
1164            return pd.getCustomEditorAction();
1165        }
1166
1167        public boolean isEnabled() {
1168            Action wrapped = getWrapped();
1169
1170            if (wrapped != null) {
1171                return wrapped.isEnabled();
1172            } else {
1173                return getProperty() != null;
1174            }
1175        }
1176    }
1177
1178    private class AccessiblePropertyPanel extends AccessibleJComponent {
1179        AccessiblePropertyPanel() {
1180        }
1181
1182        public javax.accessibility.AccessibleRole JavaDoc getAccessibleRole() {
1183            return javax.accessibility.AccessibleRole.PANEL;
1184        }
1185
1186        public String JavaDoc getAccessibleName() {
1187            String JavaDoc name = super.getAccessibleName();
1188
1189            if ((name == null) && model instanceof ExPropertyModel) {
1190                FeatureDescriptor JavaDoc fd = ((ExPropertyModel) model).getFeatureDescriptor();
1191                name = NbBundle.getMessage(PropertyPanel.class, "ACS_PropertyPanel", fd.getDisplayName()); //NOI18N
1192
}
1193
1194            return name;
1195        }
1196
1197        public String JavaDoc getAccessibleDescription() {
1198            String JavaDoc description = super.getAccessibleDescription();
1199
1200            if ((description == null) && model instanceof ExPropertyModel) {
1201                FeatureDescriptor JavaDoc fd = ((ExPropertyModel) model).getFeatureDescriptor();
1202                description = NbBundle.getMessage(PropertyPanel.class, "ACSD_PropertyPanel", fd.getShortDescription()); //NOI18N
1203
}
1204
1205            return description;
1206        }
1207    }
1208
1209    private class Listener implements PropertyChangeListener JavaDoc, VetoableChangeListener JavaDoc, ChangeListener JavaDoc {
1210        public void propertyChange(PropertyChangeEvent JavaDoc evt) {
1211            if (evt.getSource() instanceof PropertyEnv) {
1212                firePropertyChange(PropertyPanel.PROP_STATE, evt.getOldValue(), evt.getNewValue());
1213            }
1214
1215            if (evt.getSource() instanceof PropertyModel) {
1216                if ((evt.getOldValue() == null) && (evt.getNewValue() == null)) {
1217                    //the old hack of firing a null-null change to get the
1218
//infrastructure to dump everything and start from scratch
1219
hardReset();
1220                } else {
1221                    //Just notify the displayer that the value may have changed,
1222
//no need for anything radical
1223
reset();
1224                }
1225            }
1226        }
1227
1228        public void vetoableChange(PropertyChangeEvent JavaDoc evt)
1229        throws PropertyVetoException JavaDoc {
1230            //do nothing - the displayer will take care of it for us
1231
}
1232
1233        public void stateChanged(javax.swing.event.ChangeEvent JavaDoc e) {
1234            //do nothing
1235
}
1236    }
1237}
1238
Popular Tags