1. /*
  2. * @(#)MenuSelectionManager.java 1.34 03/01/23
  3. *
  4. * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing;
  8. import java.awt.*;
  9. import java.util.*;
  10. import java.awt.event.*;
  11. import javax.swing.event.*;
  12. /**
  13. * A MenuSelectionManager owns the selection in menu hierarchy.
  14. *
  15. * @version 1.34 01/23/03
  16. * @author Arnaud Weber
  17. */
  18. public class MenuSelectionManager {
  19. private static final MenuSelectionManager instance =
  20. new MenuSelectionManager();
  21. private Vector selection = new Vector();
  22. /* diagnostic aids -- should be false for production builds. */
  23. private static final boolean TRACE = false; // trace creates and disposes
  24. private static final boolean VERBOSE = false; // show reuse hits/misses
  25. private static final boolean DEBUG = false; // show bad params, misc.
  26. /**
  27. * Returns the default menu selection manager.
  28. *
  29. * @return a MenuSelectionManager object
  30. */
  31. public static MenuSelectionManager defaultManager() {
  32. return instance;
  33. }
  34. /**
  35. * Only one ChangeEvent is needed per button model instance since the
  36. * event's only state is the source property. The source of events
  37. * generated is always "this".
  38. */
  39. protected transient ChangeEvent changeEvent = null;
  40. protected EventListenerList listenerList = new EventListenerList();
  41. /**
  42. * Change the selection in the menu hierarchy.
  43. *
  44. * @param path an array of MenuElement objects specifying the selected path
  45. */
  46. public void setSelectedPath(MenuElement[] path) {
  47. int i,c;
  48. int currentSelectionCount = selection.size();
  49. int firstDifference = 0;
  50. if(path == null) {
  51. path = new MenuElement[0];
  52. }
  53. if (DEBUG) {
  54. System.out.print("Previous: "); printMenuElementArray(getSelectedPath());
  55. System.out.print("New: "); printMenuElementArray(path);
  56. }
  57. for(i=0,c=path.length;i<c;i++) {
  58. if(i < currentSelectionCount && (MenuElement)selection.elementAt(i) == path[i])
  59. firstDifference++;
  60. else
  61. break;
  62. }
  63. for(i=currentSelectionCount - 1 ; i >= firstDifference ; i--) {
  64. MenuElement me = (MenuElement)selection.elementAt(i);
  65. selection.removeElementAt(i);
  66. me.menuSelectionChanged(false);
  67. }
  68. for(i = firstDifference, c = path.length ; i < c ; i++) {
  69. if (path[i] != null) {
  70. selection.addElement(path[i]);
  71. path[i].menuSelectionChanged(true);
  72. }
  73. }
  74. fireStateChanged();
  75. }
  76. /**
  77. * Returns the path to the currently selected menu item
  78. *
  79. * @return an array of MenuElement objects representing the selected path
  80. */
  81. public MenuElement[] getSelectedPath() {
  82. MenuElement res[] = new MenuElement[selection.size()];
  83. int i,c;
  84. for(i=0,c=selection.size();i<c;i++)
  85. res[i] = (MenuElement) selection.elementAt(i);
  86. return res;
  87. }
  88. /**
  89. * Tell the menu selection to close and unselect all the menu components. Call this method
  90. * when a choice has been made
  91. */
  92. public void clearSelectedPath() {
  93. if (selection.size() > 0) {
  94. setSelectedPath(null);
  95. }
  96. }
  97. /**
  98. * Adds a ChangeListener to the button.
  99. *
  100. * @param l the listener to add
  101. */
  102. public void addChangeListener(ChangeListener l) {
  103. listenerList.add(ChangeListener.class, l);
  104. }
  105. /**
  106. * Removes a ChangeListener from the button.
  107. *
  108. * @param l the listener to remove
  109. */
  110. public void removeChangeListener(ChangeListener l) {
  111. listenerList.remove(ChangeListener.class, l);
  112. }
  113. /**
  114. * Returns an array of all the <code>ChangeListener</code>s added
  115. * to this MenuSelectionManager with addChangeListener().
  116. *
  117. * @return all of the <code>ChangeListener</code>s added or an empty
  118. * array if no listeners have been added
  119. * @since 1.4
  120. */
  121. public ChangeListener[] getChangeListeners() {
  122. return (ChangeListener[])listenerList.getListeners(
  123. ChangeListener.class);
  124. }
  125. /**
  126. * Notifies all listeners that have registered interest for
  127. * notification on this event type. The event instance
  128. * is created lazily.
  129. *
  130. * @see EventListenerList
  131. */
  132. protected void fireStateChanged() {
  133. // Guaranteed to return a non-null array
  134. Object[] listeners = listenerList.getListenerList();
  135. // Process the listeners last to first, notifying
  136. // those that are interested in this event
  137. for (int i = listeners.length-2; i>=0; i-=2) {
  138. if (listeners[i]==ChangeListener.class) {
  139. // Lazily create the event:
  140. if (changeEvent == null)
  141. changeEvent = new ChangeEvent(this);
  142. ((ChangeListener)listeners[i+1]).stateChanged(changeEvent);
  143. }
  144. }
  145. }
  146. /**
  147. * When a MenuElement receives an event from a MouseListener, it should never process the event
  148. * directly. Instead all MenuElements should call this method with the event.
  149. *
  150. * @param event a MouseEvent object
  151. */
  152. public void processMouseEvent(MouseEvent event) {
  153. int screenX,screenY;
  154. Point p;
  155. int i,c,j,d;
  156. Component mc;
  157. Rectangle r2;
  158. int cWidth,cHeight;
  159. MenuElement menuElement;
  160. MenuElement subElements[];
  161. MenuElement path[];
  162. Vector tmp;
  163. int selectionSize;
  164. p = event.getPoint();
  165. Component source = (Component)event.getSource();
  166. if (!source.isShowing()) {
  167. // This can happen if a mouseReleased removes the
  168. // containing component -- bug 4146684
  169. return;
  170. }
  171. int type = event.getID();
  172. int modifiers = event.getModifiers();
  173. // 4188027: drag enter/exit added in JDK 1.1.7A, JDK1.2
  174. if ((type==MouseEvent.MOUSE_ENTERED||
  175. type==MouseEvent.MOUSE_EXITED)
  176. && ((modifiers & (InputEvent.BUTTON1_MASK |
  177. InputEvent.BUTTON2_MASK | InputEvent.BUTTON3_MASK)) !=0 )) {
  178. return;
  179. }
  180. SwingUtilities.convertPointToScreen(p,source);
  181. screenX = p.x;
  182. screenY = p.y;
  183. tmp = (Vector)selection.clone();
  184. selectionSize = tmp.size();
  185. boolean success = false;
  186. for (i=selectionSize - 1;i >= 0 && success == false; i--) {
  187. menuElement = (MenuElement) tmp.elementAt(i);
  188. subElements = menuElement.getSubElements();
  189. path = null;
  190. for (j = 0, d = subElements.length;j < d && success == false; j++) {
  191. if (subElements[j] == null)
  192. continue;
  193. mc = subElements[j].getComponent();
  194. if(!mc.isShowing())
  195. continue;
  196. if(mc instanceof JComponent) {
  197. cWidth = ((JComponent)mc).getWidth();
  198. cHeight = ((JComponent)mc).getHeight();
  199. } else {
  200. r2 = mc.getBounds();
  201. cWidth = r2.width;
  202. cHeight = r2.height;
  203. }
  204. p.x = screenX;
  205. p.y = screenY;
  206. SwingUtilities.convertPointFromScreen(p,mc);
  207. /** Send the event to visible menu element if menu element currently in
  208. * the selected path or contains the event location
  209. */
  210. if(
  211. (p.x >= 0 && p.x < cWidth && p.y >= 0 && p.y < cHeight)) {
  212. int k;
  213. if(path == null) {
  214. path = new MenuElement[i+2];
  215. for(k=0;k<=i;k++)
  216. path[k] = (MenuElement)tmp.elementAt(k);
  217. }
  218. path[i+1] = subElements[j];
  219. MenuElement currentSelection[] = getSelectedPath();
  220. // Enter/exit detection -- needs tuning...
  221. if (currentSelection[currentSelection.length-1] !=
  222. path[i+1] &&
  223. (currentSelection.length < 2 ||
  224. currentSelection[currentSelection.length-2] !=
  225. path[i+1])) {
  226. Component oldMC = currentSelection[currentSelection.length-1].getComponent();
  227. MouseEvent exitEvent = new MouseEvent(oldMC, MouseEvent.MOUSE_EXITED,
  228. event.getWhen(),
  229. event.getModifiers(), p.x, p.y,
  230. event.getClickCount(),
  231. event.isPopupTrigger());
  232. currentSelection[currentSelection.length-1].
  233. processMouseEvent(exitEvent, path, this);
  234. MouseEvent enterEvent = new MouseEvent(mc,
  235. MouseEvent.MOUSE_ENTERED,
  236. event.getWhen(),
  237. event.getModifiers(), p.x, p.y,
  238. event.getClickCount(),
  239. event.isPopupTrigger());
  240. subElements[j].processMouseEvent(enterEvent, path, this);
  241. }
  242. MouseEvent mouseEvent = new MouseEvent(mc, event.getID(),event. getWhen(),
  243. event.getModifiers(), p.x, p.y,
  244. event.getClickCount(),
  245. event.isPopupTrigger());
  246. subElements[j].processMouseEvent(mouseEvent, path, this);
  247. success = true;
  248. event.consume();
  249. }
  250. }
  251. }
  252. }
  253. private void printMenuElementArray(MenuElement path[]) {
  254. printMenuElementArray(path, false);
  255. }
  256. private void printMenuElementArray(MenuElement path[], boolean dumpStack) {
  257. System.out.println("Path is(");
  258. int i, j;
  259. for(i=0,j=path.length; i<j ;i++){
  260. for (int k=0; k<=i; k++)
  261. System.out.print(" ");
  262. MenuElement me = (MenuElement) path[i];
  263. if(me instanceof JMenuItem) {
  264. System.out.println(((JMenuItem)me).getText() + ", ");
  265. } else if (me instanceof JMenuBar) {
  266. System.out.println("JMenuBar, ");
  267. } else if(me instanceof JPopupMenu) {
  268. System.out.println("JPopupMenu, ");
  269. } else if (me == null) {
  270. System.out.println("NULL , ");
  271. } else {
  272. System.out.println("" + me + ", ");
  273. }
  274. }
  275. System.out.println(")");
  276. if (dumpStack == true)
  277. Thread.dumpStack();
  278. }
  279. /**
  280. * Returns the component in the currently selected path
  281. * which contains sourcePoint.
  282. *
  283. * @param source The component in whose coordinate space sourcePoint
  284. * is given
  285. * @param sourcePoint The point which is being tested
  286. * @return The component in the currently selected path which
  287. * contains sourcePoint (relative to the source component's
  288. * coordinate space. If sourcePoint is not inside a component
  289. * on the currently selected path, null is returned.
  290. */
  291. public Component componentForPoint(Component source, Point sourcePoint) {
  292. int screenX,screenY;
  293. Point p = sourcePoint;
  294. int i,c,j,d;
  295. Component mc;
  296. Rectangle r2;
  297. int cWidth,cHeight;
  298. MenuElement menuElement;
  299. MenuElement subElements[];
  300. Vector tmp;
  301. int selectionSize;
  302. SwingUtilities.convertPointToScreen(p,source);
  303. screenX = p.x;
  304. screenY = p.y;
  305. tmp = (Vector)selection.clone();
  306. selectionSize = tmp.size();
  307. for(i=selectionSize - 1 ; i >= 0 ; i--) {
  308. menuElement = (MenuElement) tmp.elementAt(i);
  309. subElements = menuElement.getSubElements();
  310. for(j = 0, d = subElements.length ; j < d ; j++) {
  311. if (subElements[j] == null)
  312. continue;
  313. mc = subElements[j].getComponent();
  314. if(!mc.isShowing())
  315. continue;
  316. if(mc instanceof JComponent) {
  317. cWidth = ((JComponent)mc).getWidth();
  318. cHeight = ((JComponent)mc).getHeight();
  319. } else {
  320. r2 = mc.getBounds();
  321. cWidth = r2.width;
  322. cHeight = r2.height;
  323. }
  324. p.x = screenX;
  325. p.y = screenY;
  326. SwingUtilities.convertPointFromScreen(p,mc);
  327. /** Return the deepest component on the selection
  328. * path in whose bounds the event's point occurs
  329. */
  330. if (p.x >= 0 && p.x < cWidth && p.y >= 0 && p.y < cHeight) {
  331. return mc;
  332. }
  333. }
  334. }
  335. return null;
  336. }
  337. /**
  338. * When a MenuElement receives an event from a KeyListener, it should never process the event
  339. * directly. Instead all MenuElements should call this method with the event.
  340. *
  341. * @param e a KeyEvent object
  342. */
  343. public void processKeyEvent(KeyEvent e) {
  344. Vector tmp;
  345. int selectionSize;
  346. int i,j,d;
  347. MenuElement menuElement;
  348. MenuElement subElements[];
  349. MenuElement path[];
  350. Component mc;
  351. if (DEBUG) {
  352. System.out.println("in MenuSelectionManager.processKeyEvent");
  353. }
  354. tmp = (Vector)selection.clone();
  355. selectionSize = tmp.size();
  356. for(i=selectionSize - 1 ; i >= 0 ; i--) {
  357. menuElement = (MenuElement) tmp.elementAt(i);
  358. subElements = menuElement.getSubElements();
  359. path = null;
  360. for(j = 0, d = subElements.length ; j < d ; j++) {
  361. if (subElements[j] == null)
  362. continue;
  363. mc = subElements[j].getComponent();
  364. if(!mc.isShowing())
  365. continue;
  366. if(path == null) {
  367. int k;
  368. path = new MenuElement[i+2];
  369. for(k=0;k<=i;k++)
  370. path[k] = (MenuElement)tmp.elementAt(k);
  371. }
  372. path[i+1] = subElements[j];
  373. subElements[j].processKeyEvent(e,path,this);
  374. if(e.isConsumed())
  375. return;
  376. }
  377. }
  378. }
  379. /**
  380. * Return true if c is part of the currently used menu
  381. */
  382. public boolean isComponentPartOfCurrentMenu(Component c) {
  383. if(selection.size() > 0) {
  384. MenuElement me = (MenuElement)selection.elementAt(0);
  385. return isComponentPartOfCurrentMenu(me,c);
  386. } else
  387. return false;
  388. }
  389. private boolean isComponentPartOfCurrentMenu(MenuElement root,Component c) {
  390. MenuElement children[];
  391. int i,d;
  392. if (root == null)
  393. return false;
  394. if(root.getComponent() == c)
  395. return true;
  396. else {
  397. children = root.getSubElements();
  398. for(i=0,d=children.length;i<d;i++) {
  399. if(isComponentPartOfCurrentMenu(children[i],c))
  400. return true;
  401. }
  402. }
  403. return false;
  404. }
  405. }