/*
* RapidMiner
*
* Copyright (C) 2001-2014 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.Point;
import java.awt.Window;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JPopupMenu;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import com.rapidminer.gui.RapidMinerGUI;
import com.rapidminer.gui.flow.ProcessInteractionListener;
import com.rapidminer.gui.flow.ProcessPanel;
import com.rapidminer.gui.flow.ProcessRenderer;
import com.rapidminer.operator.Operator;
import com.rapidminer.operator.OperatorChain;
import com.rapidminer.operator.ports.Port;
import com.vlsolutions.swing.docking.event.DockingActionEvent;
import com.vlsolutions.swing.docking.event.DockingActionListener;
/**
* This class creates a speech bubble-shaped JDialog, which can be attache to
* Buttons, either by using its ID or by passing a reference.
* The bubble triggers two events which are obserable by the {@link BubbleListener};
* either if the close button was clicked, or if the corresponding button was used.
* The keys for the title and the text must be of format gui.bubble.XXX.body or gui.bubble.XXX.title .
*
* @author Thilo Kamradt
*
*/
public class BubbleToOperator extends BubbleWindow {
private static final long serialVersionUID = 7404582361212798730L;
private int split;
private Class<? extends Operator> operatorClass;
private Operator onDisplay = null;
private OperatorChain homeChain = null;
private BubbleToDockable dockBubble = null;
private ProcessRenderer renderer = RapidMinerGUI.getMainFrame().getProcessPanel().getProcessRenderer();
private ProcessInteractionListener rendererListener;
private ChangeListener viewPortListener;
private DockingActionListener assistanceDockingListener;
private ProcessInteractionListener assistanceRendererListener;
public BubbleToOperator(Window owner, AlignedSide preferredAlignment, String i18nKey, Class<? extends Operator> toAttach, Object ... arguments) {
this(owner, preferredAlignment, i18nKey, toAttach, 1, arguments);
}
public BubbleToOperator(Window owner, AlignedSide preferredAlignment, String i18nKey, Class<? extends Operator> toAttach, int split, Object ... arguments) {
super(owner, preferredAlignment, i18nKey, ProcessPanel.PROCESS_PANEL_DOCK_KEY, arguments);
operatorClass = toAttach;
this.split = split;
Operator[] matchingOperators = this.getMatchingOperatorsInChain(operatorClass, renderer.getDisplayedChain());
if(matchingOperators.length == 0) {
//TODO add information for user to enter the wanted chain with operator
this.registerMovementListener();
this.changeToAssistanceListener();
this.getBubbleToProcesspanel().setVisible(true);
} else {
onDisplay = matchingOperators[(matchingOperators.length <= split ? matchingOperators.length - 1 : split - 1)];
homeChain = renderer.getDisplayedChain();
renderer.scrollRectToVisible(renderer.getOperatorRect(onDisplay, false).getBounds());
}
super.paint(false);
}
private Operator[] getMatchingOperatorsInChain(Class<? extends Operator> operatorClass,OperatorChain displayedChain) {
ArrayList<Operator> matching = new ArrayList<Operator>();
List<Operator> operatorsInChain = displayedChain.getAllInnerOperators();
for(Operator operatorInChain : operatorsInChain) {
if(operatorClass.isAssignableFrom(operatorInChain.getClass())) {
matching.add(operatorInChain);
}
}
return matching.toArray(new Operator[0]);
}
@Override
protected void registerMovementListener() {
super.registerMovementListener();
this.registerSpecificListener();
}
@Override
protected void registerSpecificListener() {
viewPortListener = new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
BubbleToOperator.this.paint(false);
}
};
RapidMinerGUI.getMainFrame().getProcessPanel().getViewPort().addChangeListener(viewPortListener);
rendererListener = new ProcessInteractionListener() {
@Override
public void portContextMenuWillOpen(JPopupMenu menu, Port port) {
// do not care
}
@Override
public void operatorMoved(Operator op) {
if(op.equals(BubbleToOperator.this.onDisplay))
BubbleToOperator.this.paint(false);
}
@Override
public void operatorContextMenuWillOpen(JPopupMenu menu, Operator operator) {
// do not care
}
@Override
public void displayedChainChanged(OperatorChain displayedChain) {
if(onDisplay == null) {
//TODO outline because unreachable ...
Operator[] matching = BubbleToOperator.this.getMatchingOperatorsInChain(operatorClass, displayedChain);
if (matching.length != 0) {
onDisplay = matching[(matching.length <= split ? matching.length - 1 : split - 1)];
homeChain = renderer.getDisplayedChain();
if(dockBubble != null) {
triggerfireForBubbleToDockable();
}
}
} else if (homeChain.equals(displayedChain)) {
//will be handled by the the assistance listener
} else {
BubbleToOperator.this.setVisible(false);
BubbleToOperator.this.unregisterMovementListener();
BubbleToOperator.this.changeToAssistanceListener();
getBubbleToProcesspanel().setVisible(true);
}
}
};
renderer.addProcessInteractionListener(rendererListener);
}
private void changeToAssistanceListener() {
this.unregisterMovementListener();
assistanceRendererListener = new ProcessInteractionListener() {
@Override
public void portContextMenuWillOpen(JPopupMenu menu, Port port) {
// do not care
}
@Override
public void operatorMoved(Operator op) {
// do not care
}
@Override
public void operatorContextMenuWillOpen(JPopupMenu menu, Operator operator) {
// do not care
}
@Override
public void displayedChainChanged(OperatorChain displayedChain) {
if(onDisplay == null) {
Operator[] matching = BubbleToOperator.this.getMatchingOperatorsInChain(operatorClass, displayedChain);
if (matching.length != 0) {
onDisplay = matching[(matching.length <= split ? matching.length - 1 : split - 1)];
homeChain = renderer.getDisplayedChain();
triggerfireForBubbleToDockable();
BubbleToOperator.this.changeToMainListeners();
}
} else if (homeChain.equals(displayedChain)) {
BubbleToOperator.this.paint(true);
BubbleToOperator.this.setVisible(true);
triggerfireForBubbleToDockable();
BubbleToOperator.this.changeToMainListeners();
//TODO: check this for mistakes
}
}
};
renderer.addProcessInteractionListener(assistanceRendererListener);
if(dockable != null) {
assistanceDockingListener = new DockingActionListener() {
int dockingCounter = 0;
@Override
public void dockingActionPerformed(DockingActionEvent event) {
// actionType 2 indicates that a Dockable was splitted
// actionType 3 indicates that the Dockable has created his own position
// actionType 5 indicates that the Dockable was docked to another position
// actionType 6 indicates that the Dockable was separated
if (event.getActionType() == 5 || event.getActionType() == 3) {
if ((++dockingCounter) % 2 == 0) {
//get the new component of the Dockable because the current component is disabled
BubbleToOperator.this.reloadComponent(true);
}
}
if (event.getActionType() == 6 || event.getActionType() == 2) {
//get the new component of the Dockable because the current component is disabled
BubbleToOperator.this.reloadComponent(true);
}
}
@Override
public boolean acceptDockingAction(DockingActionEvent arg0) {
// no need to deny anything
return true;
}
};
desktop.addDockingActionListener(assistanceDockingListener);
}
}
private void changeToMainListeners() {
if(dockable == null) {
//remove assistance listener
renderer.removeProcessInteractionListener(assistanceRendererListener);
} else {
//remove assistance listeners
desktop.removeDockingActionListener(assistanceDockingListener);
renderer.removeProcessInteractionListener(assistanceRendererListener);
}
//add the correct listeners again
this.registerMovementListener();
}
@Override
protected void unregisterMovementListener() {
super.unregisterMovementListener();
this.unregisterSpecificListeners();
}
@Override
protected void unregisterSpecificListeners() {
renderer.removeProcessInteractionListener(rendererListener);
RapidMinerGUI.getMainFrame().getProcessPanel().getViewPort().removeChangeListener(viewPortListener);
}
@Override
protected Point getObjectLocation() {
//get all necessary parameters
int xDockable = dockable.getComponent().getLocationOnScreen().x;
int yDockable = dockable.getComponent().getLocationOnScreen().y;
Rectangle2D rec = renderer.getOperatorRect(onDisplay, false);
double width = rec.getWidth();
double height = rec.getHeight();
double xOperator = rec.getMinX();
double yOperator = rec.getMinY();
Point view = RapidMinerGUI.getMainFrame().getProcessPanel().getProcessRenderer().getVisibleRect().getLocation();
return new Point((int) (xDockable + (width*0.3) +xOperator - view.x),(int) (yDockable + height*0.85 + yOperator - view.y));
}
@Override
protected int getObjectWidth() {
return (int) Math.round(renderer.getOperatorRect(onDisplay, false).getWidth());
}
@Override
protected int getObjectHeight() {
return (int) Math.round(renderer.getOperatorRect(onDisplay, false).getHeight());
}
/** returns the current instance of the BubbleToDockable Object or a new one*/
private BubbleToDockable getBubbleToProcesspanel() {
if(dockable == null) {
dockBubble = new BubbleToDockable(owner, AlignedSide.RIGHT, "operatorNotDisplayed", super.docKey, operatorClass.getName());;
}
return dockBubble;
}
/**disposes the BubbleToDockable object*/
private void triggerfireForBubbleToDockable() {
if(dockBubble != null)
dockBubble.triggerFire();
dockBubble = null;
}
protected void reloadComponent(boolean assistanceActive) {
super.reloadComponent();
if(assistanceActive && dockable != null){
dockable.getComponent().removeComponentListener(compListener);
super.removeComponentListener(compListener);
desktop.removeDockingActionListener(dockListener);
desktop.addDockingActionListener(assistanceDockingListener);
}
}
@Override
protected void reloadComponent() {
super.reloadComponent();
// dockable = BubbleWindow.getDockableByKey(docKey);
// Operator[] matchingOperators = this.getMatchingOperatorsInChain(operatorClass, renderer.getDisplayedChain());
// if(matchingOperators.length == 0) {
// //TODO show dialog
// dockBubble = new BubbleToDockable(owner, AlignedSide.RIGHT, "operatorNotDisplayed", docKey, operatorClass.getName());
// dockBubble.setVisible(true);
// } else if(matchingOperators.length <= split) {
// onDisplay = matchingOperators[split - 1];
// } else {
// onDisplay = matchingOperators[matchingOperators.length - 1];
// }
// dockable = BubbleWindow.getDockableByKey(docKey);
}
}