com.jidesoft.swing.JideMenu Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jide-oss Show documentation
Show all versions of jide-oss Show documentation
JIDE Common Layer (Professional Swing Components)
/*
* @(#)JideMenu.java
*
* Copyright 2002 - 2004 JIDE Software Inc. All rights reserved.
*/
package com.jidesoft.swing;
import com.jidesoft.plaf.UIDefaultsLookup;
import com.jidesoft.utils.SystemInfo;
import javax.swing.*;
import javax.swing.event.MenuEvent;
import javax.swing.event.MenuListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
* A special implementation of JMenu. It is used to replace JMenu in order to use with CommandBar.
It has two
* special features. First, it has a PopupMenuCustomizer for lazy menu creation. Instead of creating menu upfront which
* might be quite expensive, you can create it using PopupMenuCustomizer. PopupMenuCustomizer is called before the menu
* is set visible. Please note, when you use PopupMenuCustomizer, you need to remove the old menu items you added
* previously using PopupMenuCustomizer. Otherwise, you will see a menu which gets longer and longer when you show it.
* See below for an example.
*
* JideMenu jideMenu = new JideMenu("Dynamic");
* jideMenu.setPopupMenuCustomizer(new JideMenu.PopupMenuCustomizer(){
* public void customize(JPopupMenu menu) {
* menu.add("item 1");
* menu.add("item 2");
* menu.add("item 3");
* menu.add("item 4");
* menu.add("item 5");
* }
* });
*
*
* Second feature is popup alignment. Usually menu and its popup align to the left side. In our case, we hope they align
* to right side. So we added a method call setPreferredPopupHorizontalAlignment(). You can set to RIGHT if you want
* to.
*
*/
public class JideMenu extends JMenu implements Alignable {
private int _preferredPopupHorizontalAlignment = LEFT;
private int _preferredPopupVerticalAlignment = BOTTOM;
private MenuCreator _menuCreator;
private PopupMenuCustomizer _customizer;
private PopupMenuOriginCalculator _originCalculator;
public static int DELAY = 400;
private int _orientation;
public JideMenu() {
initMenu();
}
public JideMenu(String s) {
super(s);
initMenu();
}
public JideMenu(Action a) {
super(a);
initMenu();
}
public JideMenu(String s, boolean b) {
super(s, b);
initMenu();
}
protected void initMenu() {
// setDelay(DELAY);
addMenuListener(new MenuListener() {
public void menuSelected(MenuEvent e) {
MenuCreator menuCreator;
if ((menuCreator = getMenuCreator()) != null) {
menuCreator.createMenu();
if (getPopupMenu().getComponentCount() == 0) {
return;
}
}
PopupMenuCustomizer customizer;
if ((customizer = getPopupMenuCustomizer()) != null) {
customizer.customize(getPopupMenu());
}
}
public void menuDeselected(MenuEvent e) {
}
public void menuCanceled(MenuEvent e) {
}
});
}
/**
* Checks if the menu is added to a top level menu container. It will be consider as top level menu when
1.
* getParent() equals null, or
2. getParent() is not an instance of JPopupMenu
Please note, the definition
* of topLevelMenu is different from that of JMenu.
*
* @return true if it's top level menu.
*/
@Override
public boolean isTopLevelMenu() {
return getParent() == null || !(getParent() instanceof JPopupMenu);//TopLevelMenuContainer || getParent() instanceof JMenuBar;
}
/**
* @deprecated The createMenu method of MenuCreator should JPopupMenu as parameter. Since it's a public API we have
* to deprecated this one and ask users to use {@link PopupMenuCustomizer} instead.
*/
@Deprecated
public interface MenuCreator {
void createMenu();
}
/**
* Customizes the popup menu. This method will be called every time before popup menu is set visible.
*/
public interface PopupMenuCustomizer {
void customize(JPopupMenu menu);
}
/**
* Calculates the origin of the popup menu if specified.
*/
public interface PopupMenuOriginCalculator {
Point getPopupMenuOrigin(JideMenu menu);
}
/**
* Gets the PopupMenuOriginCalculator or null
, if none has been specified.
*
* @return the calculator
*/
public PopupMenuOriginCalculator getOriginCalculator() {
return _originCalculator;
}
/**
* Sets the PopupMenuOriginCalculator that will be used to determine the popup menu origin.
*
* @param originCalculator the calculator
*/
public void setOriginCalculator(PopupMenuOriginCalculator originCalculator) {
this._originCalculator = originCalculator;
}
/**
* Gets the MenuCreator.
*
* @return the MenuCreator.
*
* @deprecated use{@link PopupMenuCustomizer} and {@link #getPopupMenuCustomizer()} instead.
*/
@Deprecated
public MenuCreator getMenuCreator() {
return _menuCreator;
}
/**
* Sets the MenuCreator. MenuCreator can be used to do lazy menu creation. If you put code in the MenuCreator, it
* won't be called until before the menu is set visible.
*
* @param menuCreator he menu creator
* @deprecated use{@link PopupMenuCustomizer} and {@link #setPopupMenuCustomizer(com.jidesoft.swing.JideMenu.PopupMenuCustomizer)}
* instead.
*/
@Deprecated
public void setMenuCreator(MenuCreator menuCreator) {
_menuCreator = menuCreator;
}
/**
* Gets the PopupMenuCustomizer.
*
* @return the PopupMenuCustomizer.
*/
public PopupMenuCustomizer getPopupMenuCustomizer() {
return _customizer;
}
/**
* Sets the PopupMenuCustomizer. PopupMenuCustomizer can be used to do lazy menu creation. If you put code in the
* MenuCreator, it won't be called until before the menu is set visible.
*
* PopupMenuCustomizer has a customize method. The popup menu of this menu will be passed in. You can
* add/remove/change the menu items in customize method. For example, instead of
*
* JideMenu menu = new JideMenu();
* menu.add(new JMenuItem("..."));
* menu.add(new JMenuItem("..."));
*
* You can do
*
* JideMenu menu = new JideMenu();
* menu.setPopupMenuCustomzier(new JideMenu.PopupMenuCustomizer() {
* void customize(JPopupMenu popupMenu) {
* poupMenu.removeAll();
* popupMenu.add(new JMenuItem("..."));
* popupMenu.add(new JMenuItem("..."));
* }
* }
*
* If the menu is never used, the two add methods will never be called thus improve the performance.
*
* @param customizer the popup menu customizer
*/
public void setPopupMenuCustomizer(PopupMenuCustomizer customizer) {
_customizer = customizer;
}
@Override
protected Point getPopupMenuOrigin() {
if (_originCalculator != null) {
return _originCalculator.getPopupMenuOrigin(this);
}
int x;
int y;
JPopupMenu pm = getPopupMenu();
// Figure out the sizes needed to calculate the menu position
Dimension s = getSize();
Dimension pmSize = pm.getPreferredSize();
Point position = getLocationOnScreen();
Toolkit toolkit = Toolkit.getDefaultToolkit();
GraphicsConfiguration gc = getGraphicsConfiguration();
Rectangle screenBounds = new Rectangle(toolkit.getScreenSize());
GraphicsEnvironment ge =
GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice[] gds = ge.getScreenDevices();
for (GraphicsDevice gd : gds) {
if (gd.getType() == GraphicsDevice.TYPE_RASTER_SCREEN) {
GraphicsConfiguration dgc =
gd.getDefaultConfiguration();
if (dgc.getBounds().contains(position)) {
gc = dgc;
break;
}
}
}
if (gc != null) {
screenBounds = gc.getBounds();
// take screen insets (e.g. task bar) into account
Insets screenInsets = toolkit.getScreenInsets(gc);
screenBounds.width -=
Math.abs(screenInsets.left + screenInsets.right);
screenBounds.height -=
Math.abs(screenInsets.top + screenInsets.bottom);
position.x -= Math.abs(screenInsets.left);
position.y -= Math.abs(screenInsets.top);
}
Container parent = getParent();
if (parent instanceof JPopupMenu) {
// We are a sub-menu (pull-right)
int xOffset = UIDefaultsLookup.getInt("Menu.submenuPopupOffsetX");
int yOffset = UIDefaultsLookup.getInt("Menu.submenuPopupOffsetY");
if (this.getComponentOrientation().isLeftToRight()) {
if (JideSwingUtilities.getOrientationOf(this) == HORIZONTAL) {
// First determine x:
x = s.width + xOffset; // Prefer placement to the right
if (position.x + x + pmSize.width >= screenBounds.width
+ screenBounds.x &&
// popup doesn't fit - place it wherever there's more room
screenBounds.width - s.width < 2 * (position.x
- screenBounds.x)) {
x = 0 - xOffset - pmSize.width;
}
}
else {
// First determine x:
x = s.width + xOffset; // Prefer placement to the right
if (position.x + x + pmSize.width >= screenBounds.width + screenBounds.x &&
// popup doesn't fit - place it wherever there's more room
screenBounds.width - s.width < 2 * (position.x - screenBounds.x)) {
x = 0 - xOffset - pmSize.width;
}
}
}
else {
// First determine x:
x = 0 - xOffset - pmSize.width; // Prefer placement to the left
if (position.x + x < screenBounds.x &&
// popup doesn't fit - place it wherever there's more room
screenBounds.width - s.width > 2 * (position.x -
screenBounds.x)) {
x = s.width + xOffset;
}
}
// Then the y:
y = yOffset; // Prefer dropping down
if (position.y + y + pmSize.height >= screenBounds.height + screenBounds.y &&
// popup doesn't fit - place it wherever there's more room
screenBounds.height - s.height < 2 * (position.y - screenBounds.y)) {
y = s.height - yOffset - pmSize.height;
}
}
else {
// We are a top level menu (pull-down)
int xOffset = UIDefaultsLookup.getInt("Menu.menuPopupOffsetX");
int yOffset = UIDefaultsLookup.getInt("Menu.menuPopupOffsetY");
if (this.getComponentOrientation().isLeftToRight()) {
if (JideSwingUtilities.getOrientationOf(this) == HORIZONTAL) {
// First determine the x:
if (getPreferredPopupHorizontalAlignment() == LEFT) {
x = xOffset; // Extend to the right
if (position.x + x + pmSize.width >= screenBounds.width
+ screenBounds.x &&
// popup doesn't fit - place it wherever there's more room
screenBounds.width - s.width < 2 * (position.x
- screenBounds.x)) {
x = s.width - xOffset - pmSize.width;
}
}
else {
x = -pmSize.width + xOffset + s.width; // align right
if (position.x + x < screenBounds.x) {
x = screenBounds.x - position.x;
}
}
}
else {
// First determine the x:
x = 1 - xOffset - pmSize.width; // Extend to the left
if (position.x + x < screenBounds.x &&
// popup doesn't fit - place it wherever there's more room
screenBounds.width - s.width > 2 * (position.x
- screenBounds.x)) {
x = s.width + xOffset - 1;
}
}
}
else {
// First determine the x:
if (getPreferredPopupHorizontalAlignment() == LEFT) {
x = s.width - xOffset - pmSize.width; // Extend to the left
if (position.x + x < screenBounds.x &&
// popup doesn't fit - place it wherever there's more room
screenBounds.width - s.width > 2 * (position.x
- screenBounds.x)) {
x = xOffset;
}
}
else {
x = xOffset;
}
}
// Then the y:
if (JideSwingUtilities.getOrientationOf(this) == HORIZONTAL) {
y = s.height + yOffset - 1; // Prefer dropping down
if (getPreferredPopupVerticalAlignment() == TOP || // If forced to be on TOP
(position.y + y + pmSize.height >= screenBounds.height &&
// ...or popup doesn't fit - place it wherever there's more room
screenBounds.height - s.height < 2 * (position.y
- screenBounds.y))) {
y = 1 - yOffset - pmSize.height; // Otherwise drop 'up'
}
}
else {
y = -yOffset; // Prefer dropping up
if (position.y + y + pmSize.height >= screenBounds.height &&
// popup doesn't fit - place it wherever there's more room
screenBounds.height - s.height < 2 * (position.y
- screenBounds.y)) {
y = 0 - yOffset - pmSize.height; // Otherwise drop 'up'
}
}
}
return new Point(x, y);
}
/**
* Checks if the
*
* @return false if it's top level menu. Otherwise, it will return what super.isOpaque().
*/
@Override
public boolean isOpaque() {
return SystemInfo.isMacOSX() && isSelected() ? super.isOpaque() : !isTopLevelMenu() && super.isOpaque();
}
public boolean originalIsOpaque() {
return super.isOpaque();
}
protected void hideMenu() {
MenuSelectionManager msm = MenuSelectionManager.defaultManager();
msm.clearSelectedPath();
}
public int getPreferredPopupHorizontalAlignment() {
return _preferredPopupHorizontalAlignment;
}
public void setPreferredPopupHorizontalAlignment(int preferredPopupHorizontalAlignment) {
_preferredPopupHorizontalAlignment = preferredPopupHorizontalAlignment;
}
public int getPreferredPopupVerticalAlignment() {
return _preferredPopupVerticalAlignment;
}
public void setPreferredPopupVerticalAlignment(int preferredPopupVerticalAlignment) {
_preferredPopupVerticalAlignment = preferredPopupVerticalAlignment;
}
public boolean supportVerticalOrientation() {
return true;
}
public boolean supportHorizontalOrientation() {
return true;
}
public void setOrientation(int orientation) {
int old = _orientation;
if (old != orientation) {
_orientation = orientation;
firePropertyChange(PROPERTY_ORIENTATION, old, orientation);
}
}
public int getOrientation() {
return _orientation;
}
private static JideMenu _pendingMenu;
private static HideTimer _timer;
// use this flag to disable the hide timer as there are quite a few bugs on it that we don't know how to solve.
private static final boolean DISABLE_TIMER = true;
@Override
public void setPopupMenuVisible(boolean b) {
MenuCreator menuCreator;
if (b && (menuCreator = getMenuCreator()) != null) {
menuCreator.createMenu();
}
PopupMenuCustomizer customizer;
if (b && (customizer = getPopupMenuCustomizer()) != null) {
customizer.customize(getPopupMenu());
if (shouldHidePopupMenu()) {
return;
}
}
else if (b && shouldHidePopupMenu()) {
return;
}
if (!DISABLE_TIMER) {
if (isTopLevelMenu()) {
setPopupMenuVisibleImmediately(b);
}
else {
if (b) {
// System.out.println("show new menu");
stopTimer();
setPopupMenuVisibleImmediately(b);
}
else {
// HACK: check if the calling stack has clearSelectedPath method.
StackTraceElement[] stackTraceElements = new Throwable().getStackTrace();
for (StackTraceElement stackTraceElement : stackTraceElements) {
if (stackTraceElement.getMethodName().equals("clearSelectedPath")) {
setPopupMenuVisibleImmediately(b);
return;
}
}
startTimer();
}
}
}
else {
setPopupMenuVisibleImmediately(b);
}
}
/**
* Check if the popup menu should stay hidden although {@link #setPopupMenuVisible(boolean)} is invoked.
*
* The default implementation is to check if it contains any menu items. You could override this method to change the
* default behavior.
*
* @return true if the popup menu should stay invisible. Otherwise false.
*/
protected boolean shouldHidePopupMenu() {
return getPopupMenu().getComponentCount() == 0;
}
void setPopupMenuVisibleImmediately(boolean b) {
super.setPopupMenuVisible(b);
}
private class HideTimer extends Timer implements ActionListener {
private static final long serialVersionUID = 561631364532967870L;
public HideTimer() {
super(DELAY + 300, null);
addActionListener(this);
setRepeats(false);
}
public void actionPerformed(ActionEvent e) {
stopTimer();
}
}
private void startTimer() {
// System.out.println("timer started");
if (_timer != null) {
stopTimer();
}
_pendingMenu = this;
_timer = new HideTimer();
_timer.start();
}
private void stopTimer() {
if (_timer != null) {
// System.out.println("timer stopped");
if (_pendingMenu != null) {
// System.out.println("hidding pending menu");
_pendingMenu.setPopupMenuVisibleImmediately(false);
_pendingMenu = null;
}
_timer.stop();
_timer = null;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy