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