jdk/src/share/classes/javax/swing/MenuSelectionManager.java
changeset 2 90ce3da70b43
child 459 d555ba8bbec1
--- /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;
+    }
+}