/**
* 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.tools.components;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.LinkedList;
import java.util.List;
import javax.swing.Action;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import com.rapidminer.gui.tools.Ionicon;
import com.rapidminer.gui.tools.ResourceAction;
import com.rapidminer.gui.tools.SwingTools;
import com.rapidminer.tools.I18N;
/**
* Class for a button with a JPopupMenu that acts as a dropdown menu. The popup menu opens when the
* button is clicked and closes on another button click or when clicking somewhere else.
*
* @author Marco Boeck, Gisa Schaefer
* @since 7.0.0
*/
public class DropDownPopupButton extends JButton {
/**
* The default font size for arrow and text spans.
*/
private static final int DEFAULT_FONT_SIZE = 12;
/**
* The default color used for text and arrow.
*/
private static final String DEFAULT_COLOR = "4F4F4F";
/**
* Interface providing a method to obtain a {@link JPopupMenu}.
*/
public interface PopupMenuProvider {
/**
* Returns a popup menu that should be used.
*
* @return a popup menu
*/
JPopupMenu getPopupMenu();
}
/**
* A builder for {@link DropDownPopupButton}s. The {@link #build()} method creates a button with
* text, icon and tooltip specified via {@link #with(ResourceAction)} that shows a popup menu
* with entries specified by {@link #add(Action)} and {@link #add(JMenuItem)}.
*
* @author Gisa Schaefer
*
*/
public static class DropDownPopupButtonBuilder {
private ResourceAction action;
private JPopupMenu popupMenu = new JPopupMenu();
/**
* Sets the action that specifies the text, icon and tooltip of the button created by this
* builder.
*
* @param action
* the {@link ResourceAction} specifying the text, icon and tooltip of the button
* @return the builder
*/
public DropDownPopupButtonBuilder with(ResourceAction action) {
this.action = action;
return this;
}
/**
* Adds an action to the popup menu that is displayed when the button is clicked.
*
* @param action
* the action to add
* @return the builder
*/
public DropDownPopupButtonBuilder add(Action action) {
popupMenu.add(action);
return this;
}
/**
* Adds an item to the popup menu that is displayed when the button is clicked.
*
* @param item
* the item to add
* @return the builder
*/
public DropDownPopupButtonBuilder add(JMenuItem item) {
popupMenu.add(item);
return this;
}
/**
* Adds a separator to the popup menu that is displayed when the button is clicked.
*
* @return the builder
*/
public DropDownPopupButtonBuilder addSeparator() {
popupMenu.addSeparator();
return this;
}
/**
* Creates a {@link DropDownPopupButton} from the given data.
*
* @return the button created with the given data
*/
public DropDownPopupButton build() {
return new DropDownPopupButton(action, new PopupMenuProvider() {
@Override
public JPopupMenu getPopupMenu() {
return popupMenu;
}
});
}
}
private static final long serialVersionUID = -3267770082941303097L;
private static final String DOWN_ARROW_ADDER_WITH_TEXT_AND_ARROW_SIZE = "<html><span style=\"color: %s; font-size:%d\">%s</span><span style=\"color: %s; font-size:%d\">"
+ Ionicon.ARROW_DOWN_B.getHtml() + "</span></html>";
/**
* hack to prevent popup from opening itself again when you click the button to actually close
* it while it is open
*/
private long lastPopupCloseTime;
private final PopupMenuProvider menuProvider;
// small hack to prevent the popup from opening itself when you click
// the button to actually close it
private final PopupMenuListener popupListener = new PopupMenuListener() {
@Override
public void popupMenuWillBecomeVisible(final PopupMenuEvent e) {}
@Override
public void popupMenuWillBecomeInvisible(final PopupMenuEvent e) {
lastPopupCloseTime = System.currentTimeMillis();
JPopupMenu jPopupMenu = (JPopupMenu) e.getSource();
jPopupMenu.removePopupMenuListener(this);
for (PopupMenuListener otherListener : otherListeners) {
jPopupMenu.removePopupMenuListener(otherListener);
}
}
@Override
public void popupMenuCanceled(final PopupMenuEvent e) {}
};
private List<PopupMenuListener> otherListeners = new LinkedList<>();
private int arrowSize = DEFAULT_FONT_SIZE;
private int fontSize = DEFAULT_FONT_SIZE;
private String color = DEFAULT_COLOR;
private String text;
/**
* Creates a button with text, icon and tooltip specified by the i18n together with a popup menu
* that acts as a dropdown.
*
* @param i18n
* [i18n].label for the text of the button, [i18n].icon for the icon and [i18n].tip
* for the tooltip
* @param popupMenuProvider
* the provider for the menu that is shown when the button is clicked
*/
public DropDownPopupButton(String i18n, PopupMenuProvider popupMenuProvider) {
super(I18N.getGUIMessageOrNull(i18n + ".label"), getIcon(i18n));
setToolTipText(I18N.getGUIMessageOrNull(i18n + ".tip"));
menuProvider = popupMenuProvider;
setupAction();
}
/**
* Creates a button with text, icon and tooltip specified by the action together with a popup
* menu that acts as a dropdown.
*
* @param action
* the {@link ResourceAction} that provides the text, icon and tooltip for the button
* @param popupMenuProvider
* the provider for the menu that is shown when the button is clicked
*/
public DropDownPopupButton(ResourceAction action, PopupMenuProvider popupMenuProvider) {
super(action);
menuProvider = popupMenuProvider;
setupAction();
}
private void setupAction() {
addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
JPopupMenu popupMenu = menuProvider.getPopupMenu();
if (!popupMenu.isVisible()) {
// hack to prevent filter popup from opening itself again
// when you click the button to actually close it while it
// is open
if (System.currentTimeMillis() - lastPopupCloseTime < 250) {
return;
}
popupMenu.addPopupMenuListener(popupListener);
for (PopupMenuListener listener : otherListeners) {
popupMenu.addPopupMenuListener(listener);
}
popupMenu.show(DropDownPopupButton.this, 0, DropDownPopupButton.this.getHeight() - 1);
popupMenu.requestFocusInWindow();
}
}
});
}
@Override
public void setText(String text) {
this.text = text;
if (text == null) {
text = "";
}
if (!text.isEmpty()) {
// add space between text and arrow
text = text + " ";
}
int arrowFontSize = getFontSize(arrowSize);
int textFontSize = getFontSize(fontSize);
String color = getColor();
super.setText(
String.format(DOWN_ARROW_ADDER_WITH_TEXT_AND_ARROW_SIZE, color, textFontSize, text, color, arrowFontSize));
}
private int getFontSize(int selectedSize) {
if (selectedSize <= 0) {
return DEFAULT_FONT_SIZE;
}
return selectedSize;
}
/**
* Sets the arrow size. The default size is 12.
*
* @param size
* the size of the downward pointing arrow
*/
public void setArrowSize(int size) {
arrowSize = size;
setText(text);
}
/**
* Sets the text font size. The default size is 12.
*
* @param size
* the size of the text
*/
public void setTextSize(int size) {
fontSize = size;
setText(text);
}
/**
* Sets the color for the font and arrow (in hex format). If set to {@code null} the default
* color {@link #DEFAULT_COLOR} is used.
*
* @param color
* the color (in hex format) that should be used for font and arrow
*/
public void setColor(String color) {
this.color = color;
setText(text);
}
private String getColor() {
if (color == null) {
return DEFAULT_COLOR;
}
return color;
}
/**
* Creates image icon from the i18n; static since called from within the constructor.
*/
private static ImageIcon getIcon(String i18n) {
String iconName = I18N.getGUIMessageOrNull(i18n + ".icon");
if (iconName == null) {
return null;
}
return SwingTools.createIcon("16/" + iconName);
}
/**
* Adds a {@link PopupMenuListener} that will be registered to the buttons popup menu each time
* it is shown.
*
* @param listener
* the listener to add
*/
public void addPopupMenuListener(PopupMenuListener listener) {
otherListeners.add(listener);
}
}