/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Liquid Look and Feel * * * * Author, Miroslav Lazarevic * * * * For licensing information and credits, please refer to the * * comment in file com.birosoft.liquid.LiquidLookAndFeel * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ package com.birosoft.liquid; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.AbstractAction; import javax.swing.AbstractButton; import javax.swing.ActionMap; import javax.swing.JComponent; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.KeyStroke; import javax.swing.MenuElement; import javax.swing.MenuSelectionManager; import javax.swing.SwingUtilities; import javax.swing.Timer; import javax.swing.UIManager; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.MenuDragMouseEvent; import javax.swing.event.MenuDragMouseListener; import javax.swing.event.MenuEvent; import javax.swing.event.MenuKeyEvent; import javax.swing.event.MenuKeyListener; import javax.swing.event.MenuListener; import javax.swing.event.MouseInputListener; import javax.swing.plaf.ComponentUI; /** * A default L&F implementation of MenuUI. This implementation * is a "combined" view/controller. * * @version 1.144 04/24/02 * @author Georges Saab * @author David Karlton * @author Arnaud Weber */ public class LiquidMenuUI extends LiquidMenuItemUI { protected ChangeListener changeListener; protected PropertyChangeListener propertyChangeListener; protected MenuListener menuListener; // Shared instance of the menuListener private static MenuListener sharedMenuListener; private int lastMnemonic = 0; private static final boolean DEBUG = false; // show bad params, misc. private static boolean crossMenuMnemonic = true; public static ComponentUI createUI(JComponent x) { return new LiquidMenuUI(); } public void installUI(JComponent c) { super.installUI(c); } protected void installDefaults() { super.installDefaults(); ((JMenu) menuItem).setDelay(200); crossMenuMnemonic = UIManager.getBoolean("Menu.crossMenuMnemonic"); } protected String getPropertyPrefix() { return "Menu"; } protected void installListeners() { super.installListeners(); if (changeListener == null) changeListener = createChangeListener(menuItem); if (changeListener != null) menuItem.addChangeListener(changeListener); if (propertyChangeListener == null) propertyChangeListener = createPropertyChangeListener(menuItem); if (propertyChangeListener != null) menuItem.addPropertyChangeListener(propertyChangeListener); if (menuListener == null) menuListener = createMenuListener(menuItem); if (menuListener != null) ((JMenu) menuItem).addMenuListener(menuListener); } protected void installKeyboardActions() { super.installKeyboardActions(); updateMnemonicBinding(); } void updateMnemonicBinding() { int mnemonic = menuItem.getModel().getMnemonic(); int[] shortcutKeys = (int[]) UIManager.get("Menu.shortcutKeys"); if (mnemonic == lastMnemonic || shortcutKeys == null) { return; } if (lastMnemonic != 0 && windowInputMap != null) { for (int i = 0; i < shortcutKeys.length; i++) { windowInputMap.remove(KeyStroke.getKeyStroke(lastMnemonic, shortcutKeys[i], false)); } } if (mnemonic != 0) { if (windowInputMap == null) { windowInputMap = createInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); SwingUtilities.replaceUIInputMap(menuItem, JComponent.WHEN_IN_FOCUSED_WINDOW, windowInputMap); } for (int i = 0; i < shortcutKeys.length; i++) { windowInputMap.put(KeyStroke.getKeyStroke(mnemonic, shortcutKeys[i], false), "selectMenu"); } } lastMnemonic = mnemonic; } protected void uninstallKeyboardActions() { super.uninstallKeyboardActions(); } /** * The ActionMap for BasicMenUI can not be shared, this is subclassed * to create a new one for each invocation. */ ActionMap getActionMap() { return createActionMap(); } /** * Invoked to create the ActionMap. */ ActionMap createActionMap() { ActionMap am = super.createActionMap(); if (am != null) { am.put("selectMenu", new PostAction((JMenu) menuItem, true)); } return am; } protected MouseInputListener createMouseInputListener(JComponent c) { return new MouseInputHandler(); } protected MenuListener createMenuListener(JComponent c) { if (sharedMenuListener == null) { sharedMenuListener = new MenuHandler(); } return sharedMenuListener; } protected ChangeListener createChangeListener(JComponent c) { return null; } protected PropertyChangeListener createPropertyChangeListener(JComponent c) { return new PropertyChangeHandler(); } protected void uninstallDefaults() { menuItem.setArmed(false); menuItem.setSelected(false); menuItem.resetKeyboardActions(); super.uninstallDefaults(); } protected void uninstallListeners() { super.uninstallListeners(); if (changeListener != null) menuItem.removeChangeListener(changeListener); if (propertyChangeListener != null) menuItem.removePropertyChangeListener(propertyChangeListener); if (menuListener != null) ((JMenu) menuItem).removeMenuListener(menuListener); changeListener = null; propertyChangeListener = null; menuListener = null; } protected MenuDragMouseListener createMenuDragMouseListener(JComponent c) { return new MenuDragMouseHandler(); } protected MenuKeyListener createMenuKeyListener(JComponent c) { return new MenuKeyHandler(); } public Dimension getMaximumSize(JComponent c) { if (((JMenu) menuItem).isTopLevelMenu() == true) { Dimension d = c.getPreferredSize(); return new Dimension(d.width, Short.MAX_VALUE); } return null; } protected void setupPostTimer(JMenu menu) { Timer timer = new Timer(menu.getDelay(), new PostAction(menu, false)); timer.setRepeats(false); timer.start(); } private static void appendPath(MenuElement[] path, MenuElement elem) { MenuElement newPath[] = new MenuElement[path.length + 1]; System.arraycopy(path, 0, newPath, 0, path.length); newPath[path.length] = elem; MenuSelectionManager.defaultManager().setSelectedPath(newPath); } private static class PostAction extends AbstractAction { /** * */ private static final long serialVersionUID = -6468833677203159985L; JMenu menu; boolean force = false; PostAction(JMenu menu, boolean shouldForce) { this.menu = menu; this.force = shouldForce; } public void actionPerformed(ActionEvent e) { if (!crossMenuMnemonic) { JPopupMenu pm = getActivePopupMenu(); if (pm != null && pm != menu.getParent()) { return; } } final MenuSelectionManager defaultManager = MenuSelectionManager.defaultManager(); if (force) { Container cnt = menu.getParent(); if (cnt != null && cnt instanceof JMenuBar) { MenuElement me[]; MenuElement subElements[]; subElements = menu.getPopupMenu().getSubElements(); if (subElements.length > 0) { me = new MenuElement[4]; me[0] = (MenuElement) cnt; me[1] = (MenuElement) menu; me[2] = (MenuElement) menu.getPopupMenu(); me[3] = subElements[0]; } else { me = new MenuElement[3]; me[0] = (MenuElement) cnt; me[1] = menu; me[2] = (MenuElement) menu.getPopupMenu(); } defaultManager.setSelectedPath(me); } } else { MenuElement path[] = defaultManager.getSelectedPath(); if (path.length > 0 && path[path.length - 1] == menu) { appendPath(path, menu.getPopupMenu()); } } } public boolean isEnabled() { return menu.getModel().isEnabled(); } } private class PropertyChangeHandler implements PropertyChangeListener { public void propertyChange(PropertyChangeEvent e) { String prop = e.getPropertyName(); if (prop.equals(AbstractButton.MNEMONIC_CHANGED_PROPERTY)) { updateMnemonicBinding(); } } } /** * Instantiated and used by a menu item to handle the current menu selection * from mouse events. A MouseInputHandler processes and forwards all mouse events * to a shared instance of the MenuSelectionManager. * <p> * This class is protected so that it can be subclassed by other look and * feels to implement their own mouse handling behavior. All overridden * methods should call the parent methods so that the menu selection * is correct. * * @see javax.swing.MenuSelectionManager * @since 1.4 */ protected class MouseInputHandler implements MouseInputListener { public void mouseClicked(MouseEvent e) { } /** * Invoked when the mouse has been clicked on the menu. This * method clears or sets the selection path of the * MenuSelectionManager. * * @param e the mouse event */ public void mousePressed(MouseEvent e) { JMenu menu = (JMenu) menuItem; if (!menu.isEnabled()) return; MenuSelectionManager manager = MenuSelectionManager.defaultManager(); if (menu.isTopLevelMenu()) { if (menu.isSelected()) { manager.clearSelectedPath(); } else { Container cnt = menu.getParent(); if (cnt != null && cnt instanceof JMenuBar) { MenuElement me[] = new MenuElement[2]; me[0] = (MenuElement) cnt; me[1] = menu; manager.setSelectedPath(me); } } } MenuElement selectedPath[] = manager.getSelectedPath(); if (selectedPath.length > 0 && selectedPath[selectedPath.length - 1] != menu.getPopupMenu()) { if (menu.isTopLevelMenu() || menu.getDelay() == 0) { appendPath(selectedPath, menu.getPopupMenu()); } else { setupPostTimer(menu); } } } /** * Invoked when the mouse has been released on the menu. Delegates the * mouse event to the MenuSelectionManager. * * @param e the mouse event */ public void mouseReleased(MouseEvent e) { JMenu menu = (JMenu) menuItem; if (!menu.isEnabled()) return; MenuSelectionManager manager = MenuSelectionManager.defaultManager(); manager.processMouseEvent(e); if (!e.isConsumed()) manager.clearSelectedPath(); } /** * Invoked when the cursor enters the menu. This method sets the selected * path for the MenuSelectionManager and handles the case * in which a menu item is used to pop up an additional menu, as in a * hierarchical menu system. * * @param e the mouse event; not used */ public void mouseEntered(MouseEvent e) { JMenu menu = (JMenu) menuItem; if (!menu.isEnabled()) return; menu.putClientProperty("rollover",Boolean.TRUE); menu.repaint(); MenuSelectionManager manager = MenuSelectionManager.defaultManager(); MenuElement selectedPath[] = manager.getSelectedPath(); if (!menu.isTopLevelMenu()) { if (!(selectedPath.length > 0 && selectedPath[selectedPath.length - 1] == menu.getPopupMenu())) { if (menu.getDelay() == 0) { appendPath(getPath(), menu.getPopupMenu()); } else { manager.setSelectedPath(getPath()); setupPostTimer(menu); } } } else { if (selectedPath.length > 0 && selectedPath[0] == menu.getParent()) { MenuElement newPath[] = new MenuElement[3]; // A top level menu's parent is by definition // a JMenuBar newPath[0] = (MenuElement) menu.getParent(); newPath[1] = menu; newPath[2] = menu.getPopupMenu(); manager.setSelectedPath(newPath); } } menu.repaint(); } public void mouseExited(MouseEvent e) { JMenu menu = (JMenu) menuItem; menu.putClientProperty("rollover",Boolean.FALSE); menu.repaint(); } /** * Invoked when a mouse button is pressed on the menu and then dragged. * Delegates the mouse event to the MenuSelectionManager. * * @param e the mouse event * @see java.awt.event.MouseMotionListener#mouseDragged */ public void mouseDragged(MouseEvent e) { JMenu menu = (JMenu) menuItem; if (!menu.isEnabled()) return; MenuSelectionManager.defaultManager().processMouseEvent(e); } public void mouseMoved(MouseEvent e) { } } private static class MenuHandler implements MenuListener { public void menuSelected(MenuEvent e) { } public void menuDeselected(MenuEvent e) { } public void menuCanceled(MenuEvent e) { JMenu m = (JMenu) e.getSource(); MenuSelectionManager manager = MenuSelectionManager.defaultManager(); if (manager.isComponentPartOfCurrentMenu(m)) MenuSelectionManager.defaultManager().clearSelectedPath(); } } /** * As of Java 2 platform 1.4, this previously undocumented class * is now obsolete. KeyBindings are now managed by the popup menu. */ public class ChangeHandler implements ChangeListener { public JMenu menu; public LiquidMenuUI ui; public boolean isSelected = false; public Component wasFocused; public ChangeHandler(JMenu m, LiquidMenuUI ui) { menu = m; this.ui = ui; } public void stateChanged(ChangeEvent e) { } } private class MenuDragMouseHandler implements MenuDragMouseListener { public void menuDragMouseEntered(MenuDragMouseEvent e) { } public void menuDragMouseDragged(MenuDragMouseEvent e) { if (menuItem.isEnabled() == false) return; MenuSelectionManager manager = e.getMenuSelectionManager(); MenuElement path[] = e.getPath(); Point p = e.getPoint(); if (p.x >= 0 && p.x < menuItem.getWidth() && p.y >= 0 && p.y < menuItem.getHeight()) { JMenu menu = (JMenu) menuItem; MenuElement selectedPath[] = manager.getSelectedPath(); if (!(selectedPath.length > 0 && selectedPath[selectedPath.length - 1] == menu.getPopupMenu())) { if (menu.isTopLevelMenu() || menu.getDelay() == 0 || e.getID() == MouseEvent.MOUSE_DRAGGED) { appendPath(path, menu.getPopupMenu()); } else { manager.setSelectedPath(path); setupPostTimer(menu); } } } else if (e.getID() == MouseEvent.MOUSE_RELEASED) { Component comp = manager.componentForPoint(e.getComponent(), e.getPoint()); if (comp == null) manager.clearSelectedPath(); } } public void menuDragMouseExited(MenuDragMouseEvent e) { } public void menuDragMouseReleased(MenuDragMouseEvent e) { } } static JPopupMenu getActivePopupMenu() { MenuElement[] path = MenuSelectionManager.defaultManager().getSelectedPath(); for (int i = path.length - 1; i >= 0; i--) { MenuElement elem = path[i]; if (elem instanceof JPopupMenu) { return (JPopupMenu) elem; } } return null; } /** * Handles the mnemonic handling for the JMenu and JMenuItems. */ private class MenuKeyHandler implements MenuKeyListener { // fields for handling duplicate mnemonics. private int indexes[]; private char lastMnemonic; private int lastIndex; private int matches; /** * Opens the SubMenu */ public void menuKeyTyped(MenuKeyEvent e) { if (DEBUG) { System.out.println("in LiquidMenuUI.menuKeyTyped for " + menuItem.getText()); } if (!crossMenuMnemonic) { JPopupMenu pm = getActivePopupMenu(); if (pm != null && pm != menuItem.getParent()) { return; } } int key = menuItem.getMnemonic(); if (key == 0) return; MenuElement path[] = e.getPath(); if (lower((char) key) == lower(e.getKeyChar())) { JPopupMenu popupMenu = ((JMenu) menuItem).getPopupMenu(); MenuElement sub[] = popupMenu.getSubElements(); if (sub.length > 0) { MenuSelectionManager manager = e.getMenuSelectionManager(); MenuElement newPath[] = new MenuElement[path.length + 2]; System.arraycopy(path, 0, newPath, 0, path.length); newPath[path.length] = popupMenu; newPath[path.length + 1] = sub[0]; manager.setSelectedPath(newPath); } e.consume(); } } /** * Handles the mnemonics for the menu items. Will also handle duplicate mnemonics. * Perhaps this should be moved into BasicPopupMenuUI. See 4670831 */ public void menuKeyPressed(MenuKeyEvent e) { if (DEBUG) { System.out.println("in LiquidMenuUI.menuKeyPressed for " + menuItem.getText()); } // Handle the case for Escape or Enter... char keyChar = e.getKeyChar(); if (!Character.isLetterOrDigit(keyChar)) return; MenuSelectionManager manager = e.getMenuSelectionManager(); MenuElement path[] = e.getPath(); MenuElement selectedPath[] = manager.getSelectedPath(); for (int i = selectedPath.length - 1; i >= 0; i--) { if (selectedPath[i] == menuItem) { JPopupMenu popupMenu = ((JMenu) menuItem).getPopupMenu(); MenuElement items[] = popupMenu.getSubElements(); if (indexes == null || lastMnemonic != keyChar) { matches = 0; lastIndex = 0; indexes = new int[items.length]; for (int j = 0; j < items.length; j++) { int key = ((JMenuItem) items[j]).getMnemonic(); if (lower((char) key) == lower(keyChar)) { indexes[matches++] = j; } } lastMnemonic = keyChar; } if (matches == 0) { ; // no op (consume) } else if (matches == 1) { // Invoke the menu action JMenuItem item = (JMenuItem) items[indexes[0]]; if (!(item instanceof JMenu)) { // Let Submenus be handled by menuKeyTyped manager.clearSelectedPath(); item.doClick(); } } else { // Select the menu item with the matching mnemonic. If // the same mnemonic has been invoked then select the next // menu item in the cycle. if (lastIndex == matches) { // Take care of the situation in which the // mnemonic wraps. lastIndex = 0; } MenuElement menuItem = items[indexes[lastIndex++]]; MenuElement newPath[] = new MenuElement[path.length + 2]; System.arraycopy(path, 0, newPath, 0, path.length); newPath[path.length] = popupMenu; newPath[path.length + 1] = menuItem; manager.setSelectedPath(newPath); } e.consume(); return; } } } public void menuKeyReleased(MenuKeyEvent e) { } private char lower(char keyChar) { return Character.toLowerCase(keyChar); } } }