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

Last change on this file since 6388 was 6296, checked in by Don-vip, 11 years ago

Sonar/Findbugs - Avoid commented-out lines of code, javadoc

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