001 /* BasicComboBoxUI.java --
002 Copyright (C) 2004, 2005, 2006, Free Software Foundation, Inc.
003
004 This file is part of GNU Classpath.
005
006 GNU Classpath is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU Classpath is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU Classpath; see the file COPYING. If not, write to the
018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019 02110-1301 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library. Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module. An independent module is a module which is not derived from
033 or based on this library. If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so. If you do not wish to do so, delete this
036 exception statement from your version. */
037
038
039 package javax.swing.plaf.basic;
040
041 import java.awt.Color;
042 import java.awt.Component;
043 import java.awt.Container;
044 import java.awt.Dimension;
045 import java.awt.Font;
046 import java.awt.Graphics;
047 import java.awt.Insets;
048 import java.awt.LayoutManager;
049 import java.awt.Rectangle;
050 import java.awt.event.FocusEvent;
051 import java.awt.event.FocusListener;
052 import java.awt.event.ItemEvent;
053 import java.awt.event.ItemListener;
054 import java.awt.event.KeyAdapter;
055 import java.awt.event.KeyEvent;
056 import java.awt.event.KeyListener;
057 import java.awt.event.MouseListener;
058 import java.awt.event.MouseMotionListener;
059 import java.beans.PropertyChangeEvent;
060 import java.beans.PropertyChangeListener;
061
062 import javax.accessibility.Accessible;
063 import javax.accessibility.AccessibleContext;
064 import javax.swing.CellRendererPane;
065 import javax.swing.ComboBoxEditor;
066 import javax.swing.ComboBoxModel;
067 import javax.swing.DefaultListCellRenderer;
068 import javax.swing.InputMap;
069 import javax.swing.JButton;
070 import javax.swing.JComboBox;
071 import javax.swing.JComponent;
072 import javax.swing.JList;
073 import javax.swing.ListCellRenderer;
074 import javax.swing.LookAndFeel;
075 import javax.swing.SwingUtilities;
076 import javax.swing.UIManager;
077 import javax.swing.event.ListDataEvent;
078 import javax.swing.event.ListDataListener;
079 import javax.swing.plaf.ComboBoxUI;
080 import javax.swing.plaf.ComponentUI;
081 import javax.swing.plaf.UIResource;
082
083 /**
084 * A UI delegate for the {@link JComboBox} component.
085 *
086 * @author Olga Rodimina
087 * @author Robert Schuster
088 */
089 public class BasicComboBoxUI extends ComboBoxUI
090 {
091 /**
092 * The arrow button that is displayed in the right side of JComboBox. This
093 * button is used to hide and show combo box's list of items.
094 */
095 protected JButton arrowButton;
096
097 /**
098 * The combo box represented by this UI delegate.
099 */
100 protected JComboBox comboBox;
101
102 /**
103 * The component that is responsible for displaying/editing the selected
104 * item of the combo box.
105 *
106 * @see BasicComboBoxEditor#getEditorComponent()
107 */
108 protected Component editor;
109
110 /**
111 * A listener listening to focus events occurring in the {@link JComboBox}.
112 */
113 protected FocusListener focusListener;
114
115 /**
116 * A flag indicating whether JComboBox currently has the focus.
117 */
118 protected boolean hasFocus;
119
120 /**
121 * A listener listening to item events fired by the {@link JComboBox}.
122 */
123 protected ItemListener itemListener;
124
125 /**
126 * A listener listening to key events that occur while {@link JComboBox} has
127 * the focus.
128 */
129 protected KeyListener keyListener;
130
131 /**
132 * List used when rendering selected item of the combo box. The selection
133 * and foreground colors for combo box renderer are configured from this
134 * list.
135 */
136 protected JList listBox;
137
138 /**
139 * ListDataListener listening to JComboBox model
140 */
141 protected ListDataListener listDataListener;
142
143 /**
144 * Popup list containing the combo box's menu items.
145 */
146 protected ComboPopup popup;
147
148 protected KeyListener popupKeyListener;
149
150 protected MouseListener popupMouseListener;
151
152 protected MouseMotionListener popupMouseMotionListener;
153
154 /**
155 * Listener listening to changes in the bound properties of JComboBox
156 */
157 protected PropertyChangeListener propertyChangeListener;
158
159 /* Size of the largest item in the comboBox
160 * This is package-private to avoid an accessor method.
161 */
162 Dimension displaySize = new Dimension();
163
164 /**
165 * Used to render the combo box values.
166 */
167 protected CellRendererPane currentValuePane;
168
169 /**
170 * The current minimum size if isMinimumSizeDirty is false.
171 * Setup by getMinimumSize() and invalidated by the various listeners.
172 */
173 protected Dimension cachedMinimumSize;
174
175 /**
176 * Indicates whether or not the cachedMinimumSize field is valid or not.
177 */
178 protected boolean isMinimumSizeDirty = true;
179
180 /**
181 * Creates a new <code>BasicComboBoxUI</code> object.
182 */
183 public BasicComboBoxUI()
184 {
185 currentValuePane = new CellRendererPane();
186 cachedMinimumSize = new Dimension();
187 }
188
189 /**
190 * A factory method to create a UI delegate for the given
191 * {@link JComponent}, which should be a {@link JComboBox}.
192 *
193 * @param c The {@link JComponent} a UI is being created for.
194 *
195 * @return A UI delegate for the {@link JComponent}.
196 */
197 public static ComponentUI createUI(JComponent c)
198 {
199 return new BasicComboBoxUI();
200 }
201
202 /**
203 * Installs the UI for the given {@link JComponent}.
204 *
205 * @param c the JComponent to install a UI for.
206 *
207 * @see #uninstallUI(JComponent)
208 */
209 public void installUI(JComponent c)
210 {
211 super.installUI(c);
212
213 if (c instanceof JComboBox)
214 {
215 isMinimumSizeDirty = true;
216 comboBox = (JComboBox) c;
217 installDefaults();
218 popup = createPopup();
219 listBox = popup.getList();
220
221 // Set editor and renderer for the combo box. Editor is used
222 // only if combo box becomes editable, otherwise renderer is used
223 // to paint the selected item; combobox is not editable by default.
224 ListCellRenderer renderer = comboBox.getRenderer();
225 if (renderer == null || renderer instanceof UIResource)
226 comboBox.setRenderer(createRenderer());
227
228 ComboBoxEditor currentEditor = comboBox.getEditor();
229 if (currentEditor == null || currentEditor instanceof UIResource)
230 {
231 currentEditor = createEditor();
232 comboBox.setEditor(currentEditor);
233 }
234
235 installComponents();
236 installListeners();
237 comboBox.setLayout(createLayoutManager());
238 comboBox.setFocusable(true);
239 installKeyboardActions();
240 comboBox.putClientProperty(BasicLookAndFeel.DONT_CANCEL_POPUP,
241 Boolean.TRUE);
242 }
243 }
244
245 /**
246 * Uninstalls the UI for the given {@link JComponent}.
247 *
248 * @param c The JComponent that is having this UI removed.
249 *
250 * @see #installUI(JComponent)
251 */
252 public void uninstallUI(JComponent c)
253 {
254 setPopupVisible(comboBox, false);
255 popup.uninstallingUI();
256 uninstallKeyboardActions();
257 comboBox.setLayout(null);
258 uninstallComponents();
259 uninstallListeners();
260 uninstallDefaults();
261 comboBox = null;
262 }
263
264 /**
265 * Installs the defaults that are defined in the {@link BasicLookAndFeel}
266 * for this {@link JComboBox}.
267 *
268 * @see #uninstallDefaults()
269 */
270 protected void installDefaults()
271 {
272 LookAndFeel.installColorsAndFont(comboBox, "ComboBox.background",
273 "ComboBox.foreground", "ComboBox.font");
274 LookAndFeel.installBorder(comboBox, "ComboBox.border");
275 }
276
277 /**
278 * Creates and installs the listeners for this UI.
279 *
280 * @see #uninstallListeners()
281 */
282 protected void installListeners()
283 {
284 // install combo box's listeners
285 propertyChangeListener = createPropertyChangeListener();
286 comboBox.addPropertyChangeListener(propertyChangeListener);
287
288 focusListener = createFocusListener();
289 comboBox.addFocusListener(focusListener);
290
291 itemListener = createItemListener();
292 comboBox.addItemListener(itemListener);
293
294 keyListener = createKeyListener();
295 comboBox.addKeyListener(keyListener);
296
297 // install listeners that listen to combo box model
298 listDataListener = createListDataListener();
299 comboBox.getModel().addListDataListener(listDataListener);
300
301 // Install mouse and key listeners from the popup.
302 popupMouseListener = popup.getMouseListener();
303 comboBox.addMouseListener(popupMouseListener);
304
305 popupMouseMotionListener = popup.getMouseMotionListener();
306 comboBox.addMouseMotionListener(popupMouseMotionListener);
307
308 popupKeyListener = popup.getKeyListener();
309 comboBox.addKeyListener(popupKeyListener);
310 }
311
312 /**
313 * Uninstalls the defaults and sets any objects created during
314 * install to <code>null</code>.
315 *
316 * @see #installDefaults()
317 */
318 protected void uninstallDefaults()
319 {
320 if (comboBox.getFont() instanceof UIResource)
321 comboBox.setFont(null);
322
323 if (comboBox.getForeground() instanceof UIResource)
324 comboBox.setForeground(null);
325
326 if (comboBox.getBackground() instanceof UIResource)
327 comboBox.setBackground(null);
328
329 LookAndFeel.uninstallBorder(comboBox);
330 }
331
332 /**
333 * Detaches all the listeners we attached in {@link #installListeners}.
334 *
335 * @see #installListeners()
336 */
337 protected void uninstallListeners()
338 {
339 comboBox.removePropertyChangeListener(propertyChangeListener);
340 propertyChangeListener = null;
341
342 comboBox.removeFocusListener(focusListener);
343 listBox.removeFocusListener(focusListener);
344 focusListener = null;
345
346 comboBox.removeItemListener(itemListener);
347 itemListener = null;
348
349 comboBox.removeKeyListener(keyListener);
350 keyListener = null;
351
352 comboBox.getModel().removeListDataListener(listDataListener);
353 listDataListener = null;
354
355 if (popupMouseListener != null)
356 comboBox.removeMouseListener(popupMouseListener);
357 popupMouseListener = null;
358
359 if (popupMouseMotionListener != null)
360 comboBox.removeMouseMotionListener(popupMouseMotionListener);
361 popupMouseMotionListener = null;
362
363 if (popupKeyListener != null)
364 comboBox.removeKeyListener(popupKeyListener);
365 popupKeyListener = null;
366 }
367
368 /**
369 * Creates the popup that will contain list of combo box's items.
370 *
371 * @return popup containing list of combo box's items
372 */
373 protected ComboPopup createPopup()
374 {
375 return new BasicComboPopup(comboBox);
376 }
377
378 /**
379 * Creates a {@link KeyListener} to listen to key events.
380 *
381 * @return KeyListener that listens to key events.
382 */
383 protected KeyListener createKeyListener()
384 {
385 return new KeyHandler();
386 }
387
388 /**
389 * Creates the {@link FocusListener} that will listen to changes in this
390 * JComboBox's focus.
391 *
392 * @return the FocusListener.
393 */
394 protected FocusListener createFocusListener()
395 {
396 return new FocusHandler();
397 }
398
399 /**
400 * Creates a {@link ListDataListener} to listen to the combo box's data model.
401 *
402 * @return The new listener.
403 */
404 protected ListDataListener createListDataListener()
405 {
406 return new ListDataHandler();
407 }
408
409 /**
410 * Creates an {@link ItemListener} that will listen to the changes in
411 * the JComboBox's selection.
412 *
413 * @return The ItemListener
414 */
415 protected ItemListener createItemListener()
416 {
417 return new ItemHandler();
418 }
419
420 /**
421 * Creates a {@link PropertyChangeListener} to listen to the changes in
422 * the JComboBox's bound properties.
423 *
424 * @return The PropertyChangeListener
425 */
426 protected PropertyChangeListener createPropertyChangeListener()
427 {
428 return new PropertyChangeHandler();
429 }
430
431 /**
432 * Creates and returns a layout manager for the combo box. Subclasses can
433 * override this method to provide a different layout.
434 *
435 * @return a layout manager for the combo box.
436 */
437 protected LayoutManager createLayoutManager()
438 {
439 return new ComboBoxLayoutManager();
440 }
441
442 /**
443 * Creates a component that will be responsible for rendering the
444 * selected component in the combo box.
445 *
446 * @return A renderer for the combo box.
447 */
448 protected ListCellRenderer createRenderer()
449 {
450 return new BasicComboBoxRenderer.UIResource();
451 }
452
453 /**
454 * Creates the component that will be responsible for displaying/editing
455 * the selected item in the combo box. This editor is used only when combo
456 * box is editable.
457 *
458 * @return A new component that will be responsible for displaying/editing
459 * the selected item in the combo box.
460 */
461 protected ComboBoxEditor createEditor()
462 {
463 return new BasicComboBoxEditor.UIResource();
464 }
465
466 /**
467 * Installs the components for this JComboBox. ArrowButton, main
468 * part of combo box (upper part) and popup list of items are created and
469 * configured here.
470 */
471 protected void installComponents()
472 {
473 // create and install arrow button
474 arrowButton = createArrowButton();
475 comboBox.add(arrowButton);
476 if (arrowButton != null)
477 configureArrowButton();
478
479 if (comboBox.isEditable())
480 addEditor();
481
482 comboBox.add(currentValuePane);
483 }
484
485 /**
486 * Uninstalls components from this {@link JComboBox}.
487 *
488 * @see #installComponents()
489 */
490 protected void uninstallComponents()
491 {
492 // Unconfigure arrow button.
493 if (arrowButton != null)
494 {
495 unconfigureArrowButton();
496 }
497
498 // Unconfigure editor.
499 if (editor != null)
500 {
501 unconfigureEditor();
502 }
503
504 comboBox.removeAll();
505 arrowButton = null;
506 }
507
508 /**
509 * Adds the current editor to the combo box.
510 */
511 public void addEditor()
512 {
513 removeEditor();
514 editor = comboBox.getEditor().getEditorComponent();
515 if (editor != null)
516 {
517 configureEditor();
518 comboBox.add(editor);
519 }
520 }
521
522 /**
523 * Removes the current editor from the combo box.
524 */
525 public void removeEditor()
526 {
527 if (editor != null)
528 {
529 unconfigureEditor();
530 comboBox.remove(editor);
531 }
532 }
533
534 /**
535 * Configures the editor for this combo box.
536 */
537 protected void configureEditor()
538 {
539 editor.setFont(comboBox.getFont());
540 if (popupKeyListener != null)
541 editor.addKeyListener(popupKeyListener);
542 if (keyListener != null)
543 editor.addKeyListener(keyListener);
544 comboBox.configureEditor(comboBox.getEditor(),
545 comboBox.getSelectedItem());
546 }
547
548 /**
549 * Unconfigures the editor for this combo box.
550 */
551 protected void unconfigureEditor()
552 {
553 if (popupKeyListener != null)
554 editor.removeKeyListener(popupKeyListener);
555 if (keyListener != null)
556 editor.removeKeyListener(keyListener);
557 }
558
559 /**
560 * Configures the arrow button.
561 *
562 * @see #configureArrowButton()
563 */
564 public void configureArrowButton()
565 {
566 if (arrowButton != null)
567 {
568 arrowButton.setEnabled(comboBox.isEnabled());
569 arrowButton.setFocusable(false);
570 arrowButton.addMouseListener(popup.getMouseListener());
571 arrowButton.addMouseMotionListener(popup.getMouseMotionListener());
572
573 // Mark the button as not closing the popup, we handle this ourselves.
574 arrowButton.putClientProperty(BasicLookAndFeel.DONT_CANCEL_POPUP,
575 Boolean.TRUE);
576 }
577 }
578
579 /**
580 * Unconfigures the arrow button.
581 *
582 * @see #configureArrowButton()
583 *
584 * @specnote The specification says this method is implementation specific
585 * and should not be used or overridden.
586 */
587 public void unconfigureArrowButton()
588 {
589 if (arrowButton != null)
590 {
591 if (popupMouseListener != null)
592 arrowButton.removeMouseListener(popupMouseListener);
593 if (popupMouseMotionListener != null)
594 arrowButton.removeMouseMotionListener(popupMouseMotionListener);
595 }
596 }
597
598 /**
599 * Creates an arrow button for this {@link JComboBox}. The arrow button is
600 * displayed at the right end of the combo box and is used to display/hide
601 * the drop down list of items.
602 *
603 * @return A new button.
604 */
605 protected JButton createArrowButton()
606 {
607 return new BasicArrowButton(BasicArrowButton.SOUTH);
608 }
609
610 /**
611 * Returns <code>true</code> if the popup is visible, and <code>false</code>
612 * otherwise.
613 *
614 * @param c The JComboBox to check
615 *
616 * @return <code>true</code> if popup part of the JComboBox is visible and
617 * <code>false</code> otherwise.
618 */
619 public boolean isPopupVisible(JComboBox c)
620 {
621 return popup.isVisible();
622 }
623
624 /**
625 * Displays/hides the {@link JComboBox}'s list of items on the screen.
626 *
627 * @param c The combo box, for which list of items should be
628 * displayed/hidden
629 * @param v true if show popup part of the jcomboBox and false to hide.
630 */
631 public void setPopupVisible(JComboBox c, boolean v)
632 {
633 if (v)
634 popup.show();
635 else
636 popup.hide();
637 }
638
639 /**
640 * JComboBox is focus traversable if it is editable and not otherwise.
641 *
642 * @param c combo box for which to check whether it is focus traversable
643 *
644 * @return true if focus tranversable and false otherwise
645 */
646 public boolean isFocusTraversable(JComboBox c)
647 {
648 if (!comboBox.isEditable())
649 return true;
650
651 return false;
652 }
653
654 /**
655 * Paints given menu item using specified graphics context
656 *
657 * @param g The graphics context used to paint this combo box
658 * @param c comboBox which needs to be painted.
659 */
660 public void paint(Graphics g, JComponent c)
661 {
662 hasFocus = comboBox.hasFocus();
663 if (! comboBox.isEditable())
664 {
665 Rectangle rect = rectangleForCurrentValue();
666 paintCurrentValueBackground(g, rect, hasFocus);
667 paintCurrentValue(g, rect, hasFocus);
668 }
669 }
670
671 /**
672 * Returns preferred size for the combo box.
673 *
674 * @param c comboBox for which to get preferred size
675 *
676 * @return The preferred size for the given combo box
677 */
678 public Dimension getPreferredSize(JComponent c)
679 {
680 return getMinimumSize(c);
681 }
682
683 /**
684 * Returns the minimum size for this {@link JComboBox} for this
685 * look and feel. Also makes sure cachedMinimimSize is setup correctly.
686 *
687 * @param c The {@link JComponent} to find the minimum size for.
688 *
689 * @return The dimensions of the minimum size.
690 */
691 public Dimension getMinimumSize(JComponent c)
692 {
693 if (isMinimumSizeDirty)
694 {
695 Insets i = getInsets();
696 Dimension d = getDisplaySize();
697 d.width += i.left + i.right + d.height;
698 cachedMinimumSize = new Dimension(d.width, d.height + i.top + i.bottom);
699 isMinimumSizeDirty = false;
700 }
701 return new Dimension(cachedMinimumSize);
702 }
703
704 /**
705 * Returns the maximum size for this {@link JComboBox} for this
706 * look and feel.
707 *
708 * @param c The {@link JComponent} to find the maximum size for
709 *
710 * @return The maximum size (<code>Dimension(32767, 32767)</code>).
711 */
712 public Dimension getMaximumSize(JComponent c)
713 {
714 return new Dimension(32767, 32767);
715 }
716
717 /**
718 * Returns the number of accessible children of the combobox.
719 *
720 * @param c the component (combobox) to check, ignored
721 *
722 * @return the number of accessible children of the combobox
723 */
724 public int getAccessibleChildrenCount(JComponent c)
725 {
726 int count = 1;
727 if (comboBox.isEditable())
728 count = 2;
729 return count;
730 }
731
732 /**
733 * Returns the accessible child with the specified index.
734 *
735 * @param c the component, this is ignored
736 * @param i the index of the accessible child to return
737 */
738 public Accessible getAccessibleChild(JComponent c, int i)
739 {
740 Accessible child = null;
741 switch (i)
742 {
743 case 0: // The popup.
744 if (popup instanceof Accessible)
745 {
746 AccessibleContext ctx = ((Accessible) popup).getAccessibleContext();
747 ctx.setAccessibleParent(comboBox);
748 child = (Accessible) popup;
749 }
750 break;
751 case 1: // The editor, if any.
752 if (comboBox.isEditable() && editor instanceof Accessible)
753 {
754 AccessibleContext ctx =
755 ((Accessible) editor).getAccessibleContext();
756 ctx.setAccessibleParent(comboBox);
757 child = (Accessible) editor;
758 }
759 break;
760 }
761 return child;
762 }
763
764 /**
765 * Returns true if the specified key is a navigation key and false otherwise
766 *
767 * @param keyCode a key for which to check whether it is navigation key or
768 * not.
769 *
770 * @return true if the specified key is a navigation key and false otherwis
771 */
772 protected boolean isNavigationKey(int keyCode)
773 {
774 return keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_DOWN
775 || keyCode == KeyEvent.VK_LEFT || keyCode == KeyEvent.VK_RIGHT
776 || keyCode == KeyEvent.VK_ENTER || keyCode == KeyEvent.VK_ESCAPE
777 || keyCode == KeyEvent.VK_TAB;
778 }
779
780 /**
781 * Selects next possible item relative to the current selection
782 * to be next selected item in the combo box.
783 */
784 protected void selectNextPossibleValue()
785 {
786 int index = comboBox.getSelectedIndex();
787 if (index != comboBox.getItemCount() - 1)
788 comboBox.setSelectedIndex(index + 1);
789 }
790
791 /**
792 * Selects previous item relative to current selection to be
793 * next selected item.
794 */
795 protected void selectPreviousPossibleValue()
796 {
797 int index = comboBox.getSelectedIndex();
798 if (index > 0)
799 comboBox.setSelectedIndex(index - 1);
800 }
801
802 /**
803 * Displays combo box popup if the popup is not currently shown
804 * on the screen and hides it if it is currently shown
805 */
806 protected void toggleOpenClose()
807 {
808 setPopupVisible(comboBox, ! isPopupVisible(comboBox));
809 }
810
811 /**
812 * Returns the bounds in which comboBox's selected item will be
813 * displayed.
814 *
815 * @return rectangle bounds in which comboBox's selected Item will be
816 * displayed
817 */
818 protected Rectangle rectangleForCurrentValue()
819 {
820 int w = comboBox.getWidth();
821 int h = comboBox.getHeight();
822 Insets i = comboBox.getInsets();
823 int arrowSize = h - (i.top + i.bottom);
824 if (arrowButton != null)
825 arrowSize = arrowButton.getWidth();
826 return new Rectangle(i.left, i.top, w - (i.left + i.right + arrowSize),
827 h - (i.top + i.left));
828 }
829
830 /**
831 * Returns the insets of the current border.
832 *
833 * @return Insets representing space between combo box and its border
834 */
835 protected Insets getInsets()
836 {
837 return comboBox.getInsets();
838 }
839
840 /**
841 * Paints currently selected value in the main part of the combo
842 * box (part without popup).
843 *
844 * @param g graphics context
845 * @param bounds Rectangle representing the size of the area in which
846 * selected item should be drawn
847 * @param hasFocus true if combo box has focus and false otherwise
848 */
849 public void paintCurrentValue(Graphics g, Rectangle bounds, boolean hasFocus)
850 {
851 /* Gets the component to be drawn for the current value.
852 * If there is currently no selected item we will take an empty
853 * String as replacement.
854 */
855 ListCellRenderer renderer = comboBox.getRenderer();
856 if (comboBox.getSelectedIndex() != -1)
857 {
858 Component comp;
859 if (hasFocus && ! isPopupVisible(comboBox))
860 {
861 comp = renderer.getListCellRendererComponent(listBox,
862 comboBox.getSelectedItem(), -1, true, false);
863 }
864 else
865 {
866 comp = renderer.getListCellRendererComponent(listBox,
867 comboBox.getSelectedItem(), -1, false, false);
868 Color bg = UIManager.getColor("ComboBox.disabledForeground");
869 comp.setBackground(bg);
870 }
871 comp.setFont(comboBox.getFont());
872 if (hasFocus && ! isPopupVisible(comboBox))
873 {
874 comp.setForeground(listBox.getSelectionForeground());
875 comp.setBackground(listBox.getSelectionBackground());
876 }
877 else if (comboBox.isEnabled())
878 {
879 comp.setForeground(comboBox.getForeground());
880 comp.setBackground(comboBox.getBackground());
881 }
882 else
883 {
884 Color fg = UIManager.getColor("ComboBox.disabledForeground");
885 comp.setForeground(fg);
886 Color bg = UIManager.getColor("ComboBox.disabledBackground");
887 comp.setBackground(bg);
888 }
889 currentValuePane.paintComponent(g, comp, comboBox, bounds.x, bounds.y,
890 bounds.width, bounds.height);
891 }
892 }
893
894 /**
895 * Paints the background of part of the combo box, where currently
896 * selected value is displayed. If the combo box has focus this method
897 * should also paint focus rectangle around the combo box.
898 *
899 * @param g graphics context
900 * @param bounds Rectangle representing the size of the largest item in the
901 * comboBox
902 * @param hasFocus true if combo box has fox and false otherwise
903 */
904 public void paintCurrentValueBackground(Graphics g, Rectangle bounds,
905 boolean hasFocus)
906 {
907 Color saved = g.getColor();
908 if (comboBox.isEnabled())
909 g.setColor(UIManager.getColor("UIManager.background"));
910 else
911 g.setColor(UIManager.getColor("UIManager.disabledBackground"));
912 g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
913 g.setColor(saved);
914 }
915
916 private static final ListCellRenderer DEFAULT_RENDERER
917 = new DefaultListCellRenderer();
918
919 /**
920 * Returns the default size for the display area of a combo box that does
921 * not contain any elements. This method returns the width and height of
922 * a single space in the current font, plus a margin of 1 pixel.
923 *
924 * @return The default display size.
925 *
926 * @see #getDisplaySize()
927 */
928 protected Dimension getDefaultSize()
929 {
930 Component comp = DEFAULT_RENDERER.getListCellRendererComponent(listBox,
931 " ", -1, false, false);
932 currentValuePane.add(comp);
933 comp.setFont(comboBox.getFont());
934 Dimension d = comp.getPreferredSize();
935 currentValuePane.remove(comp);
936 return d;
937 }
938
939 /**
940 * Returns the size of the display area for the combo box. This size will be
941 * the size of the combo box, not including the arrowButton.
942 *
943 * @return The size of the display area for the combo box.
944 */
945 protected Dimension getDisplaySize()
946 {
947 Dimension dim = new Dimension();
948 ListCellRenderer renderer = comboBox.getRenderer();
949 if (renderer == null)
950 {
951 renderer = DEFAULT_RENDERER;
952 }
953
954 Object prototype = comboBox.getPrototypeDisplayValue();
955 if (prototype != null)
956 {
957 Component comp = renderer.getListCellRendererComponent(listBox,
958 prototype, -1, false, false);
959 currentValuePane.add(comp);
960 comp.setFont(comboBox.getFont());
961 Dimension renderSize = comp.getPreferredSize();
962 currentValuePane.remove(comp);
963 dim.height = renderSize.height;
964 dim.width = renderSize.width;
965 }
966 else
967 {
968 ComboBoxModel model = comboBox.getModel();
969 int size = model.getSize();
970 if (size > 0)
971 {
972 for (int i = 0; i < size; ++i)
973 {
974 Component comp = renderer.getListCellRendererComponent(listBox,
975 model.getElementAt(i), -1, false, false);
976 currentValuePane.add(comp);
977 comp.setFont(comboBox.getFont());
978 Dimension renderSize = comp.getPreferredSize();
979 currentValuePane.remove(comp);
980 dim.width = Math.max(dim.width, renderSize.width);
981 dim.height = Math.max(dim.height, renderSize.height);
982 }
983 }
984 else
985 {
986 dim = getDefaultSize();
987 if (comboBox.isEditable())
988 dim.width = 100;
989 }
990 }
991 if (comboBox.isEditable())
992 {
993 Dimension editSize = editor.getPreferredSize();
994 dim.width = Math.max(dim.width, editSize.width);
995 dim.height = Math.max(dim.height, editSize.height);
996 }
997 displaySize.setSize(dim.width, dim.height);
998 return dim;
999 }
1000
1001 /**
1002 * Installs the keyboard actions for the {@link JComboBox} as specified
1003 * by the look and feel.
1004 */
1005 protected void installKeyboardActions()
1006 {
1007 SwingUtilities.replaceUIInputMap(comboBox,
1008 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
1009 (InputMap) UIManager.get("ComboBox.ancestorInputMap"));
1010 // Install any action maps here.
1011 }
1012
1013 /**
1014 * Uninstalls the keyboard actions for the {@link JComboBox} there were
1015 * installed by in {@link #installListeners}.
1016 */
1017 protected void uninstallKeyboardActions()
1018 {
1019 SwingUtilities.replaceUIInputMap(comboBox,
1020 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
1021 // Uninstall any action maps here.
1022 }
1023
1024 /**
1025 * A {@link LayoutManager} used to position the sub-components of the
1026 * {@link JComboBox}.
1027 *
1028 * @see BasicComboBoxUI#createLayoutManager()
1029 */
1030 public class ComboBoxLayoutManager implements LayoutManager
1031 {
1032 /**
1033 * Creates a new ComboBoxLayoutManager object.
1034 */
1035 public ComboBoxLayoutManager()
1036 {
1037 // Nothing to do here.
1038 }
1039
1040 /**
1041 * Adds a component to the layout. This method does nothing, since the
1042 * layout manager doesn't need to track the components.
1043 *
1044 * @param name the name to associate the component with (ignored).
1045 * @param comp the component (ignored).
1046 */
1047 public void addLayoutComponent(String name, Component comp)
1048 {
1049 // Do nothing
1050 }
1051
1052 /**
1053 * Removes a component from the layout. This method does nothing, since
1054 * the layout manager doesn't need to track the components.
1055 *
1056 * @param comp the component.
1057 */
1058 public void removeLayoutComponent(Component comp)
1059 {
1060 // Do nothing
1061 }
1062
1063 /**
1064 * Returns preferred layout size of the JComboBox.
1065 *
1066 * @param parent the Container for which the preferred size should be
1067 * calculated.
1068 *
1069 * @return The preferred size for the given container
1070 */
1071 public Dimension preferredLayoutSize(Container parent)
1072 {
1073 return parent.getPreferredSize();
1074 }
1075
1076 /**
1077 * Returns the minimum layout size.
1078 *
1079 * @param parent the container.
1080 *
1081 * @return The minimum size.
1082 */
1083 public Dimension minimumLayoutSize(Container parent)
1084 {
1085 return parent.getMinimumSize();
1086 }
1087
1088 /**
1089 * Arranges the components in the container. It puts arrow
1090 * button right end part of the comboBox. If the comboBox is editable
1091 * then editor is placed to the left of arrow button, starting from the
1092 * beginning.
1093 *
1094 * @param parent Container that should be layed out.
1095 */
1096 public void layoutContainer(Container parent)
1097 {
1098 // Position editor component to the left of arrow button if combo box is
1099 // editable
1100 Insets i = getInsets();
1101 int arrowSize = comboBox.getHeight() - (i.top + i.bottom);
1102
1103 if (arrowButton != null)
1104 arrowButton.setBounds(comboBox.getWidth() - (i.right + arrowSize),
1105 i.top, arrowSize, arrowSize);
1106 if (editor != null)
1107 editor.setBounds(rectangleForCurrentValue());
1108 }
1109 }
1110
1111 /**
1112 * Handles focus changes occuring in the combo box. This class is
1113 * responsible for repainting combo box whenever focus is gained or lost
1114 * and also for hiding popup list of items whenever combo box loses its
1115 * focus.
1116 */
1117 public class FocusHandler extends Object implements FocusListener
1118 {
1119 /**
1120 * Creates a new FocusHandler object.
1121 */
1122 public FocusHandler()
1123 {
1124 // Nothing to do here.
1125 }
1126
1127 /**
1128 * Invoked when combo box gains focus. It repaints main
1129 * part of combo box accordingly.
1130 *
1131 * @param e the FocusEvent
1132 */
1133 public void focusGained(FocusEvent e)
1134 {
1135 hasFocus = true;
1136 comboBox.repaint();
1137 }
1138
1139 /**
1140 * Invoked when the combo box loses focus. It repaints the main part
1141 * of the combo box accordingly and hides the popup list of items.
1142 *
1143 * @param e the FocusEvent
1144 */
1145 public void focusLost(FocusEvent e)
1146 {
1147 hasFocus = false;
1148 if (! e.isTemporary() && comboBox.isLightWeightPopupEnabled())
1149 setPopupVisible(comboBox, false);
1150 comboBox.repaint();
1151 }
1152 }
1153
1154 /**
1155 * Handles {@link ItemEvent}s fired by the {@link JComboBox} when its
1156 * selected item changes.
1157 */
1158 public class ItemHandler extends Object implements ItemListener
1159 {
1160 /**
1161 * Creates a new ItemHandler object.
1162 */
1163 public ItemHandler()
1164 {
1165 // Nothing to do here.
1166 }
1167
1168 /**
1169 * Invoked when selected item becomes deselected or when
1170 * new item becomes selected.
1171 *
1172 * @param e the ItemEvent representing item's state change.
1173 */
1174 public void itemStateChanged(ItemEvent e)
1175 {
1176 ComboBoxModel model = comboBox.getModel();
1177 Object v = model.getSelectedItem();
1178 if (editor != null)
1179 comboBox.configureEditor(comboBox.getEditor(), v);
1180 comboBox.repaint();
1181 }
1182 }
1183
1184 /**
1185 * KeyHandler handles key events occuring while JComboBox has focus.
1186 */
1187 public class KeyHandler extends KeyAdapter
1188 {
1189 public KeyHandler()
1190 {
1191 // Nothing to do here.
1192 }
1193
1194 /**
1195 * Invoked whenever key is pressed while JComboBox is in focus.
1196 */
1197 public void keyPressed(KeyEvent e)
1198 {
1199 if (comboBox.getModel().getSize() != 0 && comboBox.isEnabled())
1200 {
1201 if (! isNavigationKey(e.getKeyCode()))
1202 {
1203 if (! comboBox.isEditable())
1204 if (comboBox.selectWithKeyChar(e.getKeyChar()))
1205 e.consume();
1206 }
1207 else
1208 {
1209 if (e.getKeyCode() == KeyEvent.VK_UP && comboBox.isPopupVisible())
1210 selectPreviousPossibleValue();
1211 else if (e.getKeyCode() == KeyEvent.VK_DOWN)
1212 {
1213 if (comboBox.isPopupVisible())
1214 selectNextPossibleValue();
1215 else
1216 comboBox.showPopup();
1217 }
1218 else if (e.getKeyCode() == KeyEvent.VK_ENTER
1219 || e.getKeyCode() == KeyEvent.VK_ESCAPE)
1220 popup.hide();
1221 }
1222 }
1223 }
1224 }
1225
1226 /**
1227 * Handles the changes occurring in the JComboBox's data model.
1228 */
1229 public class ListDataHandler extends Object implements ListDataListener
1230 {
1231 /**
1232 * Creates a new ListDataHandler object.
1233 */
1234 public ListDataHandler()
1235 {
1236 // Nothing to do here.
1237 }
1238
1239 /**
1240 * Invoked if the content's of JComboBox's data model are changed.
1241 *
1242 * @param e ListDataEvent describing the change.
1243 */
1244 public void contentsChanged(ListDataEvent e)
1245 {
1246 if (e.getIndex0() != -1 || e.getIndex1() != -1)
1247 {
1248 isMinimumSizeDirty = true;
1249 comboBox.revalidate();
1250 }
1251 if (editor != null)
1252 comboBox.configureEditor(comboBox.getEditor(),
1253 comboBox.getSelectedItem());
1254 comboBox.repaint();
1255 }
1256
1257 /**
1258 * Invoked when items are added to the JComboBox's data model.
1259 *
1260 * @param e ListDataEvent describing the change.
1261 */
1262 public void intervalAdded(ListDataEvent e)
1263 {
1264 int start = e.getIndex0();
1265 int end = e.getIndex1();
1266 if (start == 0 && comboBox.getItemCount() - (end - start + 1) == 0)
1267 contentsChanged(e);
1268 else if (start != -1 || end != -1)
1269 {
1270 ListCellRenderer renderer = comboBox.getRenderer();
1271 ComboBoxModel model = comboBox.getModel();
1272 int w = displaySize.width;
1273 int h = displaySize.height;
1274 // TODO: Optimize using prototype here.
1275 for (int i = start; i <= end; ++i)
1276 {
1277 Component comp = renderer.getListCellRendererComponent(listBox,
1278 model.getElementAt(i), -1, false, false);
1279 currentValuePane.add(comp);
1280 comp.setFont(comboBox.getFont());
1281 Dimension dim = comp.getPreferredSize();
1282 w = Math.max(w, dim.width);
1283 h = Math.max(h, dim.height);
1284 currentValuePane.remove(comp);
1285 }
1286 if (displaySize.width < w || displaySize.height < h)
1287 {
1288 if (displaySize.width < w)
1289 displaySize.width = w;
1290 if (displaySize.height < h)
1291 displaySize.height = h;
1292 comboBox.revalidate();
1293 if (editor != null)
1294 {
1295 comboBox.configureEditor(comboBox.getEditor(),
1296 comboBox.getSelectedItem());
1297 }
1298 }
1299 }
1300
1301 }
1302
1303 /**
1304 * Invoked when items are removed from the JComboBox's
1305 * data model.
1306 *
1307 * @param e ListDataEvent describing the change.
1308 */
1309 public void intervalRemoved(ListDataEvent e)
1310 {
1311 contentsChanged(e);
1312 }
1313 }
1314
1315 /**
1316 * Handles {@link PropertyChangeEvent}s fired by the {@link JComboBox}.
1317 */
1318 public class PropertyChangeHandler extends Object
1319 implements PropertyChangeListener
1320 {
1321 /**
1322 * Creates a new instance.
1323 */
1324 public PropertyChangeHandler()
1325 {
1326 // Nothing to do here.
1327 }
1328
1329 /**
1330 * Invoked whenever bound property of JComboBox changes.
1331 *
1332 * @param e the event.
1333 */
1334 public void propertyChange(PropertyChangeEvent e)
1335 {
1336 // Lets assume every change invalidates the minimumsize.
1337 String propName = e.getPropertyName();
1338 if (propName.equals("enabled"))
1339 {
1340 boolean enabled = comboBox.isEnabled();
1341 if (editor != null)
1342 editor.setEnabled(enabled);
1343 if (arrowButton != null)
1344 arrowButton.setEnabled(enabled);
1345
1346 comboBox.repaint();
1347 }
1348 else if (propName.equals("editor") && comboBox.isEditable())
1349 {
1350 addEditor();
1351 comboBox.revalidate();
1352 }
1353 else if (e.getPropertyName().equals("editable"))
1354 {
1355 if (comboBox.isEditable())
1356 {
1357 addEditor();
1358 }
1359 else
1360 {
1361 removeEditor();
1362 }
1363
1364 comboBox.revalidate();
1365 }
1366 else if (propName.equals("model"))
1367 {
1368 // remove ListDataListener from old model and add it to new model
1369 ComboBoxModel oldModel = (ComboBoxModel) e.getOldValue();
1370 if (oldModel != null && listDataListener != null)
1371 oldModel.removeListDataListener(listDataListener);
1372
1373 ComboBoxModel newModel = (ComboBoxModel) e.getNewValue();
1374 if (newModel != null && listDataListener != null)
1375 comboBox.getModel().addListDataListener(listDataListener);
1376
1377 if (editor != null)
1378 {
1379 comboBox.configureEditor(comboBox.getEditor(),
1380 comboBox.getSelectedItem());
1381 }
1382 isMinimumSizeDirty = true;
1383 comboBox.revalidate();
1384 comboBox.repaint();
1385 }
1386 else if (propName.equals("font"))
1387 {
1388 Font font = (Font) e.getNewValue();
1389 if (editor != null)
1390 {
1391 editor.setFont(font);
1392 }
1393 listBox.setFont(font);
1394 isMinimumSizeDirty = true;
1395 comboBox.revalidate();
1396 }
1397 else if (propName.equals("prototypeDisplayValue"))
1398 {
1399 isMinimumSizeDirty = true;
1400 comboBox.revalidate();
1401 }
1402 else if (propName.equals("renderer"))
1403 {
1404 isMinimumSizeDirty = true;
1405 comboBox.revalidate();
1406 }
1407 // FIXME: Need to handle changes in other bound properties.
1408 }
1409 }
1410 }