mirror of
https://github.com/autc04/Retro68.git
synced 2024-12-13 03:29:50 +00:00
1340 lines
40 KiB
Java
1340 lines
40 KiB
Java
/* BasicMenuItemUI.java --
|
|
Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc.
|
|
|
|
This file is part of GNU Classpath.
|
|
|
|
GNU Classpath is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2, or (at your option)
|
|
any later version.
|
|
|
|
GNU Classpath is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with GNU Classpath; see the file COPYING. If not, write to the
|
|
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
02110-1301 USA.
|
|
|
|
Linking this library statically or dynamically with other modules is
|
|
making a combined work based on this library. Thus, the terms and
|
|
conditions of the GNU General Public License cover the whole
|
|
combination.
|
|
|
|
As a special exception, the copyright holders of this library give you
|
|
permission to link this library with independent modules to produce an
|
|
executable, regardless of the license terms of these independent
|
|
modules, and to copy and distribute the resulting executable under
|
|
terms of your choice, provided that you also meet, for each linked
|
|
independent module, the terms and conditions of the license of that
|
|
module. An independent module is a module which is not derived from
|
|
or based on this library. If you modify this library, you may extend
|
|
this exception to your version of the library, but you are not
|
|
obligated to do so. If you do not wish to do so, delete this
|
|
exception statement from your version. */
|
|
|
|
|
|
package javax.swing.plaf.basic;
|
|
|
|
import gnu.classpath.SystemProperties;
|
|
|
|
import java.awt.Color;
|
|
import java.awt.Component;
|
|
import java.awt.Container;
|
|
import java.awt.Dimension;
|
|
import java.awt.Font;
|
|
import java.awt.FontMetrics;
|
|
import java.awt.Graphics;
|
|
import java.awt.Insets;
|
|
import java.awt.Rectangle;
|
|
import java.awt.event.ActionEvent;
|
|
import java.awt.event.ItemEvent;
|
|
import java.awt.event.ItemListener;
|
|
import java.awt.event.KeyEvent;
|
|
import java.awt.event.MouseEvent;
|
|
import java.awt.font.FontRenderContext;
|
|
import java.awt.font.TextLayout;
|
|
import java.awt.geom.AffineTransform;
|
|
import java.beans.PropertyChangeEvent;
|
|
import java.beans.PropertyChangeListener;
|
|
import java.util.ArrayList;
|
|
|
|
import javax.swing.AbstractAction;
|
|
import javax.swing.AbstractButton;
|
|
import javax.swing.ActionMap;
|
|
import javax.swing.ButtonModel;
|
|
import javax.swing.Icon;
|
|
import javax.swing.InputMap;
|
|
import javax.swing.JCheckBoxMenuItem;
|
|
import javax.swing.JComponent;
|
|
import javax.swing.JMenu;
|
|
import javax.swing.JMenuItem;
|
|
import javax.swing.JPopupMenu;
|
|
import javax.swing.KeyStroke;
|
|
import javax.swing.LookAndFeel;
|
|
import javax.swing.MenuElement;
|
|
import javax.swing.MenuSelectionManager;
|
|
import javax.swing.SwingConstants;
|
|
import javax.swing.SwingUtilities;
|
|
import javax.swing.UIDefaults;
|
|
import javax.swing.UIManager;
|
|
import javax.swing.event.MenuDragMouseEvent;
|
|
import javax.swing.event.MenuDragMouseListener;
|
|
import javax.swing.event.MenuKeyEvent;
|
|
import javax.swing.event.MenuKeyListener;
|
|
import javax.swing.event.MouseInputListener;
|
|
import javax.swing.plaf.ActionMapUIResource;
|
|
import javax.swing.plaf.ComponentInputMapUIResource;
|
|
import javax.swing.plaf.ComponentUI;
|
|
import javax.swing.plaf.MenuItemUI;
|
|
import javax.swing.text.View;
|
|
|
|
/**
|
|
* UI Delegate for JMenuItem.
|
|
*/
|
|
public class BasicMenuItemUI extends MenuItemUI
|
|
{
|
|
/**
|
|
* Font to be used when displaying menu item's accelerator.
|
|
*/
|
|
protected Font acceleratorFont;
|
|
|
|
/**
|
|
* Color to be used when displaying menu item's accelerator.
|
|
*/
|
|
protected Color acceleratorForeground;
|
|
|
|
/**
|
|
* Color to be used when displaying menu item's accelerator when menu item is
|
|
* selected.
|
|
*/
|
|
protected Color acceleratorSelectionForeground;
|
|
|
|
/**
|
|
* Icon that is displayed after the text to indicated that this menu contains
|
|
* submenu.
|
|
*/
|
|
protected Icon arrowIcon;
|
|
|
|
/**
|
|
* Icon that is displayed before the text. This icon is only used in
|
|
* JCheckBoxMenuItem or JRadioBoxMenuItem.
|
|
*/
|
|
protected Icon checkIcon;
|
|
|
|
/**
|
|
* Number of spaces between icon and text.
|
|
*/
|
|
protected int defaultTextIconGap = 4;
|
|
|
|
/**
|
|
* Color of the text when menu item is disabled
|
|
*/
|
|
protected Color disabledForeground;
|
|
|
|
/**
|
|
* The menu Drag mouse listener listening to the menu item.
|
|
*/
|
|
protected MenuDragMouseListener menuDragMouseListener;
|
|
|
|
/**
|
|
* The menu item itself
|
|
*/
|
|
protected JMenuItem menuItem;
|
|
|
|
/**
|
|
* Menu Key listener listening to the menu item.
|
|
*/
|
|
protected MenuKeyListener menuKeyListener;
|
|
|
|
/**
|
|
* mouse input listener listening to menu item.
|
|
*/
|
|
protected MouseInputListener mouseInputListener;
|
|
|
|
/**
|
|
* Indicates if border should be painted
|
|
*/
|
|
protected boolean oldBorderPainted;
|
|
|
|
/**
|
|
* Color of text that is used when menu item is selected
|
|
*/
|
|
protected Color selectionBackground;
|
|
|
|
/**
|
|
* Color of the text that is used when menu item is selected.
|
|
*/
|
|
protected Color selectionForeground;
|
|
|
|
/**
|
|
* String that separates description of the modifiers and the key
|
|
*/
|
|
private String acceleratorDelimiter;
|
|
|
|
/**
|
|
* ItemListener to listen for item changes in the menu item
|
|
*/
|
|
private ItemListener itemListener;
|
|
|
|
/**
|
|
* A PropertyChangeListener to make UI updates after property changes.
|
|
*/
|
|
private PropertyChangeHandler propertyChangeListener;
|
|
|
|
/**
|
|
* The view rectangle used for layout of the menu item.
|
|
*/
|
|
private Rectangle viewRect;
|
|
|
|
/**
|
|
* The rectangle that holds the area of the label.
|
|
*/
|
|
private Rectangle textRect;
|
|
|
|
/**
|
|
* The rectangle that holds the area of the accelerator.
|
|
*/
|
|
private Rectangle accelRect;
|
|
|
|
/**
|
|
* The rectangle that holds the area of the icon.
|
|
*/
|
|
private Rectangle iconRect;
|
|
|
|
/**
|
|
* The rectangle that holds the area of the icon.
|
|
*/
|
|
private Rectangle arrowIconRect;
|
|
|
|
/**
|
|
* The rectangle that holds the area of the check icon.
|
|
*/
|
|
private Rectangle checkIconRect;
|
|
|
|
/**
|
|
* A rectangle used for temporary storage to avoid creation of new
|
|
* rectangles.
|
|
*/
|
|
private Rectangle cachedRect;
|
|
|
|
/**
|
|
* A class to handle PropertChangeEvents for the JMenuItem
|
|
* @author Anthony Balkissoon abalkiss at redhat dot com.
|
|
*/
|
|
class PropertyChangeHandler implements PropertyChangeListener
|
|
{
|
|
/**
|
|
* This method is called when a property of the menuItem is changed.
|
|
* Currently it is only used to update the accelerator key bindings.
|
|
*
|
|
* @param e
|
|
* the PropertyChangeEvent
|
|
*/
|
|
public void propertyChange(PropertyChangeEvent e)
|
|
{
|
|
String property = e.getPropertyName();
|
|
if (property.equals("accelerator"))
|
|
{
|
|
InputMap map = SwingUtilities.getUIInputMap(menuItem,
|
|
JComponent.WHEN_IN_FOCUSED_WINDOW);
|
|
if (map != null)
|
|
map.remove((KeyStroke) e.getOldValue());
|
|
else
|
|
map = new ComponentInputMapUIResource(menuItem);
|
|
|
|
KeyStroke accelerator = (KeyStroke) e.getNewValue();
|
|
if (accelerator != null)
|
|
map.put(accelerator, "doClick");
|
|
}
|
|
// TextLayout caching for speed-up drawing of text.
|
|
else if ((property.equals(AbstractButton.TEXT_CHANGED_PROPERTY)
|
|
|| property.equals("font"))
|
|
&& SystemProperties.getProperty("gnu.javax.swing.noGraphics2D")
|
|
== null)
|
|
{
|
|
AbstractButton b = (AbstractButton) e.getSource();
|
|
String text = b.getText();
|
|
if (text == null)
|
|
text = "";
|
|
FontRenderContext frc = new FontRenderContext(new AffineTransform(),
|
|
false, false);
|
|
TextLayout layout = new TextLayout(text, b.getFont(), frc);
|
|
b.putClientProperty(BasicGraphicsUtils.CACHED_TEXT_LAYOUT, layout);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A class to handle accelerator keys. This is the Action we will
|
|
* perform when the accelerator key for this JMenuItem is pressed.
|
|
* @author Anthony Balkissoon abalkiss at redhat dot com
|
|
*
|
|
*/
|
|
class ClickAction extends AbstractAction
|
|
{
|
|
/**
|
|
* This is what is done when the accelerator key for the JMenuItem is
|
|
* pressed.
|
|
*/
|
|
public void actionPerformed(ActionEvent event)
|
|
{
|
|
doClick(MenuSelectionManager.defaultManager());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a new BasicMenuItemUI object.
|
|
*/
|
|
public BasicMenuItemUI()
|
|
{
|
|
mouseInputListener = createMouseInputListener(menuItem);
|
|
menuDragMouseListener = createMenuDragMouseListener(menuItem);
|
|
menuKeyListener = createMenuKeyListener(menuItem);
|
|
itemListener = new ItemHandler();
|
|
propertyChangeListener = new PropertyChangeHandler();
|
|
|
|
// Initialize rectangles for layout.
|
|
viewRect = new Rectangle();
|
|
textRect = new Rectangle();
|
|
iconRect = new Rectangle();
|
|
arrowIconRect = new Rectangle();
|
|
checkIconRect = new Rectangle();
|
|
accelRect = new Rectangle();
|
|
cachedRect = new Rectangle();
|
|
}
|
|
|
|
/**
|
|
* Create MenuDragMouseListener to listen for mouse dragged events.
|
|
*
|
|
* @param c
|
|
* menu item to listen to
|
|
* @return The MenuDragMouseListener
|
|
*/
|
|
protected MenuDragMouseListener createMenuDragMouseListener(JComponent c)
|
|
{
|
|
return new MenuDragMouseHandler();
|
|
}
|
|
|
|
/**
|
|
* Creates MenuKeyListener to listen to key events occuring when menu item is
|
|
* visible on the screen.
|
|
*
|
|
* @param c
|
|
* menu item to listen to
|
|
* @return The MenuKeyListener
|
|
*/
|
|
protected MenuKeyListener createMenuKeyListener(JComponent c)
|
|
{
|
|
return new MenuKeyHandler();
|
|
}
|
|
|
|
/**
|
|
* Handles mouse input events occuring for this menu item
|
|
*
|
|
* @param c
|
|
* menu item to listen to
|
|
* @return The MouseInputListener
|
|
*/
|
|
protected MouseInputListener createMouseInputListener(JComponent c)
|
|
{
|
|
return new MouseInputHandler();
|
|
}
|
|
|
|
/**
|
|
* Factory method to create a BasicMenuItemUI for the given {@link
|
|
* JComponent}, which should be a {@link JMenuItem}.
|
|
*
|
|
* @param c
|
|
* The {@link JComponent} a UI is being created for.
|
|
* @return A BasicMenuItemUI for the {@link JComponent}.
|
|
*/
|
|
public static ComponentUI createUI(JComponent c)
|
|
{
|
|
return new BasicMenuItemUI();
|
|
}
|
|
|
|
/**
|
|
* Programatically clicks menu item.
|
|
*
|
|
* @param msm
|
|
* MenuSelectionManager for the menu hierarchy
|
|
*/
|
|
protected void doClick(MenuSelectionManager msm)
|
|
{
|
|
menuItem.doClick(0);
|
|
msm.clearSelectedPath();
|
|
}
|
|
|
|
/**
|
|
* Returns maximum size for the specified menu item
|
|
*
|
|
* @param c
|
|
* component for which to get maximum size
|
|
* @return Maximum size for the specified menu item.
|
|
*/
|
|
public Dimension getMaximumSize(JComponent c)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Returns minimum size for the specified menu item
|
|
*
|
|
* @param c
|
|
* component for which to get minimum size
|
|
* @return Minimum size for the specified menu item.
|
|
*/
|
|
public Dimension getMinimumSize(JComponent c)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Returns path to this menu item.
|
|
*
|
|
* @return $MenuElement[]$ Returns array of menu elements that constitute a
|
|
* path to this menu item.
|
|
*/
|
|
public MenuElement[] getPath()
|
|
{
|
|
ArrayList path = new ArrayList();
|
|
|
|
Component c = menuItem;
|
|
while (c instanceof MenuElement)
|
|
{
|
|
path.add(0, c);
|
|
|
|
if (c instanceof JPopupMenu)
|
|
c = ((JPopupMenu) c).getInvoker();
|
|
else
|
|
c = c.getParent();
|
|
}
|
|
|
|
MenuElement[] pathArray = new MenuElement[path.size()];
|
|
path.toArray(pathArray);
|
|
return pathArray;
|
|
}
|
|
|
|
/**
|
|
* Returns preferred size for the given menu item.
|
|
*
|
|
* @param c
|
|
* menu item for which to get preferred size
|
|
* @param checkIcon
|
|
* check icon displayed in the given menu item
|
|
* @param arrowIcon
|
|
* arrow icon displayed in the given menu item
|
|
* @param defaultTextIconGap
|
|
* space between icon and text in the given menuItem
|
|
* @return $Dimension$ preferred size for the given menu item
|
|
*/
|
|
protected Dimension getPreferredMenuItemSize(JComponent c, Icon checkIcon,
|
|
Icon arrowIcon,
|
|
int defaultTextIconGap)
|
|
{
|
|
JMenuItem m = (JMenuItem) c;
|
|
String accelText = getAcceleratorString(m);
|
|
|
|
// Layout the menu item. The result gets stored in the rectangle
|
|
// fields of this class.
|
|
resetRectangles(null);
|
|
layoutMenuItem(m, accelText);
|
|
|
|
// The union of the text and icon areas is the label area.
|
|
cachedRect.setBounds(textRect);
|
|
Rectangle pref = SwingUtilities.computeUnion(iconRect.x, iconRect.y,
|
|
iconRect.width,
|
|
iconRect.height,
|
|
cachedRect);
|
|
|
|
// Find the widest menu item text and accelerator and store it in
|
|
// client properties of the parent, so that we can align the accelerators
|
|
// properly. Of course, we only need can do this, if the parent is
|
|
// a JComponent and this menu item is not a toplevel menu.
|
|
Container parent = m.getParent();
|
|
if (parent != null && parent instanceof JComponent
|
|
&& !(m instanceof JMenu && ((JMenu) m).isTopLevelMenu()))
|
|
{
|
|
JComponent p = (JComponent) parent;
|
|
|
|
// The widest text so far.
|
|
Integer maxTextWidth = (Integer) p.getClientProperty("maxTextWidth");
|
|
int maxTextValue = maxTextWidth == null ? 0 : maxTextWidth.intValue();
|
|
if (pref.width < maxTextValue)
|
|
pref.width = maxTextValue;
|
|
else
|
|
p.putClientProperty("maxTextWidth", new Integer(pref.width));
|
|
|
|
// The widest accelerator so far.
|
|
Integer maxAccelWidth = (Integer) p.getClientProperty("maxAccelWidth");
|
|
int maxAccelValue = maxAccelWidth == null ? 0
|
|
: maxAccelWidth.intValue();
|
|
if (accelRect.width > maxAccelValue)
|
|
{
|
|
maxAccelValue = accelRect.width;
|
|
p.putClientProperty("maxAccelWidth", new Integer(accelRect.width));
|
|
}
|
|
pref.width += maxAccelValue;
|
|
pref.width += defaultTextIconGap;
|
|
}
|
|
|
|
// Add arrow and check size if appropriate.
|
|
if (! (m instanceof JMenu && ((JMenu) m).isTopLevelMenu()))
|
|
{
|
|
pref.width += checkIconRect.width;
|
|
pref.width += defaultTextIconGap;
|
|
pref.width += arrowIconRect.width;
|
|
pref.width += defaultTextIconGap;
|
|
}
|
|
|
|
// Add a gap ~2 times as wide as the defaultTextIconGap.
|
|
pref.width += 2 * defaultTextIconGap;
|
|
|
|
// Respect the insets of the menu item.
|
|
Insets i = m.getInsets();
|
|
pref.width += i.left + i.right;
|
|
pref.height += i.top + i.bottom;
|
|
|
|
// Return a copy, so that nobody messes with our textRect.
|
|
return pref.getSize();
|
|
}
|
|
|
|
/**
|
|
* Returns preferred size of the given component
|
|
*
|
|
* @param c
|
|
* component for which to return preferred size
|
|
* @return $Dimension$ preferred size for the given component
|
|
*/
|
|
public Dimension getPreferredSize(JComponent c)
|
|
{
|
|
return getPreferredMenuItemSize(c, checkIcon, arrowIcon,
|
|
defaultTextIconGap);
|
|
}
|
|
|
|
/**
|
|
* Returns the prefix for entries in the {@link UIDefaults} table.
|
|
*
|
|
* @return "MenuItem"
|
|
*/
|
|
protected String getPropertyPrefix()
|
|
{
|
|
return "MenuItem";
|
|
}
|
|
|
|
/**
|
|
* This method installs the components for this {@link JMenuItem}.
|
|
*
|
|
* @param menuItem
|
|
* The {@link JMenuItem} to install components for.
|
|
*/
|
|
protected void installComponents(JMenuItem menuItem)
|
|
{
|
|
// FIXME: Need to implement
|
|
}
|
|
|
|
/**
|
|
* This method installs the defaults that are defined in the Basic look and
|
|
* feel for this {@link JMenuItem}.
|
|
*/
|
|
protected void installDefaults()
|
|
{
|
|
String prefix = getPropertyPrefix();
|
|
LookAndFeel.installBorder(menuItem, prefix + ".border");
|
|
LookAndFeel.installColorsAndFont(menuItem, prefix + ".background",
|
|
prefix + ".foreground", prefix + ".font");
|
|
menuItem.setMargin(UIManager.getInsets(prefix + ".margin"));
|
|
acceleratorFont = UIManager.getFont(prefix + ".acceleratorFont");
|
|
acceleratorForeground = UIManager.getColor(prefix
|
|
+ ".acceleratorForeground");
|
|
acceleratorSelectionForeground = UIManager.getColor(prefix
|
|
+ ".acceleratorSelectionForeground");
|
|
selectionBackground = UIManager.getColor(prefix + ".selectionBackground");
|
|
selectionForeground = UIManager.getColor(prefix + ".selectionForeground");
|
|
acceleratorDelimiter = UIManager.getString(prefix + ".acceleratorDelimiter");
|
|
checkIcon = UIManager.getIcon(prefix + ".checkIcon");
|
|
|
|
menuItem.setHorizontalTextPosition(SwingConstants.TRAILING);
|
|
menuItem.setHorizontalAlignment(SwingConstants.LEADING);
|
|
}
|
|
|
|
/**
|
|
* This method installs the keyboard actions for this {@link JMenuItem}.
|
|
*/
|
|
protected void installKeyboardActions()
|
|
{
|
|
InputMap focusedWindowMap = SwingUtilities.getUIInputMap(menuItem,
|
|
JComponent.WHEN_IN_FOCUSED_WINDOW);
|
|
if (focusedWindowMap == null)
|
|
focusedWindowMap = new ComponentInputMapUIResource(menuItem);
|
|
KeyStroke accelerator = menuItem.getAccelerator();
|
|
if (accelerator != null)
|
|
focusedWindowMap.put(accelerator, "doClick");
|
|
SwingUtilities.replaceUIInputMap(menuItem,
|
|
JComponent.WHEN_IN_FOCUSED_WINDOW, focusedWindowMap);
|
|
|
|
ActionMap UIActionMap = SwingUtilities.getUIActionMap(menuItem);
|
|
if (UIActionMap == null)
|
|
UIActionMap = new ActionMapUIResource();
|
|
UIActionMap.put("doClick", new ClickAction());
|
|
SwingUtilities.replaceUIActionMap(menuItem, UIActionMap);
|
|
}
|
|
|
|
/**
|
|
* This method installs the listeners for the {@link JMenuItem}.
|
|
*/
|
|
protected void installListeners()
|
|
{
|
|
menuItem.addMouseListener(mouseInputListener);
|
|
menuItem.addMouseMotionListener(mouseInputListener);
|
|
menuItem.addMenuDragMouseListener(menuDragMouseListener);
|
|
menuItem.addMenuKeyListener(menuKeyListener);
|
|
menuItem.addItemListener(itemListener);
|
|
menuItem.addPropertyChangeListener(propertyChangeListener);
|
|
// Fire synthetic property change event to let the listener update
|
|
// the TextLayout cache.
|
|
propertyChangeListener.propertyChange(new PropertyChangeEvent(menuItem,
|
|
"font", null,
|
|
menuItem.getFont()));
|
|
}
|
|
|
|
/**
|
|
* Installs and initializes all fields for this UI delegate. Any properties of
|
|
* the UI that need to be initialized and/or set to defaults will be done now.
|
|
* It will also install any listeners necessary.
|
|
*
|
|
* @param c
|
|
* The {@link JComponent} that is having this UI installed.
|
|
*/
|
|
public void installUI(JComponent c)
|
|
{
|
|
super.installUI(c);
|
|
menuItem = (JMenuItem) c;
|
|
installDefaults();
|
|
installComponents(menuItem);
|
|
installListeners();
|
|
installKeyboardActions();
|
|
}
|
|
|
|
/**
|
|
* Paints given menu item using specified graphics context
|
|
*
|
|
* @param g
|
|
* The graphics context used to paint this menu item
|
|
* @param c
|
|
* Menu Item to paint
|
|
*/
|
|
public void paint(Graphics g, JComponent c)
|
|
{
|
|
paintMenuItem(g, c, checkIcon, arrowIcon, selectionBackground,
|
|
c.getForeground(), defaultTextIconGap);
|
|
}
|
|
|
|
/**
|
|
* Paints background of the menu item
|
|
*
|
|
* @param g
|
|
* The graphics context used to paint this menu item
|
|
* @param menuItem
|
|
* menu item to paint
|
|
* @param bgColor
|
|
* Background color to use when painting menu item
|
|
*/
|
|
protected void paintBackground(Graphics g, JMenuItem menuItem, Color bgColor)
|
|
{
|
|
// Menu item is considered to be highlighted when it is selected.
|
|
// But we don't want to paint the background of JCheckBoxMenuItems
|
|
ButtonModel mod = menuItem.getModel();
|
|
Color saved = g.getColor();
|
|
if (mod.isArmed() || ((menuItem instanceof JMenu) && mod.isSelected()))
|
|
{
|
|
g.setColor(bgColor);
|
|
g.fillRect(0, 0, menuItem.getWidth(), menuItem.getHeight());
|
|
}
|
|
else if (menuItem.isOpaque())
|
|
{
|
|
g.setColor(menuItem.getBackground());
|
|
g.fillRect(0, 0, menuItem.getWidth(), menuItem.getHeight());
|
|
}
|
|
g.setColor(saved);
|
|
}
|
|
|
|
/**
|
|
* Paints specified menu item
|
|
*
|
|
* @param g
|
|
* The graphics context used to paint this menu item
|
|
* @param c
|
|
* menu item to paint
|
|
* @param checkIcon
|
|
* check icon to use when painting menu item
|
|
* @param arrowIcon
|
|
* arrow icon to use when painting menu item
|
|
* @param background
|
|
* Background color of the menu item
|
|
* @param foreground
|
|
* Foreground color of the menu item
|
|
* @param defaultTextIconGap
|
|
* space to use between icon and text when painting menu item
|
|
*/
|
|
protected void paintMenuItem(Graphics g, JComponent c, Icon checkIcon,
|
|
Icon arrowIcon, Color background,
|
|
Color foreground, int defaultTextIconGap)
|
|
{
|
|
JMenuItem m = (JMenuItem) c;
|
|
|
|
// Fetch fonts.
|
|
Font oldFont = g.getFont();
|
|
Font font = c.getFont();
|
|
g.setFont(font);
|
|
FontMetrics accelFm = m.getFontMetrics(acceleratorFont);
|
|
|
|
// Create accelerator string.
|
|
String accelText = getAcceleratorString(m);
|
|
|
|
// Layout menu item. The result gets stored in the rectangle fields
|
|
// of this class.
|
|
resetRectangles(m);
|
|
|
|
layoutMenuItem(m, accelText);
|
|
|
|
// Paint the background.
|
|
paintBackground(g, m, background);
|
|
|
|
Color oldColor = g.getColor();
|
|
|
|
// Paint the check icon.
|
|
if (checkIcon != null)
|
|
{
|
|
checkIcon.paintIcon(m, g, checkIconRect.x, checkIconRect.y);
|
|
}
|
|
|
|
// Paint the icon.
|
|
ButtonModel model = m.getModel();
|
|
if (m.getIcon() != null)
|
|
{
|
|
// Determine icon depending on the menu item
|
|
// state (normal/disabled/pressed).
|
|
Icon icon;
|
|
if (! m.isEnabled())
|
|
{
|
|
icon = m.getDisabledIcon();
|
|
}
|
|
else if (model.isPressed() && model.isArmed())
|
|
{
|
|
icon = m.getPressedIcon();
|
|
if (icon == null)
|
|
{
|
|
icon = m.getIcon();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
icon = m.getIcon();
|
|
}
|
|
|
|
if (icon != null)
|
|
{
|
|
icon.paintIcon(m, g, iconRect.x, iconRect.y);
|
|
}
|
|
}
|
|
|
|
// Paint the text.
|
|
String text = m.getText();
|
|
if (text != null)
|
|
{
|
|
// Handle HTML.
|
|
View html = (View) m.getClientProperty(BasicHTML.propertyKey);
|
|
if (html != null)
|
|
{
|
|
html.paint(g, textRect);
|
|
}
|
|
else
|
|
{
|
|
paintText(g, m, textRect, text);
|
|
}
|
|
}
|
|
|
|
// Paint accelerator text.
|
|
if (! accelText.equals(""))
|
|
{
|
|
// Align the accelerator text. In getPreferredMenuItemSize() we
|
|
// store a client property 'maxAccelWidth' in the parent which holds
|
|
// the maximum accelerator width for the children of this parent.
|
|
// We use this here to align the accelerators properly.
|
|
int accelOffset = 0;
|
|
Container parent = m.getParent();
|
|
if (parent != null && parent instanceof JComponent)
|
|
{
|
|
JComponent p = (JComponent) parent;
|
|
Integer maxAccelWidth =
|
|
(Integer) p.getClientProperty("maxAccelWidth");
|
|
int maxAccelValue = maxAccelWidth == null ? 0
|
|
: maxAccelWidth.intValue();
|
|
accelOffset = maxAccelValue - accelRect.width;
|
|
}
|
|
|
|
g.setFont(acceleratorFont);
|
|
if (! m.isEnabled())
|
|
{
|
|
// Paint accelerator disabled.
|
|
g.setColor(disabledForeground);
|
|
}
|
|
else
|
|
{
|
|
if (m.isArmed() || (m instanceof JMenu && m.isSelected()))
|
|
g.setColor(acceleratorSelectionForeground);
|
|
else
|
|
g.setColor(acceleratorForeground);
|
|
}
|
|
g.drawString(accelText, accelRect.x - accelOffset,
|
|
accelRect.y + accelFm.getAscent());
|
|
}
|
|
|
|
// Paint arrow.
|
|
if (arrowIcon != null
|
|
&& ! (m instanceof JMenu && ((JMenu) m).isTopLevelMenu()))
|
|
{
|
|
arrowIcon.paintIcon(m, g, arrowIconRect.x, arrowIconRect.y);
|
|
}
|
|
|
|
g.setFont(oldFont);
|
|
g.setColor(oldColor);
|
|
|
|
}
|
|
|
|
/**
|
|
* Paints label for the given menu item
|
|
*
|
|
* @param g
|
|
* The graphics context used to paint this menu item
|
|
* @param menuItem
|
|
* menu item for which to draw its label
|
|
* @param textRect
|
|
* rectangle specifiying position of the text relative to the given
|
|
* menu item
|
|
* @param text
|
|
* label of the menu item
|
|
*/
|
|
protected void paintText(Graphics g, JMenuItem menuItem, Rectangle textRect,
|
|
String text)
|
|
{
|
|
Font f = menuItem.getFont();
|
|
g.setFont(f);
|
|
FontMetrics fm = g.getFontMetrics(f);
|
|
|
|
if (text != null && !text.equals(""))
|
|
{
|
|
if (menuItem.isEnabled())
|
|
{
|
|
// Menu item is considered to be highlighted when it is selected.
|
|
// But not if it's a JCheckBoxMenuItem
|
|
ButtonModel mod = menuItem.getModel();
|
|
if ((menuItem.isSelected() && checkIcon == null)
|
|
|| (mod != null && mod.isArmed())
|
|
&& (menuItem.getParent() instanceof MenuElement))
|
|
g.setColor(selectionForeground);
|
|
else
|
|
g.setColor(menuItem.getForeground());
|
|
}
|
|
else
|
|
// FIXME: should fix this to use 'disabledForeground', but its
|
|
// default value in BasicLookAndFeel is null.
|
|
|
|
// FIXME: should there be different foreground colours for selected
|
|
// or deselected, when disabled?
|
|
g.setColor(Color.gray);
|
|
|
|
int mnemonicIndex = menuItem.getDisplayedMnemonicIndex();
|
|
|
|
if (mnemonicIndex != -1)
|
|
BasicGraphicsUtils.drawStringUnderlineCharAt(menuItem, g, text,
|
|
mnemonicIndex,
|
|
textRect.x,
|
|
textRect.y
|
|
+ fm.getAscent());
|
|
else
|
|
BasicGraphicsUtils.drawString(menuItem, g, text, 0, textRect.x,
|
|
textRect.y + fm.getAscent());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This method uninstalls the components for this {@link JMenuItem}.
|
|
*
|
|
* @param menuItem
|
|
* The {@link JMenuItem} to uninstall components for.
|
|
*/
|
|
protected void uninstallComponents(JMenuItem menuItem)
|
|
{
|
|
// FIXME: need to implement
|
|
}
|
|
|
|
/**
|
|
* This method uninstalls the defaults and sets any objects created during
|
|
* install to null
|
|
*/
|
|
protected void uninstallDefaults()
|
|
{
|
|
menuItem.setForeground(null);
|
|
menuItem.setBackground(null);
|
|
menuItem.setBorder(null);
|
|
menuItem.setMargin(null);
|
|
menuItem.setBackground(null);
|
|
menuItem.setBorder(null);
|
|
menuItem.setFont(null);
|
|
menuItem.setForeground(null);
|
|
menuItem.setMargin(null);
|
|
acceleratorFont = null;
|
|
acceleratorForeground = null;
|
|
acceleratorSelectionForeground = null;
|
|
arrowIcon = null;
|
|
selectionBackground = null;
|
|
selectionForeground = null;
|
|
acceleratorDelimiter = null;
|
|
}
|
|
|
|
/**
|
|
* Uninstalls any keyboard actions.
|
|
*/
|
|
protected void uninstallKeyboardActions()
|
|
{
|
|
SwingUtilities.replaceUIInputMap(menuItem,
|
|
JComponent.WHEN_IN_FOCUSED_WINDOW, null);
|
|
}
|
|
|
|
/**
|
|
* Unregisters all the listeners that this UI delegate was using.
|
|
*/
|
|
protected void uninstallListeners()
|
|
{
|
|
menuItem.removeMouseListener(mouseInputListener);
|
|
menuItem.removeMenuDragMouseListener(menuDragMouseListener);
|
|
menuItem.removeMenuKeyListener(menuKeyListener);
|
|
menuItem.removeItemListener(itemListener);
|
|
menuItem.removePropertyChangeListener(propertyChangeListener);
|
|
}
|
|
|
|
/**
|
|
* Performs the opposite of installUI. Any properties or resources that need
|
|
* to be cleaned up will be done now. It will also uninstall any listeners it
|
|
* has. In addition, any properties of this UI will be nulled.
|
|
*
|
|
* @param c
|
|
* The {@link JComponent} that is having this UI uninstalled.
|
|
*/
|
|
public void uninstallUI(JComponent c)
|
|
{
|
|
uninstallListeners();
|
|
uninstallDefaults();
|
|
uninstallComponents(menuItem);
|
|
c.putClientProperty(BasicGraphicsUtils.CACHED_TEXT_LAYOUT, null);
|
|
menuItem = null;
|
|
}
|
|
|
|
/**
|
|
* This method calls paint.
|
|
*
|
|
* @param g
|
|
* The graphics context used to paint this menu item
|
|
* @param c
|
|
* The menu item to paint
|
|
*/
|
|
public void update(Graphics g, JComponent c)
|
|
{
|
|
paint(g, c);
|
|
}
|
|
|
|
/**
|
|
* This class handles mouse events occuring inside the menu item. Most of the
|
|
* events are forwarded for processing to MenuSelectionManager of the current
|
|
* menu hierarchy.
|
|
*/
|
|
protected class MouseInputHandler implements MouseInputListener
|
|
{
|
|
/**
|
|
* Creates a new MouseInputHandler object.
|
|
*/
|
|
protected MouseInputHandler()
|
|
{
|
|
// Nothing to do here.
|
|
}
|
|
|
|
/**
|
|
* This method is called when mouse is clicked on the menu item. It forwards
|
|
* this event to MenuSelectionManager.
|
|
*
|
|
* @param e
|
|
* A {@link MouseEvent}.
|
|
*/
|
|
public void mouseClicked(MouseEvent e)
|
|
{
|
|
MenuSelectionManager manager = MenuSelectionManager.defaultManager();
|
|
manager.processMouseEvent(e);
|
|
}
|
|
|
|
/**
|
|
* This method is called when mouse is dragged inside the menu item. It
|
|
* forwards this event to MenuSelectionManager.
|
|
*
|
|
* @param e
|
|
* A {@link MouseEvent}.
|
|
*/
|
|
public void mouseDragged(MouseEvent e)
|
|
{
|
|
MenuSelectionManager manager = MenuSelectionManager.defaultManager();
|
|
manager.processMouseEvent(e);
|
|
}
|
|
|
|
/**
|
|
* This method is called when mouse enters menu item. When this happens menu
|
|
* item is considered to be selected and selection path in
|
|
* MenuSelectionManager is set. This event is also forwarded to
|
|
* MenuSelection Manager for further processing.
|
|
*
|
|
* @param e
|
|
* A {@link MouseEvent}.
|
|
*/
|
|
public void mouseEntered(MouseEvent e)
|
|
{
|
|
Component source = (Component) e.getSource();
|
|
if (source.getParent() instanceof MenuElement)
|
|
{
|
|
MenuSelectionManager manager = MenuSelectionManager.defaultManager();
|
|
manager.setSelectedPath(getPath());
|
|
manager.processMouseEvent(e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This method is called when mouse exits menu item. The event is forwarded
|
|
* to MenuSelectionManager for processing.
|
|
*
|
|
* @param e
|
|
* A {@link MouseEvent}.
|
|
*/
|
|
public void mouseExited(MouseEvent e)
|
|
{
|
|
MenuSelectionManager manager = MenuSelectionManager.defaultManager();
|
|
manager.processMouseEvent(e);
|
|
}
|
|
|
|
/**
|
|
* This method is called when mouse is inside the menu item. This event is
|
|
* forwarder to MenuSelectionManager for further processing.
|
|
*
|
|
* @param e
|
|
* A {@link MouseEvent}.
|
|
*/
|
|
public void mouseMoved(MouseEvent e)
|
|
{
|
|
MenuSelectionManager manager = MenuSelectionManager.defaultManager();
|
|
manager.processMouseEvent(e);
|
|
}
|
|
|
|
/**
|
|
* This method is called when mouse is pressed. This event is forwarded to
|
|
* MenuSelectionManager for further processing.
|
|
*
|
|
* @param e
|
|
* A {@link MouseEvent}.
|
|
*/
|
|
public void mousePressed(MouseEvent e)
|
|
{
|
|
MenuSelectionManager manager = MenuSelectionManager.defaultManager();
|
|
manager.processMouseEvent(e);
|
|
}
|
|
|
|
/**
|
|
* This method is called when mouse is released. If the mouse is released
|
|
* inside this menuItem, then this menu item is considered to be chosen and
|
|
* the menu hierarchy should be closed.
|
|
*
|
|
* @param e
|
|
* A {@link MouseEvent}.
|
|
*/
|
|
public void mouseReleased(MouseEvent e)
|
|
{
|
|
MenuSelectionManager manager = MenuSelectionManager.defaultManager();
|
|
int x = e.getX();
|
|
int y = e.getY();
|
|
if (x > 0 && x < menuItem.getWidth() && y > 0
|
|
&& y < menuItem.getHeight())
|
|
{
|
|
doClick(manager);
|
|
}
|
|
else
|
|
manager.processMouseEvent(e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This class handles mouse dragged events.
|
|
*/
|
|
private class MenuDragMouseHandler implements MenuDragMouseListener
|
|
{
|
|
/**
|
|
* Tbis method is invoked when mouse is dragged over the menu item.
|
|
*
|
|
* @param e
|
|
* The MenuDragMouseEvent
|
|
*/
|
|
public void menuDragMouseDragged(MenuDragMouseEvent e)
|
|
{
|
|
MenuSelectionManager manager = e.getMenuSelectionManager();
|
|
manager.setSelectedPath(e.getPath());
|
|
}
|
|
|
|
/**
|
|
* Tbis method is invoked when mouse enters the menu item while it is being
|
|
* dragged.
|
|
*
|
|
* @param e
|
|
* The MenuDragMouseEvent
|
|
*/
|
|
public void menuDragMouseEntered(MenuDragMouseEvent e)
|
|
{
|
|
MenuSelectionManager manager = e.getMenuSelectionManager();
|
|
manager.setSelectedPath(e.getPath());
|
|
}
|
|
|
|
/**
|
|
* Tbis method is invoked when mouse exits the menu item while it is being
|
|
* dragged
|
|
*
|
|
* @param e the MenuDragMouseEvent
|
|
*/
|
|
public void menuDragMouseExited(MenuDragMouseEvent e)
|
|
{
|
|
// Nothing to do here yet.
|
|
}
|
|
|
|
/**
|
|
* Tbis method is invoked when mouse was dragged and released inside the
|
|
* menu item.
|
|
*
|
|
* @param e
|
|
* The MenuDragMouseEvent
|
|
*/
|
|
public void menuDragMouseReleased(MenuDragMouseEvent e)
|
|
{
|
|
MenuSelectionManager manager = e.getMenuSelectionManager();
|
|
int x = e.getX();
|
|
int y = e.getY();
|
|
if (x >= 0 && x < menuItem.getWidth() && y >= 0
|
|
&& y < menuItem.getHeight())
|
|
doClick(manager);
|
|
else
|
|
manager.clearSelectedPath();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This class handles key events occuring when menu item is visible on the
|
|
* screen.
|
|
*/
|
|
private class MenuKeyHandler implements MenuKeyListener
|
|
{
|
|
/**
|
|
* This method is invoked when key has been pressed
|
|
*
|
|
* @param e
|
|
* A {@link MenuKeyEvent}.
|
|
*/
|
|
public void menuKeyPressed(MenuKeyEvent e)
|
|
{
|
|
// TODO: What should be done here, if anything?
|
|
}
|
|
|
|
/**
|
|
* This method is invoked when key has been pressed
|
|
*
|
|
* @param e
|
|
* A {@link MenuKeyEvent}.
|
|
*/
|
|
public void menuKeyReleased(MenuKeyEvent e)
|
|
{
|
|
// TODO: What should be done here, if anything?
|
|
}
|
|
|
|
/**
|
|
* This method is invoked when key has been typed It handles the mnemonic
|
|
* key for the menu item.
|
|
*
|
|
* @param e
|
|
* A {@link MenuKeyEvent}.
|
|
*/
|
|
public void menuKeyTyped(MenuKeyEvent e)
|
|
{
|
|
// TODO: What should be done here, if anything?
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper class that listens for item changes to the properties of the {@link
|
|
* JMenuItem}.
|
|
*/
|
|
private class ItemHandler implements ItemListener
|
|
{
|
|
/**
|
|
* This method is called when one of the menu item changes.
|
|
*
|
|
* @param evt A {@link ItemEvent}.
|
|
*/
|
|
public void itemStateChanged(ItemEvent evt)
|
|
{
|
|
boolean state = false;
|
|
if (menuItem instanceof JCheckBoxMenuItem)
|
|
{
|
|
if (evt.getStateChange() == ItemEvent.SELECTED)
|
|
state = true;
|
|
((JCheckBoxMenuItem) menuItem).setState(state);
|
|
}
|
|
menuItem.revalidate();
|
|
menuItem.repaint();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A helper method to create the accelerator string from the menu item's
|
|
* accelerator property. The returned string is empty if there is
|
|
* no accelerator defined.
|
|
*
|
|
* @param m the menu item
|
|
*
|
|
* @return the accelerator string, not null
|
|
*/
|
|
private String getAcceleratorString(JMenuItem m)
|
|
{
|
|
// Create accelerator string.
|
|
KeyStroke accel = m.getAccelerator();
|
|
String accelText = "";
|
|
if (accel != null)
|
|
{
|
|
int mods = accel.getModifiers();
|
|
if (mods > 0)
|
|
{
|
|
accelText = KeyEvent.getKeyModifiersText(mods);
|
|
accelText += acceleratorDelimiter;
|
|
}
|
|
int keycode = accel.getKeyCode();
|
|
if (keycode != 0)
|
|
accelText += KeyEvent.getKeyText(keycode);
|
|
else
|
|
accelText += accel.getKeyChar();
|
|
}
|
|
return accelText;
|
|
}
|
|
|
|
/**
|
|
* Resets the cached layout rectangles. If <code>i</code> is not null, then
|
|
* the view rectangle is set to the inner area of the component, otherwise
|
|
* it is set to (0, 0, Short.MAX_VALUE, Short.MAX_VALUE), this is needed
|
|
* for layouting.
|
|
*
|
|
* @param i the component for which to initialize the rectangles
|
|
*/
|
|
private void resetRectangles(JMenuItem i)
|
|
{
|
|
// Reset rectangles.
|
|
iconRect.setBounds(0, 0, 0, 0);
|
|
textRect.setBounds(0, 0, 0, 0);
|
|
accelRect.setBounds(0, 0, 0, 0);
|
|
checkIconRect.setBounds(0, 0, 0, 0);
|
|
arrowIconRect.setBounds(0, 0, 0, 0);
|
|
if (i == null)
|
|
viewRect.setBounds(0, 0, Short.MAX_VALUE, Short.MAX_VALUE);
|
|
else
|
|
{
|
|
Insets insets = i.getInsets();
|
|
viewRect.setBounds(insets.left, insets.top,
|
|
i.getWidth() - insets.left - insets.right,
|
|
i.getHeight() - insets.top - insets.bottom);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A helper method that lays out the menu item. The layout is stored
|
|
* in the fields of this class.
|
|
*
|
|
* @param m the menu item to layout
|
|
* @param accelText the accelerator text
|
|
*/
|
|
private void layoutMenuItem(JMenuItem m, String accelText)
|
|
{
|
|
// Fetch the fonts.
|
|
Font font = m.getFont();
|
|
FontMetrics fm = m.getFontMetrics(font);
|
|
FontMetrics accelFm = m.getFontMetrics(acceleratorFont);
|
|
|
|
String text = m.getText();
|
|
SwingUtilities.layoutCompoundLabel(m, fm, text, m.getIcon(),
|
|
m.getVerticalAlignment(),
|
|
m.getHorizontalAlignment(),
|
|
m.getVerticalTextPosition(),
|
|
m.getHorizontalTextPosition(),
|
|
viewRect, iconRect, textRect,
|
|
defaultTextIconGap);
|
|
|
|
// Initialize accelerator width and height.
|
|
if (! accelText.equals(""))
|
|
{
|
|
accelRect.width = accelFm.stringWidth(accelText);
|
|
accelRect.height = accelFm.getHeight();
|
|
}
|
|
|
|
// Initialize check and arrow icon width and height.
|
|
if (! (m instanceof JMenu && ((JMenu) m).isTopLevelMenu()))
|
|
{
|
|
if (checkIcon != null)
|
|
{
|
|
checkIconRect.width = checkIcon.getIconWidth();
|
|
checkIconRect.height = checkIcon.getIconHeight();
|
|
}
|
|
if (arrowIcon != null)
|
|
{
|
|
arrowIconRect.width = arrowIcon.getIconWidth();
|
|
arrowIconRect.height = arrowIcon.getIconHeight();
|
|
}
|
|
}
|
|
|
|
// The union of the icon and text of the menu item is the 'label area'.
|
|
cachedRect.setBounds(textRect);
|
|
Rectangle labelRect = SwingUtilities.computeUnion(iconRect.x,
|
|
iconRect.y,
|
|
iconRect.width,
|
|
iconRect.height,
|
|
cachedRect);
|
|
textRect.x += defaultTextIconGap;
|
|
iconRect.x += defaultTextIconGap;
|
|
|
|
// Layout accelerator rect.
|
|
accelRect.x = viewRect.x + viewRect.width - arrowIconRect.width
|
|
- defaultTextIconGap - accelRect.width;
|
|
// Layout check and arrow icons only when not in toplevel menu.
|
|
if (! (m instanceof JMenu && ((JMenu) m).isTopLevelMenu()))
|
|
{
|
|
checkIconRect.x = viewRect.x + defaultTextIconGap;
|
|
textRect.x += defaultTextIconGap + checkIconRect.width;
|
|
iconRect.x += defaultTextIconGap + checkIconRect.width;
|
|
arrowIconRect.x = viewRect.x + viewRect.width - defaultTextIconGap
|
|
- arrowIconRect.width;
|
|
}
|
|
|
|
// Align the accelerator text and all the icons vertically centered to
|
|
// the menu text.
|
|
accelRect.y = labelRect.y + (labelRect.height / 2)
|
|
- (accelRect.height / 2);
|
|
if (! (m instanceof JMenu && ((JMenu) m).isTopLevelMenu()))
|
|
{
|
|
arrowIconRect.y = labelRect.y + (labelRect.height / 2)
|
|
- (arrowIconRect.height / 2);
|
|
checkIconRect.y = labelRect.y + (labelRect.height / 2)
|
|
- (checkIconRect.height / 2);
|
|
}
|
|
}
|
|
}
|