source: josm/trunk/src/org/openstreetmap/josm/gui/MenuScroller.java@ 6223

Last change on this file since 6223 was 6084, checked in by bastiK, 11 years ago

see #8902 - add missing @Override annotations (patch by shinigami)

File size: 21.0 KB
Line 
1/**
2 * @(#)MenuScroller.java 1.5.0 04/02/12
3 * License: use / modify without restrictions (see http://tips4java.wordpress.com/about/)
4 */
5package org.openstreetmap.josm.gui;
6
7import java.awt.Color;
8import java.awt.Component;
9import java.awt.Dimension;
10import java.awt.Graphics;
11import java.awt.event.ActionEvent;
12import java.awt.event.ActionListener;
13import java.awt.event.MouseWheelEvent;
14import java.awt.event.MouseWheelListener;
15
16import javax.swing.Icon;
17import javax.swing.JComponent;
18import javax.swing.JMenu;
19import javax.swing.JMenuItem;
20import javax.swing.JPopupMenu;
21import javax.swing.MenuSelectionManager;
22import javax.swing.Timer;
23import javax.swing.event.ChangeEvent;
24import javax.swing.event.ChangeListener;
25import javax.swing.event.PopupMenuEvent;
26import javax.swing.event.PopupMenuListener;
27
28/**
29 * A class that provides scrolling capabilities to a long menu dropdown or
30 * popup menu. A number of items can optionally be frozen at the top and/or
31 * bottom of the menu.
32 * <P>
33 * <B>Implementation note:</B> The default number of items to display
34 * at a time is 15, and the default scrolling interval is 125 milliseconds.
35 * <P>
36 * @author Darryl, http://tips4java.wordpress.com/2009/02/01/menu-scroller/
37 */
38public class MenuScroller {
39
40 //private JMenu menu;
41 private JPopupMenu menu;
42 private Component[] menuItems;
43 private MenuScrollItem upItem;
44 private MenuScrollItem downItem;
45 private final MenuScrollListener menuListener = new MenuScrollListener();
46 private final MouseWheelListener mouseWheelListener = new MouseScrollListener();
47 private int scrollCount;
48 private int interval;
49 private int topFixedCount;
50 private int bottomFixedCount;
51 private int firstIndex = 0;
52 private int keepVisibleIndex = -1;
53
54 /**
55 * Registers a menu to be scrolled with the default number of items to
56 * display at a time and the default scrolling interval.
57 *
58 * @param menu the menu
59 * @return the MenuScroller
60 */
61 public static MenuScroller setScrollerFor(JMenu menu) {
62 return new MenuScroller(menu);
63 }
64
65 /**
66 * Registers a popup menu to be scrolled with the default number of items to
67 * display at a time and the default scrolling interval.
68 *
69 * @param menu the popup menu
70 * @return the MenuScroller
71 */
72 public static MenuScroller setScrollerFor(JPopupMenu menu) {
73 return new MenuScroller(menu);
74 }
75
76 /**
77 * Registers a menu to be scrolled with the default number of items to
78 * display at a time and the specified scrolling interval.
79 *
80 * @param menu the menu
81 * @param scrollCount the number of items to display at a time
82 * @return the MenuScroller
83 * @throws IllegalArgumentException if scrollCount is 0 or negative
84 */
85 public static MenuScroller setScrollerFor(JMenu menu, int scrollCount) {
86 return new MenuScroller(menu, scrollCount);
87 }
88
89 /**
90 * Registers a popup menu to be scrolled with the default number of items to
91 * display at a time and the specified scrolling interval.
92 *
93 * @param menu the popup menu
94 * @param scrollCount the number of items to display at a time
95 * @return the MenuScroller
96 * @throws IllegalArgumentException if scrollCount is 0 or negative
97 */
98 public static MenuScroller setScrollerFor(JPopupMenu menu, int scrollCount) {
99 return new MenuScroller(menu, scrollCount);
100 }
101
102 /**
103 * Registers a menu to be scrolled, with the specified number of items to
104 * display at a time and the specified scrolling interval.
105 *
106 * @param menu the menu
107 * @param scrollCount the number of items to be displayed at a time
108 * @param interval the scroll interval, in milliseconds
109 * @return the MenuScroller
110 * @throws IllegalArgumentException if scrollCount or interval is 0 or negative
111 */
112 public static MenuScroller setScrollerFor(JMenu menu, int scrollCount, int interval) {
113 return new MenuScroller(menu, scrollCount, interval);
114 }
115
116 /**
117 * Registers a popup menu to be scrolled, with the specified number of items to
118 * display at a time and the specified scrolling interval.
119 *
120 * @param menu the popup menu
121 * @param scrollCount the number of items to be displayed at a time
122 * @param interval the scroll interval, in milliseconds
123 * @return the MenuScroller
124 * @throws IllegalArgumentException if scrollCount or interval is 0 or negative
125 */
126 public static MenuScroller setScrollerFor(JPopupMenu menu, int scrollCount, int interval) {
127 return new MenuScroller(menu, scrollCount, interval);
128 }
129
130 /**
131 * Registers a menu to be scrolled, with the specified number of items
132 * to display in the scrolling region, the specified scrolling interval,
133 * and the specified numbers of items fixed at the top and bottom of the
134 * menu.
135 *
136 * @param menu the menu
137 * @param scrollCount the number of items to display in the scrolling portion
138 * @param interval the scroll interval, in milliseconds
139 * @param topFixedCount the number of items to fix at the top. May be 0.
140 * @param bottomFixedCount the number of items to fix at the bottom. May be 0
141 * @throws IllegalArgumentException if scrollCount or interval is 0 or
142 * negative or if topFixedCount or bottomFixedCount is negative
143 * @return the MenuScroller
144 */
145 public static MenuScroller setScrollerFor(JMenu menu, int scrollCount, int interval,
146 int topFixedCount, int bottomFixedCount) {
147 return new MenuScroller(menu, scrollCount, interval,
148 topFixedCount, bottomFixedCount);
149 }
150
151 /**
152 * Registers a popup menu to be scrolled, with the specified number of items
153 * to display in the scrolling region, the specified scrolling interval,
154 * and the specified numbers of items fixed at the top and bottom of the
155 * popup menu.
156 *
157 * @param menu the popup menu
158 * @param scrollCount the number of items to display in the scrolling portion
159 * @param interval the scroll interval, in milliseconds
160 * @param topFixedCount the number of items to fix at the top. May be 0
161 * @param bottomFixedCount the number of items to fix at the bottom. May be 0
162 * @throws IllegalArgumentException if scrollCount or interval is 0 or
163 * negative or if topFixedCount or bottomFixedCount is negative
164 * @return the MenuScroller
165 */
166 public static MenuScroller setScrollerFor(JPopupMenu menu, int scrollCount, int interval,
167 int topFixedCount, int bottomFixedCount) {
168 return new MenuScroller(menu, scrollCount, interval,
169 topFixedCount, bottomFixedCount);
170 }
171
172 /**
173 * Constructs a <code>MenuScroller</code> that scrolls a menu with the
174 * default number of items to display at a time, and default scrolling
175 * interval.
176 *
177 * @param menu the menu
178 */
179 public MenuScroller(JMenu menu) {
180 this(menu, 15);
181 }
182
183 /**
184 * Constructs a <code>MenuScroller</code> that scrolls a popup menu with the
185 * default number of items to display at a time, and default scrolling
186 * interval.
187 *
188 * @param menu the popup menu
189 */
190 public MenuScroller(JPopupMenu menu) {
191 this(menu, 15);
192 }
193
194 /**
195 * Constructs a <code>MenuScroller</code> that scrolls a menu with the
196 * specified number of items to display at a time, and default scrolling
197 * interval.
198 *
199 * @param menu the menu
200 * @param scrollCount the number of items to display at a time
201 * @throws IllegalArgumentException if scrollCount is 0 or negative
202 */
203 public MenuScroller(JMenu menu, int scrollCount) {
204 this(menu, scrollCount, 150);
205 }
206
207 /**
208 * Constructs a <code>MenuScroller</code> that scrolls a popup menu with the
209 * specified number of items to display at a time, and default scrolling
210 * interval.
211 *
212 * @param menu the popup menu
213 * @param scrollCount the number of items to display at a time
214 * @throws IllegalArgumentException if scrollCount is 0 or negative
215 */
216 public MenuScroller(JPopupMenu menu, int scrollCount) {
217 this(menu, scrollCount, 150);
218 }
219
220 /**
221 * Constructs a <code>MenuScroller</code> that scrolls a menu with the
222 * specified number of items to display at a time, and specified scrolling
223 * interval.
224 *
225 * @param menu the menu
226 * @param scrollCount the number of items to display at a time
227 * @param interval the scroll interval, in milliseconds
228 * @throws IllegalArgumentException if scrollCount or interval is 0 or negative
229 */
230 public MenuScroller(JMenu menu, int scrollCount, int interval) {
231 this(menu, scrollCount, interval, 0, 0);
232 }
233
234 /**
235 * Constructs a <code>MenuScroller</code> that scrolls a popup menu with the
236 * specified number of items to display at a time, and specified scrolling
237 * interval.
238 *
239 * @param menu the popup menu
240 * @param scrollCount the number of items to display at a time
241 * @param interval the scroll interval, in milliseconds
242 * @throws IllegalArgumentException if scrollCount or interval is 0 or negative
243 */
244 public MenuScroller(JPopupMenu menu, int scrollCount, int interval) {
245 this(menu, scrollCount, interval, 0, 0);
246 }
247
248 /**
249 * Constructs a <code>MenuScroller</code> that scrolls a menu with the
250 * specified number of items to display in the scrolling region, the
251 * specified scrolling interval, and the specified numbers of items fixed at
252 * the top and bottom of the menu.
253 *
254 * @param menu the menu
255 * @param scrollCount the number of items to display in the scrolling portion
256 * @param interval the scroll interval, in milliseconds
257 * @param topFixedCount the number of items to fix at the top. May be 0
258 * @param bottomFixedCount the number of items to fix at the bottom. May be 0
259 * @throws IllegalArgumentException if scrollCount or interval is 0 or
260 * negative or if topFixedCount or bottomFixedCount is negative
261 */
262 public MenuScroller(JMenu menu, int scrollCount, int interval,
263 int topFixedCount, int bottomFixedCount) {
264 this(menu.getPopupMenu(), scrollCount, interval, topFixedCount, bottomFixedCount);
265 }
266
267 /**
268 * Constructs a <code>MenuScroller</code> that scrolls a popup menu with the
269 * specified number of items to display in the scrolling region, the
270 * specified scrolling interval, and the specified numbers of items fixed at
271 * the top and bottom of the popup menu.
272 *
273 * @param menu the popup menu
274 * @param scrollCount the number of items to display in the scrolling portion
275 * @param interval the scroll interval, in milliseconds
276 * @param topFixedCount the number of items to fix at the top. May be 0
277 * @param bottomFixedCount the number of items to fix at the bottom. May be 0
278 * @throws IllegalArgumentException if scrollCount or interval is 0 or
279 * negative or if topFixedCount or bottomFixedCount is negative
280 */
281 public MenuScroller(JPopupMenu menu, int scrollCount, int interval,
282 int topFixedCount, int bottomFixedCount) {
283 if (scrollCount <= 0 || interval <= 0) {
284 throw new IllegalArgumentException("scrollCount and interval must be greater than 0");
285 }
286 if (topFixedCount < 0 || bottomFixedCount < 0) {
287 throw new IllegalArgumentException("topFixedCount and bottomFixedCount cannot be negative");
288 }
289
290 upItem = new MenuScrollItem(MenuIcon.UP, -1);
291 downItem = new MenuScrollItem(MenuIcon.DOWN, +1);
292 setScrollCount(scrollCount);
293 setInterval(interval);
294 setTopFixedCount(topFixedCount);
295 setBottomFixedCount(bottomFixedCount);
296
297 this.menu = menu;
298 menu.addPopupMenuListener(menuListener);
299 menu.addMouseWheelListener(mouseWheelListener);
300 }
301
302 /**
303 * Returns the scroll interval in milliseconds
304 *
305 * @return the scroll interval in milliseconds
306 */
307 public int getInterval() {
308 return interval;
309 }
310
311 /**
312 * Sets the scroll interval in milliseconds
313 *
314 * @param interval the scroll interval in milliseconds
315 * @throws IllegalArgumentException if interval is 0 or negative
316 */
317 public void setInterval(int interval) {
318 if (interval <= 0) {
319 throw new IllegalArgumentException("interval must be greater than 0");
320 }
321 upItem.setInterval(interval);
322 downItem.setInterval(interval);
323 this.interval = interval;
324 }
325
326 /**
327 * Returns the number of items in the scrolling portion of the menu.
328 *
329 * @return the number of items to display at a time
330 */
331 public int getscrollCount() {
332 return scrollCount;
333 }
334
335 /**
336 * Sets the number of items in the scrolling portion of the menu.
337 *
338 * @param scrollCount the number of items to display at a time
339 * @throws IllegalArgumentException if scrollCount is 0 or negative
340 */
341 public void setScrollCount(int scrollCount) {
342 if (scrollCount <= 0) {
343 throw new IllegalArgumentException("scrollCount must be greater than 0");
344 }
345 this.scrollCount = scrollCount;
346 MenuSelectionManager.defaultManager().clearSelectedPath();
347 }
348
349 /**
350 * Returns the number of items fixed at the top of the menu or popup menu.
351 *
352 * @return the number of items
353 */
354 public int getTopFixedCount() {
355 return topFixedCount;
356 }
357
358 /**
359 * Sets the number of items to fix at the top of the menu or popup menu.
360 *
361 * @param topFixedCount the number of items
362 */
363 public void setTopFixedCount(int topFixedCount) {
364 if (firstIndex <= topFixedCount) {
365 firstIndex = topFixedCount;
366 } else {
367 firstIndex += (topFixedCount - this.topFixedCount);
368 }
369 this.topFixedCount = topFixedCount;
370 }
371
372 /**
373 * Returns the number of items fixed at the bottom of the menu or popup menu.
374 *
375 * @return the number of items
376 */
377 public int getBottomFixedCount() {
378 return bottomFixedCount;
379 }
380
381 /**
382 * Sets the number of items to fix at the bottom of the menu or popup menu.
383 *
384 * @param bottomFixedCount the number of items
385 */
386 public void setBottomFixedCount(int bottomFixedCount) {
387 this.bottomFixedCount = bottomFixedCount;
388 }
389
390 /**
391 * Scrolls the specified item into view each time the menu is opened. Call this method with
392 * <code>null</code> to restore the default behavior, which is to show the menu as it last
393 * appeared.
394 *
395 * @param item the item to keep visible
396 * @see #keepVisible(int)
397 */
398 public void keepVisible(JMenuItem item) {
399 if (item == null) {
400 keepVisibleIndex = -1;
401 } else {
402 int index = menu.getComponentIndex(item);
403 keepVisibleIndex = index;
404 }
405 }
406
407 /**
408 * Scrolls the item at the specified index into view each time the menu is opened. Call this
409 * method with <code>-1</code> to restore the default behavior, which is to show the menu as
410 * it last appeared.
411 *
412 * @param index the index of the item to keep visible
413 * @see #keepVisible(javax.swing.JMenuItem)
414 */
415 public void keepVisible(int index) {
416 keepVisibleIndex = index;
417 }
418
419 /**
420 * Removes this MenuScroller from the associated menu and restores the
421 * default behavior of the menu.
422 */
423 public void dispose() {
424 if (menu != null) {
425 menu.removePopupMenuListener(menuListener);
426 menu.removeMouseWheelListener(mouseWheelListener);
427 menu.setPreferredSize(null);
428 menu = null;
429 }
430 }
431
432 /**
433 * Ensures that the <code>dispose</code> method of this MenuScroller is
434 * called when there are no more refrences to it.
435 *
436 * @exception Throwable if an error occurs.
437 * @see MenuScroller#dispose()
438 */
439 @Override
440 public void finalize() throws Throwable {
441 dispose();
442 }
443
444 private void refreshMenu() {
445 if (menuItems != null && menuItems.length > 0) {
446 firstIndex = Math.max(topFixedCount, firstIndex);
447 firstIndex = Math.min(menuItems.length - bottomFixedCount - scrollCount, firstIndex);
448
449 upItem.setEnabled(firstIndex > topFixedCount);
450 downItem.setEnabled(firstIndex + scrollCount < menuItems.length - bottomFixedCount);
451
452 menu.removeAll();
453 for (int i = 0; i < topFixedCount; i++) {
454 menu.add(menuItems[i]);
455 }
456 if (topFixedCount > 0) {
457 menu.addSeparator();
458 }
459
460 menu.add(upItem);
461 for (int i = firstIndex; i < scrollCount + firstIndex; i++) {
462 menu.add(menuItems[i]);
463 }
464 menu.add(downItem);
465
466 if (bottomFixedCount > 0) {
467 menu.addSeparator();
468 }
469 for (int i = menuItems.length - bottomFixedCount; i < menuItems.length; i++) {
470 menu.add(menuItems[i]);
471 }
472
473 int preferredWidth = 0;
474 for (Component item : menuItems) {
475 preferredWidth = Math.max(preferredWidth, item.getPreferredSize().width);
476 }
477 menu.setPreferredSize(new Dimension(preferredWidth, menu.getPreferredSize().height));
478
479 JComponent parent = (JComponent) upItem.getParent();
480 parent.revalidate();
481 parent.repaint();
482 }
483 }
484
485 private class MenuScrollListener implements PopupMenuListener {
486
487 @Override
488 public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
489 setMenuItems();
490 }
491
492 @Override
493 public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
494 restoreMenuItems();
495 }
496
497 @Override
498 public void popupMenuCanceled(PopupMenuEvent e) {
499 restoreMenuItems();
500 }
501
502 private void setMenuItems() {
503 menuItems = menu.getComponents();
504 if (keepVisibleIndex >= topFixedCount
505 && keepVisibleIndex <= menuItems.length - bottomFixedCount
506 && (keepVisibleIndex > firstIndex + scrollCount
507 || keepVisibleIndex < firstIndex)) {
508 firstIndex = Math.min(firstIndex, keepVisibleIndex);
509 firstIndex = Math.max(firstIndex, keepVisibleIndex - scrollCount + 1);
510 }
511 if (menuItems.length > topFixedCount + scrollCount + bottomFixedCount) {
512 refreshMenu();
513 }
514 }
515
516 private void restoreMenuItems() {
517 menu.removeAll();
518 for (Component component : menuItems) {
519 menu.add(component);
520 }
521 }
522 }
523
524 private class MenuScrollTimer extends Timer {
525
526 public MenuScrollTimer(final int increment, int interval) {
527 super(interval, new ActionListener() {
528
529 @Override
530 public void actionPerformed(ActionEvent e) {
531 firstIndex += increment;
532 refreshMenu();
533 }
534 });
535 }
536 }
537
538 private class MenuScrollItem extends JMenuItem
539 implements ChangeListener {
540
541 private MenuScrollTimer timer;
542
543 public MenuScrollItem(MenuIcon icon, int increment) {
544 setIcon(icon);
545 setDisabledIcon(icon);
546 timer = new MenuScrollTimer(increment, interval);
547 addChangeListener(this);
548 }
549
550 public void setInterval(int interval) {
551 timer.setDelay(interval);
552 }
553
554 @Override
555 public void stateChanged(ChangeEvent e) {
556 if (isArmed() && !timer.isRunning()) {
557 timer.start();
558 }
559 if (!isArmed() && timer.isRunning()) {
560 timer.stop();
561 }
562 }
563 }
564
565 private static enum MenuIcon implements Icon {
566
567 UP(9, 1, 9),
568 DOWN(1, 9, 1);
569 final int[] xPoints = {1, 5, 9};
570 final int[] yPoints;
571
572 MenuIcon(int... yPoints) {
573 this.yPoints = yPoints;
574 }
575
576 @Override
577 public void paintIcon(Component c, Graphics g, int x, int y) {
578 Dimension size = c.getSize();
579 Graphics g2 = g.create(size.width / 2 - 5, size.height / 2 - 5, 10, 10);
580 g2.setColor(Color.GRAY);
581 g2.drawPolygon(xPoints, yPoints, 3);
582 if (c.isEnabled()) {
583 g2.setColor(Color.BLACK);
584 g2.fillPolygon(xPoints, yPoints, 3);
585 }
586 g2.dispose();
587 }
588
589 @Override
590 public int getIconWidth() {
591 return 0;
592 }
593
594 @Override
595 public int getIconHeight() {
596 return 10;
597 }
598 }
599
600 private class MouseScrollListener implements MouseWheelListener {
601 @Override
602 public void mouseWheelMoved(MouseWheelEvent mwe) {
603 if (menu.getComponents().length > scrollCount) {
604 firstIndex += mwe.getWheelRotation();
605 refreshMenu();
606 }
607 mwe.consume(); // (Comment 16, Huw)
608 }
609 }
610}
Note: See TracBrowser for help on using the repository browser.