Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

MenuScroller.java 21KB

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