/libjava/classpath/javax/swing/MenuSelectionManager.java
Java | 440 lines | 220 code | 50 blank | 170 comment | 51 complexity | 793098a4e829e4fbcddd711e183ef955 MD5 | raw file
- /* MenuSelectionManager.java --
- Copyright (C) 2002, 2004 Free Software Foundation, Inc.
- This file is part of GNU Classpath.
- GNU Classpath is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2, or (at your option)
- any later version.
- GNU Classpath 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 for more details.
- You should have received a copy of the GNU General Public License
- along with GNU Classpath; see the file COPYING. If not, write to the
- Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
- 02110-1301 USA.
- Linking this library statically or dynamically with other modules is
- making a combined work based on this library. Thus, the terms and
- conditions of the GNU General Public License cover the whole
- combination.
- As a special exception, the copyright holders of this library give you
- permission to link this library with independent modules to produce an
- executable, regardless of the license terms of these independent
- modules, and to copy and distribute the resulting executable under
- terms of your choice, provided that you also meet, for each linked
- independent module, the terms and conditions of the license of that
- module. An independent module is a module which is not derived from
- or based on this library. If you modify this library, you may extend
- this exception to your version of the library, but you are not
- obligated to do so. If you do not wish to do so, delete this
- exception statement from your version. */
- package javax.swing;
- import java.awt.Component;
- import java.awt.Dimension;
- import java.awt.Point;
- import java.awt.event.KeyEvent;
- import java.awt.event.MouseEvent;
- import java.util.ArrayList;
- import java.util.Vector;
- import javax.swing.event.ChangeEvent;
- import javax.swing.event.ChangeListener;
- import javax.swing.event.EventListenerList;
- /**
- * This class manages current menu selectection. It provides
- * methods to clear and set current selected menu path.
- * It also fires StateChange event to its registered
- * listeners whenever selected path of the current menu hierarchy
- * changes.
- *
- */
- public class MenuSelectionManager
- {
- /** ChangeEvent fired when selected path changes*/
- protected ChangeEvent changeEvent = new ChangeEvent(this);
- /** List of listeners for this MenuSelectionManager */
- protected EventListenerList listenerList = new EventListenerList();
- /** Default manager for the current menu hierarchy*/
- private static final MenuSelectionManager manager = new MenuSelectionManager();
- /** Path to the currently selected menu */
- private Vector selectedPath = new Vector();
- /**
- * Fires StateChange event to registered listeners
- */
- protected void fireStateChanged()
- {
- ChangeListener[] listeners = getChangeListeners();
- for (int i = 0; i < listeners.length; i++)
- listeners[i].stateChanged(changeEvent);
- }
- /**
- * Adds ChangeListener to this MenuSelectionManager
- *
- * @param listener ChangeListener to add
- */
- public void addChangeListener(ChangeListener listener)
- {
- listenerList.add(ChangeListener.class, listener);
- }
- /**
- * Removes ChangeListener from the list of registered listeners
- * for this MenuSelectionManager.
- *
- * @param listener ChangeListner to remove
- */
- public void removeChangeListener(ChangeListener listener)
- {
- listenerList.remove(ChangeListener.class, listener);
- }
- /**
- * Returns list of registered listeners with MenuSelectionManager
- *
- * @since 1.4
- */
- public ChangeListener[] getChangeListeners()
- {
- return (ChangeListener[]) listenerList.getListeners(ChangeListener.class);
- }
- /**
- * Unselects all the menu elements on the selection path
- */
- public void clearSelectedPath()
- {
- // Send events from the bottom most item in the menu - hierarchy to the
- // top most
- for (int i = selectedPath.size() - 1; i >= 0; i--)
- ((MenuElement) selectedPath.get(i)).menuSelectionChanged(false);
- // clear selected path
- selectedPath.clear();
- // notify all listeners that the selected path was changed
- fireStateChanged();
- }
- /**
- * This method returns menu element on the selected path that contains
- * given source point. If no menu element on the selected path contains this
- * point, then null is returned.
- *
- * @param source Component relative to which sourcePoint is given
- * @param sourcePoint point for which we want to find menu element that contains it
- *
- * @return Returns menu element that contains given source point and belongs
- * to the currently selected path. Null is return if no such menu element found.
- */
- public Component componentForPoint(Component source, Point sourcePoint)
- {
- // Convert sourcePoint to screen coordinates.
- Point sourcePointOnScreen = sourcePoint;
- if (source.isShowing())
- SwingUtilities.convertPointToScreen(sourcePointOnScreen, source);
- Point compPointOnScreen;
- Component resultComp = null;
- // For each menu element on the selected path, express its location
- // in terms of screen coordinates and check if there is any
- // menu element on the selected path that contains given source point.
- for (int i = 0; i < selectedPath.size(); i++)
- {
- Component comp = ((Component) selectedPath.get(i));
- Dimension size = comp.getSize();
- // convert location of this menu item to screen coordinates
- compPointOnScreen = comp.getLocationOnScreen();
- if (compPointOnScreen.x <= sourcePointOnScreen.x
- && sourcePointOnScreen.x < compPointOnScreen.x + size.width
- && compPointOnScreen.y <= sourcePointOnScreen.y
- && sourcePointOnScreen.y < compPointOnScreen.y + size.height)
- {
- Point p = sourcePointOnScreen;
- if (comp.isShowing())
- SwingUtilities.convertPointFromScreen(p, comp);
- resultComp = SwingUtilities.getDeepestComponentAt(comp, p.x, p.y);
- break;
- }
- }
- return resultComp;
- }
- /**
- * Returns shared instance of MenuSelection Manager
- *
- * @return default Manager
- */
- public static MenuSelectionManager defaultManager()
- {
- return manager;
- }
- /**
- * Returns path representing current menu selection
- *
- * @return Current selection path
- */
- public MenuElement[] getSelectedPath()
- {
- MenuElement[] path = new MenuElement[selectedPath.size()];
- for (int i = 0; i < path.length; i++)
- path[i] = (MenuElement) selectedPath.get(i);
- return path;
- }
- /**
- * Returns true if specified component is part of current menu
- * heirarchy and false otherwise
- *
- * @param c Component for which to check
- * @return True if specified component is part of current menu
- */
- public boolean isComponentPartOfCurrentMenu(Component c)
- {
- MenuElement[] subElements;
- boolean ret = false;
- for (int i = 0; i < selectedPath.size(); i++)
- {
- // Check first element.
- MenuElement first = (MenuElement) selectedPath.get(i);
- if (SwingUtilities.isDescendingFrom(c, first.getComponent()))
- {
- ret = true;
- break;
- }
- else
- {
- // Check sub elements.
- subElements = first.getSubElements();
- for (int j = 0; j < subElements.length; j++)
- {
- MenuElement me = subElements[j];
- if (me != null
- && (SwingUtilities.isDescendingFrom(c, me.getComponent())))
- {
- ret = true;
- break;
- }
- }
- }
- }
- return ret;
- }
- /**
- * Processes key events on behalf of the MenuElements. MenuElement
- * instances should always forward their key events to this method and
- * get their {@link MenuElement#processKeyEvent(KeyEvent, MenuElement[],
- * MenuSelectionManager)} eventually called back.
- *
- * @param e the key event
- */
- public void processKeyEvent(KeyEvent e)
- {
- MenuElement[] selection = (MenuElement[])
- selectedPath.toArray(new MenuElement[selectedPath.size()]);
- if (selection.length == 0)
- return;
- MenuElement[] path;
- for (int index = selection.length - 1; index >= 0; index--)
- {
- MenuElement el = selection[index];
- // This method's main purpose is to forward key events to the
- // relevant menu items, so that they can act in response to their
- // mnemonics beeing typed. So we also need to forward the key event
- // to all the subelements of the currently selected menu elements
- // in the path.
- MenuElement[] subEls = el.getSubElements();
- path = null;
- for (int subIndex = 0; subIndex < subEls.length; subIndex++)
- {
- MenuElement sub = subEls[subIndex];
- // Skip elements that are not showing or not enabled.
- if (sub == null || ! sub.getComponent().isShowing()
- || ! sub.getComponent().isEnabled())
- {
- continue;
- }
- if (path == null)
- {
- path = new MenuElement[index + 2];
- System.arraycopy(selection, 0, path, 0, index + 1);
- }
- path[index + 1] = sub;
- sub.processKeyEvent(e, path, this);
- if (e.isConsumed())
- break;
- }
- if (e.isConsumed())
- break;
- }
- // Dispatch to first element in selection if it hasn't been consumed.
- if (! e.isConsumed())
- {
- path = new MenuElement[1];
- path[0] = selection[0];
- path[0].processKeyEvent(e, path, this);
- }
- }
- /**
- * Forwards given mouse event to all of the source subcomponents.
- *
- * @param event Mouse event
- */
- public void processMouseEvent(MouseEvent event)
- {
- Component source = ((Component) event.getSource());
- // In the case of drag event, event.getSource() returns component
- // where drag event originated. However menu element processing this
- // event should be the one over which mouse is currently located,
- // which is not necessary the source of the drag event.
- Component mouseOverMenuComp;
- // find over which menu element the mouse is currently located
- if (event.getID() == MouseEvent.MOUSE_DRAGGED
- || event.getID() == MouseEvent.MOUSE_RELEASED)
- mouseOverMenuComp = componentForPoint(source, event.getPoint());
- else
- mouseOverMenuComp = source;
- // Process this event only if mouse is located over some menu element
- if (mouseOverMenuComp != null && (mouseOverMenuComp instanceof MenuElement))
- {
- MenuElement[] path = getPath(mouseOverMenuComp);
- ((MenuElement) mouseOverMenuComp).processMouseEvent(event, path,
- manager);
- // FIXME: Java specification says that mouse events should be
- // forwarded to subcomponents. The code below does it, but
- // menu's work fine without it. This code is commented for now.
- /*
- MenuElement[] subComponents = ((MenuElement) mouseOverMenuComp)
- .getSubElements();
- for (int i = 0; i < subComponents.length; i++)
- {
- subComponents[i].processMouseEvent(event, path, manager);
- }
- */
- }
- else
- {
- if (event.getID() == MouseEvent.MOUSE_RELEASED)
- clearSelectedPath();
- }
- }
- /**
- * Sets menu selection to the specified path
- *
- * @param path new selection path
- */
- public void setSelectedPath(MenuElement[] path)
- {
- if (path == null)
- {
- clearSelectedPath();
- return;
- }
- int minSize = path.length; // size of the smaller path.
- int currentSize = selectedPath.size();
- int firstDiff = 0;
- // Search first item that is different in the current and new path.
- for (int i = 0; i < minSize; i++)
- {
- if (i < currentSize && (MenuElement) selectedPath.get(i) == path[i])
- firstDiff++;
- else
- break;
- }
- // Remove items from selection and send notification.
- for (int i = currentSize - 1; i >= firstDiff; i--)
- {
- MenuElement el = (MenuElement) selectedPath.get(i);
- selectedPath.remove(i);
- el.menuSelectionChanged(false);
- }
- // Add new items to selection and send notification.
- for (int i = firstDiff; i < minSize; i++)
- {
- if (path[i] != null)
- {
- selectedPath.add(path[i]);
- path[i].menuSelectionChanged(true);
- }
- }
- fireStateChanged();
- }
- /**
- * Returns path to the specified component
- *
- * @param c component for which to find path for
- *
- * @return path to the specified component
- */
- private MenuElement[] getPath(Component c)
- {
- // FIXME: There is the same method in BasicMenuItemUI. However I
- // cannot use it here instead of this method, since I cannot assume that
- // all the menu elements on the selected path are JMenuItem or JMenu.
- // For now I've just duplicated it here. Please
- // fix me or delete me if another better approach will be found, and
- // this method will not be necessary.
- ArrayList path = new ArrayList();
- // if given component is JMenu, we also need to include
- // it's popup menu in the path
- if (c instanceof JMenu)
- path.add(((JMenu) c).getPopupMenu());
- while (c instanceof MenuElement)
- {
- path.add(0, (MenuElement) c);
- if (c instanceof JPopupMenu)
- c = ((JPopupMenu) c).getInvoker();
- else
- c = c.getParent();
- }
- MenuElement[] pathArray = new MenuElement[path.size()];
- path.toArray(pathArray);
- return pathArray;
- }
- }