/**
* Copyright (C) 2001-2017 by RapidMiner and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapidminer.com
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* This program 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
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.gui.new_plotter.gui.popup;
import com.rapidminer.gui.RapidMinerGUI;
import com.rapidminer.gui.tools.ResourceAction;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import javax.swing.JDialog;
import javax.swing.JToggleButton;
import javax.swing.SwingUtilities;
/**
* This action can be used to show a component as a popup on the screen. If the popup loses the
* focus to another component in the containing window it will be hidden.
* <p>
* The action's settings are taken from a .properties file being part of the GUI Resource bundles of
* RapidMiner. These might be accessed using the I18N class.
* <p>
* A resource action needs a key specifier, which will be used to build the complete keys of the
* form:
* <ul>
* <li>gui.action.-key-.label = Which will be the caption</li>
* <li>gui.action.-key-.icon = The icon of this action. For examples used in menus or buttons</li>
* <li>gui.action.-key-.acc = The accelerator key used for menu entries</li>
* <li>gui.action.-key-.tip = Which will be the tool tip</li>
* <li>gui.action.-key-.mne = Which will give you access to the mnemonics key. Please make it the
* same case as in the label</li>
* </ul>
*
* @author Nils Woehler
*
*/
public class PopupAction extends ResourceAction implements PopupComponentListener, ComponentListener {
public enum PopupPosition {
HORIZONTAL, VERTICAL
}
private class ContainerPopupDialog extends JDialog {
private static final long serialVersionUID = 1L;
public ContainerPopupDialog(Window owner, Component comp, Point point) {
super(owner != null ? owner : RapidMinerGUI.getMainFrame());
this.add(comp);
this.setLocation(point);
this.setUndecorated(true);
pack();
}
}
private static final long serialVersionUID = 1L;
private final PopupPanel popupComponent;
private Component actionSource = null;
private ContainerPopupDialog popup = null;
private PopupPosition position = PopupPosition.VERTICAL;
private static final int BORDER_OFFSET = 9;
private Window containingWindow;
private long hideTime = 0;
public PopupAction(boolean smallIcon, String i18nKey, Component component, Object... i18nArgs) {
super(smallIcon, i18nKey, i18nArgs);
this.popupComponent = new PopupPanel(component);
popupComponent.addListener(this);
}
public PopupAction(boolean smallIcon, String i18nKey, Component component, PopupPosition position, Object... i18nArgs) {
super(smallIcon, i18nKey, i18nArgs);
this.position = position;
this.popupComponent = new PopupPanel(component);
popupComponent.addListener(this);
}
public PopupAction(String i18nKey, Component component, Object... i18nArgs) {
super(i18nKey, i18nArgs);
this.popupComponent = new PopupPanel(component);
popupComponent.addListener(this);
}
public PopupAction(String i18nKey, Component component, PopupPosition position, Object... i18nArgs) {
super(i18nKey, i18nArgs);
this.position = position;
this.popupComponent = new PopupPanel(component);
popupComponent.addListener(this);
}
@Override
public synchronized void actionPerformed(ActionEvent e) {
if (System.currentTimeMillis() - hideTime > 150) {
if (hidePopup()) {
return;
}
showPopup((Component) e.getSource());
} else {
Object source = e.getSource();
if (source instanceof JToggleButton) {
((JToggleButton) e.getSource()).setSelected(false);
}
}
}
private Point calculatePosition(Component source) {
int xSource = source.getLocationOnScreen().x;
int ySource = source.getLocationOnScreen().y;
// get size of popup
Dimension popupSize = ((Component) popupComponent).getSize();
if (popupSize.width == 0) {
popupSize = ((Component) popupComponent).getPreferredSize();
}
int xPopup = 0;
int yPopup = 0;
// get max x and y window positions
Window focusedWindow = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow();
int maxX = focusedWindow.getLocationOnScreen().x + focusedWindow.getWidth();
int maxY = focusedWindow.getLocationOnScreen().y + focusedWindow.getHeight();
switch (position) {
case VERTICAL:
// place popup at sources' x position
xPopup = xSource;
// check if popup is outside active window
if (xPopup + popupSize.width > maxX) {
// move popup x position to the left
// to fit inside the active window
xPopup = maxX - popupSize.width - BORDER_OFFSET;
}
// place popup always below source (to avoid overlapping)
yPopup = ySource + source.getHeight();
// if the popup now would be moved outside of RM Studio to the left it would look
// silly, so in that case just show it at its intended position and let it be cut
// off on the right side as we cannot do anything about it
if (xPopup < focusedWindow.getLocationOnScreen().x
|| (xPopup - focusedWindow.getLocationOnScreen().x) + popupSize.width > focusedWindow.getWidth()) {
xPopup = xSource;
}
break;
case HORIZONTAL:
// place popup always to the right side of the source (to avoid overlapping)
xPopup = xSource + source.getWidth();
yPopup = ySource;
// check if popup is outside active window
if (yPopup + popupSize.height > maxY) {
// move popup upwards to fit into active window
yPopup = maxY - popupSize.height - BORDER_OFFSET;
}
// if the popup now would be moved outside of RM Studio at the top it would look
// silly, so in that case just show it at its intended position and let it be cut
// off on the bottom side as we cannot do anything about it
if (yPopup < focusedWindow.getLocationOnScreen().y
|| (yPopup - focusedWindow.getLocationOnScreen().y) + popupSize.height > focusedWindow.getHeight()) {
yPopup = ySource;
}
break;
}
return new Point(xPopup, yPopup);
}
/**
* Creates the popup, calculates position and sets focus
*/
private void showPopup(Component source) {
actionSource = source;
actionSource.addComponentListener(this);
if (actionSource instanceof JToggleButton) {
JToggleButton toggleSource = (JToggleButton) actionSource;
toggleSource.setSelected(true);
}
containingWindow = SwingUtilities.windowForComponent(actionSource);
containingWindow.addComponentListener(this);
Point position = calculatePosition(source);
popupComponent.setLocation(position);
popupComponent.setVisible(true);
popup = new ContainerPopupDialog(containingWindow, popupComponent, position);
popup.setVisible(true);
popup.requestFocus();
popupComponent.startTracking(containingWindow, actionSource);
}
/**
* Hides the popup component.
*/
private boolean hidePopup() {
if (actionSource instanceof JToggleButton) {
JToggleButton toggleSource = (JToggleButton) actionSource;
toggleSource.setSelected(false);
}
if (containingWindow != null) {
containingWindow.removeComponentListener(this);
containingWindow = null;
}
if (actionSource != null) {
actionSource.removeComponentListener(this);
actionSource = null;
}
// Check if popup is visible
if (popup != null) {
popupComponent.setVisible(false);
popupComponent.stopTracking();
// hide popup and reset
popup.dispose();
popup = null;
hideTime = System.currentTimeMillis();
return true;
}
return false;
}
@Override
public void focusLost() {
hidePopup();
}
@Override
public void componentResized(ComponentEvent e) {
hidePopup();
}
@Override
public void componentMoved(ComponentEvent e) {
// Unnecessary to hide on move event. Causes trouble on Linux
// hidePopup();
}
@Override
public void componentShown(ComponentEvent e) {
return; // Nothing to be done
}
@Override
public void componentHidden(ComponentEvent e) {
hidePopup();
}
}