2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
6
* The contents of this file are subject to the terms of either the GNU
7
* General Public License Version 2 only ("GPL") or the Common
8
* Development and Distribution License("CDDL") (collectively, the
9
* "License"). You may not use this file except in compliance with the
10
* License. You can obtain a copy of the License at
11
* http://www.netbeans.org/cddl-gplv2.html
12
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
* specific language governing permissions and limitations under the
14
* License. When distributing the software, include this License Header
15
* Notice in each file and include the License file at
16
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17
* particular file as subject to the "Classpath" exception as provided
18
* by Sun in the GPL Version 2 section of the License file that
19
* accompanied this code. If applicable, add the following below the
20
* License Header, with the fields enclosed by brackets [] replaced by
21
* your own identifying information:
22
* "Portions Copyrighted [year] [name of copyright owner]"
26
* The Original Software is NetBeans. The Initial Developer of the Original
27
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
28
* Microsystems, Inc. All Rights Reserved.
30
* If you wish your version of this file to be governed by only the CDDL
31
* or only the GPL Version 2, indicate your decision by adding
32
* "[Contributor] elects to include this software in this distribution
33
* under the [CDDL or GPL Version 2] license." If you do not indicate a
34
* single choice of license, a recipient has the option to distribute
35
* your version of this file under either the CDDL, the GPL Version 2 or
36
* to extend the choice of license to its licensees as provided above.
37
* However, if you add GPL Version 2 code and therefore, elected the GPL
38
* Version 2 license, then the option applies only if the new code is
39
* made subject to such option by the copyright holder.
41
package org.openide.explorer.propertysheet;
43
import org.openide.explorer.propertysheet.editors.EnhancedPropertyEditor;
46
import java.awt.event.*;
48
import java.beans.PropertyEditor;
51
import javax.swing.event.AncestorListener;
52
import javax.swing.plaf.ComboBoxUI;
53
import javax.swing.plaf.metal.MetalLookAndFeel;
54
import javax.swing.text.JTextComponent;
57
/** A combo box inplace editor. Does a couple of necessary things:
58
* 1. It does not allow the UI delegate to install a focus listener on
59
* it - it will manage opening and closing the popup on its own - this
60
* is to avoid a specific problem - that if the editor is moved to a
61
* different cell and updated, the focus lost event will arrive after
62
* it has been moved, and the UI delegate will try to close the popup
63
* when it should be opening. 2. Contains a replacement renderer for
64
* use on GTK look and feel - on JDK 1.4.2, combo boxes do not respect
65
* the value assigned by setBackground() (there is a fixme note about this
66
* in SynthComboBoxUI, so presumably this will be fixed at some point).
68
class ComboInplaceEditor extends JComboBox implements InplaceEditor, FocusListener, AncestorListener {
69
/*Keystrokes this inplace editor wants to consume */
70
static final KeyStroke[] cbKeyStrokes = new KeyStroke[] {
71
KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false),
72
KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true),
73
KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0, false),
74
KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0, false),
75
KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0, true), KeyStroke.getKeyStroke(
76
KeyEvent.VK_PAGE_UP, 0, true
79
private static PopupChecker checker = null;
80
protected PropertyEditor editor;
81
protected PropertyEnv env;
82
protected PropertyModel mdl;
83
boolean inSetUI = false;
84
private boolean tableUI;
85
private boolean connecting = false;
86
private boolean hasBeenEditable = false;
87
private boolean needLayout = false;
89
/** Create a ComboInplaceEditor - the tableUI flag will tell it to use
90
* less borders & such */
91
public ComboInplaceEditor(boolean tableUI) {
93
putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE); //NOI18N
96
if (Boolean.getBoolean("netbeans.ps.combohack")) { //NOI18N
97
setLightWeightPopupEnabled(false);
100
if (getClass() == ComboInplaceEditor.class) {
101
enableEvents(AWTEvent.FOCUS_EVENT_MASK);
104
this.tableUI = tableUI;
111
/** Overridden to add a listener to the editor if necessary, since the
112
* UI won't do that for us without a focus listener */
113
public void addNotify() {
116
if (isEditable() && (getClass() == ComboInplaceEditor.class)) {
117
getEditor().getEditorComponent().addFocusListener(this);
120
getLayout().layoutContainer(this);
123
public void setEditable(boolean val) {
124
boolean hadBeenEditable = hasBeenEditable;
125
hasBeenEditable |= val;
126
super.setEditable(val);
128
if (hadBeenEditable != hasBeenEditable) {
129
log("Combo editor for " + editor + " setEditable (" + val + ")");
134
/** Overridden to hide the popup and remove any listeners from the
136
public void removeNotify() {
137
log("Combo editor for " + editor + " removeNotify forcing popup close");
138
setPopupVisible(false);
139
super.removeNotify();
140
getEditor().getEditorComponent().removeFocusListener(this);
143
public Insets getInsets() {
144
if ("Aqua".equals(UIManager.getLookAndFeel().getID())) {
145
return new Insets(0, 0, 0, 0);
147
return super.getInsets();
151
public void clear() {
156
public void connect(PropertyEditor pe, PropertyEnv env) {
160
log("Combo editor connect to " + pe + " env=" + env);
164
setModel(new DefaultComboBoxModel(pe.getTags()));
166
boolean editable = (editor instanceof EnhancedPropertyEditor)
167
? ((EnhancedPropertyEditor) editor).supportsEditingTaggedValues()
168
: ((env != null) && Boolean.TRUE.equals(env.getFeatureDescriptor().getValue("canEditAsText"))); //NOI18N
170
setEditable(editable);
171
setActionCommand(COMMAND_SUCCESS);
178
private void log(String s) {
179
if (PropUtils.isLoggable(ComboInplaceEditor.class) && (getClass() == ComboInplaceEditor.class)) {
180
PropUtils.log(ComboInplaceEditor.class, s); //NOI18N
184
public void setSelectedItem(Object o) {
185
//Some property editors (i.e. IMT's choice editor) treat
186
//null as 0. Probably not the right way to do it, but needs to
188
if ((o == null) && (editor != null) && (editor.getTags() != null) && (editor.getTags().length > 0)) {
189
o = editor.getTags()[0];
193
super.setSelectedItem(o);
197
/** Overridden to not fire changes is an event is called inside the
199
public void fireActionEvent() {
200
if (connecting || (editor == null)) {
203
if (editor == null) {
207
if ("comboBoxEdited".equals(getActionCommand())) {
208
log("Translating comboBoxEdited action command to COMMAND_SUCCESS");
209
setActionCommand(COMMAND_SUCCESS);
212
log("Combo editor firing ActionPerformed command=" + getActionCommand());
213
super.fireActionEvent();
217
public void reset() {
218
String targetValue = null;
220
if (editor != null) {
221
log("Combo editor reset setting selected item to " + editor.getAsText());
222
targetValue = editor.getAsText();
224
//issue 26367, form editor needs ability to set a custom value
225
//when editing is initiated (event handler combos, part of them
226
//cleaning up their EnhancedPropertyEditors).
229
if ((getClass() == ComboInplaceEditor.class) && (env != null) && (env.getFeatureDescriptor() != null)) {
230
String initialEditValue = (String) env.getFeatureDescriptor().getValue("initialEditValue"); //NOI18N
232
if (initialEditValue != null) {
233
targetValue = initialEditValue;
237
setSelectedItem(targetValue);
240
public Object getValue() {
242
return getEditor().getItem();
244
return getSelectedItem();
248
public PropertyEditor getPropertyEditor() {
252
public PropertyModel getPropertyModel() {
256
public void setPropertyModel(PropertyModel pm) {
257
log("Combo editor set property model to " + pm);
261
public JComponent getComponent() {
265
public KeyStroke[] getKeyStrokes() {
269
public void handleInitialInputEvent(InputEvent e) {
270
//do nothing, this should get deprecated in InplaceEditor
273
/** Overridden to use CleanComboUI on Metal L&F to avoid extra borders */
274
public void updateUI() {
275
LookAndFeel lf = UIManager.getLookAndFeel();
276
String id = lf.getID();
277
boolean useClean = tableUI && (lf instanceof MetalLookAndFeel || "GTK".equals(id) || "Kunststoff".equals(id)); //NOI18N
280
super.setUI(PropUtils.createComboUI(this, tableUI));
285
if (tableUI & getEditor().getEditorComponent() instanceof JComponent) {
286
((JComponent) getEditor().getEditorComponent()).setBorder(null);
290
/** Overridden to set a flag used to block the UI from adding a focus
291
* listener, and to use an alternate renderer class on GTK look and feel
292
* to work around a painting bug in SynthComboUI (colors not set correctly)*/
293
public void setUI(ComboBoxUI ui) {
303
/** Overridden to handle a corner case - an NPE if the UI tries to display
304
* the popup, but the combo box is removed from the parent before that can
305
* happen - only happens on very rapid clicks between popups */
306
public void showPopup() {
308
log(" Combo editor show popup");
310
} catch (NullPointerException e) {
311
//An inevitable consequence - the look and feel will queue display
312
//of the popup, but it can be processed after the combo box is
314
log(" Combo editor show popup later due to npe");
316
SwingUtilities.invokeLater(
319
ComboInplaceEditor.super.showPopup();
326
private void prepareEditor() {
327
Component c = getEditor().getEditorComponent();
329
if (c instanceof JTextComponent) {
330
JTextComponent jtc = (JTextComponent) c;
331
String s = jtc.getText();
333
if ((s != null) && (s.length() > 0)) {
334
jtc.setSelectionStart(0);
335
jtc.setSelectionEnd(s.length());
339
jtc.setBackground(getBackground());
341
jtc.setBackground(PropUtils.getTextFieldBackground());
347
if (getLayout() != null) {
348
getLayout().layoutContainer(this);
354
/** Overridden to do the focus-popup handling that would normally be done
355
* by the look and feel */
356
public void processFocusEvent(FocusEvent fe) {
357
super.processFocusEvent(fe);
359
if (PropUtils.isLoggable(ComboInplaceEditor.class)) {
360
PropUtils.log(ComboInplaceEditor.class, "Focus event on combo " + "editor"); //NOI18N
361
PropUtils.log(ComboInplaceEditor.class, fe);
364
Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
366
if (isDisplayable() && (fe.getID() == fe.FOCUS_GAINED) && (focusOwner == this) && !isPopupVisible()) {
371
SwingUtilities.invokeLater(new PopupChecker());
376
//Try to beat the event mis-ordering at its own game
377
SwingUtilities.invokeLater(new PopupChecker());
382
} else if ((fe.getID() == fe.FOCUS_LOST) && isPopupVisible() && !isDisplayable()) {
383
if (!PropUtils.psCommitOnFocusLoss) {
384
setActionCommand(COMMAND_FAILURE);
388
//We were removed, but we may be immediately added. See if that's the
389
//case after other queued events run
390
SwingUtilities.invokeLater(
393
if (!isDisplayable()) {
404
public boolean isKnownComponent(Component c) {
405
return (c == getEditor().getEditorComponent());
408
public void setValue(Object o) {
412
/** Returns true if the combo box is editable */
413
public boolean supportsTextEntry() {
417
/** Overridden to install an ancestor listener which will ensure the
418
* popup is always opened correctly */
419
protected void installAncestorListener() {
420
//Use a replacement which will check to ensure the popup is
423
addAncestorListener(this);
425
super.installAncestorListener();
429
/** Overridden to block the UI from adding its own focus listener, which
430
* will close the popup at the wrong times. We will manage focus
431
* ourselves instead */
432
public void addFocusListener(FocusListener fl) {
433
if (!inSetUI || !tableUI) {
434
super.addFocusListener(fl);
438
public void focusGained(FocusEvent e) {
443
/** If the editor loses focus, we're done editing - fire COMMAND_FAILURE */
444
public void focusLost(FocusEvent e) {
445
Component c = e.getOppositeComponent();
447
if (!isAncestorOf(c) && (c != getEditor().getEditorComponent())) {
448
if ((c == this) || (c instanceof SheetTable && ((SheetTable) c).isAncestorOf(this))) {
449
//workaround for issue 38029 - editable combo editor can lose focus to ...itself
453
setActionCommand(COMMAND_FAILURE);
454
log(" Combo editor lost focus - setting action command to " + COMMAND_FAILURE);
455
getEditor().getEditorComponent().removeFocusListener(this);
457
if (checker == null) {
458
log("No active popup checker, firing action event");
464
/** Overridden to ensure the editor gets focus if editable */
465
public void firePopupMenuCanceled() {
466
super.firePopupMenuCanceled();
469
Component focus = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
471
if (isDisplayable() && (focus == this)) {
472
log("combo editor popup menu canceled. Requesting focus on editor component");
473
getEditor().getEditorComponent().requestFocus();
478
/** Overridden to fire COMMAND_FAILURE on Escape */
479
public void processKeyEvent(KeyEvent ke) {
480
super.processKeyEvent(ke);
482
if ((ke.getID() == ke.KEY_PRESSED) && (ke.getKeyCode() == ke.VK_ESCAPE)) {
483
setActionCommand(COMMAND_FAILURE);
488
public void ancestorAdded(javax.swing.event.AncestorEvent event) {
489
//This is where we typically have a problem with popups not showing,
490
//and below is the cure... Problem is that the popup is hidden
491
//because the combo's ancestor is changed (even though we blocked
492
//the normal ancestor listener from being added)
493
checker = new PopupChecker();
494
SwingUtilities.invokeLater(checker);
497
public void ancestorMoved(javax.swing.event.AncestorEvent event) {
499
if (needLayout && (getLayout() != null)) {
500
getLayout().layoutContainer(this);
504
public void ancestorRemoved(javax.swing.event.AncestorEvent event) {
508
public void paintChildren(Graphics g) {
509
if ((editor != null) && !hasFocus() && editor.isPaintable()) {
512
super.paintChildren(g);
516
public void paintComponent(Graphics g) {
517
//For property panel usage, allow the editor to paint
518
if ((editor != null) && !hasFocus() && editor.isPaintable()) {
519
Insets ins = getInsets();
520
Color c = g.getColor();
523
g.setColor(getBackground());
524
g.fillRect(0, 0, getWidth(), getHeight());
529
ins.left += PropUtils.getTextMargin();
533
ins.left, ins.top, getWidth() - (ins.right + ins.left), getHeight() - (ins.top + ins.bottom)
537
super.paintComponent(g);
541
/** A handy runnable which will ensure the popup is really displayed */
542
private class PopupChecker implements Runnable {
544
Window w = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow();
546
//in Java 1.5+ KeyboardFocusManager.getActiveWindow() may return null
547
if (null != w && w.isAncestorOf(ComboInplaceEditor.this)) {
548
if (isShowing() && !isPopupVisible()) {
549
log("Popup checker ensuring editor prepared or popup visible");
562
/* Replacement renderer class to hack around bug in SynthComboUI - will
563
* only be used on GTK look & feel. GTK does not set background/highlight
564
* colors correctly */
565
private class Renderer extends DefaultListCellRenderer {
566
private boolean sel = false;
568
/** Overridden to return the combo box's background color if selected
569
* and focused - in GTK L&F combo boxes are always white (there's even
570
* a "fixme" note in the code. */
571
public Color getBackground() {
572
//This method can be called in the superclass constructor, thanks
573
//to updateUI(). At that time, this==null, so an NPE would happen
574
//if we tried tor reference the outer class
575
if (ComboInplaceEditor.this == null) {
579
if (!sel && ((getText() != null) && (getSelectedItem() != null) && getText().equals(getSelectedItem()))) {
580
return ComboInplaceEditor.this.getBackground();
582
return super.getBackground();
586
public Component getListCellRendererComponent(
587
JList list, Object value, int index, boolean isSelected, boolean cellHasFocus
591
return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);