/* * This file is part of DRBD Management Console by LINBIT HA-Solutions GmbH * written by Rasto Levrinc. * * Copyright (C) 2009, LINBIT HA-Solutions GmbH. * Copyright (C) 2011-2012, Rastislav Levrinc. * * DRBD Management Console 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. * * DRBD Management Console 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 drbd; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ package lcmc.common.ui.utils; import java.awt.AWTException; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.MouseInfo; import java.awt.Point; import java.awt.PointerInfo; import java.awt.Rectangle; import java.awt.Robot; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.geom.Point2D; import javax.inject.Inject; import javax.inject.Named; import javax.swing.ImageIcon; import javax.swing.JMenuItem; import javax.swing.JToolTip; import lcmc.common.domain.AccessMode; import lcmc.common.domain.Application; import lcmc.common.domain.EnablePredicate; import lcmc.common.ui.Access; import lcmc.logger.Logger; import lcmc.logger.LoggerFactory; import lcmc.common.domain.Predicate; import lcmc.common.domain.util.Tools; import lcmc.common.domain.VisiblePredicate; /** * A menu item that can have an alternate text depending on the predicate() * method and be enabled/disabled depending on the enablePredicate() method. */ @Named public class MyMenuItem extends JMenuItem implements ActionListener, UpdatableItem, ComponentWithTest { private static final Logger LOG = LoggerFactory.getLogger(MyMenuItem.class); private static final long serialVersionUID = 1L; private static final GraphicsDevice SCREEN_DEVICE = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); private String text1; private ImageIcon icon1; /** Short decription of the item for tool tip. */ private String shortDesc1; private String text2; private ImageIcon icon2; /** Alternate short decription of the item for tool tip. */ private String shortDesc2; private JToolTip toolTip = null; /** Pos of the click that can be used in the overriden action method. */ private Point2D pos; /** Robot to move a mouse a little if a tooltip has changed. */ private Robot robot; private Color toolTipBackground = null; private AccessMode enableAccessMode; private AccessMode visibleAccessMode; private String origToolTipText = ""; @Inject private Application application; @Inject private SwingUtils swingUtils; @Inject private Access access; private MenuAction menuAction; private Predicate predicate = new Predicate() { @Override public boolean check() { return true; } }; private EnablePredicate enablePredicate = new EnablePredicate() { @Override public String check() { return null; } }; private VisiblePredicate visiblePredicate = new VisiblePredicate() { @Override public boolean check() { return true; } }; private Runnable update = new Runnable() { @Override public void run() { } }; protected void init(final String text, final ImageIcon icon, final AccessMode enableAccessMode, final AccessMode visibleAccessMode) { super.setText(text); text1 = text; icon1 = icon; this.enableAccessMode = enableAccessMode; this.visibleAccessMode = visibleAccessMode; toolTip = createToolTip(); toolTip.setTipText(text); setNormalFont(); addActionListener(this); this.robot = createRobot(); processAccessMode(); setIconAndTooltip(); swingUtils.isSwingThread(); } protected void init(final String text, final ImageIcon icon, final String shortDesc, final AccessMode enableAccessMode, final AccessMode visibleAccessMode) { super.setText(text); if (shortDesc != null && !shortDesc.isEmpty()) { toolTip = createToolTip(); toolTip.setTipText(shortDesc); } setNormalFont(); text1 = text; icon1 = icon; if (shortDesc == null) { shortDesc1 = ""; } else { shortDesc1 = shortDesc; } this.enableAccessMode = enableAccessMode; this.visibleAccessMode = visibleAccessMode; addActionListener(this); robot = createRobot(); processAccessMode(); setIconAndTooltip(); swingUtils.isSwingThread(); } private Robot createRobot() { try { return new Robot(SCREEN_DEVICE); } catch (final AWTException e) { LOG.appError("MyMenuItem: robot error"); } return null; } /** * Prepares a new {@code MyMenuItem} object. The alternate item is * selected if predicate() returns false. * * @param text1a * text of the item * @param icon1a * icon of the item * @param shortDesc1a * short description for the tool tip of the item * @param text2 * text of the alternate item * @param icon2 * icon of the alternate item * @param shortDesc2 * short description for the tool tip of the alternate item */ protected void init(final String text1a, final ImageIcon icon1a, final String shortDesc1a, final String text2, final ImageIcon icon2, final String shortDesc2, final AccessMode enableAccessMode, final AccessMode visibleAccessMode) { init(text1a, icon1a, shortDesc1a, enableAccessMode, visibleAccessMode); this.text2 = text2; this.icon2 = icon2; if (shortDesc2 == null) { this.shortDesc2 = ""; } else { this.shortDesc2 = shortDesc2; } processAccessMode(); setIconAndTooltip(); } /** * Sets the pos of the click that can be used in the overriden action * method. */ @Override public final void setPos(final Point2D pos) { this.pos = pos; } /** Returns the saved position. */ public final Point2D getPos() { return pos; } /** Sets normal font for this menu item. */ private void setNormalFont() { final Font font = getFont(); final String name = font.getFontName(); final int style = Font.PLAIN; final int size = font.getSize(); swingUtils.invokeLater(new Runnable() { @Override public void run() { setFont(new Font(name, style, size)); } }); } /** Sets special font for this menu item. */ public final void setSpecialFont() { final Font font = getFont(); final String name = font.getFontName(); final int style = Font.ITALIC; final int size = font.getSize(); swingUtils.invokeLater(new Runnable() { @Override public void run() { setFont(new Font(name, style, size)); } }); } // /** // * This method can be overriden to define an action that should be taken // * after the item is selected. // */ // public abstract void action(); // /** Returns false if the alternate menu item text etc. should be shown. */ // public boolean predicate() { // return true; // } // /** // * Returns whether the item should be enabled or not. // * null if it should be enabled or some string that can be used as // * tooltip if it should be disabled. // */ // public String enablePredicate() { // return null; // } // /** Returns whether the item should be visible or not. */ // public boolean visiblePredicate() { // return true; // } /** Updates the menu item, checking the predicate and enablePredicate. */ @Override public void updateAndWait() { update.run(); processAccessMode(); setIconAndTooltip(); } private void setIconAndTooltip() { if (predicate.check()) { setText(text1); if (icon1 != null) { setIcon(icon1); } if (toolTip != null && shortDesc1 != null && !shortDesc1.equals(text1)) { origToolTipText = shortDesc1; toolTip.setTipText(shortDesc1); } } else { setText(text2); if (icon1 != null) { /* icon1 is here on purpose */ setIcon(icon2); } if (toolTip != null && shortDesc2 != null && !shortDesc2.equals(text2)) { origToolTipText = shortDesc2; toolTip.setTipText(shortDesc2); } } } /** Sets this item enabled and visible according to its access type. */ private void processAccessMode() { final boolean accessible = access.isAccessible(enableAccessMode); final String disableTooltip = enablePredicate.check(); final boolean visible = visiblePredicate.check(); setEnabled(disableTooltip == null && accessible); setVisible(visible && access.isAccessible(visibleAccessMode)); if (toolTip != null && isVisible()) { if (!accessible && enableAccessMode.getType() != AccessMode.NEVER) { String advanced = ""; if (enableAccessMode.isAdvancedMode()) { advanced = "Advanced "; } setToolTipText0("<html><b>" + getText() + " (disabled)</b><br>available in \"" + advanced + AccessMode.OP_MODES_MAP.get(enableAccessMode.getType()) + "\" mode</html>"); } else if (disableTooltip != null) { setToolTipText0("<html><b>" + getText() + " (disabled)</b><br>" + disableTooltip + "</html>"); } else if (origToolTipText != null) { setToolTipText0(origToolTipText); } } } /** * When an item was selected this calls an action method that can be * overridden. */ @Override public void actionPerformed(final ActionEvent e) { LOG.debug1("actionPerformed: ACTION: " + e.getSource()); actionThread(); } public void actionThread() { final Thread thread = new Thread( new Runnable() { @Override public void run() { menuAction.run(getText()); } } ); thread.start(); } /** Returns the text of the menu item. */ @Override public final String toString() { return getText(); } /** Creates tooltip. */ @Override public final JToolTip createToolTip() { if (toolTip != null) { toolTip.setComponent(null); } toolTip = super.createToolTip(); if (toolTipBackground != null) { toolTip.setBackground(toolTipBackground); } return toolTip; } /** Sets tooltip's background color. */ @Override public final void setToolTipBackground(final Color toolTipBackground) { this.toolTipBackground = toolTipBackground; } /** Returns location of the tooltip, so that it does not cover the menu * item. */ @Override public Point getToolTipLocation(final MouseEvent event) { final Point screenLocation = getLocationOnScreen(); final Rectangle sBounds = Tools.getScreenBounds(this); final Dimension size = toolTip.getPreferredSize(); if (screenLocation.x + size.width + event.getX() + 5 > sBounds.width) { return new Point(event.getX() - size.width - 5, event.getY() + 20); } return new Point(event.getX() + 5, /* to not cover the pointer. */ event.getY() + 20); } /** Sets tooltip and wiggles the mouse to refresh it. */ @Override public final void setToolTipText(final String text) { if (toolTip == null || text == null) { return; } origToolTipText = text; setToolTipText0(text); } /** Wiggle the mouse. */ private void moveMouse() { if (robot != null) { final GraphicsDevice[] devices = GraphicsEnvironment.getLocalGraphicsEnvironment() .getScreenDevices(); int xOffset = 0; if (devices.length >= 2) { /* workaround for dual monitors that are flipped. */ //TODO: not sure how is it with three monitors final int x1 = devices[0].getDefaultConfiguration().getBounds().x; final int x2 = devices[1].getDefaultConfiguration().getBounds().x; if (x1 > x2) { xOffset = -x1; } } final PointerInfo pi = MouseInfo.getPointerInfo(); if (pi != null) { final Point2D p = pi.getLocation(); robot.mouseMove((int) p.getX() + xOffset - 1, (int) p.getY()); robot.mouseMove((int) p.getX() + xOffset + 1, (int) p.getY()); robot.mouseMove((int) p.getX() + xOffset, (int) p.getY()); } } } /** Sets tooltip and wiggles the mouse to refresh it. */ private void setToolTipText0(String toolTipText) { if (toolTip == null) { return; } if (toolTipText != null && toolTipText.isEmpty()) { toolTipText = text1; } toolTip.setTipText(toolTipText); super.setToolTipText(toolTipText); if (toolTip != null && robot != null && toolTip.isShowing()) { final Thread t = new Thread(new Runnable() { @Override public void run() { Tools.sleep(1000); /* well, doesn't work all the time */ moveMouse(); Tools.sleep(2000); moveMouse(); } }); t.start(); } } /** Clean up. */ @Override public final void cleanup() { if (toolTip != null) { toolTip.setComponent(null); } } /** Set text1. */ public final void setText1(final String text1) { this.text1 = text1; } public MyMenuItem addAction(final MenuAction menuAction) { this.menuAction = menuAction; return this; } public MyMenuItem predicate(final Predicate predicate) { this.predicate = predicate; return this; } public MyMenuItem enablePredicate(final EnablePredicate enablePredicate) { this.enablePredicate = enablePredicate; return this; } public MyMenuItem visiblePredicate(final VisiblePredicate visiblePredicate) { this.visiblePredicate = visiblePredicate; return this; } public MyMenuItem onUpdate(final Runnable update) { this.update = update; return this; } }