/**
* 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;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.LinkedList;
import javax.swing.AbstractAction;
import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JPopupMenu;
import javax.swing.event.EventListenerList;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import com.rapidminer.gui.tools.components.LinkLocalButton;
import com.rapidminer.tools.I18N;
/**
*
* @author Tobias Malbrecht
*/
public class ParentButtonPanel<T> extends ExtendedJToolBar {
private static final String BREADCRUMB_ABBREVIATION = "...";
private static final int MAX_BREADCRUMB_LENGTH = 28;
private static final long serialVersionUID = 1L;
private static final String RIGHT_ARROW = "<html><span style=\"color: 4F4F4F;\">" + Ionicon.ARROW_RIGHT_B.getHtml()
+ "</span></html>";
private static final String DOWN_ARROW = "<html><span style=\"color: 4F4F4F;\">" + Ionicon.ARROW_DOWN_B.getHtml()
+ "</span></html>";
private static final int HISTORY_SIZE = 20;
private LinkedList<T> backward = new LinkedList<>();
private LinkedList<T> forward = new LinkedList<>();
private ParentButtonModel<T> model;
private T currentNode;
private T selectedNode;
private boolean selectionByHistory = false;
private JButton upButton = new JButton(new ResourceAction(true, "select_parent") {
private static final long serialVersionUID = -5411675828764033039L;
@Override
public void actionPerformed(ActionEvent e) {
selectedNode = model.getParent(currentNode);
fireAction();
}
});
public ParentButtonPanel() {
this(null);
}
public ParentButtonPanel(ParentButtonModel<T> model) {
setModel(model);
setOpaque(false);
}
private final MouseListener borderListener = new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
if (e.getComponent() instanceof AbstractButton) {
((AbstractButton) e.getComponent()).setBorderPainted(true);
}
}
@Override
public void mouseExited(MouseEvent e) {
if (e.getComponent() instanceof AbstractButton) {
((AbstractButton) e.getComponent()).setBorderPainted(false);
}
}
};
@Override
public Component add(Component c) {
Component result = super.add(c);
c.addMouseListener(borderListener);
return result;
}
public void setSelectedNode(T node) {
this.currentNode = node;
upButton.setEnabled(model.getParent(currentNode) != null);
setup();
}
public void setModel(ParentButtonModel<T> model) {
this.model = model;
setup();
}
private void setup() {
removeAll();
addSeparator();
addSeparator();
add(upButton);
if (model != null) {
LinkedList<T> nodes = new LinkedList<>();
T node = currentNode;
while (node != null) {
nodes.addFirst(node);
node = model.getParent(node);
}
boolean maxDepthReached = false;
for (T n : nodes) {
if (n.equals(nodes.getFirst()) || n.equals(nodes.getLast()) || n.equals(nodes.get(nodes.size() - 2))) {
// always show the first, n-th and (n-1)th button
add(makeButton(n));
// only show last drop down button in case any children are available
if (model.getNumberOfChildren(n) != 0) {
add(makeDropdownButton(n));
}
} else if (!maxDepthReached) {
JButton emptyButton = new JButton(BREADCRUMB_ABBREVIATION);
// remove all mouse actions for this button, so it can't be clicked or
// highlighted
MouseListener[] actions = emptyButton.getMouseListeners();
for (MouseListener a : actions) {
emptyButton.removeMouseListener(a);
}
add(emptyButton);
maxDepthReached = true;
} else {
// don't add anymore buttons
}
}
}
revalidate();
repaint();
}
private LinkLocalButton makeButton(final T node) {
String name = model.toString(node);
if (name.length() > MAX_BREADCRUMB_LENGTH) {
name = name.substring(0, MAX_BREADCRUMB_LENGTH - BREADCRUMB_ABBREVIATION.length()) + BREADCRUMB_ABBREVIATION;
}
if (node.equals(currentNode)) {
Action action = new AbstractAction("<span style=\"font-weight: bold; text-decoration: none; color: #000000\">"
+ name + "</span>") {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
selectedNode = node;
fireAction();
}
};
LinkLocalButton button = new LinkLocalButton(action);
button.setToolTipText(I18N.getGUIMessage("gui.button.process_panel.breadcrumbs.current.tip"));
button.setBorder(BorderFactory.createEmptyBorder(7, 0, 0, 0));
return button;
} else {
Action action = new AbstractAction(name) {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
selectedNode = node;
fireAction();
}
};
LinkLocalButton button = new LinkLocalButton(action);
button.setToolTipText(I18N.getGUIMessage("gui.button.process_panel.breadcrumbs.any.tip"));
button.setBorder(BorderFactory.createEmptyBorder(6, 0, 0, 0));
return button;
}
}
private JButton makeDropdownButton(final T node) {
final JButton button = new JButton(RIGHT_ARROW);
button.setOpaque(false);
button.setBorderPainted(false);
button.setMargin(new Insets(0, 0, 0, 0));
button.setPreferredSize(new Dimension(16, 16));
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// hack to prevent filter popup from opening itself again when you click the button
// to actually close it while it is open
try {
long lastClose = Long.parseLong(String.valueOf(button.getClientProperty("lastCloseTime")));
if (System.currentTimeMillis() - lastClose < 250) {
return;
}
} catch (NumberFormatException e1) {
// ignore
}
JPopupMenu menu = makeMenu(node);
button.setText(DOWN_ARROW);
menu.addPopupMenuListener(new PopupMenuListener() {
@Override
public void popupMenuCanceled(PopupMenuEvent e) {}
@Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
button.setText(RIGHT_ARROW);
button.putClientProperty("lastCloseTime", System.currentTimeMillis());
}
@Override
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {}
});
menu.show(button, 0, button.getHeight() - 1);
}
});
return button;
}
private JPopupMenu makeMenu(final T node) {
JPopupMenu menu = new JPopupMenu();
for (int i = 0; i < model.getNumberOfChildren(node); i++) {
final T child = model.getChild(node, i);
menu.add(new AbstractAction(model.toString(child)) {
private static final long serialVersionUID = 7232177147279985209L;
{
putValue(SMALL_ICON, model.getIcon(child));
}
@Override
public void actionPerformed(ActionEvent e) {
selectedNode = child;
fireAction();
}
});
}
return menu;
}
public T getSelectedNode() {
return selectedNode;
}
private EventListenerList listeners = new EventListenerList();
public void addActionListener(ActionListener l) {
listeners.add(ActionListener.class, l);
}
public void removeActionListener(ActionListener l) {
listeners.remove(ActionListener.class, l);
}
private int eventId = 0;
public void clearHistory() {
backward.clear();
forward.clear();
}
public void addToHistory(T node) {
if (node == null) {
return;
}
if (backward.size() > 0) {
if (backward.getFirst() != node) {
backward.remove(node);
backward.addFirst(node);
if (!selectionByHistory) {
forward.clear();
}
}
} else {
backward.remove(node);
backward.addFirst(node);
if (!selectionByHistory) {
forward.clear();
}
}
while (backward.size() > HISTORY_SIZE) {
backward.removeLast();
}
selectionByHistory = false;
}
private void fireAction() {
ActionEvent e = new ActionEvent(this, eventId++, "select");
for (ActionListener l : listeners.getListeners(ActionListener.class)) {
l.actionPerformed(e);
}
upButton.setEnabled(model.getParent(currentNode) != null);
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
}
}