From 50ffd7b4a9e70ab3513baaea05790dd42333310f Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Wed, 21 Apr 2021 19:37:05 +0200 Subject: [PATCH 1/3] Support scrolling when Popup has too many entries --- .../src/main/java/forge/gui/MenuScroller.java | 589 ++++++++++++++++++ .../java/forge/screens/match/CMatchUI.java | 3 + 2 files changed, 592 insertions(+) create mode 100644 forge-gui-desktop/src/main/java/forge/gui/MenuScroller.java diff --git a/forge-gui-desktop/src/main/java/forge/gui/MenuScroller.java b/forge-gui-desktop/src/main/java/forge/gui/MenuScroller.java new file mode 100644 index 00000000000..8a9da774b35 --- /dev/null +++ b/forge-gui-desktop/src/main/java/forge/gui/MenuScroller.java @@ -0,0 +1,589 @@ +/** + * @(#)MenuScroller.java 1.5.0 04/02/12 + * You are free to use and/or modify and/or distribute any or all code posted on the Java Tips Weblog without restriction. + */ +package forge.gui; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.Icon; +import javax.swing.JComponent; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.MenuSelectionManager; +import javax.swing.Timer; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; + +/** + * A class that provides scrolling capabilities to a long menu dropdown or + * popup menu. A number of items can optionally be frozen at the top and/or + * bottom of the menu. + *

+ * Implementation note: The default number of items to display + * at a time is 15, and the default scrolling interval is 125 milliseconds. + *

+ * + * @version 1.5.0 04/05/12 + * @author Darryl Burke + * @see https://tips4java.wordpress.com/2009/02/01/menu-scroller/ + */ +public class MenuScroller { + + //private JMenu menu; + private JPopupMenu menu; + private Component[] menuItems; + private MenuScrollItem upItem; + private MenuScrollItem downItem; + private final MenuScrollListener menuListener = new MenuScrollListener(); + private int scrollCount; + private int interval; + private int topFixedCount; + private int bottomFixedCount; + private int firstIndex = 0; + private int keepVisibleIndex = -1; + + /** + * Registers a menu to be scrolled with the default number of items to + * display at a time and the default scrolling interval. + * + * @param menu the menu + * @return the MenuScroller + */ + public static MenuScroller setScrollerFor(JMenu menu) { + return new MenuScroller(menu); + } + + /** + * Registers a popup menu to be scrolled with the default number of items to + * display at a time and the default scrolling interval. + * + * @param menu the popup menu + * @return the MenuScroller + */ + public static MenuScroller setScrollerFor(JPopupMenu menu) { + return new MenuScroller(menu); + } + + /** + * Registers a menu to be scrolled with the default number of items to + * display at a time and the specified scrolling interval. + * + * @param menu the menu + * @param scrollCount the number of items to display at a time + * @return the MenuScroller + * @throws IllegalArgumentException if scrollCount is 0 or negative + */ + public static MenuScroller setScrollerFor(JMenu menu, int scrollCount) { + return new MenuScroller(menu, scrollCount); + } + + /** + * Registers a popup menu to be scrolled with the default number of items to + * display at a time and the specified scrolling interval. + * + * @param menu the popup menu + * @param scrollCount the number of items to display at a time + * @return the MenuScroller + * @throws IllegalArgumentException if scrollCount is 0 or negative + */ + public static MenuScroller setScrollerFor(JPopupMenu menu, int scrollCount) { + return new MenuScroller(menu, scrollCount); + } + + /** + * Registers a menu to be scrolled, with the specified number of items to + * display at a time and the specified scrolling interval. + * + * @param menu the menu + * @param scrollCount the number of items to be displayed at a time + * @param interval the scroll interval, in milliseconds + * @return the MenuScroller + * @throws IllegalArgumentException if scrollCount or interval is 0 or negative + */ + public static MenuScroller setScrollerFor(JMenu menu, int scrollCount, int interval) { + return new MenuScroller(menu, scrollCount, interval); + } + + /** + * Registers a popup menu to be scrolled, with the specified number of items to + * display at a time and the specified scrolling interval. + * + * @param menu the popup menu + * @param scrollCount the number of items to be displayed at a time + * @param interval the scroll interval, in milliseconds + * @return the MenuScroller + * @throws IllegalArgumentException if scrollCount or interval is 0 or negative + */ + public static MenuScroller setScrollerFor(JPopupMenu menu, int scrollCount, int interval) { + return new MenuScroller(menu, scrollCount, interval); + } + + /** + * Registers a menu to be scrolled, with the specified number of items + * to display in the scrolling region, the specified scrolling interval, + * and the specified numbers of items fixed at the top and bottom of the + * menu. + * + * @param menu the menu + * @param scrollCount the number of items to display in the scrolling portion + * @param interval the scroll interval, in milliseconds + * @param topFixedCount the number of items to fix at the top. May be 0. + * @param bottomFixedCount the number of items to fix at the bottom. May be 0 + * @throws IllegalArgumentException if scrollCount or interval is 0 or + * negative or if topFixedCount or bottomFixedCount is negative + * @return the MenuScroller + */ + public static MenuScroller setScrollerFor(JMenu menu, int scrollCount, int interval, + int topFixedCount, int bottomFixedCount) { + return new MenuScroller(menu, scrollCount, interval, + topFixedCount, bottomFixedCount); + } + + /** + * Registers a popup menu to be scrolled, with the specified number of items + * to display in the scrolling region, the specified scrolling interval, + * and the specified numbers of items fixed at the top and bottom of the + * popup menu. + * + * @param menu the popup menu + * @param scrollCount the number of items to display in the scrolling portion + * @param interval the scroll interval, in milliseconds + * @param topFixedCount the number of items to fix at the top. May be 0 + * @param bottomFixedCount the number of items to fix at the bottom. May be 0 + * @throws IllegalArgumentException if scrollCount or interval is 0 or + * negative or if topFixedCount or bottomFixedCount is negative + * @return the MenuScroller + */ + public static MenuScroller setScrollerFor(JPopupMenu menu, int scrollCount, int interval, + int topFixedCount, int bottomFixedCount) { + return new MenuScroller(menu, scrollCount, interval, + topFixedCount, bottomFixedCount); + } + + /** + * Constructs a MenuScroller that scrolls a menu with the + * default number of items to display at a time, and default scrolling + * interval. + * + * @param menu the menu + */ + public MenuScroller(JMenu menu) { + this(menu, 15); + } + + /** + * Constructs a MenuScroller that scrolls a popup menu with the + * default number of items to display at a time, and default scrolling + * interval. + * + * @param menu the popup menu + */ + public MenuScroller(JPopupMenu menu) { + this(menu, 15); + } + + /** + * Constructs a MenuScroller that scrolls a menu with the + * specified number of items to display at a time, and default scrolling + * interval. + * + * @param menu the menu + * @param scrollCount the number of items to display at a time + * @throws IllegalArgumentException if scrollCount is 0 or negative + */ + public MenuScroller(JMenu menu, int scrollCount) { + this(menu, scrollCount, 150); + } + + /** + * Constructs a MenuScroller that scrolls a popup menu with the + * specified number of items to display at a time, and default scrolling + * interval. + * + * @param menu the popup menu + * @param scrollCount the number of items to display at a time + * @throws IllegalArgumentException if scrollCount is 0 or negative + */ + public MenuScroller(JPopupMenu menu, int scrollCount) { + this(menu, scrollCount, 150); + } + + /** + * Constructs a MenuScroller that scrolls a menu with the + * specified number of items to display at a time, and specified scrolling + * interval. + * + * @param menu the menu + * @param scrollCount the number of items to display at a time + * @param interval the scroll interval, in milliseconds + * @throws IllegalArgumentException if scrollCount or interval is 0 or negative + */ + public MenuScroller(JMenu menu, int scrollCount, int interval) { + this(menu, scrollCount, interval, 0, 0); + } + + /** + * Constructs a MenuScroller that scrolls a popup menu with the + * specified number of items to display at a time, and specified scrolling + * interval. + * + * @param menu the popup menu + * @param scrollCount the number of items to display at a time + * @param interval the scroll interval, in milliseconds + * @throws IllegalArgumentException if scrollCount or interval is 0 or negative + */ + public MenuScroller(JPopupMenu menu, int scrollCount, int interval) { + this(menu, scrollCount, interval, 0, 0); + } + + /** + * Constructs a MenuScroller that scrolls a menu with the + * specified number of items to display in the scrolling region, the + * specified scrolling interval, and the specified numbers of items fixed at + * the top and bottom of the menu. + * + * @param menu the menu + * @param scrollCount the number of items to display in the scrolling portion + * @param interval the scroll interval, in milliseconds + * @param topFixedCount the number of items to fix at the top. May be 0 + * @param bottomFixedCount the number of items to fix at the bottom. May be 0 + * @throws IllegalArgumentException if scrollCount or interval is 0 or + * negative or if topFixedCount or bottomFixedCount is negative + */ + public MenuScroller(JMenu menu, int scrollCount, int interval, + int topFixedCount, int bottomFixedCount) { + this(menu.getPopupMenu(), scrollCount, interval, topFixedCount, bottomFixedCount); + } + + /** + * Constructs a MenuScroller that scrolls a popup menu with the + * specified number of items to display in the scrolling region, the + * specified scrolling interval, and the specified numbers of items fixed at + * the top and bottom of the popup menu. + * + * @param menu the popup menu + * @param scrollCount the number of items to display in the scrolling portion + * @param interval the scroll interval, in milliseconds + * @param topFixedCount the number of items to fix at the top. May be 0 + * @param bottomFixedCount the number of items to fix at the bottom. May be 0 + * @throws IllegalArgumentException if scrollCount or interval is 0 or + * negative or if topFixedCount or bottomFixedCount is negative + */ + public MenuScroller(JPopupMenu menu, int scrollCount, int interval, + int topFixedCount, int bottomFixedCount) { + if (scrollCount <= 0 || interval <= 0) { + throw new IllegalArgumentException("scrollCount and interval must be greater than 0"); + } + if (topFixedCount < 0 || bottomFixedCount < 0) { + throw new IllegalArgumentException("topFixedCount and bottomFixedCount cannot be negative"); + } + + upItem = new MenuScrollItem(MenuIcon.UP, -1); + downItem = new MenuScrollItem(MenuIcon.DOWN, +1); + setScrollCount(scrollCount); + setInterval(interval); + setTopFixedCount(topFixedCount); + setBottomFixedCount(bottomFixedCount); + + this.menu = menu; + menu.addPopupMenuListener(menuListener); + } + + /** + * Returns the scroll interval in milliseconds + * + * @return the scroll interval in milliseconds + */ + public int getInterval() { + return interval; + } + + /** + * Sets the scroll interval in milliseconds + * + * @param interval the scroll interval in milliseconds + * @throws IllegalArgumentException if interval is 0 or negative + */ + public void setInterval(int interval) { + if (interval <= 0) { + throw new IllegalArgumentException("interval must be greater than 0"); + } + upItem.setInterval(interval); + downItem.setInterval(interval); + this.interval = interval; + } + + /** + * Returns the number of items in the scrolling portion of the menu. + * + * @return the number of items to display at a time + */ + public int getscrollCount() { + return scrollCount; + } + + /** + * Sets the number of items in the scrolling portion of the menu. + * + * @param scrollCount the number of items to display at a time + * @throws IllegalArgumentException if scrollCount is 0 or negative + */ + public void setScrollCount(int scrollCount) { + if (scrollCount <= 0) { + throw new IllegalArgumentException("scrollCount must be greater than 0"); + } + this.scrollCount = scrollCount; + MenuSelectionManager.defaultManager().clearSelectedPath(); + } + + /** + * Returns the number of items fixed at the top of the menu or popup menu. + * + * @return the number of items + */ + public int getTopFixedCount() { + return topFixedCount; + } + + /** + * Sets the number of items to fix at the top of the menu or popup menu. + * + * @param topFixedCount the number of items + */ + public void setTopFixedCount(int topFixedCount) { + if (firstIndex <= topFixedCount) { + firstIndex = topFixedCount; + } else { + firstIndex += (topFixedCount - this.topFixedCount); + } + this.topFixedCount = topFixedCount; + } + + /** + * Returns the number of items fixed at the bottom of the menu or popup menu. + * + * @return the number of items + */ + public int getBottomFixedCount() { + return bottomFixedCount; + } + + /** + * Sets the number of items to fix at the bottom of the menu or popup menu. + * + * @param bottomFixedCount the number of items + */ + public void setBottomFixedCount(int bottomFixedCount) { + this.bottomFixedCount = bottomFixedCount; + } + + /** + * Scrolls the specified item into view each time the menu is opened. Call this method with + * null to restore the default behavior, which is to show the menu as it last + * appeared. + * + * @param item the item to keep visible + * @see #keepVisible(int) + */ + public void keepVisible(JMenuItem item) { + if (item == null) { + keepVisibleIndex = -1; + } else { + int index = menu.getComponentIndex(item); + keepVisibleIndex = index; + } + } + + /** + * Scrolls the item at the specified index into view each time the menu is opened. Call this + * method with -1 to restore the default behavior, which is to show the menu as + * it last appeared. + * + * @param index the index of the item to keep visible + * @see #keepVisible(javax.swing.JMenuItem) + */ + public void keepVisible(int index) { + keepVisibleIndex = index; + } + + /** + * Removes this MenuScroller from the associated menu and restores the + * default behavior of the menu. + */ + public void dispose() { + if (menu != null) { + menu.removePopupMenuListener(menuListener); + menu = null; + } + } + + /** + * Ensures that the dispose method of this MenuScroller is + * called when there are no more refrences to it. + * + * @exception Throwable if an error occurs. + * @see MenuScroller#dispose() + */ + @Override + public void finalize() throws Throwable { + dispose(); + } + + private void refreshMenu() { + if (menuItems != null && menuItems.length > 0) { + firstIndex = Math.max(topFixedCount, firstIndex); + firstIndex = Math.min(menuItems.length - bottomFixedCount - scrollCount, firstIndex); + + upItem.setEnabled(firstIndex > topFixedCount); + downItem.setEnabled(firstIndex + scrollCount < menuItems.length - bottomFixedCount); + + menu.removeAll(); + for (int i = 0; i < topFixedCount; i++) { + menu.add(menuItems[i]); + } + if (topFixedCount > 0) { + menu.addSeparator(); + } + + menu.add(upItem); + for (int i = firstIndex; i < scrollCount + firstIndex; i++) { + menu.add(menuItems[i]); + } + menu.add(downItem); + + if (bottomFixedCount > 0) { + menu.addSeparator(); + } + for (int i = menuItems.length - bottomFixedCount; i < menuItems.length; i++) { + menu.add(menuItems[i]); + } + + JComponent parent = (JComponent) upItem.getParent(); + parent.revalidate(); + parent.repaint(); + } + } + + private class MenuScrollListener implements PopupMenuListener { + + @Override + public void popupMenuWillBecomeVisible(PopupMenuEvent e) { + setMenuItems(); + } + + @Override + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { + restoreMenuItems(); + } + + @Override + public void popupMenuCanceled(PopupMenuEvent e) { + restoreMenuItems(); + } + + private void setMenuItems() { + menuItems = menu.getComponents(); + if (keepVisibleIndex >= topFixedCount + && keepVisibleIndex <= menuItems.length - bottomFixedCount + && (keepVisibleIndex > firstIndex + scrollCount + || keepVisibleIndex < firstIndex)) { + firstIndex = Math.min(firstIndex, keepVisibleIndex); + firstIndex = Math.max(firstIndex, keepVisibleIndex - scrollCount + 1); + } + if (menuItems.length > topFixedCount + scrollCount + bottomFixedCount) { + refreshMenu(); + } + } + + private void restoreMenuItems() { + menu.removeAll(); + for (Component component : menuItems) { + menu.add(component); + } + } + } + + private class MenuScrollTimer extends Timer { + + public MenuScrollTimer(final int increment, int interval) { + super(interval, new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + firstIndex += increment; + refreshMenu(); + } + }); + } + } + + private class MenuScrollItem extends JMenuItem + implements ChangeListener { + + private MenuScrollTimer timer; + + public MenuScrollItem(MenuIcon icon, int increment) { + setIcon(icon); + setDisabledIcon(icon); + timer = new MenuScrollTimer(increment, interval); + addChangeListener(this); + } + + public void setInterval(int interval) { + timer.setDelay(interval); + } + + @Override + public void stateChanged(ChangeEvent e) { + if (isArmed() && !timer.isRunning()) { + timer.start(); + } + if (!isArmed() && timer.isRunning()) { + timer.stop(); + } + } + } + + private static enum MenuIcon implements Icon { + + UP(9, 1, 9), + DOWN(1, 9, 1); + final int[] xPoints = {1, 5, 9}; + final int[] yPoints; + + MenuIcon(int... yPoints) { + this.yPoints = yPoints; + } + + @Override + public void paintIcon(Component c, Graphics g, int x, int y) { + Dimension size = c.getSize(); + Graphics g2 = g.create(size.width / 2 - 5, size.height / 2 - 5, 10, 10); + g2.setColor(Color.GRAY); + g2.drawPolygon(xPoints, yPoints, 3); + if (c.isEnabled()) { + g2.setColor(Color.BLACK); + g2.fillPolygon(xPoints, yPoints, 3); + } + g2.dispose(); + } + + @Override + public int getIconWidth() { + return 0; + } + + @Override + public int getIconHeight() { + return 10; + } + } +} diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java b/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java index 01887035ddb..a6998bec1ac 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java @@ -84,6 +84,7 @@ import forge.gui.GuiBase; import forge.gui.GuiChoose; import forge.gui.GuiDialog; import forge.gui.GuiUtils; +import forge.gui.MenuScroller; import forge.gui.SOverlayUtils; import forge.gui.framework.DragCell; import forge.gui.framework.EDocID; @@ -906,6 +907,8 @@ public final class CMatchUI //show menu if mouse was trigger for ability final JPopupMenu menu = new JPopupMenu(Localizer.getInstance().getMessage("lblAbilities")); + //add scroll area when too big + MenuScroller.setScrollerFor(menu, 8, 125, 3, 1); boolean enabled; int firstEnabled = -1; From c00a384591859d0d6edb03961ef5685f2399a9d1 Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Wed, 21 Apr 2021 19:55:14 +0200 Subject: [PATCH 2/3] Add MouseWheel support --- .../src/main/java/forge/gui/MenuScroller.java | 12 ++++++++++++ .../src/main/java/forge/screens/match/CMatchUI.java | 1 + 2 files changed, 13 insertions(+) diff --git a/forge-gui-desktop/src/main/java/forge/gui/MenuScroller.java b/forge-gui-desktop/src/main/java/forge/gui/MenuScroller.java index 8a9da774b35..76eeaac0d50 100644 --- a/forge-gui-desktop/src/main/java/forge/gui/MenuScroller.java +++ b/forge-gui-desktop/src/main/java/forge/gui/MenuScroller.java @@ -10,6 +10,9 @@ import java.awt.Dimension; import java.awt.Graphics; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.MouseWheelEvent; +import java.awt.event.MouseWheelListener; + import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.JMenu; @@ -295,6 +298,7 @@ public class MenuScroller { this.menu = menu; menu.addPopupMenuListener(menuListener); + menu.addMouseWheelListener(new MouseScrollListener()); } /** @@ -586,4 +590,12 @@ public class MenuScroller { return 10; } } + + private class MouseScrollListener implements MouseWheelListener { + public void mouseWheelMoved(MouseWheelEvent mwe){ + firstIndex += mwe.getWheelRotation(); + refreshMenu(); + mwe.consume(); + } + } } diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java b/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java index a6998bec1ac..6af24207671 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java @@ -908,6 +908,7 @@ public final class CMatchUI //show menu if mouse was trigger for ability final JPopupMenu menu = new JPopupMenu(Localizer.getInstance().getMessage("lblAbilities")); //add scroll area when too big + // TODO: do we need a user setting for the scrollCount? MenuScroller.setScrollerFor(menu, 8, 125, 3, 1); boolean enabled; From 982400b74d797865feb6b7e343c1e160e0e839f8 Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Wed, 21 Apr 2021 20:50:13 +0200 Subject: [PATCH 3/3] Clean up --- .../src/main/java/forge/gui/MenuScroller.java | 1067 +++++++++-------- 1 file changed, 550 insertions(+), 517 deletions(-) diff --git a/forge-gui-desktop/src/main/java/forge/gui/MenuScroller.java b/forge-gui-desktop/src/main/java/forge/gui/MenuScroller.java index 76eeaac0d50..b20fb3261f3 100644 --- a/forge-gui-desktop/src/main/java/forge/gui/MenuScroller.java +++ b/forge-gui-desktop/src/main/java/forge/gui/MenuScroller.java @@ -8,6 +8,8 @@ import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Graphics; +import java.awt.Point; +import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseWheelEvent; @@ -19,6 +21,7 @@ import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.MenuSelectionManager; +import javax.swing.SwingUtilities; import javax.swing.Timer; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; @@ -40,562 +43,592 @@ import javax.swing.event.PopupMenuListener; */ public class MenuScroller { - //private JMenu menu; - private JPopupMenu menu; - private Component[] menuItems; - private MenuScrollItem upItem; - private MenuScrollItem downItem; - private final MenuScrollListener menuListener = new MenuScrollListener(); - private int scrollCount; - private int interval; - private int topFixedCount; - private int bottomFixedCount; - private int firstIndex = 0; - private int keepVisibleIndex = -1; + //private JMenu menu; + private JPopupMenu menu; + private Component[] menuItems; + private MenuScrollItem upItem; + private MenuScrollItem downItem; + private final MenuScrollListener menuListener = new MenuScrollListener(); + private final MouseWheelListener mouseWheelListener = new MouseScrollListener(); + private int scrollCount; + private int interval; + private int topFixedCount; + private int bottomFixedCount; + private int firstIndex = 0; + private int keepVisibleIndex = -1; - /** - * Registers a menu to be scrolled with the default number of items to - * display at a time and the default scrolling interval. - * - * @param menu the menu - * @return the MenuScroller - */ - public static MenuScroller setScrollerFor(JMenu menu) { - return new MenuScroller(menu); - } - - /** - * Registers a popup menu to be scrolled with the default number of items to - * display at a time and the default scrolling interval. - * - * @param menu the popup menu - * @return the MenuScroller - */ - public static MenuScroller setScrollerFor(JPopupMenu menu) { - return new MenuScroller(menu); - } - - /** - * Registers a menu to be scrolled with the default number of items to - * display at a time and the specified scrolling interval. - * - * @param menu the menu - * @param scrollCount the number of items to display at a time - * @return the MenuScroller - * @throws IllegalArgumentException if scrollCount is 0 or negative - */ - public static MenuScroller setScrollerFor(JMenu menu, int scrollCount) { - return new MenuScroller(menu, scrollCount); - } - - /** - * Registers a popup menu to be scrolled with the default number of items to - * display at a time and the specified scrolling interval. - * - * @param menu the popup menu - * @param scrollCount the number of items to display at a time - * @return the MenuScroller - * @throws IllegalArgumentException if scrollCount is 0 or negative - */ - public static MenuScroller setScrollerFor(JPopupMenu menu, int scrollCount) { - return new MenuScroller(menu, scrollCount); - } - - /** - * Registers a menu to be scrolled, with the specified number of items to - * display at a time and the specified scrolling interval. - * - * @param menu the menu - * @param scrollCount the number of items to be displayed at a time - * @param interval the scroll interval, in milliseconds - * @return the MenuScroller - * @throws IllegalArgumentException if scrollCount or interval is 0 or negative - */ - public static MenuScroller setScrollerFor(JMenu menu, int scrollCount, int interval) { - return new MenuScroller(menu, scrollCount, interval); - } - - /** - * Registers a popup menu to be scrolled, with the specified number of items to - * display at a time and the specified scrolling interval. - * - * @param menu the popup menu - * @param scrollCount the number of items to be displayed at a time - * @param interval the scroll interval, in milliseconds - * @return the MenuScroller - * @throws IllegalArgumentException if scrollCount or interval is 0 or negative - */ - public static MenuScroller setScrollerFor(JPopupMenu menu, int scrollCount, int interval) { - return new MenuScroller(menu, scrollCount, interval); - } - - /** - * Registers a menu to be scrolled, with the specified number of items - * to display in the scrolling region, the specified scrolling interval, - * and the specified numbers of items fixed at the top and bottom of the - * menu. - * - * @param menu the menu - * @param scrollCount the number of items to display in the scrolling portion - * @param interval the scroll interval, in milliseconds - * @param topFixedCount the number of items to fix at the top. May be 0. - * @param bottomFixedCount the number of items to fix at the bottom. May be 0 - * @throws IllegalArgumentException if scrollCount or interval is 0 or - * negative or if topFixedCount or bottomFixedCount is negative - * @return the MenuScroller - */ - public static MenuScroller setScrollerFor(JMenu menu, int scrollCount, int interval, - int topFixedCount, int bottomFixedCount) { - return new MenuScroller(menu, scrollCount, interval, - topFixedCount, bottomFixedCount); - } - - /** - * Registers a popup menu to be scrolled, with the specified number of items - * to display in the scrolling region, the specified scrolling interval, - * and the specified numbers of items fixed at the top and bottom of the - * popup menu. - * - * @param menu the popup menu - * @param scrollCount the number of items to display in the scrolling portion - * @param interval the scroll interval, in milliseconds - * @param topFixedCount the number of items to fix at the top. May be 0 - * @param bottomFixedCount the number of items to fix at the bottom. May be 0 - * @throws IllegalArgumentException if scrollCount or interval is 0 or - * negative or if topFixedCount or bottomFixedCount is negative - * @return the MenuScroller - */ - public static MenuScroller setScrollerFor(JPopupMenu menu, int scrollCount, int interval, - int topFixedCount, int bottomFixedCount) { - return new MenuScroller(menu, scrollCount, interval, - topFixedCount, bottomFixedCount); - } - - /** - * Constructs a MenuScroller that scrolls a menu with the - * default number of items to display at a time, and default scrolling - * interval. - * - * @param menu the menu - */ - public MenuScroller(JMenu menu) { - this(menu, 15); - } - - /** - * Constructs a MenuScroller that scrolls a popup menu with the - * default number of items to display at a time, and default scrolling - * interval. - * - * @param menu the popup menu - */ - public MenuScroller(JPopupMenu menu) { - this(menu, 15); - } - - /** - * Constructs a MenuScroller that scrolls a menu with the - * specified number of items to display at a time, and default scrolling - * interval. - * - * @param menu the menu - * @param scrollCount the number of items to display at a time - * @throws IllegalArgumentException if scrollCount is 0 or negative - */ - public MenuScroller(JMenu menu, int scrollCount) { - this(menu, scrollCount, 150); - } - - /** - * Constructs a MenuScroller that scrolls a popup menu with the - * specified number of items to display at a time, and default scrolling - * interval. - * - * @param menu the popup menu - * @param scrollCount the number of items to display at a time - * @throws IllegalArgumentException if scrollCount is 0 or negative - */ - public MenuScroller(JPopupMenu menu, int scrollCount) { - this(menu, scrollCount, 150); - } - - /** - * Constructs a MenuScroller that scrolls a menu with the - * specified number of items to display at a time, and specified scrolling - * interval. - * - * @param menu the menu - * @param scrollCount the number of items to display at a time - * @param interval the scroll interval, in milliseconds - * @throws IllegalArgumentException if scrollCount or interval is 0 or negative - */ - public MenuScroller(JMenu menu, int scrollCount, int interval) { - this(menu, scrollCount, interval, 0, 0); - } - - /** - * Constructs a MenuScroller that scrolls a popup menu with the - * specified number of items to display at a time, and specified scrolling - * interval. - * - * @param menu the popup menu - * @param scrollCount the number of items to display at a time - * @param interval the scroll interval, in milliseconds - * @throws IllegalArgumentException if scrollCount or interval is 0 or negative - */ - public MenuScroller(JPopupMenu menu, int scrollCount, int interval) { - this(menu, scrollCount, interval, 0, 0); - } - - /** - * Constructs a MenuScroller that scrolls a menu with the - * specified number of items to display in the scrolling region, the - * specified scrolling interval, and the specified numbers of items fixed at - * the top and bottom of the menu. - * - * @param menu the menu - * @param scrollCount the number of items to display in the scrolling portion - * @param interval the scroll interval, in milliseconds - * @param topFixedCount the number of items to fix at the top. May be 0 - * @param bottomFixedCount the number of items to fix at the bottom. May be 0 - * @throws IllegalArgumentException if scrollCount or interval is 0 or - * negative or if topFixedCount or bottomFixedCount is negative - */ - public MenuScroller(JMenu menu, int scrollCount, int interval, - int topFixedCount, int bottomFixedCount) { - this(menu.getPopupMenu(), scrollCount, interval, topFixedCount, bottomFixedCount); - } - - /** - * Constructs a MenuScroller that scrolls a popup menu with the - * specified number of items to display in the scrolling region, the - * specified scrolling interval, and the specified numbers of items fixed at - * the top and bottom of the popup menu. - * - * @param menu the popup menu - * @param scrollCount the number of items to display in the scrolling portion - * @param interval the scroll interval, in milliseconds - * @param topFixedCount the number of items to fix at the top. May be 0 - * @param bottomFixedCount the number of items to fix at the bottom. May be 0 - * @throws IllegalArgumentException if scrollCount or interval is 0 or - * negative or if topFixedCount or bottomFixedCount is negative - */ - public MenuScroller(JPopupMenu menu, int scrollCount, int interval, - int topFixedCount, int bottomFixedCount) { - if (scrollCount <= 0 || interval <= 0) { - throw new IllegalArgumentException("scrollCount and interval must be greater than 0"); - } - if (topFixedCount < 0 || bottomFixedCount < 0) { - throw new IllegalArgumentException("topFixedCount and bottomFixedCount cannot be negative"); + /** + * Registers a menu to be scrolled with the default number of items to + * display at a time and the default scrolling interval. + * + * @param menu the menu + * @return the MenuScroller + */ + public static MenuScroller setScrollerFor(JMenu menu) { + return new MenuScroller(menu); } - upItem = new MenuScrollItem(MenuIcon.UP, -1); - downItem = new MenuScrollItem(MenuIcon.DOWN, +1); - setScrollCount(scrollCount); - setInterval(interval); - setTopFixedCount(topFixedCount); - setBottomFixedCount(bottomFixedCount); - - this.menu = menu; - menu.addPopupMenuListener(menuListener); - menu.addMouseWheelListener(new MouseScrollListener()); - } - - /** - * Returns the scroll interval in milliseconds - * - * @return the scroll interval in milliseconds - */ - public int getInterval() { - return interval; - } - - /** - * Sets the scroll interval in milliseconds - * - * @param interval the scroll interval in milliseconds - * @throws IllegalArgumentException if interval is 0 or negative - */ - public void setInterval(int interval) { - if (interval <= 0) { - throw new IllegalArgumentException("interval must be greater than 0"); + /** + * Registers a popup menu to be scrolled with the default number of items to + * display at a time and the default scrolling interval. + * + * @param menu the popup menu + * @return the MenuScroller + */ + public static MenuScroller setScrollerFor(JPopupMenu menu) { + return new MenuScroller(menu); } - upItem.setInterval(interval); - downItem.setInterval(interval); - this.interval = interval; - } - /** - * Returns the number of items in the scrolling portion of the menu. - * - * @return the number of items to display at a time - */ - public int getscrollCount() { - return scrollCount; - } - - /** - * Sets the number of items in the scrolling portion of the menu. - * - * @param scrollCount the number of items to display at a time - * @throws IllegalArgumentException if scrollCount is 0 or negative - */ - public void setScrollCount(int scrollCount) { - if (scrollCount <= 0) { - throw new IllegalArgumentException("scrollCount must be greater than 0"); + /** + * Registers a menu to be scrolled with the default number of items to + * display at a time and the specified scrolling interval. + * + * @param menu the menu + * @param scrollCount the number of items to display at a time + * @return the MenuScroller + * @throws IllegalArgumentException if scrollCount is 0 or negative + */ + public static MenuScroller setScrollerFor(JMenu menu, int scrollCount) { + return new MenuScroller(menu, scrollCount); } - this.scrollCount = scrollCount; - MenuSelectionManager.defaultManager().clearSelectedPath(); - } - /** - * Returns the number of items fixed at the top of the menu or popup menu. - * - * @return the number of items - */ - public int getTopFixedCount() { - return topFixedCount; - } - - /** - * Sets the number of items to fix at the top of the menu or popup menu. - * - * @param topFixedCount the number of items - */ - public void setTopFixedCount(int topFixedCount) { - if (firstIndex <= topFixedCount) { - firstIndex = topFixedCount; - } else { - firstIndex += (topFixedCount - this.topFixedCount); + /** + * Registers a popup menu to be scrolled with the default number of items to + * display at a time and the specified scrolling interval. + * + * @param menu the popup menu + * @param scrollCount the number of items to display at a time + * @return the MenuScroller + * @throws IllegalArgumentException if scrollCount is 0 or negative + */ + public static MenuScroller setScrollerFor(JPopupMenu menu, int scrollCount) { + return new MenuScroller(menu, scrollCount); } - this.topFixedCount = topFixedCount; - } - /** - * Returns the number of items fixed at the bottom of the menu or popup menu. - * - * @return the number of items - */ - public int getBottomFixedCount() { - return bottomFixedCount; - } - - /** - * Sets the number of items to fix at the bottom of the menu or popup menu. - * - * @param bottomFixedCount the number of items - */ - public void setBottomFixedCount(int bottomFixedCount) { - this.bottomFixedCount = bottomFixedCount; - } - - /** - * Scrolls the specified item into view each time the menu is opened. Call this method with - * null to restore the default behavior, which is to show the menu as it last - * appeared. - * - * @param item the item to keep visible - * @see #keepVisible(int) - */ - public void keepVisible(JMenuItem item) { - if (item == null) { - keepVisibleIndex = -1; - } else { - int index = menu.getComponentIndex(item); - keepVisibleIndex = index; + /** + * Registers a menu to be scrolled, with the specified number of items to + * display at a time and the specified scrolling interval. + * + * @param menu the menu + * @param scrollCount the number of items to be displayed at a time + * @param interval the scroll interval, in milliseconds + * @return the MenuScroller + * @throws IllegalArgumentException if scrollCount or interval is 0 or negative + */ + public static MenuScroller setScrollerFor(JMenu menu, int scrollCount, int interval) { + return new MenuScroller(menu, scrollCount, interval); } - } - /** - * Scrolls the item at the specified index into view each time the menu is opened. Call this - * method with -1 to restore the default behavior, which is to show the menu as - * it last appeared. - * - * @param index the index of the item to keep visible - * @see #keepVisible(javax.swing.JMenuItem) - */ - public void keepVisible(int index) { - keepVisibleIndex = index; - } - - /** - * Removes this MenuScroller from the associated menu and restores the - * default behavior of the menu. - */ - public void dispose() { - if (menu != null) { - menu.removePopupMenuListener(menuListener); - menu = null; + /** + * Registers a popup menu to be scrolled, with the specified number of items to + * display at a time and the specified scrolling interval. + * + * @param menu the popup menu + * @param scrollCount the number of items to be displayed at a time + * @param interval the scroll interval, in milliseconds + * @return the MenuScroller + * @throws IllegalArgumentException if scrollCount or interval is 0 or negative + */ + public static MenuScroller setScrollerFor(JPopupMenu menu, int scrollCount, int interval) { + return new MenuScroller(menu, scrollCount, interval); } - } - /** - * Ensures that the dispose method of this MenuScroller is - * called when there are no more refrences to it. - * - * @exception Throwable if an error occurs. - * @see MenuScroller#dispose() - */ - @Override - public void finalize() throws Throwable { - dispose(); - } - - private void refreshMenu() { - if (menuItems != null && menuItems.length > 0) { - firstIndex = Math.max(topFixedCount, firstIndex); - firstIndex = Math.min(menuItems.length - bottomFixedCount - scrollCount, firstIndex); - - upItem.setEnabled(firstIndex > topFixedCount); - downItem.setEnabled(firstIndex + scrollCount < menuItems.length - bottomFixedCount); - - menu.removeAll(); - for (int i = 0; i < topFixedCount; i++) { - menu.add(menuItems[i]); - } - if (topFixedCount > 0) { - menu.addSeparator(); - } - - menu.add(upItem); - for (int i = firstIndex; i < scrollCount + firstIndex; i++) { - menu.add(menuItems[i]); - } - menu.add(downItem); - - if (bottomFixedCount > 0) { - menu.addSeparator(); - } - for (int i = menuItems.length - bottomFixedCount; i < menuItems.length; i++) { - menu.add(menuItems[i]); - } - - JComponent parent = (JComponent) upItem.getParent(); - parent.revalidate(); - parent.repaint(); + /** + * Registers a menu to be scrolled, with the specified number of items + * to display in the scrolling region, the specified scrolling interval, + * and the specified numbers of items fixed at the top and bottom of the + * menu. + * + * @param menu the menu + * @param scrollCount the number of items to display in the scrolling portion + * @param interval the scroll interval, in milliseconds + * @param topFixedCount the number of items to fix at the top. May be 0. + * @param bottomFixedCount the number of items to fix at the bottom. May be 0 + * @throws IllegalArgumentException if scrollCount or interval is 0 or + * negative or if topFixedCount or bottomFixedCount is negative + * @return the MenuScroller + */ + public static MenuScroller setScrollerFor(JMenu menu, int scrollCount, int interval, + int topFixedCount, int bottomFixedCount) { + return new MenuScroller(menu, scrollCount, interval, + topFixedCount, bottomFixedCount); } - } - private class MenuScrollListener implements PopupMenuListener { + /** + * Registers a popup menu to be scrolled, with the specified number of items + * to display in the scrolling region, the specified scrolling interval, + * and the specified numbers of items fixed at the top and bottom of the + * popup menu. + * + * @param menu the popup menu + * @param scrollCount the number of items to display in the scrolling portion + * @param interval the scroll interval, in milliseconds + * @param topFixedCount the number of items to fix at the top. May be 0 + * @param bottomFixedCount the number of items to fix at the bottom. May be 0 + * @throws IllegalArgumentException if scrollCount or interval is 0 or + * negative or if topFixedCount or bottomFixedCount is negative + * @return the MenuScroller + */ + public static MenuScroller setScrollerFor(JPopupMenu menu, int scrollCount, int interval, + int topFixedCount, int bottomFixedCount) { + return new MenuScroller(menu, scrollCount, interval, + topFixedCount, bottomFixedCount); + } + /** + * Constructs a MenuScroller that scrolls a menu with the + * default number of items to display at a time, and default scrolling + * interval. + * + * @param menu the menu + */ + public MenuScroller(JMenu menu) { + this(menu, 15); + } + + /** + * Constructs a MenuScroller that scrolls a popup menu with the + * default number of items to display at a time, and default scrolling + * interval. + * + * @param menu the popup menu + */ + public MenuScroller(JPopupMenu menu) { + this(menu, 15); + } + + /** + * Constructs a MenuScroller that scrolls a menu with the + * specified number of items to display at a time, and default scrolling + * interval. + * + * @param menu the menu + * @param scrollCount the number of items to display at a time + * @throws IllegalArgumentException if scrollCount is 0 or negative + */ + public MenuScroller(JMenu menu, int scrollCount) { + this(menu, scrollCount, 150); + } + + /** + * Constructs a MenuScroller that scrolls a popup menu with the + * specified number of items to display at a time, and default scrolling + * interval. + * + * @param menu the popup menu + * @param scrollCount the number of items to display at a time + * @throws IllegalArgumentException if scrollCount is 0 or negative + */ + public MenuScroller(JPopupMenu menu, int scrollCount) { + this(menu, scrollCount, 150); + } + + /** + * Constructs a MenuScroller that scrolls a menu with the + * specified number of items to display at a time, and specified scrolling + * interval. + * + * @param menu the menu + * @param scrollCount the number of items to display at a time + * @param interval the scroll interval, in milliseconds + * @throws IllegalArgumentException if scrollCount or interval is 0 or negative + */ + public MenuScroller(JMenu menu, int scrollCount, int interval) { + this(menu, scrollCount, interval, 0, 0); + } + + /** + * Constructs a MenuScroller that scrolls a popup menu with the + * specified number of items to display at a time, and specified scrolling + * interval. + * + * @param menu the popup menu + * @param scrollCount the number of items to display at a time + * @param interval the scroll interval, in milliseconds + * @throws IllegalArgumentException if scrollCount or interval is 0 or negative + */ + public MenuScroller(JPopupMenu menu, int scrollCount, int interval) { + this(menu, scrollCount, interval, 0, 0); + } + + /** + * Constructs a MenuScroller that scrolls a menu with the + * specified number of items to display in the scrolling region, the + * specified scrolling interval, and the specified numbers of items fixed at + * the top and bottom of the menu. + * + * @param menu the menu + * @param scrollCount the number of items to display in the scrolling portion + * @param interval the scroll interval, in milliseconds + * @param topFixedCount the number of items to fix at the top. May be 0 + * @param bottomFixedCount the number of items to fix at the bottom. May be 0 + * @throws IllegalArgumentException if scrollCount or interval is 0 or + * negative or if topFixedCount or bottomFixedCount is negative + */ + public MenuScroller(JMenu menu, int scrollCount, int interval, + int topFixedCount, int bottomFixedCount) { + this(menu.getPopupMenu(), scrollCount, interval, topFixedCount, bottomFixedCount); + } + + /** + * Constructs a MenuScroller that scrolls a popup menu with the + * specified number of items to display in the scrolling region, the + * specified scrolling interval, and the specified numbers of items fixed at + * the top and bottom of the popup menu. + * + * @param menu the popup menu + * @param scrollCount the number of items to display in the scrolling portion + * @param interval the scroll interval, in milliseconds + * @param topFixedCount the number of items to fix at the top. May be 0 + * @param bottomFixedCount the number of items to fix at the bottom. May be 0 + * @throws IllegalArgumentException if scrollCount or interval is 0 or + * negative or if topFixedCount or bottomFixedCount is negative + */ + public MenuScroller(JPopupMenu menu, int scrollCount, int interval, + int topFixedCount, int bottomFixedCount) { + if (scrollCount <= 0 || interval <= 0) { + throw new IllegalArgumentException("scrollCount and interval must be greater than 0"); + } + if (topFixedCount < 0 || bottomFixedCount < 0) { + throw new IllegalArgumentException("topFixedCount and bottomFixedCount cannot be negative"); + } + + upItem = new MenuScrollItem(MenuIcon.UP, -1); + downItem = new MenuScrollItem(MenuIcon.DOWN, +1); + setScrollCount(scrollCount); + setInterval(interval); + setTopFixedCount(topFixedCount); + setBottomFixedCount(bottomFixedCount); + + this.menu = menu; + menu.addPopupMenuListener(menuListener); + menu.addMouseWheelListener(mouseWheelListener); + } + + /** + * Returns the scroll interval in milliseconds + * + * @return the scroll interval in milliseconds + */ + public int getInterval() { + return interval; + } + + /** + * Sets the scroll interval in milliseconds + * + * @param interval the scroll interval in milliseconds + * @throws IllegalArgumentException if interval is 0 or negative + */ + public void setInterval(int interval) { + if (interval <= 0) { + throw new IllegalArgumentException("interval must be greater than 0"); + } + upItem.setInterval(interval); + downItem.setInterval(interval); + this.interval = interval; + } + + /** + * Returns the number of items in the scrolling portion of the menu. + * + * @return the number of items to display at a time + */ + public int getscrollCount() { + return scrollCount; + } + + /** + * Sets the number of items in the scrolling portion of the menu. + * + * @param scrollCount the number of items to display at a time + * @throws IllegalArgumentException if scrollCount is 0 or negative + */ + public void setScrollCount(int scrollCount) { + if (scrollCount <= 0) { + throw new IllegalArgumentException("scrollCount must be greater than 0"); + } + this.scrollCount = scrollCount; + MenuSelectionManager.defaultManager().clearSelectedPath(); + } + + /** + * Returns the number of items fixed at the top of the menu or popup menu. + * + * @return the number of items + */ + public int getTopFixedCount() { + return topFixedCount; + } + + /** + * Sets the number of items to fix at the top of the menu or popup menu. + * + * @param topFixedCount the number of items + */ + public void setTopFixedCount(int topFixedCount) { + if (firstIndex <= topFixedCount) { + firstIndex = topFixedCount; + } else { + firstIndex += (topFixedCount - this.topFixedCount); + } + this.topFixedCount = topFixedCount; + } + + /** + * Returns the number of items fixed at the bottom of the menu or popup menu. + * + * @return the number of items + */ + public int getBottomFixedCount() { + return bottomFixedCount; + } + + /** + * Sets the number of items to fix at the bottom of the menu or popup menu. + * + * @param bottomFixedCount the number of items + */ + public void setBottomFixedCount(int bottomFixedCount) { + this.bottomFixedCount = bottomFixedCount; + } + + /** + * Scrolls the specified item into view each time the menu is opened. Call this method with + * null to restore the default behavior, which is to show the menu as it last + * appeared. + * + * @param item the item to keep visible + * @see #keepVisible(int) + */ + public void keepVisible(JMenuItem item) { + if (item == null) { + keepVisibleIndex = -1; + } else { + int index = menu.getComponentIndex(item); + keepVisibleIndex = index; + } + } + + /** + * Scrolls the item at the specified index into view each time the menu is opened. Call this + * method with -1 to restore the default behavior, which is to show the menu as + * it last appeared. + * + * @param index the index of the item to keep visible + * @see #keepVisible(javax.swing.JMenuItem) + */ + public void keepVisible(int index) { + keepVisibleIndex = index; + } + + /** + * Removes this MenuScroller from the associated menu and restores the + * default behavior of the menu. + */ + public void dispose() { + if (menu != null) { + menu.removePopupMenuListener(menuListener); + menu.removeMouseWheelListener(mouseWheelListener); + menu = null; + } + } + + /** + * Ensures that the dispose method of this MenuScroller is + * called when there are no more references to it. + * + * @exception Throwable if an error occurs. + * @see MenuScroller#dispose() + */ @Override - public void popupMenuWillBecomeVisible(PopupMenuEvent e) { - setMenuItems(); + public void finalize() throws Throwable { + dispose(); } - @Override - public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { - restoreMenuItems(); + private void refreshMenu() { + if (menuItems != null && menuItems.length > 0) { + firstIndex = Math.max(topFixedCount, firstIndex); + firstIndex = Math.min(menuItems.length - bottomFixedCount - scrollCount, firstIndex); + + upItem.setEnabled(firstIndex > topFixedCount); + downItem.setEnabled(firstIndex + scrollCount < menuItems.length - bottomFixedCount); + + menu.removeAll(); + for (int i = 0; i < topFixedCount; i++) { + menu.add(menuItems[i]); + } + if (topFixedCount > 0) { + menu.addSeparator(); + } + + menu.add(upItem); + for (int i = firstIndex; i < scrollCount + firstIndex; i++) { + menu.add(menuItems[i]); + } + menu.add(downItem); + + if (bottomFixedCount > 0) { + menu.addSeparator(); + } + for (int i = menuItems.length - bottomFixedCount; i < menuItems.length; i++) { + menu.add(menuItems[i]); + } + + int preferredWidth = 0; + for (Component item : menuItems) { + preferredWidth = Math.max(preferredWidth, item.getPreferredSize().width); + } + menu.setPreferredSize(new Dimension(preferredWidth, menu.getPreferredSize().height)); + + JComponent parent = (JComponent) upItem.getParent(); + parent.revalidate(); + parent.repaint(); + } } - @Override - public void popupMenuCanceled(PopupMenuEvent e) { - restoreMenuItems(); - } - - private void setMenuItems() { - menuItems = menu.getComponents(); - if (keepVisibleIndex >= topFixedCount - && keepVisibleIndex <= menuItems.length - bottomFixedCount - && (keepVisibleIndex > firstIndex + scrollCount - || keepVisibleIndex < firstIndex)) { - firstIndex = Math.min(firstIndex, keepVisibleIndex); - firstIndex = Math.max(firstIndex, keepVisibleIndex - scrollCount + 1); - } - if (menuItems.length > topFixedCount + scrollCount + bottomFixedCount) { - refreshMenu(); - } - } - - private void restoreMenuItems() { - menu.removeAll(); - for (Component component : menuItems) { - menu.add(component); - } - } - } - - private class MenuScrollTimer extends Timer { - - public MenuScrollTimer(final int increment, int interval) { - super(interval, new ActionListener() { + private class MenuScrollListener implements PopupMenuListener { @Override - public void actionPerformed(ActionEvent e) { - firstIndex += increment; - refreshMenu(); + public void popupMenuWillBecomeVisible(PopupMenuEvent e) { + setMenuItems(); } - }); - } - } - private class MenuScrollItem extends JMenuItem - implements ChangeListener { + @Override + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { + restoreMenuItems(); + } - private MenuScrollTimer timer; + @Override + public void popupMenuCanceled(PopupMenuEvent e) { + restoreMenuItems(); + } - public MenuScrollItem(MenuIcon icon, int increment) { - setIcon(icon); - setDisabledIcon(icon); - timer = new MenuScrollTimer(increment, interval); - addChangeListener(this); + private void setMenuItems() { + menuItems = menu.getComponents(); + if (keepVisibleIndex >= topFixedCount + && keepVisibleIndex <= menuItems.length - bottomFixedCount + && (keepVisibleIndex > firstIndex + scrollCount + || keepVisibleIndex < firstIndex)) { + firstIndex = Math.min(firstIndex, keepVisibleIndex); + firstIndex = Math.max(firstIndex, keepVisibleIndex - scrollCount + 1); + } + if (menuItems.length > topFixedCount + scrollCount + bottomFixedCount) { + refreshMenu(); + } + } + + private void restoreMenuItems() { + menu.removeAll(); + for (Component component : menuItems) { + menu.add(component); + } + } } - public void setInterval(int interval) { - timer.setDelay(interval); + private class MenuScrollTimer extends Timer { + + public MenuScrollTimer(final int increment, int interval) { + super(interval, new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + firstIndex += increment; + refreshMenu(); + } + }); + } } - @Override - public void stateChanged(ChangeEvent e) { - if (isArmed() && !timer.isRunning()) { - timer.start(); - } - if (!isArmed() && timer.isRunning()) { - timer.stop(); - } - } - } + private class MenuScrollItem extends JMenuItem + implements ChangeListener { - private static enum MenuIcon implements Icon { + private MenuScrollTimer timer; - UP(9, 1, 9), - DOWN(1, 9, 1); - final int[] xPoints = {1, 5, 9}; - final int[] yPoints; + public MenuScrollItem(MenuIcon icon, int increment) { + setIcon(icon); + setDisabledIcon(icon); + timer = new MenuScrollTimer(increment, interval); + addChangeListener(this); + } - MenuIcon(int... yPoints) { - this.yPoints = yPoints; + public void setInterval(int interval) { + timer.setDelay(interval); + } + + @Override + public void stateChanged(ChangeEvent e) { + if (isArmed() && !timer.isRunning()) { + timer.start(); + } + if (!isArmed() && timer.isRunning()) { + timer.stop(); + } + } } - @Override - public void paintIcon(Component c, Graphics g, int x, int y) { - Dimension size = c.getSize(); - Graphics g2 = g.create(size.width / 2 - 5, size.height / 2 - 5, 10, 10); - g2.setColor(Color.GRAY); - g2.drawPolygon(xPoints, yPoints, 3); - if (c.isEnabled()) { - g2.setColor(Color.BLACK); - g2.fillPolygon(xPoints, yPoints, 3); - } - g2.dispose(); + private static enum MenuIcon implements Icon { + + UP(9, 1, 9), + DOWN(1, 9, 1); + final int[] xPoints = {1, 5, 9}; + final int[] yPoints; + + MenuIcon(int... yPoints) { + this.yPoints = yPoints; + } + + @Override + public void paintIcon(Component c, Graphics g, int x, int y) { + Dimension size = c.getSize(); + Graphics g2 = g.create(size.width / 2 - 5, size.height / 2 - 5, 10, 10); + g2.setColor(Color.GRAY); + g2.drawPolygon(xPoints, yPoints, 3); + if (c.isEnabled()) { + g2.setColor(Color.BLACK); + g2.fillPolygon(xPoints, yPoints, 3); + } + g2.dispose(); + } + + @Override + public int getIconWidth() { + return 0; + } + + @Override + public int getIconHeight() { + return 10; + } } - @Override - public int getIconWidth() { - return 0; + private class MouseScrollListener implements MouseWheelListener { + public void mouseWheelMoved(MouseWheelEvent mwe){ + firstIndex += mwe.getWheelRotation(); + refreshMenu(); + mwe.consume(); + } } - @Override - public int getIconHeight() { - return 10; - } - } + /** + * Calculates the number for scrollCount such that the menu fills the available + * vertical space from the point (mouse press) to the bottom of the screen. + * + * @param c The component on which the point parameter is based + * @param pt The point at which the top of the menu will appear (in component coordinate space) + * @param item A menuitem of prototypical height off of which the average height is determined + * @param bottomFixedCount Needed to offset the returned scrollCount + * @return the scrollCount + */ + public static int scrollCountForScreen(Component c, Point pt, JMenuItem item, int bottomFixedCount) { + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + Point ptScreen = new Point(pt); + SwingUtilities.convertPointToScreen(ptScreen, c); + int height = screenSize.height - ptScreen.y; - private class MouseScrollListener implements MouseWheelListener { - public void mouseWheelMoved(MouseWheelEvent mwe){ - firstIndex += mwe.getWheelRotation(); - refreshMenu(); - mwe.consume(); - } - } + int miHeight = item.getPreferredSize().height; + int scrollCount = (height / miHeight) - bottomFixedCount - 2; // 2 just takes the menu up a bit from the bottom which looks nicer + + return scrollCount; + } }