1 /* MenuSelectionManager.java --
2 Copyright (C) 2002, 2004 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
41 import java.awt.Component;
42 import java.awt.Dimension;
43 import java.awt.Point;
44 import java.awt.event.KeyEvent;
45 import java.awt.event.MouseEvent;
46 import java.util.ArrayList;
47 import java.util.Vector;
49 import javax.swing.event.ChangeEvent;
50 import javax.swing.event.ChangeListener;
51 import javax.swing.event.EventListenerList;
54 * This class manages current menu selectection. It provides
55 * methods to clear and set current selected menu path.
56 * It also fires StateChange event to its registered
57 * listeners whenever selected path of the current menu hierarchy
61 public class MenuSelectionManager
63 /** ChangeEvent fired when selected path changes*/
64 protected ChangeEvent changeEvent = new ChangeEvent(this);
66 /** List of listeners for this MenuSelectionManager */
67 protected EventListenerList listenerList = new EventListenerList();
69 /** Default manager for the current menu hierarchy*/
70 private static final MenuSelectionManager manager = new MenuSelectionManager();
72 /** Path to the currently selected menu */
73 private Vector selectedPath = new Vector();
76 * Fires StateChange event to registered listeners
78 protected void fireStateChanged()
80 ChangeListener[] listeners = getChangeListeners();
82 for (int i = 0; i < listeners.length; i++)
83 listeners[i].stateChanged(changeEvent);
87 * Adds ChangeListener to this MenuSelectionManager
89 * @param listener ChangeListener to add
91 public void addChangeListener(ChangeListener listener)
93 listenerList.add(ChangeListener.class, listener);
97 * Removes ChangeListener from the list of registered listeners
98 * for this MenuSelectionManager.
100 * @param listener ChangeListner to remove
102 public void removeChangeListener(ChangeListener listener)
104 listenerList.remove(ChangeListener.class, listener);
108 * Returns list of registered listeners with MenuSelectionManager
112 public ChangeListener[] getChangeListeners()
114 return (ChangeListener[]) listenerList.getListeners(ChangeListener.class);
118 * Unselects all the menu elements on the selection path
120 public void clearSelectedPath()
122 // Send events from the bottom most item in the menu - hierarchy to the
124 for (int i = selectedPath.size() - 1; i >= 0; i--)
125 ((MenuElement) selectedPath.get(i)).menuSelectionChanged(false);
127 // clear selected path
128 selectedPath.clear();
130 // notify all listeners that the selected path was changed
135 * This method returns menu element on the selected path that contains
136 * given source point. If no menu element on the selected path contains this
137 * point, then null is returned.
139 * @param source Component relative to which sourcePoint is given
140 * @param sourcePoint point for which we want to find menu element that contains it
142 * @return Returns menu element that contains given source point and belongs
143 * to the currently selected path. Null is return if no such menu element found.
145 public Component componentForPoint(Component source, Point sourcePoint)
147 // Convert sourcePoint to screen coordinates.
148 Point sourcePointOnScreen = sourcePoint;
150 if (source.isShowing())
151 SwingUtilities.convertPointToScreen(sourcePointOnScreen, source);
153 Point compPointOnScreen;
154 Component resultComp = null;
156 // For each menu element on the selected path, express its location
157 // in terms of screen coordinates and check if there is any
158 // menu element on the selected path that contains given source point.
159 for (int i = 0; i < selectedPath.size(); i++)
161 Component comp = ((Component) selectedPath.get(i));
162 Dimension size = comp.getSize();
164 // convert location of this menu item to screen coordinates
165 compPointOnScreen = comp.getLocationOnScreen();
167 if (compPointOnScreen.x <= sourcePointOnScreen.x
168 && sourcePointOnScreen.x < compPointOnScreen.x + size.width
169 && compPointOnScreen.y <= sourcePointOnScreen.y
170 && sourcePointOnScreen.y < compPointOnScreen.y + size.height)
172 Point p = sourcePointOnScreen;
174 if (comp.isShowing())
175 SwingUtilities.convertPointFromScreen(p, comp);
177 resultComp = SwingUtilities.getDeepestComponentAt(comp, p.x, p.y);
185 * Returns shared instance of MenuSelection Manager
187 * @return default Manager
189 public static MenuSelectionManager defaultManager()
195 * Returns path representing current menu selection
197 * @return Current selection path
199 public MenuElement[] getSelectedPath()
201 MenuElement[] path = new MenuElement[selectedPath.size()];
203 for (int i = 0; i < path.length; i++)
204 path[i] = (MenuElement) selectedPath.get(i);
210 * Returns true if specified component is part of current menu
211 * heirarchy and false otherwise
213 * @param c Component for which to check
214 * @return True if specified component is part of current menu
216 public boolean isComponentPartOfCurrentMenu(Component c)
218 MenuElement[] subElements;
220 for (int i = 0; i < selectedPath.size(); i++)
222 // Check first element.
223 MenuElement first = (MenuElement) selectedPath.get(i);
224 if (SwingUtilities.isDescendingFrom(c, first.getComponent()))
231 // Check sub elements.
232 subElements = first.getSubElements();
233 for (int j = 0; j < subElements.length; j++)
235 MenuElement me = subElements[j];
237 && (SwingUtilities.isDescendingFrom(c, me.getComponent())))
250 * Processes key events on behalf of the MenuElements. MenuElement
251 * instances should always forward their key events to this method and
252 * get their {@link MenuElement#processKeyEvent(KeyEvent, MenuElement[],
253 * MenuSelectionManager)} eventually called back.
255 * @param e the key event
257 public void processKeyEvent(KeyEvent e)
259 MenuElement[] selection = (MenuElement[])
260 selectedPath.toArray(new MenuElement[selectedPath.size()]);
261 if (selection.length == 0)
265 for (int index = selection.length - 1; index >= 0; index--)
267 MenuElement el = selection[index];
268 // This method's main purpose is to forward key events to the
269 // relevant menu items, so that they can act in response to their
270 // mnemonics beeing typed. So we also need to forward the key event
271 // to all the subelements of the currently selected menu elements
273 MenuElement[] subEls = el.getSubElements();
275 for (int subIndex = 0; subIndex < subEls.length; subIndex++)
277 MenuElement sub = subEls[subIndex];
278 // Skip elements that are not showing or not enabled.
279 if (sub == null || ! sub.getComponent().isShowing()
280 || ! sub.getComponent().isEnabled())
287 path = new MenuElement[index + 2];
288 System.arraycopy(selection, 0, path, 0, index + 1);
290 path[index + 1] = sub;
291 sub.processKeyEvent(e, path, this);
299 // Dispatch to first element in selection if it hasn't been consumed.
300 if (! e.isConsumed())
302 path = new MenuElement[1];
303 path[0] = selection[0];
304 path[0].processKeyEvent(e, path, this);
309 * Forwards given mouse event to all of the source subcomponents.
311 * @param event Mouse event
313 public void processMouseEvent(MouseEvent event)
315 Component source = ((Component) event.getSource());
317 // In the case of drag event, event.getSource() returns component
318 // where drag event originated. However menu element processing this
319 // event should be the one over which mouse is currently located,
320 // which is not necessary the source of the drag event.
321 Component mouseOverMenuComp;
323 // find over which menu element the mouse is currently located
324 if (event.getID() == MouseEvent.MOUSE_DRAGGED
325 || event.getID() == MouseEvent.MOUSE_RELEASED)
326 mouseOverMenuComp = componentForPoint(source, event.getPoint());
328 mouseOverMenuComp = source;
330 // Process this event only if mouse is located over some menu element
331 if (mouseOverMenuComp != null && (mouseOverMenuComp instanceof MenuElement))
333 MenuElement[] path = getPath(mouseOverMenuComp);
334 ((MenuElement) mouseOverMenuComp).processMouseEvent(event, path,
337 // FIXME: Java specification says that mouse events should be
338 // forwarded to subcomponents. The code below does it, but
339 // menu's work fine without it. This code is commented for now.
342 MenuElement[] subComponents = ((MenuElement) mouseOverMenuComp)
345 for (int i = 0; i < subComponents.length; i++)
347 subComponents[i].processMouseEvent(event, path, manager);
353 if (event.getID() == MouseEvent.MOUSE_RELEASED)
359 * Sets menu selection to the specified path
361 * @param path new selection path
363 public void setSelectedPath(MenuElement[] path)
371 int minSize = path.length; // size of the smaller path.
372 int currentSize = selectedPath.size();
375 // Search first item that is different in the current and new path.
376 for (int i = 0; i < minSize; i++)
378 if (i < currentSize && (MenuElement) selectedPath.get(i) == path[i])
384 // Remove items from selection and send notification.
385 for (int i = currentSize - 1; i >= firstDiff; i--)
387 MenuElement el = (MenuElement) selectedPath.get(i);
388 selectedPath.remove(i);
389 el.menuSelectionChanged(false);
392 // Add new items to selection and send notification.
393 for (int i = firstDiff; i < minSize; i++)
397 selectedPath.add(path[i]);
398 path[i].menuSelectionChanged(true);
406 * Returns path to the specified component
408 * @param c component for which to find path for
410 * @return path to the specified component
412 private MenuElement[] getPath(Component c)
414 // FIXME: There is the same method in BasicMenuItemUI. However I
415 // cannot use it here instead of this method, since I cannot assume that
416 // all the menu elements on the selected path are JMenuItem or JMenu.
417 // For now I've just duplicated it here. Please
418 // fix me or delete me if another better approach will be found, and
419 // this method will not be necessary.
420 ArrayList path = new ArrayList();
422 // if given component is JMenu, we also need to include
423 // it's popup menu in the path
424 if (c instanceof JMenu)
425 path.add(((JMenu) c).getPopupMenu());
426 while (c instanceof MenuElement)
428 path.add(0, (MenuElement) c);
430 if (c instanceof JPopupMenu)
431 c = ((JPopupMenu) c).getInvoker();
436 MenuElement[] pathArray = new MenuElement[path.size()];
437 path.toArray(pathArray);