OSDN Git Service

2005-04-19 Roman Kennke <roman@kennke.org>
[pf3gnuchains/gcc-fork.git] / libjava / 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., 59 Temple Place, Suite 330, Boston, MA
19 02111-1307 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     SwingUtilities.convertPointToScreen(sourcePointOnScreen, source);
150
151     Point compPointOnScreen;
152     Component resultComp = null;
153
154     // For each menu element on the selected path, express its location 
155     // in terms of screen coordinates and check if there is any 
156     // menu element on the selected path that contains given source point.
157     for (int i = 0; i < selectedPath.size(); i++)
158       {
159         Component comp = ((Component) selectedPath.get(i));
160         Dimension size = comp.getSize();
161
162         // convert location of this menu item to screen coordinates
163         compPointOnScreen = comp.getLocationOnScreen();
164
165         if (compPointOnScreen.x <= sourcePointOnScreen.x
166             && sourcePointOnScreen.x < compPointOnScreen.x + size.width
167             && compPointOnScreen.y <= sourcePointOnScreen.y
168             && sourcePointOnScreen.y < compPointOnScreen.y + size.height)
169           {
170             Point p = sourcePointOnScreen;
171             SwingUtilities.convertPointFromScreen(p, comp);
172             resultComp = SwingUtilities.getDeepestComponentAt(comp, p.x, p.y);
173             break;
174           }
175       }
176     return resultComp;
177   }
178
179   /**
180    * Returns shared instance of MenuSelection Manager
181    *
182    * @return default Manager
183    */
184   public static MenuSelectionManager defaultManager()
185   {
186     return manager;
187   }
188
189   /**
190    * Returns path representing current menu selection
191    *
192    * @return Current selection path
193    */
194   public MenuElement[] getSelectedPath()
195   {
196     MenuElement[] path = new MenuElement[selectedPath.size()];
197
198     for (int i = 0; i < path.length; i++)
199       path[i] = (MenuElement) selectedPath.get(i);
200
201     return path;
202   }
203
204   /**
205    * Returns true if specified component is part of current menu
206    * heirarchy and false otherwise
207    *
208    * @param c Component for which to check
209    * @return True if specified component is part of current menu
210    */
211   public boolean isComponentPartOfCurrentMenu(Component c)
212   {
213     MenuElement[] subElements;
214     for (int i = 0; i < selectedPath.size(); i++)
215       {
216         subElements = ((MenuElement) selectedPath.get(i)).getSubElements();
217         for (int j = 0; j < subElements.length; j++)
218           {
219             if ((subElements[j].getComponent()).equals(c))
220               return true;
221           }
222       }
223
224     return false;
225   }
226
227   /**
228    * DOCUMENT ME!
229    *
230    * @param e DOCUMENT ME!
231    */
232   public void processKeyEvent(KeyEvent e)
233   {
234     throw new UnsupportedOperationException("not implemented");
235   }
236
237   /**
238    * Forwards given mouse event to all of the source subcomponents.
239    *
240    * @param event Mouse event
241    */
242   public void processMouseEvent(MouseEvent event)
243   {
244     Component source = ((Component) event.getSource());
245
246     // In the case of drag event, event.getSource() returns component
247     // where drag event originated. However menu element processing this 
248     // event should be the one over which mouse is currently located, 
249     // which is not necessary the source of the drag event.     
250     Component mouseOverMenuComp;
251
252     // find over which menu element the mouse is currently located
253     if (event.getID() == MouseEvent.MOUSE_DRAGGED
254         || event.getID() == MouseEvent.MOUSE_RELEASED)
255       mouseOverMenuComp = componentForPoint(source, event.getPoint());
256     else
257       mouseOverMenuComp = source;
258
259     // Process this event only if mouse is located over some menu element
260     if (mouseOverMenuComp != null && (mouseOverMenuComp instanceof MenuElement))
261       {
262         MenuElement[] path = getPath(mouseOverMenuComp);
263         ((MenuElement) mouseOverMenuComp).processMouseEvent(event, path,
264                                                             manager);
265
266         // FIXME: Java specification says that mouse events should be
267         // forwarded to subcomponents. The code below does it, but
268         // menu's work fine without it. This code is commented for now.   
269
270         /*
271         MenuElement[] subComponents = ((MenuElement) mouseOverMenuComp)
272                                       .getSubElements();
273
274         for (int i = 0; i < subComponents.length; i++)
275          {
276               subComponents[i].processMouseEvent(event, path, manager);
277          }
278         */
279       }
280   }
281
282   /**
283    * Sets menu selection to the specified path
284    *
285    * @param path new selection path
286    */
287   public void setSelectedPath(MenuElement[] path)
288   {
289     if (path == null)
290       {
291         clearSelectedPath();
292         return;
293       }
294
295     int i;
296     int minSize = path.length; // size of the smaller path. 
297
298     if (path.length > selectedPath.size())
299       {
300         minSize = selectedPath.size();
301
302         // if new selected path contains more elements then current
303         // selection then first add all elements at 
304         // the indexes > selectedPath.size 
305         for (i = selectedPath.size(); i < path.length; i++)
306           {
307             selectedPath.add(path[i]);
308             path[i].menuSelectionChanged(true);
309           }
310       }
311
312     else if (path.length < selectedPath.size())
313       {
314         // if new selected path contains less elements then current 
315         // selection then first remove all elements from the selection
316         // at the indexes > path.length
317         for (i = selectedPath.size() - 1; i >= path.length; i--)
318           {
319             ((MenuElement) selectedPath.get(i)).menuSelectionChanged(false);
320             selectedPath.remove(i);
321           }
322
323         minSize = path.length;
324       }
325
326     // Now compare elements in new and current selection path at the 
327     // same location and adjust selection until 
328     // same menu elements will be encountered at the
329     // same index in both current and new selection path.
330     MenuElement oldSelectedItem;
331
332     for (i = minSize - 1; i >= 0; i--)
333       {
334         oldSelectedItem = (MenuElement) selectedPath.get(i);
335
336         if (path[i].equals(oldSelectedItem))
337           break;
338
339         oldSelectedItem.menuSelectionChanged(false);
340         path[i].menuSelectionChanged(true);
341         selectedPath.setElementAt(path[i], i);
342       }
343
344     fireStateChanged();
345   }
346
347   /**
348    * Returns path to the specified component
349    *
350    * @param c component for which to find path for
351    *
352    * @return path to the specified component
353    */
354   private MenuElement[] getPath(Component c)
355   {
356     // FIXME: There is the same method in BasicMenuItemUI. However I
357     // cannot use it here instead of this method, since I cannot assume that 
358     // all the menu elements on the selected path are JMenuItem or JMenu.
359     // For now I've just duplicated it here. Please 
360     // fix me or delete me if another better approach will be found, and 
361     // this method will not be necessary.
362     ArrayList path = new ArrayList();
363
364     // if given component is JMenu, we also need to include 
365     // it's popup menu in the path 
366     if (c instanceof JMenu)
367       path.add(((JMenu) c).getPopupMenu());
368     while (c instanceof MenuElement)
369       {
370         path.add(0, (MenuElement) c);
371
372         if (c instanceof JPopupMenu)
373           c = ((JPopupMenu) c).getInvoker();
374         else
375           c = c.getParent();
376       }
377
378     MenuElement[] pathArray = new MenuElement[path.size()];
379     path.toArray(pathArray);
380     return pathArray;
381   }
382 }