001/* BasicMenuUI.java
002   Copyright (C) 2002, 2004, 2005  Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.swing.plaf.basic;
040
041import gnu.classpath.NotImplementedException;
042
043import java.awt.Component;
044import java.awt.Container;
045import java.awt.Dimension;
046import java.awt.Point;
047import java.awt.event.ActionEvent;
048import java.awt.event.MouseEvent;
049import java.beans.PropertyChangeListener;
050
051import javax.swing.AbstractAction;
052import javax.swing.JComponent;
053import javax.swing.JMenu;
054import javax.swing.JMenuBar;
055import javax.swing.JPopupMenu;
056import javax.swing.LookAndFeel;
057import javax.swing.MenuElement;
058import javax.swing.MenuSelectionManager;
059import javax.swing.Timer;
060import javax.swing.UIDefaults;
061import javax.swing.UIManager;
062import javax.swing.event.ChangeEvent;
063import javax.swing.event.ChangeListener;
064import javax.swing.event.MenuDragMouseEvent;
065import javax.swing.event.MenuDragMouseListener;
066import javax.swing.event.MenuEvent;
067import javax.swing.event.MenuKeyEvent;
068import javax.swing.event.MenuKeyListener;
069import javax.swing.event.MenuListener;
070import javax.swing.event.MouseInputListener;
071import javax.swing.plaf.ComponentUI;
072
073/**
074 * UI Delegate for JMenu
075 */
076public 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}