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 }