From 17598368ac26a953a23634229b666e8b9a569ab6 Mon Sep 17 00:00:00 2001 From: Federico Fissore Date: Thu, 21 May 2015 11:28:35 +0200 Subject: [PATCH] Updated MenuScroller with latest from https://tips4java.wordpress.com/2009/02/01/menu-scroller/ Reviewed its getMaximumItems method (now getMaximumDrawableMenuItems) so that it returns a 1/3rd less the max number, avoiding a bad crash on linuxes. Fixes #3174 --- .../processing/app/tools/MenuScroller.java | 310 ++++++++---------- 1 file changed, 132 insertions(+), 178 deletions(-) diff --git a/app/src/processing/app/tools/MenuScroller.java b/app/src/processing/app/tools/MenuScroller.java index cb7495650..e5ac193ae 100644 --- a/app/src/processing/app/tools/MenuScroller.java +++ b/app/src/processing/app/tools/MenuScroller.java @@ -3,43 +3,31 @@ */ package processing.app.tools; -import java.awt.Color; -import java.awt.Component; -import java.awt.Dimension; -import java.awt.Graphics; +import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; +import java.awt.*; 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; -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; -import javax.swing.plaf.ButtonUI; - /** * 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 + * @version 1.5.0 04/05/12 */ public class MenuScroller { - //private JMenu menu; private JPopupMenu menu; private Component[] menuItems; private MenuScrollItem upItem; @@ -53,32 +41,13 @@ public class MenuScroller { private int firstIndex = 0; private int keepVisibleIndex = -1; - private static int getMaximumItems(JPopupMenu menu) { - JMenuItem test = new JMenuItem("test"); - ButtonUI ui = test.getUI(); - Dimension d = ui.getPreferredSize(test); - double item_height = d.getHeight(); - //System.out.println("JMenuItem Height " + item_height); - JMenuItem up = new JMenuItem(MenuIcon.UP); - ui = up.getUI(); - d = ui.getPreferredSize(up); - double icon_height = d.getHeight(); - //System.out.println("icon item height " + icon_height); - double menu_border_height = 8.0; // kludge - how to detect this? - double screen_height = java.awt.Toolkit.getDefaultToolkit().getScreenSize().getHeight(); - //System.out.println("screen height " + screen_height); - int n = (int)((screen_height - icon_height * 2 - menu_border_height) / item_height); - //System.out.println("max items " + n); - return n; - } - /** * 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); } @@ -86,7 +55,7 @@ public class MenuScroller { /** * 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 */ @@ -97,8 +66,8 @@ public class MenuScroller { /** * 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 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 @@ -110,8 +79,8 @@ public class MenuScroller { /** * 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 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 @@ -123,10 +92,10 @@ public class MenuScroller { /** * 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 menu the menu * @param scrollCount the number of items to be displayed at a time - * @param interval the scroll interval, in milliseconds + * @param interval the scroll interval, in milliseconds * @return the MenuScroller * @throws IllegalArgumentException if scrollCount or interval is 0 or negative */ @@ -137,10 +106,10 @@ public class MenuScroller { /** * 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 menu the popup menu * @param scrollCount the number of items to be displayed at a time - * @param interval the scroll interval, in milliseconds + * @param interval the scroll interval, in milliseconds * @return the MenuScroller * @throws IllegalArgumentException if scrollCount or interval is 0 or negative */ @@ -153,20 +122,20 @@ public class MenuScroller { * 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 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 + * @throws IllegalArgumentException if scrollCount or interval is 0 or + * negative or if topFixedCount or bottomFixedCount is negative */ public static MenuScroller setScrollerFor(JMenu menu, int scrollCount, int interval, - int topFixedCount, int bottomFixedCount) { + int topFixedCount, int bottomFixedCount) { return new MenuScroller(menu, scrollCount, interval, - topFixedCount, bottomFixedCount); + topFixedCount, bottomFixedCount); } /** @@ -174,50 +143,50 @@ public class MenuScroller { * 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 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 + * @throws IllegalArgumentException if scrollCount or interval is 0 or + * negative or if topFixedCount or bottomFixedCount is negative */ public static MenuScroller setScrollerFor(JPopupMenu menu, int scrollCount, int interval, - int topFixedCount, int bottomFixedCount) { + int topFixedCount, int bottomFixedCount) { return new MenuScroller(menu, scrollCount, interval, - topFixedCount, bottomFixedCount); + 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, -1); + 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, -1); + 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 menu the menu * @param scrollCount the number of items to display at a time * @throws IllegalArgumentException if scrollCount is 0 or negative */ @@ -229,8 +198,8 @@ public class MenuScroller { * 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 menu the popup menu * @param scrollCount the number of items to display at a time * @throws IllegalArgumentException if scrollCount is 0 or negative */ @@ -242,10 +211,10 @@ public class MenuScroller { * 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 menu the menu * @param scrollCount the number of items to display at a time - * @param interval the scroll interval, in milliseconds + * @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) { @@ -256,10 +225,10 @@ public class MenuScroller { * 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 menu the popup menu * @param scrollCount the number of items to display at a time - * @param interval the scroll interval, in milliseconds + * @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) { @@ -271,17 +240,17 @@ public class MenuScroller { * 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 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 + * negative or if topFixedCount or bottomFixedCount is negative */ public MenuScroller(JMenu menu, int scrollCount, int interval, - int topFixedCount, int bottomFixedCount) { + int topFixedCount, int bottomFixedCount) { this(menu.getPopupMenu(), scrollCount, interval, topFixedCount, bottomFixedCount); } @@ -290,24 +259,23 @@ public class MenuScroller { * 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 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 + * negative or if topFixedCount or bottomFixedCount is negative */ public MenuScroller(JPopupMenu menu, int scrollCount, int interval, - int topFixedCount, int bottomFixedCount) { - - if(scrollCount == -1) - scrollCount = getMaximumItems(menu)-topFixedCount-bottomFixedCount; // Autosize - - if(interval == -1) - interval = 150; // Default value - + int topFixedCount, int bottomFixedCount) { + + int autoSizeScrollCount = getMaximumDrawableMenuItems(); + if (autoSizeScrollCount > scrollCount) { + scrollCount = autoSizeScrollCount; + } + if (scrollCount <= 0 || interval <= 0) { throw new IllegalArgumentException("scrollCount and interval must be greater than 0"); } @@ -329,7 +297,7 @@ public class MenuScroller { /** * Returns the scroll interval in milliseconds - * + * * @return the scroll interval in milliseconds */ public int getInterval() { @@ -338,7 +306,7 @@ public class MenuScroller { /** * Sets the scroll interval in milliseconds - * + * * @param interval the scroll interval in milliseconds * @throws IllegalArgumentException if interval is 0 or negative */ @@ -362,7 +330,7 @@ public class MenuScroller { /** * 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 */ @@ -376,7 +344,7 @@ public class MenuScroller { /** * Returns the number of items fixed at the top of the menu or popup menu. - * + * * @return the number of items */ public int getTopFixedCount() { @@ -385,7 +353,7 @@ public class MenuScroller { /** * 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) { @@ -399,7 +367,7 @@ public class MenuScroller { /** * Returns the number of items fixed at the bottom of the menu or popup menu. - * + * * @return the number of items */ public int getBottomFixedCount() { @@ -408,7 +376,7 @@ public class MenuScroller { /** * 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) { @@ -427,8 +395,7 @@ public class MenuScroller { if (item == null) { keepVisibleIndex = -1; } else { - int index = menu.getComponentIndex(item); - keepVisibleIndex = index; + keepVisibleIndex = menu.getComponentIndex(item); } } @@ -459,56 +426,64 @@ public class MenuScroller { /** * Ensures that the dispose method of this MenuScroller is * called when there are no more refrences to it. - * - * @exception Throwable if an error occurs. + * + * @throws Throwable if an error occurs. * @see MenuScroller#dispose() */ @Override public void finalize() throws Throwable { + super.finalize(); dispose(); } private void refreshMenu() { - if (menuItems == null || menuItems.length == 0) { - return; - } + if (menuItems != null && menuItems.length > 0) { + firstIndex = Math.max(topFixedCount, firstIndex); + firstIndex = Math.min(menuItems.length - bottomFixedCount - scrollCount, firstIndex); - int newFirstIndex = Math.max(topFixedCount, firstIndex); - newFirstIndex = Math.min(menuItems.length - bottomFixedCount - scrollCount, newFirstIndex); + upItem.setEnabled(firstIndex > topFixedCount); + downItem.setEnabled(firstIndex + scrollCount < menuItems.length - bottomFixedCount); - if (newFirstIndex < 0) { - return; - } - - firstIndex = newFirstIndex; - - 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.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); + menu.add(upItem); + for (int i = firstIndex; i < scrollCount + firstIndex; i++) { + menu.add(menuItems[i]); + } + menu.add(downItem); - /*if (bottomFixedCount > 0) { + if (bottomFixedCount > 0) { menu.addSeparator(); - }*/ - for (int i = menuItems.length - bottomFixedCount; i < menuItems.length; i++) { - menu.add(menuItems[i]); - } + } + for (int i = menuItems.length - bottomFixedCount; i < menuItems.length; i++) { + menu.add(menuItems[i]); + } - JComponent parent = (JComponent) upItem.getParent(); - parent.revalidate(); - parent.repaint(); + JComponent parent = (JComponent) upItem.getParent(); + parent.revalidate(); + parent.repaint(); + } + } + + private int getMaximumDrawableMenuItems() { + JMenuItem test = new JMenuItem("test"); + double itemHeight = test.getUI().getPreferredSize(test).getHeight(); + + JMenuItem arrowMenuItem = new JMenuItem(MenuIcon.UP); + double arrowMenuItemHeight = arrowMenuItem.getUI().getPreferredSize(arrowMenuItem).getHeight(); + + double menuBorderHeight = 8.0; // kludge - how to detect this? + double screenHeight = java.awt.Toolkit.getDefaultToolkit().getScreenSize().getHeight(); + + int maxItems = (int) ((screenHeight - arrowMenuItemHeight * 2 - menuBorderHeight) / itemHeight); + maxItems -= maxItems / 3; + return maxItems; } private class MouseScrollListener implements MouseWheelListener { @@ -518,7 +493,7 @@ public class MenuScroller { mwe.consume(); } } - + private class MenuScrollListener implements PopupMenuListener { @Override @@ -538,29 +513,10 @@ public class MenuScroller { private void setMenuItems() { menuItems = menu.getComponents(); - - // Hack for auto detect the topFixed total - /*int topFixedCountPrev = topFixedCount; - for(int i=menuItems.length-1;i>0;i--) - { - if(menuItems[i].getClass().getName().endsWith("Separator")) - { - System.out.println(i); - setTopFixedCount(i+1); - - if(topFixedCount!=topFixedCountPrev) - { - scrollCount = getMaximumItems()-topFixedCount; - System.out.println(getMaximumItems()-topFixedCount); - } - break; - } - }*/ - if (keepVisibleIndex >= topFixedCount - && keepVisibleIndex <= menuItems.length - bottomFixedCount - && (keepVisibleIndex > firstIndex + scrollCount - || keepVisibleIndex < firstIndex)) { + && keepVisibleIndex <= menuItems.length - bottomFixedCount + && (keepVisibleIndex > firstIndex + scrollCount + || keepVisibleIndex < firstIndex)) { firstIndex = Math.min(firstIndex, keepVisibleIndex); firstIndex = Math.max(firstIndex, keepVisibleIndex - scrollCount + 1); } @@ -577,7 +533,6 @@ public class MenuScroller { } } - @SuppressWarnings("serial") private class MenuScrollTimer extends Timer { public MenuScrollTimer(final int increment, int interval) { @@ -592,11 +547,10 @@ public class MenuScroller { } } - @SuppressWarnings("serial") private class MenuScrollItem extends JMenuItem - implements ChangeListener { + implements ChangeListener { - private MenuScrollTimer timer; + private final MenuScrollTimer timer; public MenuScrollItem(MenuIcon icon, int increment) { setIcon(icon); @@ -620,7 +574,7 @@ public class MenuScroller { } } - private static enum MenuIcon implements Icon { + private enum MenuIcon implements Icon { UP(9, 1, 9), DOWN(1, 9, 1);