001package jmri.util; 002 003import java.awt.Color; 004import java.awt.Component; 005import java.awt.Dimension; 006import java.awt.Graphics; 007import java.awt.event.ActionEvent; 008import java.awt.event.ActionListener; 009import java.awt.event.KeyEvent; 010import java.awt.event.MouseWheelEvent; 011import java.awt.event.MouseWheelListener; 012import javax.swing.Icon; 013import javax.swing.JComponent; 014import javax.swing.JMenu; 015import javax.swing.JMenuItem; 016import javax.swing.JPopupMenu; 017import javax.swing.MenuSelectionManager; 018import javax.swing.Timer; 019import javax.swing.event.ChangeEvent; 020import javax.swing.event.ChangeListener; 021import javax.swing.event.MenuKeyEvent; 022import javax.swing.event.MenuKeyListener; 023import javax.swing.event.PopupMenuEvent; 024import javax.swing.event.PopupMenuListener; 025import org.slf4j.Logger; 026import org.slf4j.LoggerFactory; 027 028/** 029 * A class that provides scrolling capabilities to a long menu dropdown or popup 030 * menu. A number of items can optionally be frozen at the top and/or bottom of 031 * the menu. 032 * <p> 033 * <b>Implementation note:</b> The default number of items to display at a time 034 * is 15, and the default scrolling interval is 125 milliseconds. 035 * 036 * @version 1.5.0 04/05/12 037 * @author Darryl 038 * @version 1.5.1 07/20/17 - added scrollwheel support 039 * @author George Warner 040 */ 041// NOTE: Provided by Darryl Burke from <https://tips4java.wordpress.com/2009/02/01/menu-scroller/> 042// (Thank you DarrylB! ;-) 043public class MenuScroller 044 implements MouseWheelListener { 045 046 //private JMenu menu; 047 private JPopupMenu menu; 048 private Component[] menuItems; 049 private MenuScrollerItem upItem; 050 private MenuScrollerItem downItem; 051 private final MenuScrollerPopupMenuListener menuScrollerPopupMenuListener = new MenuScrollerPopupMenuListener(); 052 private final MenuScrollerMenuKeyListener menuScrollerMenuKeyListener = new MenuScrollerMenuKeyListener(); 053 private int scrollCount; 054 private int interval; 055 private int topFixedCount; 056 private int bottomFixedCount; 057 private int firstIndex = 0; 058 private int keepVisibleIndex = -1; 059 060// private Component lastFocused = null; 061 062 /** 063 * Register a menu to be scrolled with the default number of items to 064 * display at a time and the default scrolling interval. 065 * 066 * @param menu the menu 067 * @return the MenuScroller 068 */ 069 public static MenuScroller setScrollerFor(JMenu menu) { 070 return new MenuScroller(menu); 071 } 072 073 /** 074 * Register a popup menu to be scrolled with the default number of items to 075 * display at a time and the default scrolling interval. 076 * 077 * @param menu the popup menu 078 * @return the MenuScroller 079 */ 080 public static MenuScroller setScrollerFor(JPopupMenu menu) { 081 return new MenuScroller(menu); 082 } 083 084 /** 085 * Register a menu to be scrolled with the default number of items to 086 * display at a time and the specified scrolling interval. 087 * 088 * @param menu the menu 089 * @param scrollCount the number of items to display at a time 090 * @return the MenuScroller 091 * @throws IllegalArgumentException if scrollCount is 0 or negative 092 */ 093 public static MenuScroller setScrollerFor(JMenu menu, int scrollCount) { 094 return new MenuScroller(menu, scrollCount); 095 } 096 097 /** 098 * Register a popup menu to be scrolled with the default number of items to 099 * display at a time and the specified scrolling interval. 100 * 101 * @param menu the popup menu 102 * @param scrollCount the number of items to display at a time 103 * @return the MenuScroller 104 * @throws IllegalArgumentException if scrollCount is 0 or negative 105 */ 106 public static MenuScroller setScrollerFor(JPopupMenu menu, int scrollCount) { 107 return new MenuScroller(menu, scrollCount); 108 } 109 110 /** 111 * Register a menu to be scrolled, with the specified number of items to 112 * display at a time and the specified scrolling interval. 113 * 114 * @param menu the menu 115 * @param scrollCount the number of items to be displayed at a time 116 * @param interval the scroll interval, in milliseconds 117 * @return the MenuScroller 118 * @throws IllegalArgumentException if scrollCount or interval is 0 or 119 * negative 120 */ 121 public static MenuScroller setScrollerFor(JMenu menu, int scrollCount, int interval) { 122 return new MenuScroller(menu, scrollCount, interval); 123 } 124 125 /** 126 * Register a popup menu to be scrolled, with the specified number of items 127 * to display at a time and the specified scrolling interval. 128 * 129 * @param menu the popup menu 130 * @param scrollCount the number of items to be displayed at a time 131 * @param interval the scroll interval, in milliseconds 132 * @return the MenuScroller 133 * @throws IllegalArgumentException if scrollCount or interval is 0 or 134 * negative 135 */ 136 public static MenuScroller setScrollerFor(JPopupMenu menu, int scrollCount, int interval) { 137 return new MenuScroller(menu, scrollCount, interval); 138 } 139 140 /** 141 * Register a menu to be scrolled, with the specified number of items to 142 * display in the scrolling region, the specified scrolling interval, and 143 * the specified numbers of items fixed at the top and bottom of the menu. 144 * 145 * @param menu the menu 146 * @param scrollCount the number of items to display in the scrolling 147 * portion 148 * @param interval the scroll interval, in milliseconds 149 * @param topFixedCount the number of items to fix at the top. May be 0. 150 * @param bottomFixedCount the number of items to fix at the bottom. May be 151 * 0 152 * @throws IllegalArgumentException if scrollCount or interval is 0 or 153 * negative or if topFixedCount or 154 * bottomFixedCount is negative 155 * @return the MenuScroller 156 */ 157 public static MenuScroller setScrollerFor(JMenu menu, int scrollCount, int interval, 158 int topFixedCount, int bottomFixedCount) { 159 return new MenuScroller(menu, scrollCount, interval, 160 topFixedCount, bottomFixedCount); 161 } 162 163 /** 164 * Register a popup menu to be scrolled, with the specified number of items 165 * to display in the scrolling region, the specified scrolling interval, and 166 * the specified numbers of items fixed at the top and bottom of the popup 167 * menu. 168 * 169 * @param menu the popup menu 170 * @param scrollCount the number of items to display in the scrolling 171 * portion 172 * @param interval the scroll interval, in milliseconds 173 * @param topFixedCount the number of items to fix at the top. May be 0 174 * @param bottomFixedCount the number of items to fix at the bottom. May be 175 * 0 176 * @throws IllegalArgumentException if scrollCount or interval is 0 or 177 * negative or if topFixedCount or 178 * bottomFixedCount is negative 179 * @return the MenuScroller 180 */ 181 public static MenuScroller setScrollerFor(JPopupMenu menu, int scrollCount, int interval, 182 int topFixedCount, int bottomFixedCount) { 183 return new MenuScroller(menu, scrollCount, interval, 184 topFixedCount, bottomFixedCount); 185 } 186 187 /** 188 * Construct a <code>MenuScroller</code> that scrolls a menu with the 189 * default number of items to display at a time, and default scrolling 190 * interval. 191 * 192 * @param menu the menu 193 */ 194 public MenuScroller(JMenu menu) { 195 this(menu, 15); 196 } 197 198 /** 199 * Construct a <code>MenuScroller</code> that scrolls a popup menu with the 200 * default number of items to display at a time, and default scrolling 201 * interval. 202 * 203 * @param menu the popup menu 204 */ 205 public MenuScroller(JPopupMenu menu) { 206 this(menu, 15); 207 } 208 209 /** 210 * Construct a <code>MenuScroller</code> that scrolls a menu with the 211 * specified number of items to display at a time, and default scrolling 212 * interval. 213 * 214 * @param menu the menu 215 * @param scrollCount the number of items to display at a time 216 * @throws IllegalArgumentException if scrollCount is 0 or negative 217 */ 218 public MenuScroller(JMenu menu, int scrollCount) { 219 this(menu, scrollCount, 150); 220 } 221 222 /** 223 * Construct a <code>MenuScroller</code> that scrolls a popup menu with the 224 * specified number of items to display at a time, and default scrolling 225 * interval. 226 * 227 * @param menu the popup menu 228 * @param scrollCount the number of items to display at a time 229 * @throws IllegalArgumentException if scrollCount is 0 or negative 230 */ 231 public MenuScroller(JPopupMenu menu, int scrollCount) { 232 this(menu, scrollCount, 150); 233 } 234 235 /** 236 * Construct a <code>MenuScroller</code> that scrolls a menu with the 237 * specified number of items to display at a time, and specified scrolling 238 * interval. 239 * 240 * @param menu the menu 241 * @param scrollCount the number of items to display at a time 242 * @param interval the scroll interval, in milliseconds 243 * @throws IllegalArgumentException if scrollCount or interval is 0 or 244 * negative 245 */ 246 public MenuScroller(JMenu menu, int scrollCount, int interval) { 247 this(menu, scrollCount, interval, 0, 0); 248 } 249 250 /** 251 * Construct a <code>MenuScroller</code> that scrolls a popup menu with the 252 * specified number of items to display at a time, and specified scrolling 253 * interval. 254 * 255 * @param menu the popup menu 256 * @param scrollCount the number of items to display at a time 257 * @param interval the scroll interval, in milliseconds 258 * @throws IllegalArgumentException if scrollCount or interval is 0 or 259 * negative 260 */ 261 public MenuScroller(JPopupMenu menu, int scrollCount, int interval) { 262 this(menu, scrollCount, interval, 0, 0); 263 } 264 265 /** 266 * Construct a <code>MenuScroller</code> that scrolls a menu with the 267 * specified number of items to display in the scrolling region, the 268 * specified scrolling interval, and the specified numbers of items fixed at 269 * the top and bottom of the menu. 270 * 271 * @param menu the menu 272 * @param scrollCount the number of items to display in the scrolling 273 * 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 277 * 0 278 * @throws IllegalArgumentException if scrollCount or interval is 0 or 279 * negative or if topFixedCount or 280 * bottomFixedCount is negative 281 */ 282 public MenuScroller(JMenu menu, int scrollCount, int interval, 283 int topFixedCount, int bottomFixedCount) { 284 this(menu.getPopupMenu(), scrollCount, interval, topFixedCount, bottomFixedCount); 285 } 286 287 /** 288 * Construct a <code>MenuScroller</code> that scrolls a popup menu with the 289 * specified number of items to display in the scrolling region, the 290 * specified scrolling interval, and the specified numbers of items fixed at 291 * the top and bottom of the popup menu. 292 * 293 * @param menu the popup menu 294 * @param scrollCount the number of items to display in the scrolling 295 * portion 296 * @param interval the scroll interval, in milliseconds 297 * @param topFixedCount the number of items to fix at the top. May be 0 298 * @param bottomFixedCount the number of items to fix at the bottom. May be 299 * 0 300 * @throws IllegalArgumentException if scrollCount or interval is 0 or 301 * negative or if topFixedCount or 302 * bottomFixedCount is negative 303 */ 304 public MenuScroller(JPopupMenu menu, int scrollCount, int interval, 305 int topFixedCount, int bottomFixedCount) { 306 if (scrollCount <= 0 || interval <= 0) { 307 throw new IllegalArgumentException("scrollCount and interval must be greater than 0"); 308 } 309 if (topFixedCount < 0 || bottomFixedCount < 0) { 310 throw new IllegalArgumentException("topFixedCount and bottomFixedCount cannot be negative"); 311 } 312 313 upItem = new MenuScrollerItem(MenuScrollerIcon.UP, -1); 314 downItem = new MenuScrollerItem(MenuScrollerIcon.DOWN, +1); 315 setScrollCount(scrollCount); 316 setInterval(interval); 317 setTopFixedCount(topFixedCount); 318 setBottomFixedCount(bottomFixedCount); 319 320 this.menu = menu; 321 322 installListeners(); 323 } 324 325 private void installListeners() { 326 327 // remove all menu key listeners 328 for (MenuKeyListener mkl : menu.getMenuKeyListeners()) { 329 menu.removeMenuKeyListener(mkl); 330 } 331 332 // add our menu key listener 333 menu.addMenuKeyListener(menuScrollerMenuKeyListener); 334 335// Dead code commented out 2021-12-13 336// if (false) { // not working 337// KeyListenerInstaller.installKeyListenerOnAllComponents(new KeyAdapter() { 338// @Override 339// public void keyTyped(KeyEvent e) { 340// int keyCode = e.getKeyCode(); 341// log.debug("keyTyped(" + keyCode + ")"); 342// } 343// }, menu); 344// 345// menu.addKeyListener(new KeyAdapter() { 346// @Override 347// public void keyTyped(KeyEvent e) { 348// int keyCode = e.getKeyCode(); 349// log.debug("keyTyped({})", keyCode); 350// } 351// }); 352// 353// } 354 355 // add a Popup Menu listener 356 menu.addPopupMenuListener(menuScrollerPopupMenuListener); 357 358 // add my mouse wheel listener 359 // (so mouseWheelMoved (below) will be called) 360 menu.addMouseWheelListener(this); 361 362// lastFocused = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); 363// if (menu.isFocusable()) { 364// menu.requestFocus(); 365// } 366 } 367 368 private void removeListeners() { 369// lastFocused.requestFocus(); 370// menu.removeMenuKeyListener(menuScrollerMenuKeyListener); 371 menu.removePopupMenuListener(menuScrollerPopupMenuListener); 372 menu.removeMouseWheelListener(this); 373 } 374 375 @Override 376 public void mouseWheelMoved(MouseWheelEvent e) { 377 // compute how much to scroll the menu 378 int amount = e.getScrollAmount() * e.getWheelRotation(); 379 firstIndex += amount; 380 refreshMenu(); 381 e.consume(); 382 } 383 384 /** 385 * Return the scroll interval in milliseconds. 386 * 387 * @return the scroll interval in milliseconds 388 */ 389 public int getInterval() { 390 return interval; 391 } 392 393 /** 394 * Set the scroll interval in milliseconds. 395 * 396 * @param interval the scroll interval in milliseconds 397 * @throws IllegalArgumentException if interval is 0 or negative 398 */ 399 public void setInterval(int interval) { 400 if (interval <= 0) { 401 throw new IllegalArgumentException("interval must be greater than 0"); 402 } 403 upItem.setInterval(interval); 404 downItem.setInterval(interval); 405 this.interval = interval; 406 } 407 408 /** 409 * Return the number of items in the scrolling portion of the menu. 410 * 411 * @return the number of items to display at a time 412 */ 413 public int getscrollCount() { 414 return scrollCount; 415 } 416 417 /** 418 * Set the number of items in the scrolling portion of the menu. 419 * 420 * @param scrollCount the number of items to display at a time 421 * @throws IllegalArgumentException if scrollCount is 0 or negative 422 */ 423 public void setScrollCount(int scrollCount) { 424 if (scrollCount <= 0) { 425 throw new IllegalArgumentException("scrollCount must be greater than 0"); 426 } 427 this.scrollCount = scrollCount; 428 MenuSelectionManager.defaultManager().clearSelectedPath(); 429 } 430 431 /** 432 * Return the number of items fixed at the top of the menu or popup menu. 433 * 434 * @return the number of items 435 */ 436 public int getTopFixedCount() { 437 return topFixedCount; 438 } 439 440 /** 441 * Set the number of items to fix at the top of the menu or popup menu. 442 * 443 * @param topFixedCount the number of items 444 */ 445 public void setTopFixedCount(int topFixedCount) { 446 if (firstIndex <= topFixedCount) { 447 firstIndex = topFixedCount; 448 } else { 449 firstIndex += (topFixedCount - this.topFixedCount); 450 } 451 this.topFixedCount = topFixedCount; 452 } 453 454 /** 455 * Return the number of items fixed at the bottom of the menu or popup 456 * menu. 457 * 458 * @return the number of items 459 */ 460 public int getBottomFixedCount() { 461 return bottomFixedCount; 462 } 463 464 /** 465 * Set the number of items to fix at the bottom of the menu or popup menu. 466 * 467 * @param bottomFixedCount the number of items 468 */ 469 public void setBottomFixedCount(int bottomFixedCount) { 470 this.bottomFixedCount = bottomFixedCount; 471 } 472 473 /** 474 * Scroll the specified item into view each time the menu is opened. Call 475 * this method with <code>null</code> to restore the default behavior, which 476 * is to show the menu as it last appeared. 477 * 478 * @param item the item to keep visible 479 * @see #keepVisible(int) 480 */ 481 public void keepVisible(JMenuItem item) { 482 if (item == null) { 483 keepVisibleIndex = -1; 484 } else { 485 int index = menu.getComponentIndex(item); 486 keepVisibleIndex = index; 487 } 488 } 489 490 /** 491 * Scroll the item at the specified index into view each time the menu is 492 * opened. Call this method with <code>-1</code> to restore the default 493 * behavior, which is to show the menu as it last appeared. 494 * 495 * @param index the index of the item to keep visible 496 * @see #keepVisible(javax.swing.JMenuItem) 497 */ 498 public void keepVisible(int index) { 499 keepVisibleIndex = index; 500 } 501 502 /** 503 * Remove this MenuScroller from the associated menu and restores the 504 * default behavior of the menu. 505 */ 506 public void dispose() { 507 if (menu != null) { 508 removeListeners(); 509 menu = null; 510 } 511 } 512 513 /** 514 * Ensure that the <code>dispose</code> method of this MenuScroller is 515 * called when there are no more refrences to it. 516 * 517 * @exception Throwable if an error occurs. 518 * @see MenuScroller#dispose() 519 */ 520 @Override 521 @SuppressWarnings("deprecation") // Object.finalize 522 protected void finalize() throws Throwable { 523 dispose(); 524 } 525 526 private void refreshMenu() { 527 if (menuItems != null && menuItems.length > 0) { 528 firstIndex = Math.max(topFixedCount, firstIndex); 529 firstIndex = Math.min(menuItems.length - bottomFixedCount - scrollCount, firstIndex); 530 531 upItem.setEnabled(firstIndex > topFixedCount); 532 downItem.setEnabled(firstIndex + scrollCount < menuItems.length - bottomFixedCount); 533 534 menu.removeAll(); 535 for (int i = 0; i < topFixedCount; i++) { 536 menu.add(menuItems[i]); 537 } 538 if (topFixedCount > 0) { 539 menu.addSeparator(); 540 } 541 542 menu.add(upItem); 543 for (int i = firstIndex; i < scrollCount + firstIndex; i++) { 544 menu.add(menuItems[i]); 545 } 546 menu.add(downItem); 547 548 if (bottomFixedCount > 0) { 549 menu.addSeparator(); 550 } 551 for (int i = menuItems.length - bottomFixedCount; i < menuItems.length; i++) { 552 menu.add(menuItems[i]); 553 } 554 555 int maxPreferredWidth = 0; 556 for (Component item : menuItems) { 557 maxPreferredWidth = Math.max(maxPreferredWidth, item.getPreferredSize().width); 558 } 559 menu.setPreferredSize(new Dimension(maxPreferredWidth, menu.getPreferredSize().height)); 560 561 java.awt.Container cont = upItem.getParent(); 562 if (cont instanceof JComponent) { 563 ((JComponent) cont).revalidate(); 564 ((JComponent) cont).repaint(); 565 } 566 } 567 } 568 569 private class MenuScrollerPopupMenuListener implements PopupMenuListener { 570 571 @Override 572 public void popupMenuWillBecomeVisible(PopupMenuEvent e) { 573 setMenuItems(); 574 } 575 576 @Override 577 public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { 578 restoreMenuItems(); 579 } 580 581 @Override 582 public void popupMenuCanceled(PopupMenuEvent e) { 583 restoreMenuItems(); 584 } 585 586 private void setMenuItems() { 587 menuItems = menu.getComponents(); 588 589 // Dead code commented out 2021-12-13 590 // if (false) { // not working 591 // for (Component c : menuItems) { 592 // JMenuItem jmi = (JMenuItem) c; 593 // // remove all menu key listeners 594 // for (MenuKeyListener mkl : jmi.getMenuKeyListeners()) { 595 // jmi.removeMenuKeyListener(mkl); 596 // } 597 // jmi.addMenuKeyListener(menuScrollerMenuKeyListener); 598 // } 599 // } 600 601 if (keepVisibleIndex >= topFixedCount 602 && keepVisibleIndex <= menuItems.length - bottomFixedCount 603 && (keepVisibleIndex > firstIndex + scrollCount 604 || keepVisibleIndex < firstIndex)) { 605 firstIndex = Math.min(firstIndex, keepVisibleIndex); 606 firstIndex = Math.max(firstIndex, keepVisibleIndex - scrollCount + 1); 607 } 608 if (menuItems.length > topFixedCount + scrollCount + bottomFixedCount) { 609 refreshMenu(); 610 } 611 } 612 613 private void restoreMenuItems() { 614 menu.removeAll(); 615 for (Component c : menuItems) { 616 617 // Dead code commented out 2021-12-13 618 // if (false) { // not working 619 // JMenuItem jmi = (JMenuItem) c; 620 // jmi.removeMenuKeyListener(menuScrollerMenuKeyListener); 621 // } 622 623 menu.add(c); 624 } 625 } 626 } 627 628 private class MenuScrollerTimer extends Timer { 629 630 public MenuScrollerTimer(final int increment, int interval) { 631 super(interval, new ActionListener() { 632 633 @Override 634 public void actionPerformed(ActionEvent e) { 635 firstIndex += increment; 636 refreshMenu(); 637 } 638 }); 639 } 640 } 641 642 private class MenuScrollerItem extends JMenuItem 643 implements ChangeListener { 644 645 private MenuScrollerTimer timer; 646 647 public MenuScrollerItem(MenuScrollerIcon icon, int increment) { 648 setIcon(icon); 649 setDisabledIcon(icon); 650 timer = new MenuScrollerTimer(increment, interval); 651 addChangeListener(this); 652 } 653 654 public void setInterval(int interval) { 655 timer.setDelay(interval); 656 } 657 658 @Override 659 public void stateChanged(ChangeEvent e) { 660 if (isArmed() && !timer.isRunning()) { 661 timer.start(); 662 } 663 if (!isArmed() && timer.isRunning()) { 664 timer.stop(); 665 } 666 } 667 } // class MenuScrollerItem 668 669 // TODO: Determine why these methods are not being called 670 private class MenuScrollerMenuKeyListener implements MenuKeyListener { 671 672 @Override 673 public void menuKeyTyped(MenuKeyEvent e) { 674 int keyCode = e.getKeyCode(); 675 log.debug("MenuScroller.keyTyped({})", keyCode); 676 } 677 678 @Override 679 public void menuKeyPressed(MenuKeyEvent e) { 680 int keyCode = e.getKeyCode(); 681 log.debug("MenuScroller.keyPressed({})", keyCode); 682 } 683 684 @Override 685 public void menuKeyReleased(MenuKeyEvent e) { 686 int keyCode = e.getKeyCode(); 687 switch (keyCode) { 688 case KeyEvent.VK_UP: { 689 log.debug("MenuScroller.keyReleased(VK_UP)"); 690 firstIndex--; 691 refreshMenu(); 692 e.consume(); 693 break; 694 } 695 696 case KeyEvent.VK_DOWN: { 697 log.debug("MenuScroller.keyReleased(VK_DOWN)"); 698 firstIndex++; 699 refreshMenu(); 700 e.consume(); 701 break; 702 } 703 704 default: { 705 log.debug("MenuScroller.keyReleased({})", keyCode); 706 break; 707 } 708 } //switch 709 } 710 } 711 712 private static enum MenuScrollerIcon implements Icon { 713 714 UP(9, 1, 9), 715 DOWN(1, 9, 1); 716 final int[] xPoints = {1, 5, 9}; 717 final int[] yPoints; 718 719 MenuScrollerIcon(int... yPoints) { 720 this.yPoints = yPoints; 721 } 722 723 @Override 724 public void paintIcon(Component c, Graphics g, int x, int y) { 725 Dimension size = c.getSize(); 726 Graphics g2 = g.create(size.width / 2 - 5, size.height / 2 - 5, 10, 10); 727 g2.setColor(Color.GRAY); 728 g2.drawPolygon(xPoints, yPoints, 3); 729 if (c.isEnabled()) { 730 g2.setColor(Color.BLACK); 731 g2.fillPolygon(xPoints, yPoints, 3); 732 } 733 g2.dispose(); 734 } 735 736 @Override 737 public int getIconWidth() { 738 return 0; 739 } 740 741 @Override 742 public int getIconHeight() { 743 return 10; 744 } 745 } 746 747 private final static Logger log = LoggerFactory.getLogger(MenuScroller.class); 748 749}