source: josm/trunk/src/org/openstreetmap/josm/gui/widgets/JosmComboBox.java@ 8777

Last change on this file since 8777 was 8777, checked in by Don-vip, 9 years ago

Basic Java 9 support: make code compile, and Linux scripts detect it

  • Property svn:eol-style set to native
File size: 9.3 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.widgets;
3
4import java.awt.Component;
5import java.awt.Dimension;
6import java.awt.Toolkit;
7import java.awt.event.MouseAdapter;
8import java.awt.event.MouseEvent;
9import java.beans.PropertyChangeEvent;
10import java.beans.PropertyChangeListener;
11import java.util.ArrayList;
12import java.util.Arrays;
13import java.util.Collection;
14import java.util.List;
15
16import javax.accessibility.Accessible;
17import javax.swing.ComboBoxModel;
18import javax.swing.DefaultComboBoxModel;
19import javax.swing.JComboBox;
20import javax.swing.JList;
21import javax.swing.plaf.basic.ComboPopup;
22import javax.swing.text.JTextComponent;
23
24/**
25 * Class overriding each {@link JComboBox} in JOSM to control consistently the number of displayed items at once.<br>
26 * This is needed because of the default Java behaviour that may display the top-down list off the screen (see #7917).
27 * @param <E> the type of the elements of this combo box
28 *
29 * @since 5429 (creation)
30 * @since 7015 (generics for Java 7)
31 */
32public class JosmComboBox<E> extends JComboBox<E> {
33
34 /**
35 * Creates a <code>JosmComboBox</code> with a default data model.
36 * The default data model is an empty list of objects.
37 * Use <code>addItem</code> to add items. By default the first item
38 * in the data model becomes selected.
39 *
40 * @see DefaultComboBoxModel
41 */
42 public JosmComboBox() {
43 init(null);
44 }
45
46 /**
47 * Creates a <code>JosmComboBox</code> with a default data model and
48 * the specified prototype display value.
49 * The default data model is an empty list of objects.
50 * Use <code>addItem</code> to add items. By default the first item
51 * in the data model becomes selected.
52 *
53 * @param prototypeDisplayValue the <code>Object</code> used to compute
54 * the maximum number of elements to be displayed at once before
55 * displaying a scroll bar
56 *
57 * @see DefaultComboBoxModel
58 * @since 5450
59 */
60 public JosmComboBox(E prototypeDisplayValue) {
61 init(prototypeDisplayValue);
62 }
63
64 /**
65 * Creates a <code>JosmComboBox</code> that takes its items from an
66 * existing <code>ComboBoxModel</code>. Since the
67 * <code>ComboBoxModel</code> is provided, a combo box created using
68 * this constructor does not create a default combo box model and
69 * may impact how the insert, remove and add methods behave.
70 *
71 * @param aModel the <code>ComboBoxModel</code> that provides the
72 * displayed list of items
73 * @see DefaultComboBoxModel
74 */
75 public JosmComboBox(ComboBoxModel<E> aModel) {
76 super(aModel);
77 List<E> list = new ArrayList<>(aModel.getSize());
78 for (int i = 0; i < aModel.getSize(); i++) {
79 list.add(aModel.getElementAt(i));
80 }
81 init(findPrototypeDisplayValue(list));
82 }
83
84 /**
85 * Creates a <code>JosmComboBox</code> that contains the elements
86 * in the specified array. By default the first item in the array
87 * (and therefore the data model) becomes selected.
88 *
89 * @param items an array of objects to insert into the combo box
90 * @see DefaultComboBoxModel
91 */
92 public JosmComboBox(E[] items) {
93 super(items);
94 init(findPrototypeDisplayValue(Arrays.asList(items)));
95 }
96
97 /**
98 * Finds the prototype display value to use among the given possible candidates.
99 * @param possibleValues The possible candidates that will be iterated.
100 * @return The value that needs the largest display height on screen.
101 * @since 5558
102 */
103 protected final E findPrototypeDisplayValue(Collection<E> possibleValues) {
104 E result = null;
105 int maxHeight = -1;
106 if (possibleValues != null) {
107 // Remind old prototype to restore it later
108 E oldPrototype = getPrototypeDisplayValue();
109 // Get internal JList to directly call the renderer
110 @SuppressWarnings("rawtypes")
111 JList list = getList();
112 try {
113 // Index to give to renderer
114 int i = 0;
115 for (E value : possibleValues) {
116 if (value != null) {
117 // With a "classic" renderer, we could call setPrototypeDisplayValue(value) + getPreferredSize()
118 // but not with TaggingPreset custom renderer that return a dummy height if index is equal to -1
119 // So we explicitely call the renderer by simulating a correct index for the current value
120 @SuppressWarnings("unchecked")
121 Component c = getRenderer().getListCellRendererComponent(list, value, i, true, true);
122 if (c != null) {
123 // Get the real preferred size for the current value
124 Dimension dim = c.getPreferredSize();
125 if (dim.height > maxHeight) {
126 // Larger ? This is our new prototype
127 maxHeight = dim.height;
128 result = value;
129 }
130 }
131 }
132 i++;
133 }
134 } finally {
135 // Restore original prototype
136 setPrototypeDisplayValue(oldPrototype);
137 }
138 }
139 return result;
140 }
141
142 @SuppressWarnings("unchecked")
143 protected final JList<Object> getList() {
144 for (int i = 0; i < getUI().getAccessibleChildrenCount(this); i++) {
145 Accessible child = getUI().getAccessibleChild(this, i);
146 if (child instanceof ComboPopup) {
147 return ((ComboPopup) child).getList();
148 }
149 }
150 return null;
151 }
152
153 protected final void init(E prototype) {
154 if (prototype != null) {
155 setPrototypeDisplayValue(prototype);
156 int screenHeight = Toolkit.getDefaultToolkit().getScreenSize().height;
157 // Compute maximum number of visible items based on the preferred size of the combo box.
158 // This assumes that items have the same height as the combo box, which is not granted by the look and feel
159 int maxsize = (screenHeight/getPreferredSize().height) / 2;
160 // If possible, adjust the maximum number of items with the real height of items
161 // It is not granted this works on every platform (tested OK on Windows)
162 JList<Object> list = getList();
163 if (list != null) {
164 if (!prototype.equals(list.getPrototypeCellValue())) {
165 list.setPrototypeCellValue(prototype);
166 }
167 int height = list.getFixedCellHeight();
168 if (height > 0) {
169 maxsize = (screenHeight/height) / 2;
170 }
171 }
172 setMaximumRowCount(Math.max(getMaximumRowCount(), maxsize));
173 }
174 // Handle text contextual menus for editable comboboxes
175 ContextMenuHandler handler = new ContextMenuHandler();
176 addPropertyChangeListener("editable", handler);
177 addPropertyChangeListener("editor", handler);
178 }
179
180 protected class ContextMenuHandler extends MouseAdapter implements PropertyChangeListener {
181
182 private JTextComponent component;
183 private PopupMenuLauncher launcher;
184
185 @Override
186 public void propertyChange(PropertyChangeEvent evt) {
187 if ("editable".equals(evt.getPropertyName())) {
188 if (evt.getNewValue().equals(Boolean.TRUE)) {
189 enableMenu();
190 } else {
191 disableMenu();
192 }
193 } else if ("editor".equals(evt.getPropertyName())) {
194 disableMenu();
195 if (isEditable()) {
196 enableMenu();
197 }
198 }
199 }
200
201 private void enableMenu() {
202 if (launcher == null && editor != null) {
203 Component editorComponent = editor.getEditorComponent();
204 if (editorComponent instanceof JTextComponent) {
205 component = (JTextComponent) editorComponent;
206 component.addMouseListener(this);
207 launcher = TextContextualPopupMenu.enableMenuFor(component, true);
208 }
209 }
210 }
211
212 private void disableMenu() {
213 if (launcher != null) {
214 TextContextualPopupMenu.disableMenuFor(component, launcher);
215 launcher = null;
216 component.removeMouseListener(this);
217 component = null;
218 }
219 }
220
221 @Override
222 public void mousePressed(MouseEvent e) {
223 processEvent(e);
224 }
225
226 @Override
227 public void mouseReleased(MouseEvent e) {
228 processEvent(e);
229 }
230
231 private void processEvent(MouseEvent e) {
232 if (launcher != null && !e.isPopupTrigger() && launcher.getMenu().isShowing()) {
233 launcher.getMenu().setVisible(false);
234 }
235 }
236 }
237
238 /**
239 * Reinitializes this {@link JosmComboBox} to the specified values. This may needed if a custom renderer is used.
240 * @param values The values displayed in the combo box.
241 * @since 5558
242 */
243 public final void reinitialize(Collection<E> values) {
244 init(findPrototypeDisplayValue(values));
245 }
246}
Note: See TracBrowser for help on using the repository browser.