/*******************************************************************************
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Tiny Look and Feel * * (C) Copyright 2003 - 2007 Hans Bickel * * For
* licensing information and credits, please refer to the * comment in file
* de.muntjak.tinylookandfeel.TinyLookAndFeel * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*/
package de.muntjak.tinylookandfeel;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.KeyEventPostProcessor;
import java.awt.Point;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
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.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JRootPane;
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.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;
import javax.swing.plaf.basic.ComboPopup;
/**
* 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
*/
@SuppressWarnings (
{ "all" } )
public class TinyMenuUI extends TinyMenuItemUI
{
/* diagnostic aids -- should be false for production builds. */
private static final boolean TRACE = false; // trace creates and disposes
private static final boolean VERBOSE = false; // show reuse hits/misses
private static final boolean DEBUG = false; // show bad params, misc.
static final AltProcessor altProcessor = new AltProcessor ();
protected ChangeListener changeListener;
protected PropertyChangeListener propertyChangeListener;
protected MenuListener menuListener;
private int lastMnemonic = 0;
private static boolean crossMenuMnemonic = true;
// Shared instance of the menuListener
private static MenuListener sharedMenuListener;
public static ComponentUI createUI ( JComponent x )
{
return new TinyMenuUI ();
}
protected void installDefaults ()
{
super.installDefaults ();
( ( JMenu ) menuItem ).setDelay ( 200 );
crossMenuMnemonic = UIManager.getBoolean ( "Menu.crossMenuMnemonic" );
// ((JMenu)menuItem).addMenuListener(new MenuListener() {
// public void menuSelected(MenuEvent e) {}
//
// public void menuDeselected(MenuEvent e) {
// Thread.dumpStack();
// }
//
// public void menuCanceled(MenuEvent e) {}
// });
}
/**
* 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 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 );
// Removed in 1.3.6 because installing additional listeners
// is unnecessary and caused malfunctions with sub menus
// if(menuListener == null)
// menuListener = createMenuListener(menuItem);
//
// if(menuListener != null)
// ((JMenu) menuItem).addMenuListener(menuListener);
// Note: We install the MenuKeyListener only if we are
// running Java 1.4, with Java 1.5 and higher, mnemonics
// are handled in BasicPopupMenuUI
if ( TinyLookAndFeel.is1dot4 () )
{
if ( ( menuKeyListener = createMenuKeyListener ( menuItem ) ) != null )
{
menuItem.addMenuKeyListener ( menuKeyListener );
}
}
}
protected void installKeyboardActions ()
{
super.installKeyboardActions (); // installs only ActionMap
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 MouseInputListener createMouseInputListener ( JComponent c )
{
return new MouseInputHandler ();
}
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 );
changeListener = null;
}
if ( propertyChangeListener != null )
{
menuItem.removePropertyChangeListener ( propertyChangeListener );
propertyChangeListener = null;
}
if ( menuKeyListener != null )
{
menuItem.removeMenuKeyListener ( menuKeyListener );
menuKeyListener = null;
}
}
protected MenuDragMouseListener createMenuDragMouseListener ( JComponent c )
{
return new MenuDragMouseHandler ();
}
protected MenuKeyListener createMenuKeyListener ( JComponent c )
{
return new MenuKeyHandler ();
}
// protected MenuListener createMenuListener(JComponent c) {
// if(sharedMenuListener == null) {
// sharedMenuListener = new MenuHandler();
// }
// return sharedMenuListener;
// }
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
{
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 ()
|| menu.getClientProperty ( "isSystemMenu" ) == Boolean.TRUE )
{
return;
}
menu.putClientProperty ( "rollover", Boolean.TRUE );
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 );
}
}
if ( menu.isTopLevelMenu () )
{
menu.repaint ();
}
}
public void mouseExited ( MouseEvent e )
{
JMenu menu = ( JMenu ) menuItem;
if ( !menu.isEnabled ()
|| menu.getClientProperty ( "isSystemMenu" ) == Boolean.TRUE )
{
return;
}
menu.putClientProperty ( "rollover", Boolean.FALSE );
if ( menu.isTopLevelMenu () )
{
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 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;
}
// Currently not installed (1.3.6)
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 ();
}
}
/**
* 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 ( menuItem == null )
return;
if ( DEBUG )
{
System.out.println ( "in TinyMenuUI.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 ( menuItem == null )
return;
if ( DEBUG )
{
System.out.println ( "in TinyMenuUI.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 );
}
}
static class AltProcessor implements KeyEventPostProcessor
{
static boolean altKeyPressed = false;
static boolean menuCanceledOnPress = false;
static JRootPane root = null;
static Window winAncestor = null;
void altPressed ( KeyEvent ev )
{
MenuSelectionManager msm = MenuSelectionManager.defaultManager ();
MenuElement [] path = msm.getSelectedPath ();
if ( path.length > 0 && ! ( path [ 0 ] instanceof ComboPopup ) )
{
msm.clearSelectedPath ();
menuCanceledOnPress = true;
ev.consume ();
}
else if ( path.length > 0 )
{ // We are in ComboBox
menuCanceledOnPress = false;
ev.consume ();
}
else
{
menuCanceledOnPress = false;
JMenuBar mbar = root != null ? root.getJMenuBar () : null;
if ( mbar == null && winAncestor instanceof JFrame )
{
mbar = ( ( JFrame ) winAncestor ).getJMenuBar ();
}
JMenu menu = mbar != null ? mbar.getMenu ( 0 ) : null;
if ( menu != null )
{
ev.consume ();
}
}
}
void altReleased ( KeyEvent ev )
{
if ( menuCanceledOnPress )
{
return;
}
MenuSelectionManager msm = MenuSelectionManager.defaultManager ();
if ( msm.getSelectedPath ().length == 0 )
{
// if no menu is active, we try activating the menubar
JMenuBar mbar = root != null ? root.getJMenuBar () : null;
if ( mbar == null && winAncestor instanceof JFrame )
{
mbar = ( ( JFrame ) winAncestor ).getJMenuBar ();
}
JMenu menu = mbar != null ? mbar.getMenu ( 0 ) : null;
if ( menu != null )
{
MenuElement [] path = new MenuElement [ 2 ];
path [ 0 ] = mbar;
path [ 1 ] = menu;
msm.setSelectedPath ( path );
}
}
}
public boolean postProcessKeyEvent ( KeyEvent ev )
{
if ( ev.getKeyCode () == KeyEvent.VK_ALT )
{
root = SwingUtilities.getRootPane ( ev.getComponent () );
winAncestor = SwingUtilities.getWindowAncestor ( root );
if ( ev.getID () == KeyEvent.KEY_PRESSED )
{
if ( !altKeyPressed )
{
altPressed ( ev );
}
altKeyPressed = true;
return true;
}
else if ( ev.getID () == KeyEvent.KEY_RELEASED )
{
if ( altKeyPressed )
{
altReleased ( ev );
}
altKeyPressed = false;
}
}
else
{
altKeyPressed = false;
}
return false;
}
}
}