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

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

ensures consistency of upload comment:

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