OSDN Git Service

Imported GNU Classpath 0.90
[pf3gnuchains/gcc-fork.git] / libjava / classpath / javax / swing / MenuSelectionManager.java
1 /* MenuSelectionManager.java --
2    Copyright (C) 2002, 2004 Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
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)
9 any later version.
10
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.
15
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
19 02110-1301 USA.
20
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
24 combination.
25
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. */
37
38
39 package javax.swing;
40
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;
48
49 import javax.swing.event.ChangeEvent;
50 import javax.swing.event.ChangeListener;
51 import javax.swing.event.EventListenerList;
52
53 /**
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
58  * changes.
59  *
60  */
61 public class MenuSelectionManager
62 {
63   /** ChangeEvent fired when selected path changes*/
64   protected ChangeEvent changeEvent = new ChangeEvent(this);
65
66   /** List of listeners for this MenuSelectionManager */
67   protected EventListenerList listenerList = new EventListenerList();
68
69   /** Default manager for the current menu hierarchy*/
70   private static final MenuSelectionManager manager = new MenuSelectionManager();
71
72   /** Path to the currently selected menu */
73   private Vector selectedPath = new Vector();
74
75   /**
76    * Fires StateChange event to registered listeners
77    */
78   protected void fireStateChanged()
79   {
80     ChangeListener[] listeners = getChangeListeners();
81
82     for (int i = 0; i < listeners.length; i++)
83       listeners[i].stateChanged(changeEvent);
84   }
85
86   /**
87    * Adds ChangeListener to this MenuSelectionManager
88    *
89    * @param listener ChangeListener to add
90    */
91   public void addChangeListener(ChangeListener listener)
92   {
93     listenerList.add(ChangeListener.class, listener);
94   }
95
96   /**
97    * Removes ChangeListener from the list of registered listeners
98    * for this MenuSelectionManager.
99    *
100    * @param listener ChangeListner to remove
101    */
102   public void removeChangeListener(ChangeListener listener)
103   {
104     listenerList.remove(ChangeListener.class, listener);
105   }
106
107   /**
108    * Returns list of registered listeners with MenuSelectionManager
109    *
110    * @since 1.4
111    */
112   public ChangeListener[] getChangeListeners()
113   {
114     return (ChangeListener[]) listenerList.getListeners(ChangeListener.class);
115   }
116
117   /**
118    * Unselects all the menu elements on the selection path
119    */
120   public void clearSelectedPath()
121   {
122     // Send events from the bottom most item in the menu - hierarchy to the
123     // top most
124     for (int i = selectedPath.size() - 1; i >= 0; i--)
125       ((MenuElement) selectedPath.get(i)).menuSelectionChanged(false);
126
127     // clear selected path
128     selectedPath.clear();
129
130     // notify all listeners that the selected path was changed    
131     fireStateChanged();
132   }
133
134   /**
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.
138    *
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
141    *
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.
144    */
145   public Component componentForPoint(Component source, Point sourcePoint)
146   {
147     // Convert sourcePoint to screen coordinates.
148     Point sourcePointOnScreen = sourcePoint;
149     
150     if (source.isShowing())
151       SwingUtilities.convertPointToScreen(sourcePointOnScreen, source);
152
153     Point compPointOnScreen;
154     Component resultComp = null;
155
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++)
160       {
161         Component comp = ((Component) selectedPath.get(i));
162         Dimension size = comp.getSize();
163
164         // convert location of this menu item to screen coordinates
165         compPointOnScreen = comp.getLocationOnScreen();
166
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)
171           {
172             Point p = sourcePointOnScreen;
173         
174         if (comp.isShowing())
175           SwingUtilities.convertPointFromScreen(p, comp);
176         
177             resultComp = SwingUtilities.getDeepestComponentAt(comp, p.x, p.y);
178             break;
179           }
180       }
181     return resultComp;
182   }
183
184   /**
185    * Returns shared instance of MenuSelection Manager
186    *
187    * @return default Manager
188    */
189   public static MenuSelectionManager defaultManager()
190   {
191     return manager;
192   }
193
194   /**
195    * Returns path representing current menu selection
196    *
197    * @return Current selection path
198    */
199   public MenuElement[] getSelectedPath()
200   {
201     MenuElement[] path = new MenuElement[selectedPath.size()];
202
203     for (int i = 0; i < path.length; i++)
204       path[i] = (MenuElement) selectedPath.get(i);
205
206     return path;
207   }
208
209   /**
210    * Returns true if specified component is part of current menu
211    * heirarchy and false otherwise
212    *
213    * @param c Component for which to check
214    * @return True if specified component is part of current menu
215    */
216   public boolean isComponentPartOfCurrentMenu(Component c)
217   {
218     MenuElement[] subElements;
219     boolean ret = false;
220     for (int i = 0; i < selectedPath.size(); i++)
221       {
222         // Check first element.
223         MenuElement first = (MenuElement) selectedPath.get(i);
224         if (SwingUtilities.isDescendingFrom(c, first.getComponent()))
225           {
226             ret = true;
227             break;
228           }
229         else
230           {
231             // Check sub elements.
232             subElements = first.getSubElements();
233             for (int j = 0; j < subElements.length; j++)
234               {
235                 MenuElement me = subElements[j]; 
236                 if (me != null
237                     && (SwingUtilities.isDescendingFrom(c, me.getComponent())))
238                   {
239                     ret = true;
240                     break;
241                   }
242               }
243           }
244       }
245
246       return ret;
247   }
248
249   /**
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.
254    *
255    * @param e the key event
256    */
257   public void processKeyEvent(KeyEvent e)
258   {
259     MenuElement[] selection = (MenuElement[])
260                     selectedPath.toArray(new MenuElement[selectedPath.size()]);
261     MenuElement[] path;
262     for (int index = selection.length - 1; index >= 0; index--)
263       {
264         MenuElement el = selection[index];
265         // This method's main purpose is to forward key events to the
266         // relevant menu items, so that they can act in response to their
267         // mnemonics beeing typed. So we also need to forward the key event
268         // to all the subelements of the currently selected menu elements
269         // in the path.
270         MenuElement[] subEls = el.getSubElements();
271         path = null;
272         for (int subIndex = 0; subIndex < subEls.length; subIndex++)
273           {
274             MenuElement sub = subEls[subIndex];
275             // Skip elements that are not showing or not enabled.
276             if (sub == null || ! sub.getComponent().isShowing()
277                 || ! sub.getComponent().isEnabled())
278               {
279                 continue;
280               }
281
282             if (path == null)
283               {
284                 path = new MenuElement[index + 2];
285                 System.arraycopy(selection, 0, path, 0, index + 1);
286               }
287             path[index + 1] = sub;
288             sub.processKeyEvent(e, path, this);
289             if (e.isConsumed())
290               break;
291           }
292         if (e.isConsumed())
293           break;
294       }
295
296     // Dispatch to first element in selection if it hasn't been consumed.
297     if (! e.isConsumed())
298       {
299         path = new MenuElement[1];
300         path[0] = selection[0];
301         path[0].processKeyEvent(e, path, this);
302       }
303   }
304
305   /**
306    * Forwards given mouse event to all of the source subcomponents.
307    *
308    * @param event Mouse event
309    */
310   public void processMouseEvent(MouseEvent event)
311   {
312     Component source = ((Component) event.getSource());
313
314     // In the case of drag event, event.getSource() returns component
315     // where drag event originated. However menu element processing this 
316     // event should be the one over which mouse is currently located, 
317     // which is not necessary the source of the drag event.     
318     Component mouseOverMenuComp;
319
320     // find over which menu element the mouse is currently located
321     if (event.getID() == MouseEvent.MOUSE_DRAGGED
322         || event.getID() == MouseEvent.MOUSE_RELEASED)
323       mouseOverMenuComp = componentForPoint(source, event.getPoint());
324     else
325       mouseOverMenuComp = source;
326
327     // Process this event only if mouse is located over some menu element
328     if (mouseOverMenuComp != null && (mouseOverMenuComp instanceof MenuElement))
329       {
330         MenuElement[] path = getPath(mouseOverMenuComp);
331         ((MenuElement) mouseOverMenuComp).processMouseEvent(event, path,
332                                                             manager);
333
334         // FIXME: Java specification says that mouse events should be
335         // forwarded to subcomponents. The code below does it, but
336         // menu's work fine without it. This code is commented for now.   
337
338         /*
339         MenuElement[] subComponents = ((MenuElement) mouseOverMenuComp)
340                                       .getSubElements();
341
342         for (int i = 0; i < subComponents.length; i++)
343          {
344               subComponents[i].processMouseEvent(event, path, manager);
345          }
346         */
347       }
348     else
349       {
350         if (event.getID() == MouseEvent.MOUSE_RELEASED)
351           clearSelectedPath();
352       }
353   }
354
355   /**
356    * Sets menu selection to the specified path
357    *
358    * @param path new selection path
359    */
360   public void setSelectedPath(MenuElement[] path)
361   {
362     if (path == null)
363       {
364         clearSelectedPath();
365         return;
366       }
367
368     int minSize = path.length; // size of the smaller path.
369     int currentSize = selectedPath.size();
370     int firstDiff = 0;
371
372     // Search first item that is different in the current and new path.
373     for (int i = 0; i < minSize; i++)
374       {
375         if (i < currentSize && (MenuElement) selectedPath.get(i) == path[i])
376           firstDiff++;
377         else
378           break;
379       }
380
381     // Remove items from selection and send notification.
382     for (int i = currentSize - 1; i >= firstDiff; i--)
383       {
384         MenuElement el = (MenuElement) selectedPath.get(i);
385         selectedPath.remove(i);
386         el.menuSelectionChanged(false);
387       }
388
389     // Add new items to selection and send notification.
390     for (int i = firstDiff; i < minSize; i++)
391       {
392         if (path[i] != null)
393           {
394             selectedPath.add(path[i]);
395             path[i].menuSelectionChanged(true);
396           }
397       }
398
399     fireStateChanged();
400   }
401
402   /**
403    * Returns path to the specified component
404    *
405    * @param c component for which to find path for
406    *
407    * @return path to the specified component
408    */
409   private MenuElement[] getPath(Component c)
410   {
411     // FIXME: There is the same method in BasicMenuItemUI. However I
412     // cannot use it here instead of this method, since I cannot assume that 
413     // all the menu elements on the selected path are JMenuItem or JMenu.
414     // For now I've just duplicated it here. Please 
415     // fix me or delete me if another better approach will be found, and 
416     // this method will not be necessary.
417     ArrayList path = new ArrayList();
418
419     // if given component is JMenu, we also need to include 
420     // it's popup menu in the path 
421     if (c instanceof JMenu)
422       path.add(((JMenu) c).getPopupMenu());
423     while (c instanceof MenuElement)
424       {
425         path.add(0, (MenuElement) c);
426
427         if (c instanceof JPopupMenu)
428           c = ((JPopupMenu) c).getInvoker();
429         else
430           c = c.getParent();
431       }
432
433     MenuElement[] pathArray = new MenuElement[path.size()];
434     path.toArray(pathArray);
435     return pathArray;
436   }
437 }