KickJava   Java API By Example, From Geeks To Geeks.

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


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.explorer.propertysheet.editors.EnhancedPropertyEditor;
22
23 import java.awt.*;
24 import java.awt.event.*;
25
26 import java.beans.PropertyEditor JavaDoc;
27
28 import javax.swing.*;
29 import javax.swing.event.AncestorListener JavaDoc;
30 import javax.swing.plaf.ComboBoxUI JavaDoc;
31 import javax.swing.plaf.metal.MetalLookAndFeel JavaDoc;
32 import javax.swing.text.JTextComponent JavaDoc;
33
34
35 /** A combo box inplace editor. Does a couple of necessary things:
36  * 1. It does not allow the UI delegate to install a focus listener on
37  * it - it will manage opening and closing the popup on its own - this
38  * is to avoid a specific problem - that if the editor is moved to a
39  * different cell and updated, the focus lost event will arrive after
40  * it has been moved, and the UI delegate will try to close the popup
41  * when it should be opening. 2. Contains a replacement renderer for
42  * use on GTK look and feel - on JDK 1.4.2, combo boxes do not respect
43  * the value assigned by setBackground() (there is a fixme note about this
44  * in SynthComboBoxUI, so presumably this will be fixed at some point).
45  */

46 class ComboInplaceEditor extends JComboBox implements InplaceEditor, FocusListener, AncestorListener JavaDoc {
47     /*Keystrokes this inplace editor wants to consume */
48     static final KeyStroke[] cbKeyStrokes = new KeyStroke[] {
49             KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false),
50             KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true),
51             KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0, false),
52             KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0, false),
53             KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0, true), KeyStroke.getKeyStroke(
54                 KeyEvent.VK_PAGE_UP, 0, true
55             )
56         };
57     private static PopupChecker checker = null;
58     protected PropertyEditor editor;
59     protected PropertyEnv env;
60     protected PropertyModel mdl;
61     boolean inSetUI = false;
62     private boolean tableUI;
63     private boolean connecting = false;
64     private boolean hasBeenEditable = false;
65     private boolean needLayout = false;
66
67     /** Create a ComboInplaceEditor - the tableUI flag will tell it to use
68      * less borders & such */

69     public ComboInplaceEditor(boolean tableUI) {
70         if (tableUI) {
71             putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE); //NOI18N
72
}
73
74         if (Boolean.getBoolean("netbeans.ps.combohack")) { //NOI18N
75
setLightWeightPopupEnabled(false);
76         }
77
78         if (getClass() == ComboInplaceEditor.class) {
79             enableEvents(AWTEvent.FOCUS_EVENT_MASK);
80         }
81
82         this.tableUI = tableUI;
83
84         if (tableUI) {
85             updateUI();
86         }
87     }
88
89     /** Overridden to add a listener to the editor if necessary, since the
90      * UI won't do that for us without a focus listener */

91     public void addNotify() {
92         super.addNotify();
93
94         if (isEditable() && (getClass() == ComboInplaceEditor.class)) {
95             getEditor().getEditorComponent().addFocusListener(this);
96         }
97
98         getLayout().layoutContainer(this);
99     }
100
101     public void setEditable(boolean val) {
102         boolean hadBeenEditable = hasBeenEditable;
103         hasBeenEditable |= val;
104         super.setEditable(val);
105
106         if (hadBeenEditable != hasBeenEditable) {
107             log("Combo editor for " + editor + " setEditable (" + val + ")");
108             needLayout = true;
109         }
110     }
111
112     /** Overridden to hide the popup and remove any listeners from the
113      * combo editor */

114     public void removeNotify() {
115         log("Combo editor for " + editor + " removeNotify forcing popup close");
116         setPopupVisible(false);
117         super.removeNotify();
118         getEditor().getEditorComponent().removeFocusListener(this);
119     }
120
121     public Insets getInsets() {
122         if ("Aqua".equals(UIManager.getLookAndFeel().getID())) {
123             return new Insets(0, 0, 0, 0);
124         } else {
125             return super.getInsets();
126         }
127     }
128
129     public void clear() {
130         editor = null;
131         env = null;
132     }
133
134     public void connect(PropertyEditor pe, PropertyEnv env) {
135         connecting = true;
136
137         try {
138             log("Combo editor connect to " + pe + " env=" + env);
139
140             this.env = env;
141             this.editor = pe;
142             setModel(new DefaultComboBoxModel(pe.getTags()));
143
144             boolean editable = (editor instanceof EnhancedPropertyEditor)
145                 ? ((EnhancedPropertyEditor) editor).supportsEditingTaggedValues()
146                 : ((env != null) && Boolean.TRUE.equals(env.getFeatureDescriptor().getValue("canEditAsText"))); //NOI18N
147

148             setEditable(editable);
149             setActionCommand(COMMAND_SUCCESS);
150             reset();
151         } finally {
152             connecting = false;
153         }
154     }
155
156     private void log(String JavaDoc s) {
157         if (PropUtils.isLoggable(ComboInplaceEditor.class) && (getClass() == ComboInplaceEditor.class)) {
158             PropUtils.log(ComboInplaceEditor.class, s); //NOI18N
159
}
160     }
161
162     public void setSelectedItem(Object JavaDoc o) {
163         //Some property editors (i.e. IMT's choice editor) treat
164
//null as 0. Probably not the right way to do it, but needs to
165
//be handled.
166
if ((o == null) && (editor != null) && (editor.getTags() != null) && (editor.getTags().length > 0)) {
167             o = editor.getTags()[0];
168         }
169
170         if (o != null) {
171             super.setSelectedItem(o);
172         }
173     }
174
175     /** Overridden to not fire changes is an event is called inside the
176      * connect method */

177     public void fireActionEvent() {
178         if (connecting || (editor == null)) {
179             return;
180         } else {
181             if (editor == null) {
182                 return;
183             }
184
185             if ("comboBoxEdited".equals(getActionCommand())) {
186                 log("Translating comboBoxEdited action command to COMMAND_SUCCESS");
187                 setActionCommand(COMMAND_SUCCESS);
188             }
189
190             log("Combo editor firing ActionPerformed command=" + getActionCommand());
191             super.fireActionEvent();
192         }
193     }
194
195     public void reset() {
196         String JavaDoc targetValue = null;
197
198         if (editor != null) {
199             log("Combo editor reset setting selected item to " + editor.getAsText());
200             targetValue = editor.getAsText();
201
202             //issue 26367, form editor needs ability to set a custom value
203
//when editing is initiated (event handler combos, part of them
204
//cleaning up their EnhancedPropertyEditors).
205
}
206
207         if ((getClass() == ComboInplaceEditor.class) && (env != null) && (env.getFeatureDescriptor() != null)) {
208             String JavaDoc initialEditValue = (String JavaDoc) env.getFeatureDescriptor().getValue("initialEditValue"); //NOI18N
209

210             if (initialEditValue != null) {
211                 targetValue = initialEditValue;
212             }
213         }
214
215         setSelectedItem(targetValue);
216     }
217
218     public Object JavaDoc getValue() {
219         if (isEditable()) {
220             return getEditor().getItem();
221         } else {
222             return getSelectedItem();
223         }
224     }
225
226     public PropertyEditor getPropertyEditor() {
227         return editor;
228     }
229
230     public PropertyModel getPropertyModel() {
231         return mdl;
232     }
233
234     public void setPropertyModel(PropertyModel pm) {
235         log("Combo editor set property model to " + pm);
236         this.mdl = pm;
237     }
238
239     public JComponent getComponent() {
240         return this;
241     }
242
243     public KeyStroke[] getKeyStrokes() {
244         return cbKeyStrokes;
245     }
246
247     public void handleInitialInputEvent(InputEvent e) {
248         //do nothing, this should get deprecated in InplaceEditor
249
}
250
251     /** Overridden to use CleanComboUI on Metal L&F to avoid extra borders */
252     public void updateUI() {
253         LookAndFeel JavaDoc lf = UIManager.getLookAndFeel();
254         String JavaDoc id = lf.getID();
255         boolean useClean = tableUI && (lf instanceof MetalLookAndFeel JavaDoc || "GTK".equals(id) || "Kunststoff".equals(id)); //NOI18N
256

257         if (useClean) {
258             super.setUI(PropUtils.createComboUI(this, tableUI));
259         } else {
260             super.updateUI();
261         }
262
263         if (tableUI & getEditor().getEditorComponent() instanceof JComponent) {
264             ((JComponent) getEditor().getEditorComponent()).setBorder(null);
265         }
266     }
267
268     /** Overridden to set a flag used to block the UI from adding a focus
269      * listener, and to use an alternate renderer class on GTK look and feel
270      * to work around a painting bug in SynthComboUI (colors not set correctly)*/

271     public void setUI(ComboBoxUI JavaDoc ui) {
272         inSetUI = true;
273
274         try {
275             super.setUI(ui);
276         } finally {
277             inSetUI = false;
278         }
279     }
280
281     /** Overridden to handle a corner case - an NPE if the UI tries to display
282      * the popup, but the combo box is removed from the parent before that can
283      * happen - only happens on very rapid clicks between popups */

284     public void showPopup() {
285         try {
286             log(" Combo editor show popup");
287             super.showPopup();
288         } catch (NullPointerException JavaDoc e) {
289             //An inevitable consequence - the look and feel will queue display
290
//of the popup, but it can be processed after the combo box is
291
//offscreen
292
log(" Combo editor show popup later due to npe");
293
294             SwingUtilities.invokeLater(
295                 new Runnable JavaDoc() {
296                     public void run() {
297                         ComboInplaceEditor.super.showPopup();
298                     }
299                 }
300             );
301         }
302     }
303
304     private void prepareEditor() {
305         Component JavaDoc c = getEditor().getEditorComponent();
306
307         if (c instanceof JTextComponent JavaDoc) {
308             JTextComponent JavaDoc jtc = (JTextComponent JavaDoc) c;
309             String JavaDoc s = jtc.getText();
310
311             if ((s != null) && (s.length() > 0)) {
312                 jtc.setSelectionStart(0);
313                 jtc.setSelectionEnd(s.length());
314             }
315
316             if (tableUI) {
317                 jtc.setBackground(getBackground());
318             } else {
319                 jtc.setBackground(PropUtils.getTextFieldBackground());
320             }
321             if( tableUI )
322                 jtc.requestFocus();
323         }
324
325         if (getLayout() != null) {
326             getLayout().layoutContainer(this);
327         }
328
329         repaint();
330     }
331
332     /** Overridden to do the focus-popup handling that would normally be done
333      * by the look and feel */

334     public void processFocusEvent(FocusEvent fe) {
335         super.processFocusEvent(fe);
336
337         if (PropUtils.isLoggable(ComboInplaceEditor.class)) {
338             PropUtils.log(ComboInplaceEditor.class, "Focus event on combo " + "editor"); //NOI18N
339
PropUtils.log(ComboInplaceEditor.class, fe);
340         }
341
342         Component JavaDoc focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
343
344         if (isDisplayable() && (fe.getID() == fe.FOCUS_GAINED) && (focusOwner == this) && !isPopupVisible()) {
345             if (isEditable()) {
346                 prepareEditor();
347
348                 if( tableUI )
349                     SwingUtilities.invokeLater(new PopupChecker());
350             } else {
351                 if (tableUI) {
352                     showPopup();
353
354                     //Try to beat the event mis-ordering at its own game
355
SwingUtilities.invokeLater(new PopupChecker());
356                 }
357             }
358
359             repaint();
360         } else if ((fe.getID() == fe.FOCUS_LOST) && isPopupVisible() && !isDisplayable()) {
361             if (!PropUtils.psCommitOnFocusLoss) {
362                 setActionCommand(COMMAND_FAILURE);
363                 fireActionEvent();
364             }
365
366             //We were removed, but we may be immediately added. See if that's the
367
//case after other queued events run
368
SwingUtilities.invokeLater(
369                 new Runnable JavaDoc() {
370                     public void run() {
371                         if (!isDisplayable()) {
372                             hidePopup();
373                         }
374                     }
375                 }
376             );
377         }
378
379         repaint();
380     }
381
382     public boolean isKnownComponent(Component JavaDoc c) {
383         return (c == getEditor().getEditorComponent());
384     }
385
386     public void setValue(Object JavaDoc o) {
387         setSelectedItem(o);
388     }
389
390     /** Returns true if the combo box is editable */
391     public boolean supportsTextEntry() {
392         return isEditable();
393     }
394
395     /** Overridden to install an ancestor listener which will ensure the
396      * popup is always opened correctly */

397     protected void installAncestorListener() {
398         //Use a replacement which will check to ensure the popup is
399
//displayed
400
if (tableUI) {
401             addAncestorListener(this);
402         } else {
403             super.installAncestorListener();
404         }
405     }
406
407     /** Overridden to block the UI from adding its own focus listener, which
408      * will close the popup at the wrong times. We will manage focus
409      * ourselves instead */

410     public void addFocusListener(FocusListener fl) {
411         if (!inSetUI || !tableUI) {
412             super.addFocusListener(fl);
413         }
414     }
415
416     public void focusGained(FocusEvent e) {
417         //do nothing
418
prepareEditor();
419     }
420
421     /** If the editor loses focus, we're done editing - fire COMMAND_FAILURE */
422     public void focusLost(FocusEvent e) {
423         Component JavaDoc c = e.getOppositeComponent();
424
425         if (!isAncestorOf(c) && (c != getEditor().getEditorComponent())) {
426             if ((c == this) || (c instanceof SheetTable && ((SheetTable) c).isAncestorOf(this))) {
427                 //workaround for issue 38029 - editable combo editor can lose focus to ...itself
428
return;
429             }
430
431             setActionCommand(COMMAND_FAILURE);
432             log(" Combo editor lost focus - setting action command to " + COMMAND_FAILURE);
433             getEditor().getEditorComponent().removeFocusListener(this);
434
435             if (checker == null) {
436                 log("No active popup checker, firing action event");
437                 fireActionEvent();
438             }
439         }
440     }
441
442     /** Overridden to ensure the editor gets focus if editable */
443     public void firePopupMenuCanceled() {
444         super.firePopupMenuCanceled();
445
446         if (isEditable()) {
447             Component JavaDoc focus = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
448
449             if (isDisplayable() && (focus == this)) {
450                 log("combo editor popup menu canceled. Requesting focus on editor component");
451                 getEditor().getEditorComponent().requestFocus();
452             }
453         }
454     }
455
456     /** Overridden to fire COMMAND_FAILURE on Escape */
457     public void processKeyEvent(KeyEvent ke) {
458         super.processKeyEvent(ke);
459
460         if ((ke.getID() == ke.KEY_PRESSED) && (ke.getKeyCode() == ke.VK_ESCAPE)) {
461             setActionCommand(COMMAND_FAILURE);
462             fireActionEvent();
463         }
464     }
465
466     public void ancestorAdded(javax.swing.event.AncestorEvent JavaDoc event) {
467         //This is where we typically have a problem with popups not showing,
468
//and below is the cure... Problem is that the popup is hidden
469
//because the combo's ancestor is changed (even though we blocked
470
//the normal ancestor listener from being added)
471
checker = new PopupChecker();
472         SwingUtilities.invokeLater(checker);
473     }
474
475     public void ancestorMoved(javax.swing.event.AncestorEvent JavaDoc event) {
476         //do nothing
477
if (needLayout && (getLayout() != null)) {
478             getLayout().layoutContainer(this);
479         }
480     }
481
482     public void ancestorRemoved(javax.swing.event.AncestorEvent JavaDoc event) {
483         //do nothing
484
}
485
486     public void paintChildren(Graphics g) {
487         if ((editor != null) && !hasFocus() && editor.isPaintable()) {
488             return;
489         } else {
490             super.paintChildren(g);
491         }
492     }
493
494     public void paintComponent(Graphics g) {
495         //For property panel usage, allow the editor to paint
496
if ((editor != null) && !hasFocus() && editor.isPaintable()) {
497             Insets ins = getInsets();
498             Color c = g.getColor();
499
500             try {
501                 g.setColor(getBackground());
502                 g.fillRect(0, 0, getWidth(), getHeight());
503             } finally {
504                 g.setColor(c);
505             }
506
507             ins.left += PropUtils.getTextMargin();
508             editor.paintValue(
509                 g,
510                 new Rectangle(
511                     ins.left, ins.top, getWidth() - (ins.right + ins.left), getHeight() - (ins.top + ins.bottom)
512                 )
513             );
514         } else {
515             super.paintComponent(g);
516         }
517     }
518
519     /** A handy runnable which will ensure the popup is really displayed */
520     private class PopupChecker implements Runnable JavaDoc {
521         public void run() {
522             Window w = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow();
523
524             //in Java 1.5+ KeyboardFocusManager.getActiveWindow() may return null
525
if (null != w && w.isAncestorOf(ComboInplaceEditor.this)) {
526                 if (isShowing() && !isPopupVisible()) {
527                     log("Popup checker ensuring editor prepared or popup visible");
528
529                     if (isEditable()) {
530                         prepareEditor();
531                     }
532                     showPopup();
533                 }
534
535                 checker = null;
536             }
537         }
538     }
539
540     /* Replacement renderer class to hack around bug in SynthComboUI - will
541     * only be used on GTK look & feel. GTK does not set background/highlight
542     * colors correctly */

543     private class Renderer extends DefaultListCellRenderer {
544         private boolean sel = false;
545
546         /** Overridden to return the combo box's background color if selected
547          * and focused - in GTK L&F combo boxes are always white (there's even
548          * a "fixme" note in the code. */

549         public Color getBackground() {
550             //This method can be called in the superclass constructor, thanks
551
//to updateUI(). At that time, this==null, so an NPE would happen
552
//if we tried tor reference the outer class
553
if (ComboInplaceEditor.this == null) {
554                 return null;
555             }
556
557             if (!sel && ((getText() != null) && (getSelectedItem() != null) && getText().equals(getSelectedItem()))) {
558                 return ComboInplaceEditor.this.getBackground();
559             } else {
560                 return super.getBackground();
561             }
562         }
563
564         public Component JavaDoc getListCellRendererComponent(
565             JList list, Object JavaDoc value, int index, boolean isSelected, boolean cellHasFocus
566         ) {
567             sel = isSelected;
568
569             return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
570         }
571     }
572 }
573
Popular Tags