/**
* 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.composite;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Enumeration;
import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.ButtonGroup;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.SwingConstants;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import com.rapidminer.gui.tools.Ionicon;
import com.rapidminer.tools.I18N;
/**
* A {@link CompositeToggleButton} that can be used to display {@link Action}s in a
* {@link JPopupMenu}. The button itself will only be selected if one of the defined
* {@link JMenuItem}s items will be selected.
*
* Whether the {@code CompositeButton} is the left-most, a center, or the right-most element of the
* composition can be specified in the constructors via the Swing constants
* {@link SwingConstants#LEFT}, {@link SwingConstants#CENTER}, and {@link SwingConstants#RIGHT}
* respectively.
*
* @author Marcel Michel
* @since 7.0.0
*/
class CompositeMenuToggleButton extends CompositeToggleButton {
private static final long serialVersionUID = 1L;
private static final String DOWN_ARROW_ADDER = "<html>%s<span style=\"color: 4F4F4F;\">" + Ionicon.ARROW_DOWN_B.getHtml()
+ "</span></html>";
protected int arrowSize;
protected String text;
protected final JPopupMenu popupMenu;
protected final ButtonGroup popupMenuGroup = new ButtonGroup();
/** Remember the last time the popup was closed. */
private long lastPopupCloseTime = 0;
/**
* Creates a new {@code CompositeMenuToggleButton} with the given {@link Action} to be used at
* the given position.
*
* @param action
* the button action
* @param position
* the position in the composite element ({@link SwingConstants#LEFT},
* {@link SwingConstants#CENTER}, or {@link SwingConstants#RIGHT})
*/
public CompositeMenuToggleButton(int position, Action... actions) {
super(I18N.getGUILabel("workspace_more"), position);
popupMenu = new JPopupMenu();
addActions(actions);
popupMenu.addPopupMenuListener(new PopupMenuListener() {
@Override
public void popupMenuWillBecomeVisible(final PopupMenuEvent e) {}
@Override
public void popupMenuWillBecomeInvisible(final PopupMenuEvent e) {
lastPopupCloseTime = System.currentTimeMillis();
}
@Override
public void popupMenuCanceled(final PopupMenuEvent e) {}
});
// display pop up menu below drop down button (if possible)
addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
setSelected(isPopupMenuItemSelected());
// 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.show(CompositeMenuToggleButton.this, 0, getHeight() - 1);
}
});
}
/**
* Adds the given {@link Action}s to the {@link #popupMenu}.
*
* @param actions
* the actions which should be added to the menu
*/
public void addActions(Action... actions) {
for (Action action : actions) {
JRadioButtonMenuItem item = new JRadioButtonMenuItem(action);
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
updateSelectionStatus();
}
});
popupMenuGroup.add(item);
popupMenu.add(item);
}
}
/**
* Updates the selection status of the toggle button in regard to the menu selection.
*/
protected void updateSelectionStatus() {
setSelected(isPopupMenuItemSelected());
}
/**
* Clears the selection of the button and also the toggle button.
*/
protected void clearMenuSelection() {
popupMenuGroup.clearSelection();
updateSelectionStatus();
}
/**
* Getter for the selection state of the menu.
*
* @return {@code true} if an item of the menu is selected otherwise {@code false}
*/
public boolean isPopupMenuItemSelected() {
return popupMenuGroup.getSelection() != null;
}
/**
* Selects the given action in the menu. This methods also updates the selection status of the
* toggle button.
*
* @param action
* the action which should be selected
*/
public void setSelected(Action action) {
Enumeration<AbstractButton> menuEnum = popupMenuGroup.getElements();
boolean found = false;
while (menuEnum.hasMoreElements()) {
AbstractButton button = menuEnum.nextElement();
if (action == button.getAction()) {
button.setSelected(true);
found = true;
} else {
button.setSelected(false);
}
}
if (found) {
setFont(getFont().deriveFont(Font.BOLD));
updateSelectionStatus();
} else {
setFont(getFont().deriveFont(Font.PLAIN));
clearMenuSelection();
}
}
@Override
public void setText(String text) {
this.text = text;
if (text == null) {
text = "";
}
if (!text.isEmpty()) {
// add space between text and arrow
text = text + " ";
}
super.setText(String.format(DOWN_ARROW_ADDER, text));
}
}