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 }