001    /* BasicMenuUI.java
002       Copyright (C) 2002, 2004, 2005  Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.swing.plaf.basic;
040    
041    import gnu.classpath.NotImplementedException;
042    
043    import java.awt.Component;
044    import java.awt.Container;
045    import java.awt.Dimension;
046    import java.awt.Point;
047    import java.awt.event.ActionEvent;
048    import java.awt.event.MouseEvent;
049    import java.beans.PropertyChangeListener;
050    
051    import javax.swing.AbstractAction;
052    import javax.swing.JComponent;
053    import javax.swing.JMenu;
054    import javax.swing.JMenuBar;
055    import javax.swing.JPopupMenu;
056    import javax.swing.LookAndFeel;
057    import javax.swing.MenuElement;
058    import javax.swing.MenuSelectionManager;
059    import javax.swing.Timer;
060    import javax.swing.UIDefaults;
061    import javax.swing.UIManager;
062    import javax.swing.event.ChangeEvent;
063    import javax.swing.event.ChangeListener;
064    import javax.swing.event.MenuDragMouseEvent;
065    import javax.swing.event.MenuDragMouseListener;
066    import javax.swing.event.MenuEvent;
067    import javax.swing.event.MenuKeyEvent;
068    import javax.swing.event.MenuKeyListener;
069    import javax.swing.event.MenuListener;
070    import javax.swing.event.MouseInputListener;
071    import javax.swing.plaf.ComponentUI;
072    
073    /**
074     * UI Delegate for JMenu
075     */
076    public class BasicMenuUI extends BasicMenuItemUI
077    {
078      /**
079       * Selects a menu. This is used to delay menu selection.
080       */
081      class SelectMenuAction
082        extends AbstractAction
083      {
084        /**
085         * Performs the action.
086         */
087        public void actionPerformed(ActionEvent event)
088        {
089          JMenu menu = (JMenu) menuItem;
090          MenuSelectionManager defaultManager =
091            MenuSelectionManager.defaultManager();
092          MenuElement path[] = defaultManager.getSelectedPath();
093          if(path.length > 0 && path[path.length - 1] == menu)
094            {
095              MenuElement newPath[] = new MenuElement[path.length + 1];
096              System.arraycopy(path, 0, newPath, 0, path.length);
097              newPath[path.length] = menu.getPopupMenu();
098              defaultManager.setSelectedPath(newPath);
099          }
100        }
101    
102      }
103    
104      protected ChangeListener changeListener;
105    
106      /* MenuListener listens to MenuEvents fired by JMenu */
107      protected MenuListener menuListener;
108    
109      /* PropertyChangeListner that listens to propertyChangeEvents occuring in JMenu*/
110      protected PropertyChangeListener propertyChangeListener;
111    
112      /**
113       * Creates a new BasicMenuUI object.
114       */
115      public BasicMenuUI()
116      {
117        mouseInputListener = createMouseInputListener((JMenu) menuItem);
118        menuListener = createMenuListener((JMenu) menuItem);
119        propertyChangeListener = createPropertyChangeListener((JMenu) menuItem);
120      }
121    
122      /**
123       * This method creates a new ChangeListener.
124       *
125       * @return A new ChangeListener.
126       */
127      protected ChangeListener createChangeListener(JComponent c)
128      {
129        return new ChangeHandler((JMenu) c, this);
130      }
131    
132      /**
133       * This method creates new MenuDragMouseListener to listen to mouse dragged events
134       * occuring in the Menu
135       *
136       * @param c the menu to listen to
137       *
138       * @return The MenuDrageMouseListener
139       */
140      protected MenuDragMouseListener createMenuDragMouseListener(JComponent c)
141      {
142        return new MenuDragMouseHandler();
143      }
144    
145      /**
146       * This method creates new MenuDragKeyListener to listen to key events
147       *
148       * @param c the menu to listen to
149       *
150       * @return The MenuKeyListener
151       */
152      protected MenuKeyListener createMenuKeyListener(JComponent c)
153      {
154        return new MenuKeyHandler();
155      }
156    
157      /**
158       * This method creates new MenuListener to listen to menu events
159       * occuring in the Menu
160       *
161       * @param c the menu to listen to
162       *
163       * @return The MenuListener
164       */
165      protected MenuListener createMenuListener(JComponent c)
166      {
167        return new MenuHandler();
168      }
169    
170      /**
171       * This method creates new MouseInputListener to listen to mouse input events
172       * occuring in the Menu
173       *
174       * @param c the menu to listen to
175       *
176       * @return The MouseInputListener
177       */
178      protected MouseInputListener createMouseInputListener(JComponent c)
179      {
180        return new MouseInputHandler();
181      }
182    
183      /**
184       * This method creates newPropertyChangeListener to listen to property changes
185       * occuring in the Menu
186       *
187       * @param c the menu to listen to
188       *
189       * @return The PropertyChangeListener
190       */
191      protected PropertyChangeListener createPropertyChangeListener(JComponent c)
192      {
193        return new PropertyChangeHandler();
194      }
195    
196      /**
197       * This method creates a new BasicMenuUI.
198       *
199       * @param c The JComponent to create a UI for.
200       *
201       * @return A new BasicMenuUI.
202       */
203      public static ComponentUI createUI(JComponent c)
204      {
205        return new BasicMenuUI();
206      }
207    
208      /**
209       * Get the component's maximum size.
210       *
211       * @param c The JComponent for which to get maximum size
212       *
213       * @return The maximum size of the component
214       */
215      public Dimension getMaximumSize(JComponent c)
216      {
217        return c.getPreferredSize();
218      }
219    
220      /**
221       * Returns the prefix for entries in the {@link UIDefaults} table.
222       *
223       * @return "Menu"
224       */
225      protected String getPropertyPrefix()
226      {
227        return "Menu";
228      }
229    
230      /**
231       * Initializes any default properties that this UI has from the defaults for
232       * the Basic look and feel.
233       */
234      protected void installDefaults()
235      {
236    
237        LookAndFeel.installBorder(menuItem, "Menu.border");
238        LookAndFeel.installColorsAndFont(menuItem, "Menu.background",
239                                         "Menu.foreground", "Menu.font");
240        menuItem.setMargin(UIManager.getInsets("Menu.margin"));
241        acceleratorFont = UIManager.getFont("Menu.acceleratorFont");
242        acceleratorForeground = UIManager.getColor("Menu.acceleratorForeground");
243        acceleratorSelectionForeground = UIManager.getColor("Menu.acceleratorSelectionForeground");
244        selectionBackground = UIManager.getColor("Menu.selectionBackground");
245        selectionForeground = UIManager.getColor("Menu.selectionForeground");
246        arrowIcon = UIManager.getIcon("Menu.arrowIcon");
247        oldBorderPainted = UIManager.getBoolean("Menu.borderPainted");
248        ((JMenu) menuItem).setDelay(200);
249      }
250    
251      /**
252       * Installs any keyboard actions. The list of keys that need to be bound are
253       * listed in Basic look and feel's defaults.
254       *
255       */
256      protected void installKeyboardActions()
257      {
258        super.installKeyboardActions();
259      }
260    
261      /**
262       * Creates and registers all the listeners for this UI delegate.
263       */
264      protected void installListeners()
265      {
266        super.installListeners();
267        ((JMenu) menuItem).addMenuListener(menuListener);
268      }
269    
270      protected void setupPostTimer(JMenu menu)
271      {
272        Timer timer = new Timer(menu.getDelay(), new SelectMenuAction());
273        timer.setRepeats(false);
274        timer.start();
275      }
276    
277      /**
278       * This method uninstalls the defaults and sets any objects created during
279       * install to null
280       */
281      protected void uninstallDefaults()
282      {
283        menuItem.setBackground(null);
284        menuItem.setBorder(null);
285        menuItem.setFont(null);
286        menuItem.setForeground(null);
287        menuItem.setMargin(null);
288        acceleratorFont = null;
289        acceleratorForeground = null;
290        acceleratorSelectionForeground = null;
291        selectionBackground = null;
292        selectionForeground = null;
293        arrowIcon = null;
294      }
295    
296      /**
297       * Uninstalls any keyboard actions. The list of keys used  are listed in
298       * Basic look and feel's defaults.
299       */
300      protected void uninstallKeyboardActions()
301      {
302        super.installKeyboardActions();
303      }
304    
305      /**
306       * Unregisters all the listeners that this UI delegate was using. In
307       * addition, it will also null any listeners that it was using.
308       */
309      protected void uninstallListeners()
310      {
311        super.uninstallListeners();
312        ((JMenu) menuItem).removeMenuListener(menuListener);
313      }
314    
315      /**
316       * This class is used by menus to handle mouse events occuring in the
317       * menu.
318       */
319      protected class MouseInputHandler implements MouseInputListener
320      {
321        public void mouseClicked(MouseEvent e)
322        {
323          // Nothing to do here.
324        }
325    
326        public void mouseDragged(MouseEvent e)
327        {
328          MenuSelectionManager manager = MenuSelectionManager.defaultManager();
329          manager.processMouseEvent(e);
330        }
331    
332        private boolean popupVisible()
333        {
334          JMenuBar mb = (JMenuBar) ((JMenu) menuItem).getParent();
335          // check if mb.isSelected because if no menus are selected
336          // we don't have to look through the list for popup menus
337          if (!mb.isSelected())
338            return false;
339          for (int i = 0; i < mb.getMenuCount(); i++)
340          {
341             JMenu m = mb.getMenu(i);
342            if (m != null && m.isPopupMenuVisible())
343              return true;
344          }
345          return false;
346        }
347    
348        public void mouseEntered(MouseEvent e)
349        {
350          JMenu menu = (JMenu) menuItem;
351          if (menu.isEnabled())
352            {
353              MenuSelectionManager manager =
354                MenuSelectionManager.defaultManager();
355              MenuElement[] selectedPath = manager.getSelectedPath();
356              if (! menu.isTopLevelMenu())
357                {
358                  // Open the menu immediately or delayed, depending on the
359                  // delay value.
360                  if(! (selectedPath.length > 0
361                      && selectedPath[selectedPath.length - 1] == menu.getPopupMenu()))
362                    {
363                      if(menu.getDelay() == 0)
364                        {
365                          MenuElement[] path = getPath();
366                          MenuElement[] newPath = new MenuElement[path.length + 1];
367                          System.arraycopy(path, 0, newPath, 0, path.length);
368                          newPath[path.length] = menu.getPopupMenu();
369                          manager.setSelectedPath(newPath);
370                        }
371                      else
372                        {
373                          manager.setSelectedPath(getPath());
374                          setupPostTimer(menu);
375                        }
376                    }
377                }
378              else
379                {
380                  if(selectedPath.length > 0
381                      && selectedPath[0] == menu.getParent())
382                    {
383                      MenuElement[] newPath = new MenuElement[3];
384                      newPath[0] = (MenuElement) menu.getParent();
385                      newPath[1] = menu;
386                      newPath[2] = menu.getPopupMenu();
387                      manager.setSelectedPath(newPath);
388                    }
389                }
390            }
391        }
392    
393        public void mouseExited(MouseEvent e)
394        {
395          MenuSelectionManager manager = MenuSelectionManager.defaultManager();
396          manager.processMouseEvent(e);
397        }
398    
399        public void mouseMoved(MouseEvent e)
400        {
401          // Nothing to do here.
402        }
403    
404        public void mousePressed(MouseEvent e)
405        {
406          MenuSelectionManager manager = MenuSelectionManager.defaultManager();
407          JMenu menu = (JMenu) menuItem;
408          if (menu.isEnabled())
409            {
410              // Open up the menu immediately if it's a toplevel menu.
411              // But not yet the popup, which might be opened delayed, see below.
412              if (menu.isTopLevelMenu())
413                {
414                  if (menu.isSelected())
415                    manager.clearSelectedPath();
416                  else
417                    {
418                      Container cnt = menu.getParent();
419                      if (cnt != null && cnt instanceof JMenuBar)
420                        {
421                          MenuElement[] me = new MenuElement[2];
422                          me[0] = (MenuElement) cnt;
423                          me[1] = menu;
424                          manager.setSelectedPath(me);
425                       }
426                    }
427                }
428    
429              // Open the menu's popup. Either do that immediately if delay == 0,
430              // or delayed when delay > 0.
431              MenuElement[] selectedPath = manager.getSelectedPath();
432              if (selectedPath.length > 0
433                  && selectedPath[selectedPath.length - 1] != menu.getPopupMenu())
434                {
435                  if(menu.isTopLevelMenu() || menu.getDelay() == 0)
436                    {
437                      MenuElement[] newPath =
438                        new MenuElement[selectedPath.length + 1];
439                      System.arraycopy(selectedPath, 0, newPath, 0,
440                                       selectedPath.length);
441                      newPath[selectedPath.length] = menu.getPopupMenu();
442                      manager.setSelectedPath(newPath);
443                    }
444                  else
445                    {
446                      setupPostTimer(menu);
447                    }
448                }
449    
450            }
451        }
452    
453        public void mouseReleased(MouseEvent e)
454        {
455          MenuSelectionManager manager = MenuSelectionManager.defaultManager();
456          manager.processMouseEvent(e);
457        }
458      }
459    
460      /**
461       * This class handles MenuEvents fired by the JMenu
462       */
463      private class MenuHandler implements MenuListener
464      {
465        /**
466         * This method is called when menu is cancelled. The menu is cancelled
467         * when its popup menu is closed without selection. It clears selected index
468         * in the selectionModel of the menu parent.
469         *
470         * @param e The MenuEvent.
471         */
472        public void menuCanceled(MenuEvent e)
473        {
474          menuDeselected(e);
475        }
476    
477        /**
478         * This method is called when menu is deselected. It clears selected index
479         * in the selectionModel of the menu parent.
480         *
481         * @param e The MenuEvent.
482         */
483        public void menuDeselected(MenuEvent e)
484        {
485          JMenu menu = (JMenu) menuItem;
486          if (menu.getParent() != null)
487            {
488              if (menu.isTopLevelMenu())
489                ((JMenuBar) menu.getParent()).getSelectionModel().clearSelection();
490              else
491                ((JPopupMenu) menu.getParent()).getSelectionModel().clearSelection();
492            }
493        }
494    
495        /**
496         * This method is called when menu is selected.  It sets selected index
497         * in the selectionModel of the menu parent.
498         *
499         * @param e The MenuEvent.
500         */
501        public void menuSelected(MenuEvent e)
502        {
503          JMenu menu = (JMenu) menuItem;
504          if (menu.isTopLevelMenu())
505            ((JMenuBar) menu.getParent()).setSelected(menu);
506          else
507            ((JPopupMenu) menu.getParent()).setSelected(menu);
508        }
509      }
510    
511      /**
512       * Obsolete as of JDK1.4.
513       */
514      public class ChangeHandler implements ChangeListener
515      {
516        /**
517         * Not used.
518         */
519        public boolean isSelected;
520    
521        /**
522         * Not used.
523         */
524        public JMenu menu;
525    
526        /**
527         * Not used.
528         */
529        public BasicMenuUI ui;
530    
531        /**
532         * Not used.
533         */
534        public Component wasFocused;
535    
536        /**
537         * Not used.
538         */
539        public ChangeHandler(JMenu m, BasicMenuUI ui)
540        {
541          menu = m;
542          this.ui = ui;
543        }
544    
545        /**
546         * Not used.
547         */
548        public void stateChanged(ChangeEvent e)
549        {
550          // Not used.
551        }
552      }
553    
554      /**
555       * This class handles mouse dragged events occuring in the menu.
556       */
557      private class MenuDragMouseHandler implements MenuDragMouseListener
558      {
559        /**
560         * This method is invoked when mouse is dragged over the menu item.
561         *
562         * @param e The MenuDragMouseEvent
563         */
564        public void menuDragMouseDragged(MenuDragMouseEvent e)
565        {
566          if (menuItem.isEnabled())
567            {
568              MenuSelectionManager manager = e.getMenuSelectionManager();
569              MenuElement path[] = e.getPath();
570    
571              Point p = e.getPoint();
572              if(p.x >= 0 && p.x < menuItem.getWidth()
573                  && p.y >= 0 && p.y < menuItem.getHeight())
574                {
575                  JMenu menu = (JMenu) menuItem;
576                  MenuElement[] selectedPath = manager.getSelectedPath();
577                  if(! (selectedPath.length > 0
578                      && selectedPath[selectedPath.length-1]
579                                      == menu.getPopupMenu()))
580                    {
581                      if(menu.isTopLevelMenu() || menu.getDelay() == 0
582                         || e.getID() == MouseEvent.MOUSE_DRAGGED)
583                        {
584                          MenuElement[] newPath = new MenuElement[path.length + 1];
585                          System.arraycopy(path, 0, newPath, 0, path.length);
586                          newPath[path.length] = menu.getPopupMenu();
587                          manager.setSelectedPath(newPath);
588                        }
589                      else
590                        {
591                          manager.setSelectedPath(path);
592                          setupPostTimer(menu);
593                        }
594                    }
595                }
596              else if (e.getID() == MouseEvent.MOUSE_RELEASED)
597                {
598                  Component comp = manager.componentForPoint(e.getComponent(),
599                                                             e.getPoint());
600                  if (comp == null)
601                    manager.clearSelectedPath();
602                }
603            }
604        }
605    
606        /**
607         * This method is invoked when mouse enters the menu item while it is
608         * being dragged.
609         *
610         * @param e The MenuDragMouseEvent
611         */
612        public void menuDragMouseEntered(MenuDragMouseEvent e)
613        {
614          // Nothing to do here.
615        }
616    
617        /**
618         * This method is invoked when mouse exits the menu item while
619         * it is being dragged
620         *
621         * @param e The MenuDragMouseEvent
622         */
623        public void menuDragMouseExited(MenuDragMouseEvent e)
624        {
625          // Nothing to do here.
626        }
627    
628        /**
629         * This method is invoked when mouse was dragged and released
630         * inside the menu item.
631         *
632         * @param e The MenuDragMouseEvent
633         */
634        public void menuDragMouseReleased(MenuDragMouseEvent e)
635        {
636          // Nothing to do here.
637        }
638      }
639    
640      /**
641       * This class handles key events occuring when menu item is visible on the
642       * screen.
643       */
644      private class MenuKeyHandler implements MenuKeyListener
645      {
646        /**
647         * This method is invoked when key has been pressed
648         *
649         * @param e A {@link MenuKeyEvent}.
650         */
651        public void menuKeyPressed(MenuKeyEvent e)
652        {
653          // Nothing to do here.
654        }
655    
656        /**
657         * This method is invoked when key has been pressed
658         *
659         * @param e A {@link MenuKeyEvent}.
660         */
661        public void menuKeyReleased(MenuKeyEvent e)
662        {
663          // Nothing to do here.
664        }
665    
666        /**
667         * This method is invoked when key has been typed
668         * It handles the mnemonic key for the menu item.
669         *
670         * @param e A {@link MenuKeyEvent}.
671         */
672        public void menuKeyTyped(MenuKeyEvent e)
673        throws NotImplementedException
674        {
675          // TODO: What should be done here, if anything?
676        }
677      }
678    }