--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/swing/MenuSelectionManager.java Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,499 @@
+/*
+ * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing;
+
+import java.awt.*;
+import java.util.*;
+import java.awt.event.*;
+import javax.swing.event.*;
+
+import sun.awt.AppContext;
+
+/**
+ * A MenuSelectionManager owns the selection in menu hierarchy.
+ *
+ * @author Arnaud Weber
+ */
+public class MenuSelectionManager {
+ private Vector selection = new Vector();
+
+ /* diagnostic aids -- should be false for production builds. */
+ private static final boolean TRACE = false; // trace creates and disposes
+ private static final boolean VERBOSE = false; // show reuse hits/misses
+ private static final boolean DEBUG = false; // show bad params, misc.
+
+ private static final StringBuilder MENU_SELECTION_MANAGER_KEY =
+ new StringBuilder("javax.swing.MenuSelectionManager");
+
+ /**
+ * Returns the default menu selection manager.
+ *
+ * @return a MenuSelectionManager object
+ */
+ public static MenuSelectionManager defaultManager() {
+ synchronized (MENU_SELECTION_MANAGER_KEY) {
+ AppContext context = AppContext.getAppContext();
+ MenuSelectionManager msm = (MenuSelectionManager)context.get(
+ MENU_SELECTION_MANAGER_KEY);
+ if (msm == null) {
+ msm = new MenuSelectionManager();
+ context.put(MENU_SELECTION_MANAGER_KEY, msm);
+ }
+
+ return msm;
+ }
+ }
+
+ /**
+ * Only one ChangeEvent is needed per button model instance since the
+ * event's only state is the source property. The source of events
+ * generated is always "this".
+ */
+ protected transient ChangeEvent changeEvent = null;
+ protected EventListenerList listenerList = new EventListenerList();
+
+ /**
+ * Changes the selection in the menu hierarchy. The elements
+ * in the array are sorted in order from the root menu
+ * element to the currently selected menu element.
+ * <p>
+ * Note that this method is public but is used by the look and
+ * feel engine and should not be called by client applications.
+ *
+ * @param path an array of <code>MenuElement</code> objects specifying
+ * the selected path
+ */
+ public void setSelectedPath(MenuElement[] path) {
+ int i,c;
+ int currentSelectionCount = selection.size();
+ int firstDifference = 0;
+
+ if(path == null) {
+ path = new MenuElement[0];
+ }
+
+ if (DEBUG) {
+ System.out.print("Previous: "); printMenuElementArray(getSelectedPath());
+ System.out.print("New: "); printMenuElementArray(path);
+ }
+
+ for(i=0,c=path.length;i<c;i++) {
+ if(i < currentSelectionCount && (MenuElement)selection.elementAt(i) == path[i])
+ firstDifference++;
+ else
+ break;
+ }
+
+ for(i=currentSelectionCount - 1 ; i >= firstDifference ; i--) {
+ MenuElement me = (MenuElement)selection.elementAt(i);
+ selection.removeElementAt(i);
+ me.menuSelectionChanged(false);
+ }
+
+ for(i = firstDifference, c = path.length ; i < c ; i++) {
+ if (path[i] != null) {
+ selection.addElement(path[i]);
+ path[i].menuSelectionChanged(true);
+ }
+ }
+
+ fireStateChanged();
+ }
+
+ /**
+ * Returns the path to the currently selected menu item
+ *
+ * @return an array of MenuElement objects representing the selected path
+ */
+ public MenuElement[] getSelectedPath() {
+ MenuElement res[] = new MenuElement[selection.size()];
+ int i,c;
+ for(i=0,c=selection.size();i<c;i++)
+ res[i] = (MenuElement) selection.elementAt(i);
+ return res;
+ }
+
+ /**
+ * Tell the menu selection to close and unselect all the menu components. Call this method
+ * when a choice has been made
+ */
+ public void clearSelectedPath() {
+ if (selection.size() > 0) {
+ setSelectedPath(null);
+ }
+ }
+
+ /**
+ * Adds a ChangeListener to the button.
+ *
+ * @param l the listener to add
+ */
+ public void addChangeListener(ChangeListener l) {
+ listenerList.add(ChangeListener.class, l);
+ }
+
+ /**
+ * Removes a ChangeListener from the button.
+ *
+ * @param l the listener to remove
+ */
+ public void removeChangeListener(ChangeListener l) {
+ listenerList.remove(ChangeListener.class, l);
+ }
+
+ /**
+ * Returns an array of all the <code>ChangeListener</code>s added
+ * to this MenuSelectionManager with addChangeListener().
+ *
+ * @return all of the <code>ChangeListener</code>s added or an empty
+ * array if no listeners have been added
+ * @since 1.4
+ */
+ public ChangeListener[] getChangeListeners() {
+ return (ChangeListener[])listenerList.getListeners(
+ ChangeListener.class);
+ }
+
+ /**
+ * Notifies all listeners that have registered interest for
+ * notification on this event type. The event instance
+ * is created lazily.
+ *
+ * @see EventListenerList
+ */
+ protected void fireStateChanged() {
+ // Guaranteed to return a non-null array
+ Object[] listeners = listenerList.getListenerList();
+ // Process the listeners last to first, notifying
+ // those that are interested in this event
+ for (int i = listeners.length-2; i>=0; i-=2) {
+ if (listeners[i]==ChangeListener.class) {
+ // Lazily create the event:
+ if (changeEvent == null)
+ changeEvent = new ChangeEvent(this);
+ ((ChangeListener)listeners[i+1]).stateChanged(changeEvent);
+ }
+ }
+ }
+
+ /**
+ * When a MenuElement receives an event from a MouseListener, it should never process the event
+ * directly. Instead all MenuElements should call this method with the event.
+ *
+ * @param event a MouseEvent object
+ */
+ public void processMouseEvent(MouseEvent event) {
+ int screenX,screenY;
+ Point p;
+ int i,c,j,d;
+ Component mc;
+ Rectangle r2;
+ int cWidth,cHeight;
+ MenuElement menuElement;
+ MenuElement subElements[];
+ MenuElement path[];
+ Vector tmp;
+ int selectionSize;
+ p = event.getPoint();
+
+ Component source = (Component)event.getSource();
+
+ if (!source.isShowing()) {
+ // This can happen if a mouseReleased removes the
+ // containing component -- bug 4146684
+ return;
+ }
+
+ int type = event.getID();
+ int modifiers = event.getModifiers();
+ // 4188027: drag enter/exit added in JDK 1.1.7A, JDK1.2
+ if ((type==MouseEvent.MOUSE_ENTERED||
+ type==MouseEvent.MOUSE_EXITED)
+ && ((modifiers & (InputEvent.BUTTON1_MASK |
+ InputEvent.BUTTON2_MASK | InputEvent.BUTTON3_MASK)) !=0 )) {
+ return;
+ }
+
+ SwingUtilities.convertPointToScreen(p,source);
+
+ screenX = p.x;
+ screenY = p.y;
+
+ tmp = (Vector)selection.clone();
+ selectionSize = tmp.size();
+ boolean success = false;
+ for (i=selectionSize - 1;i >= 0 && success == false; i--) {
+ menuElement = (MenuElement) tmp.elementAt(i);
+ subElements = menuElement.getSubElements();
+
+ path = null;
+ for (j = 0, d = subElements.length;j < d && success == false; j++) {
+ if (subElements[j] == null)
+ continue;
+ mc = subElements[j].getComponent();
+ if(!mc.isShowing())
+ continue;
+ if(mc instanceof JComponent) {
+ cWidth = ((JComponent)mc).getWidth();
+ cHeight = ((JComponent)mc).getHeight();
+ } else {
+ r2 = mc.getBounds();
+ cWidth = r2.width;
+ cHeight = r2.height;
+ }
+ p.x = screenX;
+ p.y = screenY;
+ SwingUtilities.convertPointFromScreen(p,mc);
+
+ /** Send the event to visible menu element if menu element currently in
+ * the selected path or contains the event location
+ */
+ if(
+ (p.x >= 0 && p.x < cWidth && p.y >= 0 && p.y < cHeight)) {
+ int k;
+ if(path == null) {
+ path = new MenuElement[i+2];
+ for(k=0;k<=i;k++)
+ path[k] = (MenuElement)tmp.elementAt(k);
+ }
+ path[i+1] = subElements[j];
+ MenuElement currentSelection[] = getSelectedPath();
+
+ // Enter/exit detection -- needs tuning...
+ if (currentSelection[currentSelection.length-1] !=
+ path[i+1] &&
+ (currentSelection.length < 2 ||
+ currentSelection[currentSelection.length-2] !=
+ path[i+1])) {
+ Component oldMC = currentSelection[currentSelection.length-1].getComponent();
+
+ MouseEvent exitEvent = new MouseEvent(oldMC, MouseEvent.MOUSE_EXITED,
+ event.getWhen(),
+ event.getModifiers(), p.x, p.y,
+ event.getXOnScreen(),
+ event.getYOnScreen(),
+ event.getClickCount(),
+ event.isPopupTrigger(),
+ MouseEvent.NOBUTTON);
+ currentSelection[currentSelection.length-1].
+ processMouseEvent(exitEvent, path, this);
+
+ MouseEvent enterEvent = new MouseEvent(mc,
+ MouseEvent.MOUSE_ENTERED,
+ event.getWhen(),
+ event.getModifiers(), p.x, p.y,
+ event.getXOnScreen(),
+ event.getYOnScreen(),
+ event.getClickCount(),
+ event.isPopupTrigger(),
+ MouseEvent.NOBUTTON);
+ subElements[j].processMouseEvent(enterEvent, path, this);
+ }
+ MouseEvent mouseEvent = new MouseEvent(mc, event.getID(),event. getWhen(),
+ event.getModifiers(), p.x, p.y,
+ event.getXOnScreen(),
+ event.getYOnScreen(),
+ event.getClickCount(),
+ event.isPopupTrigger(),
+ MouseEvent.NOBUTTON);
+ subElements[j].processMouseEvent(mouseEvent, path, this);
+ success = true;
+ event.consume();
+ }
+ }
+ }
+ }
+
+ private void printMenuElementArray(MenuElement path[]) {
+ printMenuElementArray(path, false);
+ }
+
+ private void printMenuElementArray(MenuElement path[], boolean dumpStack) {
+ System.out.println("Path is(");
+ int i, j;
+ for(i=0,j=path.length; i<j ;i++){
+ for (int k=0; k<=i; k++)
+ System.out.print(" ");
+ MenuElement me = (MenuElement) path[i];
+ if(me instanceof JMenuItem) {
+ System.out.println(((JMenuItem)me).getText() + ", ");
+ } else if (me instanceof JMenuBar) {
+ System.out.println("JMenuBar, ");
+ } else if(me instanceof JPopupMenu) {
+ System.out.println("JPopupMenu, ");
+ } else if (me == null) {
+ System.out.println("NULL , ");
+ } else {
+ System.out.println("" + me + ", ");
+ }
+ }
+ System.out.println(")");
+
+ if (dumpStack == true)
+ Thread.dumpStack();
+ }
+
+ /**
+ * Returns the component in the currently selected path
+ * which contains sourcePoint.
+ *
+ * @param source The component in whose coordinate space sourcePoint
+ * is given
+ * @param sourcePoint The point which is being tested
+ * @return The component in the currently selected path which
+ * contains sourcePoint (relative to the source component's
+ * coordinate space. If sourcePoint is not inside a component
+ * on the currently selected path, null is returned.
+ */
+ public Component componentForPoint(Component source, Point sourcePoint) {
+ int screenX,screenY;
+ Point p = sourcePoint;
+ int i,c,j,d;
+ Component mc;
+ Rectangle r2;
+ int cWidth,cHeight;
+ MenuElement menuElement;
+ MenuElement subElements[];
+ Vector tmp;
+ int selectionSize;
+
+ SwingUtilities.convertPointToScreen(p,source);
+
+ screenX = p.x;
+ screenY = p.y;
+
+ tmp = (Vector)selection.clone();
+ selectionSize = tmp.size();
+ for(i=selectionSize - 1 ; i >= 0 ; i--) {
+ menuElement = (MenuElement) tmp.elementAt(i);
+ subElements = menuElement.getSubElements();
+
+ for(j = 0, d = subElements.length ; j < d ; j++) {
+ if (subElements[j] == null)
+ continue;
+ mc = subElements[j].getComponent();
+ if(!mc.isShowing())
+ continue;
+ if(mc instanceof JComponent) {
+ cWidth = ((JComponent)mc).getWidth();
+ cHeight = ((JComponent)mc).getHeight();
+ } else {
+ r2 = mc.getBounds();
+ cWidth = r2.width;
+ cHeight = r2.height;
+ }
+ p.x = screenX;
+ p.y = screenY;
+ SwingUtilities.convertPointFromScreen(p,mc);
+
+ /** Return the deepest component on the selection
+ * path in whose bounds the event's point occurs
+ */
+ if (p.x >= 0 && p.x < cWidth && p.y >= 0 && p.y < cHeight) {
+ return mc;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * When a MenuElement receives an event from a KeyListener, it should never process the event
+ * directly. Instead all MenuElements should call this method with the event.
+ *
+ * @param e a KeyEvent object
+ */
+ public void processKeyEvent(KeyEvent e) {
+ MenuElement[] sel2 = new MenuElement[0];
+ sel2 = (MenuElement[])selection.toArray(sel2);
+ int selSize = sel2.length;
+ MenuElement[] path;
+
+ if (selSize < 1) {
+ return;
+ }
+
+ for (int i=selSize-1; i>=0; i--) {
+ MenuElement elem = sel2[i];
+ MenuElement[] subs = elem.getSubElements();
+ path = null;
+
+ for (int j=0; j<subs.length; j++) {
+ if (subs[j] == null || !subs[j].getComponent().isShowing()
+ || !subs[j].getComponent().isEnabled()) {
+ continue;
+ }
+
+ if(path == null) {
+ path = new MenuElement[i+2];
+ System.arraycopy(sel2, 0, path, 0, i+1);
+ }
+ path[i+1] = subs[j];
+ subs[j].processKeyEvent(e, path, this);
+ if (e.isConsumed()) {
+ return;
+ }
+ }
+ }
+
+ // finally dispatch event to the first component in path
+ path = new MenuElement[1];
+ path[0] = sel2[0];
+ path[0].processKeyEvent(e, path, this);
+ if (e.isConsumed()) {
+ return;
+ }
+ }
+
+ /**
+ * Return true if c is part of the currently used menu
+ */
+ public boolean isComponentPartOfCurrentMenu(Component c) {
+ if(selection.size() > 0) {
+ MenuElement me = (MenuElement)selection.elementAt(0);
+ return isComponentPartOfCurrentMenu(me,c);
+ } else
+ return false;
+ }
+
+ private boolean isComponentPartOfCurrentMenu(MenuElement root,Component c) {
+ MenuElement children[];
+ int i,d;
+
+ if (root == null)
+ return false;
+
+ if(root.getComponent() == c)
+ return true;
+ else {
+ children = root.getSubElements();
+ for(i=0,d=children.length;i<d;i++) {
+ if(isComponentPartOfCurrentMenu(children[i],c))
+ return true;
+ }
+ }
+ return false;
+ }
+}