Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
edu.cmu.tetradapp.workbench.AbstractWorkbench Maven / Gradle / Ivy
///////////////////////////////////////////////////////////////////////////////
// For information as to what this class does, see the Javadoc, below. //
// Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, //
// 2007, 2008, 2009, 2010, 2014, 2015 by Peter Spirtes, Richard Scheines, Joseph //
// Ramsey, and Clark Glymour. //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation; either version 2 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 General Public License for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program; if not, write to the Free Software //
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA //
///////////////////////////////////////////////////////////////////////////////
package edu.cmu.tetradapp.workbench;
import edu.cmu.tetrad.data.Knowledge;
import edu.cmu.tetrad.graph.*;
import edu.cmu.tetrad.search.utils.GraphSearchUtils;
import edu.cmu.tetrad.util.JOptionUtils;
import edu.cmu.tetradapp.util.LayoutEditable;
import edu.cmu.tetradapp.util.PasteLayoutAction;
import org.apache.commons.math3.util.FastMath;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;
import java.util.*;
import java.util.prefs.Preferences;
/**
* The functionality of the workbench which is shared between the workbench workbench and the session (and any other
* workbenches which want to use this functionality).
*
* @author Aaron Powell
* @author josephramsey
* @author Willie Wheeler
* @see DisplayNode
* @see DisplayEdge
*/
public abstract class AbstractWorkbench extends JComponent implements WorkbenchModel, LayoutEditable {
/**
* The mode in which the user is permitted to select workbench items or move nodes.
*/
public static final int SELECT_MOVE = 0;
// ===================PUBLIC STATIC FINAL FIELDS=====================//
/**
* The mode in which the user is permitted to select workbench items or move nodes.
*/
public static final int ADD_NODE = 1;
/**
* The mode in which the user is permitted to select workbench items or move nodes.
*/
public static final int ADD_EDGE = 2;
private static final long serialVersionUID = 6718395673225983249L;
// =========================PRIVATE FIELDS=============================//
/**
* Indicates whether multiple node selection is allowed.
*/
private final boolean allowMultipleSelection = true;
/**
* ` Handler for ComponentEvents.
*/
private final ComponentHandler compHandler = new ComponentHandler(this);
/**
* Handler for MouseEvents.
*/
private final MouseHandler mouseHandler = new MouseHandler(this);
/**
* Handler MouseMotionEvents.
*/
private final MouseMotionHandler mouseMotionHandler = new MouseMotionHandler(this);
/**
* Handler for PropertyChangeEvents.
*/
private final PropertyChangeHandler propChangeHandler = new PropertyChangeHandler(this);
/**
* The workbench which this workbench displays.
*/
private Graph graph;
/**
* The map from model edges to display elements.
*/
private Map modelEdgesToDisplay;
/**
* The map from model nodes to display elements.
*/
private Map modelNodesToDisplay;
/**
* The map from display elements to model elements.
*/
private Map displayToModel;
/**
* The map from edges to edge labels.
*/
private Map displayToLabels;
/**
* The getModel mode of the workbench.
*/
private int workbenchMode = AbstractWorkbench.SELECT_MOVE;
/**
* When edges are being constructed, one edge is anchored to a node and the other edge tracks mouse dragged events;
* this is the edge that does this. This edge should be null unless an edge is actually being tracked.
*/
private IDisplayEdge trackedEdge;
/**
* For dragging nodes, a click point is needed; this is that click point.
*/
private Point clickPoint;
/**
* For dragging nodes, the set of selected nodes at the start of dragging is needed, since the selected nodes need
* to be moved in sync during the drag.
*/
private List dragNodes;
/**
* For selecting multiple nodes using a rubberband, a rubberband is needed; this is it.
*/
private Rubberband rubberband;
/**
* Indicates whether user editing is permitted.
*/
private boolean allowDoubleClickActions = true;
/**
* Indicates whether nodes may be moved by the user.
*/
private boolean allowNodeDragging = true;
/**
* Indicates whether user editing is permitted.
*/
private boolean allowNodeEdgeSelection = true;
/**
* Indicates whether edge reorientations are permitted.
*/
private boolean allowEdgeReorientations = true;
/**
* Maximum x value (for dragging).
*/
private int maxX = 10000;
/**
* Maximum y value (for dragging).
*/
private int maxY = 10000;
/**
* True iff node/edge adding/removing errors should be reported to the user.
*/
private boolean nodeEdgeErrorsReported;
/**
* True iff layout is permitted using a right click popup.
*/
private boolean rightClickPopupAllowed;
/**
* A key dispatcher to allow pressing the control key to control whether edges will be drawn in the workbench.
*/
private KeyEventDispatcher controlDispatcher;
private Point currentMouseLocation;
/**
* Returns the current displayed mouseover equation label. Returns null if none is displayed. Used for removing the
* label.
*/
private boolean enableEditing = true;
private boolean doPagColoring = false;
private Graph samplingGraph;
private Knowledge knowledge = new Knowledge();
// ==============================CONSTRUCTOR============================//
/**
* Constructs a new workbench.
*
* @param graph The graph that this workbench will display.
*/
protected AbstractWorkbench(Graph graph) {
setGraph(graph);
addMouseListener(this.mouseHandler);
addMouseMotionListener(this.mouseMotionHandler);
// setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
setBackground(new Color(254, 254, 255));
setFocusable(true);
addMouseListener(new MouseAdapter() {
public void mouseEntered(MouseEvent e) {
grabFocus();
}
});
setEnabled(this.enableEditing);
new PasteLayoutAction(this).actionPerformed(null);
}
// ============================PUBLIC METHODS==========================//
/**
* Calculates the offset in pixels of a given edge - this could use a little tweaking still.
*
* @param i the index of the given edge
* @param n the number of edges
*/
private static double calcEdgeOffset(int i, int n, boolean away_from) {
double offset = (double) 35 * (2.0 * (double) i + 1.0 - (double) n) / 2.0 / (double) n;
double direction = away_from ? 1.0 : -1.0;
return direction * offset;
}
/**
* Calculates the distance between two points.
*
* @param p1 the 'from' point.
* @param p2 the 'to' point.
* @return the distance between p1 and p2.
*/
private static double distance(Point p1, Point p2) {
double d = (p1.x - p2.x) * (p1.x - p2.x);
d += (p1.y - p2.y) * (p1.y - p2.y);
d = FastMath.sqrt(d);
return d;
}
/**
* Deletes all selected nodes in the workbench plus any edges that have had one of their nodes deleted in the
* process.
*/
public final void deleteSelectedObjects() {
Component[] components = getComponents();
List graphNodes = new ArrayList<>();
List graphEdges = new ArrayList<>();
for (Component comp : components) {
if (comp instanceof DisplayNode) {
if (!isDeleteVariablesAllowed()) {
continue;
}
DisplayNode node = (DisplayNode) comp;
if (node.isSelected()) {
graphNodes.add(node);
}
} else if (comp instanceof IDisplayEdge) {
IDisplayEdge edge = (IDisplayEdge) comp;
if (edge.isSelected()) {
graphEdges.add(edge);
}
}
}
for (DisplayNode graphNode : graphNodes) {
removeNode(graphNode);
}
for (IDisplayEdge displayEdge : graphEdges) {
try {
removeEdge(displayEdge);
resetEdgeOffsets(displayEdge);
} catch (Exception e) {
if (isNodeEdgeErrorsReported()) {
JOptionPane.showMessageDialog(JOptionUtils.centeringComp(), e.getMessage());
}
}
}
}
/**
* Deselects all edges and nodes in the workbench.
*/
public final void deselectAll() {
Component[] components = getComponents();
for (Component comp : components) {
if (comp instanceof IDisplayEdge) {
((IDisplayEdge) comp).setSelected(false);
} else if (comp instanceof DisplayNode) {
((DisplayNode) comp).setSelected(false);
}
}
repaint();
firePropertyChange("BackgroundClicked", null, null);
}
/**
* Returns the workbench mode. One of SELECT_MOVE, ADD_NODE, ADD_EDGE.
*
* @return the workbench mode. One of SELECT_MOVE, ADD_NODE, ADD_EDGE.
*/
public final int getWorkbenchMode() {
return this.workbenchMode;
}
/**
* Sets the mode of the workbench to the indicated new mode. (Ignores unrecognized modes.)
*
* @param workbenchMode One of SELECT_MOVE, ADD_NODE, ADD_EDGE.
*/
public final void setWorkbenchMode(int workbenchMode) {
if (workbenchMode == AbstractWorkbench.SELECT_MOVE) {
if (this.workbenchMode != AbstractWorkbench.SELECT_MOVE) {
this.workbenchMode = AbstractWorkbench.SELECT_MOVE;
setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
deselectAll();
} else {
setCursor(new Cursor(Cursor.HAND_CURSOR));
}
} else if (workbenchMode == AbstractWorkbench.ADD_NODE) {
if (this.workbenchMode != AbstractWorkbench.ADD_NODE) {
this.workbenchMode = AbstractWorkbench.ADD_NODE;
deselectAll();
}
} else if (workbenchMode == AbstractWorkbench.ADD_EDGE) {
if (this.workbenchMode != AbstractWorkbench.ADD_EDGE) {
this.workbenchMode = AbstractWorkbench.ADD_EDGE;
deselectAll();
}
} else {
throw new IllegalArgumentException("Must be SELECT_MOVE, " + "ADD_NODE, or ADD_EDGE.");
}
}
/**
* @return the Graph this workbench displays.
*/
public final Graph getGraph() {
return this.graph;
}
/**
* Sets the display workbench graph to the graph
and then notifies listeners of the change. (Called
* when the workbench is first constructed as well as whenever the workbench model is changed.)
*
* @param graph Ibid.
*/
public final void setGraph(Graph graph) {
setGraphWithoutNotify(graph);
// if this workbench is sitting inside of a scrollpane,
// let the scrollpane know how big it is.
scrollRectToVisible(getVisibleRect());
registerKeys();
firePropertyChange("graph", null, graph);
firePropertyChange("modelChanged", null, null);
}
/**
* Returns the currently selected nodes as a list.
*
* @return the currently selected nodes as a list.
*/
public final List getSelectedNodes() {
List selectedNodes = new ArrayList<>();
Component[] components = getComponents();
for (Component comp : components) {
if ((comp instanceof DisplayNode) && ((DisplayNode) comp).isSelected()) {
selectedNodes.add((DisplayNode) comp);
}
}
return selectedNodes;
}
/**
* Returns the current selected node, if exactly one is selected; otherwise, return null.
*
* @return the current selected node, if exactly one is selected; otherwise, return null.
*/
public final DisplayNode getSelectedNode() {
List selectedNodes = getSelectedNodes();
if (selectedNodes.size() == 1) {
return selectedNodes.get(0);
} else {
return null;
}
}
/**
* @return the currently selected nodes as a vector.
*/
public final List getSelectedComponents() {
List selectedComponents = new ArrayList<>();
Component[] components = getComponents();
for (Component comp : components) {
if (comp instanceof DisplayNode && ((DisplayNode) comp).isSelected()) {
selectedComponents.add(comp);
} else if (comp instanceof IDisplayEdge && ((IDisplayEdge) comp).isSelected()) {
selectedComponents.add(comp);
}
}
return selectedComponents;
}
/**
* @param displayEdge Ibid.
* @return the model edge for the given display edge.
*/
public final Edge getModelEdge(IDisplayEdge displayEdge) {
return (Edge) getDisplayToModel().get(displayEdge);
}
private boolean isAllowMultipleNodeSelection() {
return this.allowMultipleSelection;
}
/**
* Returns true iff nodes and edges may be added/removed by the user or node/edge properties edited.
*
* @return Ibid.
*/
private boolean isAllowDoubleClickActions() {
return this.allowDoubleClickActions;
}
/**
* Sets whether adding or removing of nodes or edges will be allowed, or node/edge properties edited.
*
* @param allowDoubleClickActions Ibid.
*/
public final void setAllowDoubleClickActions(boolean allowDoubleClickActions) {
if (isAllowDoubleClickActions() && !allowDoubleClickActions) {
// unregisterKeys();
this.allowDoubleClickActions = false;
} else if (!isAllowDoubleClickActions() && allowDoubleClickActions) {
// registerKeys();
this.allowDoubleClickActions = true;
}
}
/**
* Returns true iff nodes may be dragged to new locations by the user.
*
* @return Ibid.
*/
private boolean isAllowNodeDragging() {
return this.allowNodeDragging;
}
/**
* Sets whether nodes may be dragged by the user to new locations.
*
* @param allowNodeDragging Ibid.
*/
public void setAllowNodeDragging(boolean allowNodeDragging) {
this.allowNodeDragging = allowNodeDragging;
}
/**
* Returns true iff nodes and edges may be selected by the user.
*
* @return Ibid.
*/
private boolean isAllowNodeEdgeSelection() {
return this.allowNodeEdgeSelection;
}
/**
* Sets whether nodes and edges may be selected by the user.
*
* @param allowNodeEdgeSelection Ibid.
*/
public void setAllowNodeEdgeSelection(boolean allowNodeEdgeSelection) {
this.allowNodeEdgeSelection = allowNodeEdgeSelection;
}
/**
* Returns true iff edge reorientations are permitted.
*
* @return Ibid.
*/
private boolean isAllowEdgeReorientation() {
return this.allowEdgeReorientations;
}
/**
* Returns true iff multiple nodes may be selected by the user using a rubberband.
*
* @return Ibid.
*/
public boolean isAllowMultipleSelection() {
return this.allowMultipleSelection;
}
/**
* Sets whether edge reorientations are permitted.
*
* @param allowEdgeReorientations Ibid.
*/
public final void setAllowEdgeReorientations(boolean allowEdgeReorientations) {
this.allowEdgeReorientations = allowEdgeReorientations;
}
public final Graph getSamplingGraph() {
return samplingGraph;
}
public final void setSamplingGraph(Graph graph) {
samplingGraph = graph;
}
/**
* Sets the label for an edge to a particular JComponent. The label will be displayed halfway along the edge
* slightly off to the side.
*
* @param modelEdge the edge for the label.
* @param label the label for the component.
*/
public final void setEdgeLabel(Edge modelEdge, JComponent label) {
if (modelEdge == null) {
throw new NullPointerException("Attempt to set a label on a " + "null model edge: " + null);
} else if (!getModelEdgesToDisplay().containsKey(modelEdge)) {
throw new IllegalArgumentException("Attempt to set a label on " + "a model edge that's not " + "in the editor: " + modelEdge);
}
// retrieve display edge from map, or create one if not
// there...
DisplayEdge displayEdge = (DisplayEdge) getModelEdgesToDisplay().get(modelEdge);
GraphEdgeLabel oldLabel = getEdgeLabel(displayEdge);
if (oldLabel != null) {
remove(oldLabel);
}
if (label != null) {
GraphEdgeLabel edgeLabel = new GraphEdgeLabel(displayEdge, label);
edgeLabel.setSize(edgeLabel.getPreferredSize());
add(edgeLabel, 0);
setEdgeLabel(displayEdge, edgeLabel);
}
revalidate();
repaint();
}
/**
* Node tooltip to show the node attributes - Added by Kong
*/
public final void setNodeToolTip(Node modelNode, String toolTipText) {
if (modelNode == null) {
throw new NullPointerException("Attempt to set a label on a " + "null model node: " + null);
} else if (!getModelNodesToDisplay().containsKey(modelNode)) {
throw new IllegalArgumentException("Attempt to set a label on " + "a model node that's not " + "in the editor: " + modelNode);
}
DisplayNode displayNode = (DisplayNode) getModelNodesToDisplay().get(modelNode);
displayNode.setToolTipText(toolTipText);
}
/**
* Edge tooltip to show the edge type and probabilities - Added by Zhou
*/
public final void setEdgeToolTip(Edge modelEdge, String toolTipText) {
if (modelEdge == null) {
throw new NullPointerException("Attempt to set a label on a " + "null model edge: " + null);
} else if (!getModelEdgesToDisplay().containsKey(modelEdge)) {
throw new IllegalArgumentException("Attempt to set a label on " + "a model edge that's not " + "in the editor: " + modelEdge);
}
DisplayEdge displayEdge = (DisplayEdge) getModelEdgesToDisplay().get(modelEdge);
displayEdge.setToolTipText(toolTipText);
}
/**
* Sets the label for a node to a particular JComponent. The label will be displayed slightly off to the right of
* the node.
*
* @param label Ibid.
*/
public final void setNodeLabel(Node modelNode, JComponent label, int x, int y) {
if (modelNode == null) {
throw new NullPointerException("Attempt to set a label on a " + "null model node.");
} else if (!getModelNodesToDisplay().containsKey(modelNode)) {
return;
}
// retrieve display node from map, or create one if not
// there...
DisplayNode displayNode = (DisplayNode) getModelNodesToDisplay().get(modelNode);
GraphNodeLabel oldLabel = getNodeLabel(displayNode);
if (oldLabel != null) {
remove(oldLabel);
}
if (label != null) {
GraphNodeLabel nodeLabel = new GraphNodeLabel(displayNode, label, x, y);
nodeLabel.setSize(nodeLabel.getPreferredSize());
add(nodeLabel, 0);
setNodeLabel(displayNode, nodeLabel);
}
revalidate();
repaint();
}
/**
* Sets the stoke width for an edge.
*
* @param edge The edge in question.
* @param width The stroke width. By detault this is 1.0f. 5.0f is pretty thick.
*/
public final void setStrokeWidth(Edge edge, float width) {
IDisplayEdge displayEdge = (IDisplayEdge) getModelEdgesToDisplay().get(edge);
displayEdge.setStrokeWidth(width);
}
private void setEdgeLabel(IDisplayEdge displayEdge, GraphEdgeLabel edgeLabel) {
getDisplayToLabels().put(displayEdge, edgeLabel);
}
private GraphEdgeLabel getEdgeLabel(IDisplayEdge displayEdge) {
return (GraphEdgeLabel) getDisplayToLabels().get(displayEdge);
}
private void setNodeLabel(DisplayNode displayNode, GraphNodeLabel nodeLabel) {
getDisplayToLabels().put(displayNode, nodeLabel);
}
private GraphNodeLabel getNodeLabel(DisplayNode displayNode) {
return (GraphNodeLabel) getDisplayToLabels().get(displayNode);
}
/**
* Removes the label from an edge.
*
* @param edge the edge from which a label is to be removed.
*/
private void removeEdgeLabel(Edge edge) {
IDisplayEdge displayEdge = (IDisplayEdge) getModelEdgesToDisplay().get(edge);
GraphEdgeLabel edgeLabel = getEdgeLabel(displayEdge);
if (edgeLabel == null) {
return;
}
remove(edgeLabel);
getDisplayToLabels().remove(displayEdge);
}
public final Map getModelEdgesToDisplay() {
return this.modelEdgesToDisplay;
}
public final Map getModelNodesToDisplay() {
return this.modelNodesToDisplay;
}
private Map getDisplayToModel() {
return this.displayToModel;
}
/**
* Selects the editor node corresponding to the given model node.
*/
public final void selectNode(Node modelNode) {
if (!isAllowNodeEdgeSelection()) {
return;
}
DisplayNode graphNode = (DisplayNode) getModelNodesToDisplay().get(modelNode);
if (graphNode != null) {
graphNode.setSelected(true);
}
}
/**
* Selects the editor edge corresponding to the given model edge.
*/
public final void selectEdge(Edge modelEdge) {
IDisplayEdge graphEdge = (IDisplayEdge) getModelEdgesToDisplay().get(modelEdge);
graphEdge.setSelected(true);
}
/**
* Selects all and only those edges that are connecting selected nodes. Should be called after every time the node
* selection is changed.
*/
public final void selectConnectingEdges() {
if (!isAllowNodeEdgeSelection()) {
return;
}
Component[] components = getComponents();
for (Component comp : components) {
if (comp instanceof IDisplayEdge) {
IDisplayEdge graphEdge = (IDisplayEdge) comp;
DisplayNode node1 = graphEdge.getComp1();
DisplayNode node2 = graphEdge.getComp2();
if (node2 != null) {
boolean selected = node1.isSelected() && node2.isSelected();
graphEdge.setSelected(selected);
}
}
}
}
/**
* Selects all and only those edges that are connecting selected nodes. Should be called after every time the node
* selection is changed.
*/
private void selectConnectingEdges(List displayNodes) {
if (!isAllowNodeEdgeSelection()) {
return;
}
Component[] components = getComponents();
for (Component comp : components) {
if (comp instanceof IDisplayEdge) {
IDisplayEdge graphEdge = (IDisplayEdge) comp;
DisplayNode node1 = graphEdge.getComp1();
DisplayNode node2 = graphEdge.getComp2();
if (node1 instanceof GraphNodeError) {
continue;
}
if (node2 instanceof GraphNodeError) {
continue;
}
if (node2 != null) {
boolean selected = displayNodes.contains(node1) && displayNodes.contains(node2);
graphEdge.setSelected(selected);
}
}
}
}
/**
* Paints the background of the workbench.
*/
public final void paint(Graphics g) {
g.setColor(getBackground());
g.fillRect(0, 0, getWidth(), getHeight());
super.paint(g);
}
/**
* Scrolls the workbench image so that the given node is in view, then selects that node.
*
* @param modelNode the model node to show.
*/
public final void scrollWorkbenchToNode(Node modelNode) {
Object o = getModelNodesToDisplay().get(modelNode);
DisplayNode displayNode = (DisplayNode) o;
if (displayNode != null) {
Rectangle bounds = displayNode.getBounds();
scrollRectToVisible(bounds);
deselectAll();
if (isAllowNodeEdgeSelection()) {
displayNode.setSelected(true);
}
}
}
public Color getBackground() {
return super.getBackground();
}
public void setBackground(Color color) {
super.setBackground(color);
repaint();
}
public void layoutByGraph(Graph layoutGraph) {
LayoutUtil.arrangeBySourceGraph(this.graph, layoutGraph);
for (Node modelNode : this.graph.getNodes()) {
DisplayNode displayNode = (DisplayNode) getModelNodesToDisplay().get(modelNode);
if (displayNode == null) {
continue;
}
Dimension dim = displayNode.getPreferredSize();
int centerX = modelNode.getCenterX();
int centerY = modelNode.getCenterY();
displayNode.setSize(dim);
displayNode.setLocation(centerX - dim.width / 2, centerY - dim.height / 2);
}
// setGraphWithoutNotify(graph);
}
public Knowledge getKnowledge() {
return this.knowledge;
}
public void setKnowledge(Knowledge knowledge) {
this.knowledge = knowledge;
}
public Graph getSourceGraph() {
return getGraph();
}
/**
* Not implemented for the workbench.
*/
public void layoutByKnowledge() {
GraphSearchUtils.arrangeByKnowledgeTiers(this.graph, getKnowledge());
revalidate();
repaint();
}
public Rectangle getVisibleRect() {
List nodes = this.graph.getNodes();
if (nodes.isEmpty()) {
return new Rectangle();
}
DisplayNode displayNode = (DisplayNode) getModelNodesToDisplay().get(nodes.get(0));
Rectangle rect = displayNode.getBounds();
for (int i = 1; i < nodes.size(); i++) {
displayNode = (DisplayNode) getModelNodesToDisplay().get(nodes.get(i));
rect = rect.union(displayNode.getBounds());
}
rect = rect.union(super.getVisibleRect());
return rect;
// return super.getVisibleRect();
}
public void scrollNodesToVisible(List nodes) {
if (nodes == null || nodes.isEmpty()) {
return;
}
DisplayNode displayNode = (DisplayNode) getModelNodesToDisplay().get(nodes.get(0));
Rectangle rect = displayNode.getBounds();
for (int i = 1; i < nodes.size(); i++) {
displayNode = (DisplayNode) getModelNodesToDisplay().get(nodes.get(i));
rect = rect.union(displayNode.getBounds());
}
adjustPreferredSize();
scrollRectToVisible(rect);
}
public Component getComponent(Edge edge) {
return (DisplayEdge) this.modelEdgesToDisplay.get(edge);
}
public Component getComponent(Node node) {
return (DisplayNode) this.modelNodesToDisplay.get(node);
}
public abstract Node getNewModelNode();
public abstract DisplayNode getNewDisplayNode(Node modelNode);
public abstract IDisplayEdge getNewDisplayEdge(Edge modelEdge);
public abstract Edge getNewModelEdge(Node node1, Node node2);
// ============================PRIVATE METHODS=========================//
public abstract IDisplayEdge getNewTrackingEdge(DisplayNode displayNode, Point mouseLoc);
/**
* Sets the display workbench model to the indicated model. (Called when the workbench is first constructed as well
* as whenever the workbench model is changed.)
*/
private void setGraphWithoutNotify(Graph graph) {
if (graph == null) {
throw new IllegalArgumentException("Graph model cannot be null.");
}
this.graph = graph;
this.modelEdgesToDisplay = new HashMap<>();
this.modelNodesToDisplay = new HashMap<>();
this.displayToModel = new HashMap<>();
this.displayToLabels = new HashMap<>();
removeAll();
graph.addPropertyChangeListener(this.propChangeHandler);
// extract the current contents from the model...
List nodes = graph.getNodes();
for (Node node : nodes) {
if (!getModelNodesToDisplay().containsKey(node)) {
addNode(node);
}
}
Set edges = graph.getEdges();
for (Edge edge : edges) {
if (!getModelEdgesToDisplay().containsKey(edge)) {
addEdge(edge);
}
}
adjustPreferredSize();
if (getPreferredSize().getWidth() > getMaxX()) {
setMaxX((int) getPreferredSize().getWidth());
}
if (getPreferredSize().getHeight() > getMaxY()) {
setMaxY((int) getPreferredSize().getHeight());
}
// Create a graph's legend
if (graph.getAllAttributes().size() > 0) {
final int margin = 5;
DisplayLegend legend = new DisplayLegend(graph.getAllAttributes());
legend.setLocation(margin, margin);
// add the display node
add(legend, 0);
}
revalidate();
repaint();
}
/**
* @return the maximum x value (for dragging).
*/
private int getMaxX() {
return this.maxX;
}
/**
* Sets the maximum x value (for dragging).
*
* @param maxX the maximum x value (Must be greater than or equal to 100).
*/
private void setMaxX(int maxX) {
if (maxX < 100) {
throw new IllegalArgumentException();
}
this.maxX = maxX;
}
/**
* Adjusts the bounds of the workbench to included the point (0, 0) and the union of the bounds rectangles of all of
* the components in the workbench. This allows for scrollbars to automatically reflect the position of a component
* which is being dragged.
*/
private void adjustPreferredSize() {
Component[] components = getComponents();
Rectangle r = new Rectangle(0, 0, 400, 400);
for (Component component1 : components) {
r = r.union(component1.getBounds());
}
// Apparently both of these are required to get the scrollbars to reset.
// I'm
// guessing the scrollbars pay attention to preferred size but the
// setSize() e
// call throws an event. jdramsey 1/24/2014
setPreferredSize(new Dimension(r.width, r.height));
setSize(new Dimension(r.width, r.height));
}
/**
* Adds a session node to the workbench centered at the specified location; the type of node added is determined by
* the mode of the workbench.
*
* @param loc the location of the center of the session node.
*/
private void addNode(Point loc) throws IllegalArgumentException {
Node modelNode = getNewModelNode();
if (modelNode.getNodeType() == NodeType.MEASURED && !isAddMeasuredVarsAllowed()) {
throw new IllegalArgumentException("Attempt to add measured variable " + "when this has been disallowed.");
}
// Add the model node to the display workbench model.
modelNode.setCenterX(loc.x);
modelNode.setCenterY(loc.y);
getGraph().addNode(modelNode);
firePropertyChange("modelChanged", null, null);
}
/**
* Adds the given model node to the model and adds a corresponding display node to the display.
*
* @param modelNode the model node.
*/
private void addNode(Node modelNode) {
if (getModelNodesToDisplay().containsKey(modelNode)) {
return;
}
if (modelNode.getNodeType() == NodeType.MEASURED && !isAddMeasuredVarsAllowed()) {
throw new IllegalArgumentException("Attempt to add measured variable " + "when this has been disallowed.");
}
// Pick a location for the node:
// (1) If the node has a location in the workbench info object, use
// that.
// (2) If not, then if the node is an error term, pick a location
// that's down and to the right of its associated term.
// (3) If it's an error term that doesn't have an associated node,
// don't add it.
// (4) Otherwise, pick a random location.
int centerX = modelNode.getCenterX();
int centerY = modelNode.getCenterY();
// Construct a display node for the model node.
DisplayNode displayNode = getNewDisplayNode(modelNode);
// Link the display node to the model node.
getModelNodesToDisplay().put(modelNode, displayNode);
getDisplayToModel().put(displayNode, modelNode);
// Set the bounds of the display node.
Dimension dim = displayNode.getPreferredSize();
displayNode.setSize(dim);
displayNode.setLocation(centerX - dim.width / 2, centerY - dim.height / 2);
// add the display node
add(displayNode, 0);
snapNodeToGrid(displayNode);
// Add listeners.
displayNode.addComponentListener(this.compHandler);
displayNode.addMouseListener(this.mouseHandler);
displayNode.addMouseMotionListener(this.mouseMotionHandler);
displayNode.addPropertyChangeListener(this.propChangeHandler);
adjustForNewModelNodes();
repaint();
validate();
firePropertyChange("nodeAdded", null, displayNode);
firePropertyChange("allNodesAdded", null, null);
}
private void adjustForNewModelNodes() {
this.graph.getNodes().forEach(node -> {
if (this.modelNodesToDisplay.get(node) == null) {
int centerX = node.getCenterX();
int centerY = node.getCenterY();
// Construct a display node for the model node.
DisplayNode displayNode = getNewDisplayNode(node);
// Link the display node to the model node.
this.modelNodesToDisplay.put(node, displayNode);
this.displayToModel.put(displayNode, node);
// Set the bounds of the display node.
Dimension dim = displayNode.getPreferredSize();
displayNode.setSize(dim);
displayNode.setLocation(centerX - dim.width / 2, centerY - dim.height / 2);
// add the display node
add(displayNode, 0);
// Add listeners.
displayNode.addComponentListener(this.compHandler);
displayNode.addMouseListener(this.mouseHandler);
displayNode.addMouseMotionListener(this.mouseMotionHandler);
displayNode.addPropertyChangeListener(this.propChangeHandler);
// Fire notification event. jdramsey 12/11/01
firePropertyChange("nodeAdded", null, displayNode);
}
});
Map trashMap = new HashMap<>();
this.displayToModel.forEach((k, v) -> {
if (k instanceof DisplayNode && v instanceof Node) {
DisplayNode displayNode = (DisplayNode) k;
Node node = (Node) v;
if (!this.graph.containsNode(node)) {
trashMap.put(displayNode, node);
}
}
});
trashMap.forEach((k, v) -> {
this.displayToModel.remove(k);
this.modelNodesToDisplay.remove(v);
firePropertyChange("nodeRemoved", null, k);
});
this.graph.getNodes().forEach(node -> {
int centerX = node.getCenterX();
int centerY = node.getCenterY();
DisplayNode displayNode = (DisplayNode) this.modelNodesToDisplay.get(node);
// Set the bounds of the display node.
Dimension dim = displayNode.getPreferredSize();
displayNode.setSize(dim);
displayNode.setLocation(centerX - dim.width / 2, centerY - dim.height / 2);
});
}
/**
* Adds the specified edge to the model and updates the display to match.
*
* @param modelEdge the mode edge.
*/
private void addEdge(Edge modelEdge) {
if (modelEdge == null) {
return;
}
if (modelEdge.isNull()) {
return;
}
if (modelEdge.getNode1() == modelEdge.getNode2()) {
return;
}
if (getModelEdgesToDisplay().containsKey(modelEdge)) {
return;
}
if (!getGraph().containsEdge(modelEdge)) {
System.out.println("Attempt to add edge not in model: " + modelEdge);
return;
// throw new IllegalArgumentException("Attempt to add edge not in model.");
}
// construct a display edge for the model edge
Node modelNodeA = modelEdge.getNode1();
Node modelNodeB = modelEdge.getNode2();
DisplayNode displayNodeA = displayNode(modelNodeA);
DisplayNode displayNodeB = displayNode(modelNodeB);
if ((displayNodeA == null) || (displayNodeB == null)) {
return;
}
IDisplayEdge displayEdge = getNewDisplayEdge(modelEdge);
if (displayEdge == null) {
return;
}
if (modelEdge.isHighlighted()) {
displayEdge.setHighlighted(true);
}
if (doPagColoring) {
// visible edges.
boolean solid = modelEdge.getProperties().contains(Edge.Property.nl);
// definitely direct edges.
boolean thick = modelEdge.getProperties().contains(Edge.Property.dd);
// definitely direct edges.
// Color green = Color.green.darker();
// Color lineColor = modelEdge.getProperties().contains(Edge.Property.nl) ? green
// : this.graph.isHighlighted(modelEdge) ? displayEdge.getHighlightedColor() : modelEdge.getLineColor();
// displayEdge.setLineColor(lineColor);
displayEdge.setSolid(solid);
displayEdge.setThick(thick);
}
// Link the display edge to the model edge.
getModelEdgesToDisplay().put(modelEdge, displayEdge);
getDisplayToModel().put(displayEdge, modelEdge);
// Add the display edge to the workbench. (Add it to the "back".)
add((Component) displayEdge, -1);
// Add listeners.
((Component) displayEdge).addComponentListener(this.compHandler);
((Component) displayEdge).addMouseListener(this.mouseHandler);
((Component) displayEdge).addMouseMotionListener(this.mouseMotionHandler);
((Component) displayEdge).addPropertyChangeListener(this.propChangeHandler);
// Reset offsets (for multiple edges between node pairs).
resetEdgeOffsets(displayEdge);
// Fire notification. jdramsey 12/11/01
firePropertyChange("edgeAdded", null, displayEdge);
}
/**
* Scans through all edges between two nodes, resets those edge's offset values. Note that these offsets are stored
* in the edges themselves so this does not have to be recomputed all the time
*/
private void resetEdgeOffsets(IDisplayEdge graphEdge) {
try {
DisplayNode displayNode1 = graphEdge.getNode1();
DisplayNode displayNode2 = graphEdge.getNode2();
Node node1 = displayNode1.getModelNode();
Node node2 = displayNode2.getModelNode();
Graph graph = getGraph();
List edges = graph.getEdges(node1, node2);
for (int i = 0; i < edges.size(); i++) {
Edge edge = edges.iterator().next();
Node _node1 = edge.getNode1();
boolean awayFrom = (_node1 == node1);
IDisplayEdge displayEdge = (IDisplayEdge) getModelEdgesToDisplay().get(edge);
if (displayEdge != null) {
displayEdge.setOffset(AbstractWorkbench.calcEdgeOffset(i, edges.size(), awayFrom));
}
}
} catch (UnsupportedOperationException e) {
// This happens for the session workbench. The getEdges() method
// is not implemented for it. Not sure if we'll ever need it to
// be implemented. jdramsey 4/14/2004
}
}
private DisplayNode displayNode(Node modelNodeA) {
Object o = getModelNodesToDisplay().get(modelNodeA);
if (o == null) {
reconstiteMaps();
o = getModelNodesToDisplay().get(modelNodeA);
}
return (DisplayNode) o;
}
/**
* Finds the nearest node to a given point. More specifically, finds the node whose center point is nearest to the
* given point. (If more than one such node exists, the one with lowest z-order is returned.)
*
* @param p the point for which the nearest node is requested.
* @return the nearest node to point p.
*/
private DisplayNode findNearestNode(Point p) {
Component[] components = getComponents();
double distance, leastDistance = Double.POSITIVE_INFINITY;
int index = -1;
for (int i = 0; i < components.length; i++) {
if (components[i] instanceof DisplayNode) {
DisplayNode node = (DisplayNode) components[i];
distance = AbstractWorkbench.distance(p, node.getCenterPoint());
if (distance < leastDistance) {
leastDistance = distance;
index = i;
}
}
}
if (index != -1) {
return (DisplayNode) (components[index]);
} else {
return null;
}
}
/**
* Finishes drawing a rubberband.
*
* @see #startRubberband
*/
private void finishRubberband() {
if (this.rubberband != null) {
remove(this.rubberband);
this.rubberband = null;
repaint();
}
}
/**
* Finishes the drawing of a new edge.
*
* @see #startEdge
*/
private void finishEdge() {
if (getTrackedEdge() == null) {
return;
}
// Retrieve the two display components this edge should connect.
DisplayNode comp1 = getTrackedEdge().getComp1();
Point p = getTrackedEdge().getTrackPoint();
DisplayNode comp2 = findNearestNode(p);
// Edges to self are not supported.
if (comp1 != comp2) {
// Construct the model edge
try {
Node node1 = (Node) (getDisplayToModel().get(comp1));
Node node2 = (Node) (getDisplayToModel().get(comp2));
Edge modelEdge = getNewModelEdge(node1, node2);
// Add model edge to model; this will result in an event fired
// back from the model to add a display edge, so we can remove
// the tracked edge and forget about it.
this.graph.addEdge(modelEdge);
setGraph(this.graph);
firePropertyChange("modelChanged", null, null);
} catch (Exception e) {
e.printStackTrace();
if (isNodeEdgeErrorsReported()) {
JOptionPane.showMessageDialog(JOptionUtils.centeringComp(), e.getMessage());
}
}
}
remove((Component) getTrackedEdge());
repaint();
// reset the tracked edge to null to wait for the next attempt
// at adding an edge.
this.trackedEdge = null;
}
/**
* Fires a property change event, property name = "selectedNodes", with the new node selection as its new value (a
* List).
*/
private void fireNodeSelection() {
Component[] components = getComponents();
List selection = new LinkedList<>();
for (Component component : components) {
if (component instanceof DisplayNode) {
DisplayNode displayNode = (DisplayNode) component;
if (displayNode.isSelected()) {
Node modelNode = (Node) (getDisplayToModel().get(displayNode));
selection.add(modelNode);
}
}
}
if (isAllowMultipleNodeSelection()) {
firePropertyChange("selectedNodes", null, selection);
} else {
if (selection.size() == 1) {
firePropertyChange("selectedNode", null, selection.get(0));
} else {
throw new IllegalStateException(
"Multiple or null selection detected " + "when single selection mode is set.");
}
}
}
/**
* Registers the remove and backspace keys to remove selected objects.
*/
private void registerKeys() {
getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), "DELETE");
getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "DELETE");
Action deleteAction = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
AbstractWorkbench workbench = (AbstractWorkbench) e.getSource();
List components = workbench.getSelectedComponents();
int numNodes = 0, numEdges = 0;
for (Component c : components) {
if (c instanceof DisplayNode) {
numNodes++;
} else if (c instanceof DisplayEdge) {
numEdges++;
}
}
StringBuilder buf = new StringBuilder();
if (isDeleteVariablesAllowed()) {
buf.append("Number of nodes selected = ");
buf.append(numNodes);
}
buf.append("\nNumber of edges selected = ");
buf.append(numEdges);
buf.append("\n\nDelete selected items?");
int ret = JOptionPane.showConfirmDialog(workbench, buf.toString());
if (ret != JOptionPane.YES_OPTION) {
return;
}
deleteSelectedObjects();
}
};
getActionMap().put("DELETE", deleteAction);
if (this.controlDispatcher == null) {
this.controlDispatcher = this::respondToControlKey;
}
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(this.controlDispatcher);
}
private boolean respondToControlKey(KeyEvent e) {
if (hasFocus()) {
int keyCode = e.getKeyCode();
int id = e.getID();
if (keyCode == KeyEvent.VK_ALT) {
if (id == KeyEvent.KEY_PRESSED) {
this.workbenchMode = AbstractWorkbench.ADD_EDGE;
setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR));
} else if (id == KeyEvent.KEY_RELEASED) {
finishEdge();
this.workbenchMode = AbstractWorkbench.SELECT_MOVE;
setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
}
}
}
return false;
}
AbstractWorkbench getWorkbench() {
return this;
}
/**
* In response to a request from the model, removes the display node corresponding to the given model node from the
* display. Assumes that the model node was removed from the model and that this request is coming from the
* propertyChange() method. DO NOT CALL THIS METHOD DIRECTLY; RATHER, REMOVE THE MODEL NODE DIRECTLY FROM THE MODEL
* AND LET THE ENSUING EVENTS FROM MODEL TO DISPLAY REMOVE THE NODE FROM THE DISPLAY. OTHERWISE, THE DISPLAY AND
* MODEL WILL GET OUT OF SYNC!
*
* @param modelNode the model node to be removed.
*/
private void removeNode(Node modelNode) {
if (modelNode == null) {
throw new NullPointerException("Attempt to remove a null model node.");
}
DisplayNode displayNode = (DisplayNode) (getModelNodesToDisplay().get(modelNode));
if (displayNode == null) {
getModelNodesToDisplay().remove(modelNode);
} else {
setNodeLabel(modelNode, null, 0, 0);
remove(displayNode);
getDisplayToModel().remove(displayNode);
getModelEdgesToDisplay().remove(modelNode);
displayNode.removePropertyChangeListener(this.propChangeHandler);
repaint();
// Fire notification.
firePropertyChange("nodeRemoved", displayNode, null);
}
}
/**
* Removes the given display node from the workbench by requesting that the model remove the corresponding model
* node.
*
* @param displayNode the display node.
*/
private void removeNode(DisplayNode displayNode) {
if (displayNode == null) {
return;
}
Node modelNode = (Node) (getDisplayToModel().get(displayNode));
if (modelNode == null) {
return;
}
// Error nodes cannot be removed explicitly; they must be removed by
// removing the nodes they are attached to.
if (!(modelNode.getNodeType() == NodeType.ERROR)) {
getGraph().removeNode(modelNode);
}
adjustForNewModelNodes();
firePropertyChange("modelChanged", null, null);
}
/**
* In response to a request from the model, removes the display edge corresponding to the given model edge from the
* display. Assumes that the model edge was removed from the model and that this request is coming from the
* propertyChange() method. DO NOT CALL THIS METHOD DIRECTLY; RATHER, REMOVE THE MODEL EDGE FROM THE MODEL AND LET
* THE ENSUING EVENTS (FROM MODEL TO DISPLAY) REMOVE THE EDGE FROM THE DISPLAY. OTHERWISE, THE DISPLAY AND MODEL
* WILL GET OUT OF SYNC.
*/
private void removeEdge(Edge modelEdge) {
if (modelEdge == null) {
return;
}
IDisplayEdge displayEdge = (IDisplayEdge) (getModelEdgesToDisplay().get(modelEdge));
if (displayEdge == null) {
getModelEdgesToDisplay().remove(modelEdge);
} else {
removeEdgeLabel(modelEdge);
remove((Component) displayEdge);
getDisplayToModel().remove(displayEdge);
getModelEdgesToDisplay().remove(modelEdge);
((Component) displayEdge).removePropertyChangeListener(this.propChangeHandler);
repaint();
firePropertyChange("edgeRemoved", displayEdge, null);
}
}
/**
* Removes the given display edge from the workbench by requesting that the model remove the corresponding model
* edge.
*/
private void removeEdge(IDisplayEdge displayEdge) {
if (displayEdge == null) {
return;
}
Edge modelEdge = (Edge) (getDisplayToModel().get(displayEdge));
try {
getGraph().removeEdge(modelEdge);
firePropertyChange("modelChanged", null, null);
} catch (Exception e) {
if (isNodeEdgeErrorsReported()) {
JOptionPane.showMessageDialog(JOptionUtils.centeringComp(), e.getMessage());
}
}
}
/**
* Selects all of the nodes inside the rubberband and all edges connecting selected nodes.
*
* @param rubberband The rubberband shape appearing in the GUI.
* @param edgesOnly Whether the shift key is down.
*/
private void selectAllInRubberband(Rubberband rubberband, boolean edgesOnly) {
if (!isAllowNodeEdgeSelection()) {
return;
}
if (!edgesOnly) {
deselectAll();
}
Shape rubberShape = rubberband.getShape();
Point rubberLoc = rubberband.getLocation();
Component[] components = getComponents();
List selectedNodes = new ArrayList<>();
for (Component comp : components) {
if (comp instanceof DisplayNode) {
Rectangle bounds = comp.getBounds();
bounds.translate(-rubberLoc.x, -rubberLoc.y);
DisplayNode graphNode = (DisplayNode) comp;
if (rubberShape.intersects(bounds)) {
selectedNodes.add(graphNode);
}
}
}
if (edgesOnly) {
selectConnectingEdges(selectedNodes);
} else {
for (DisplayNode graphNode : selectedNodes) {
graphNode.setSelected(true);
}
selectConnectingEdges();
}
}
/**
* @return the maximum y value (for dragging).
*/
private int getMaxY() {
return this.maxY;
}
/**
* Sets the maximum x value (for dragging).
*
* @param maxY the maximum Y value (Must be greater than or equal to 100).
*/
private void setMaxY(int maxY) {
if (maxY < 100) {
throw new IllegalArgumentException();
}
this.maxY = maxY;
}
/**
* Starts a tracked edge by anchoring it to one node and specifying the initial mouse track point.
*
* @param node the initial anchored node.
* @param mouseLoc the initial tracking mouse location.
*/
private void startEdge(DisplayNode node, Point mouseLoc) {
if (getTrackedEdge() != null) {
remove((Component) getTrackedEdge());
this.trackedEdge = null;
repaint();
}
this.trackedEdge = getNewTrackingEdge(node, mouseLoc);
add((Component) getTrackedEdge(), -1);
deselectAll();
}
/**
* Starts dragging a node.
*
* @param p the click point for the drag.
*/
private void startNodeDrag(Point p) {
if (!this.allowNodeDragging) {
return;
}
this.clickPoint = p;
this.dragNodes = getSelectedNodes();
}
/**
* Starts drawing a rubberband to allow selection of multiple nodes.
*
* @param p the point where the rubberband begins.
* @see #finishRubberband
*/
private void startRubberband(Point p) {
if (this.rubberband != null) {
remove(this.rubberband);
this.rubberband = null;
repaint();
}
if (isAllowNodeEdgeSelection() && isAllowMultipleNodeSelection()) {
this.rubberband = new Rubberband(p);
add(this.rubberband, 0);
this.rubberband.repaint();
}
}
private void snapNodeToGrid(DisplayNode node) {
final int gridSize = 20;
int x = node.getCenterPoint().x;
int y = node.getCenterPoint().y;
x = gridSize * ((x + gridSize / 2) / gridSize);
y = gridSize * ((y + gridSize / 2) / gridSize);
node.setLocation(x - node.getSize().width / 2, y - node.getSize().height / 2);
}
private void handleMouseClicked(MouseEvent e) {
Object source = e.getSource();
if (!isAllowNodeEdgeSelection()) {
return;
}
if (source instanceof DisplayNode) {
nodeClicked(source, e);
} else if (source instanceof IDisplayEdge) {
edgeClicked(source, e);
} else {
// This shouldn't be here, but I can't get it to work higher up.
if (e.isAltDown() && e.isControlDown() && e.isMetaDown()) {
if (Preferences.userRoot().getBoolean("experimental", false)) {
JOptionPane.showMessageDialog(JOptionUtils.centeringComp(),
"Setting to published interface on next restart.");
Preferences.userRoot().putBoolean("experimental", false);
} else {
JOptionPane.showMessageDialog(JOptionUtils.centeringComp(),
"Setting to experimental interface on next restart.");
Preferences.userRoot().putBoolean("experimental", true);
}
}
deselectAll();
}
}
private void edgeClicked(Object source, MouseEvent e) {
IDisplayEdge graphEdge = (IDisplayEdge) (source);
if (e.getClickCount() == 2) {
deselectAll();
graphEdge.launchAssociatedEditor();
firePropertyChange("edgeLaunch", graphEdge, graphEdge);
} else {
if (isAllowEdgeReorientation()) {
reorientEdge(source, e);
}
if (graphEdge.isSelected()) {
graphEdge.setSelected(false);
} else if (e.isShiftDown()) {
graphEdge.setSelected(true);
} else {
deselectAll();
graphEdge.setSelected(true);
}
}
// if (SearchGraphUtils.isLegalPag(graph).isLegalPag() || doPagColoring) {
// GraphUtils.addPagColoring(new EdgeListGraph(graph));
// }
}
private void nodeClicked(Object source, MouseEvent e) {
DisplayNode node = (DisplayNode) source;
if (e.getClickCount() == 2) {
if (isAllowDoubleClickActions()) {
doDoubleClickAction(node);
}
} else {
if (node.isSelected()) {
node.setSelected(false);
} else {
if (!e.isShiftDown()) {
deselectAll();
}
node.setSelected(true);
}
selectConnectingEdges();
fireNodeSelection();
}
// if (SearchGraphUtils.isLegalPag(graph).isLegalPag() || doPagColoring) {
// GraphUtils.addPagColoring(new EdgeListGraph(graph));
// }
}
private void reorientEdge(Object source, MouseEvent e) {
IDisplayEdge graphEdge = (IDisplayEdge) (source);
Point point = e.getPoint();
PointPair connectedPoints = graphEdge.getConnectedPoints();
Point pointA = connectedPoints.getFrom();
Point pointB = connectedPoints.getTo();
double length = AbstractWorkbench.distance(pointA, pointB);
double endpointRadius = FastMath.min(20.0, length / 3.0);
if (e.isShiftDown()) {
if (AbstractWorkbench.distance(point, pointA) < endpointRadius) {
toggleEndpoint(graphEdge, 1);
fireModelChanged();
} else if (AbstractWorkbench.distance(point, pointB) < endpointRadius) {
toggleEndpoint(graphEdge, 2);
firePropertyChange("modelChanged", null, null);
}
} else {
if (AbstractWorkbench.distance(point, pointA) < endpointRadius) {
directEdge(graphEdge, 1);
firePropertyChange("modelChanged", null, null);
} else if (AbstractWorkbench.distance(point, pointB) < endpointRadius) {
directEdge(graphEdge, 2);
firePropertyChange("modelChanged", null, null);
}
}
// if (SearchGraphUtils.isLegalPag(graph).isLegalPag() || doPagColoring) {
// GraphUtils.addPagColoring(new EdgeListGraph(graph));
// }
}
private void fireModelChanged() {
firePropertyChange("modelChanged", null, null);
}
private void handleMousePressed(MouseEvent e) {
grabFocus();
Object source = e.getSource();
Point loc = e.getPoint();
if (isRightClickPopupAllowed() && source == this && (SwingUtilities.isRightMouseButton(e) || e.isControlDown())) {
launchPopup(e);
return;
}
switch (this.workbenchMode) {
case AbstractWorkbench.SELECT_MOVE:
if (source == this) {
startRubberband(loc);
} else if (source instanceof DisplayNode) {
startNodeDrag(loc);
}
break;
case AbstractWorkbench.ADD_NODE:
if (!isAllowDoubleClickActions()) {
return;
}
if (source == this) {
addNode(loc);
}
break;
case AbstractWorkbench.ADD_EDGE:
if (source instanceof IDisplayEdge) {
return;
}
if (source instanceof DisplayNode) {
Point o = ((Component) (source)).getLocation();
loc.translate(o.x, o.y);
}
DisplayNode nearestNode = findNearestNode(loc);
if (nearestNode != null) {
startEdge(nearestNode, loc);
}
break;
}
// if (SearchGraphUtils.isLegalPag(graph).isLegalPag() || doPagColoring) {
// GraphUtils.addPagColoring(new EdgeListGraph(graph));
// }
}
private void launchPopup(MouseEvent e) {
JPopupMenu popup = new JPopupMenu();
popup.add(new LayoutMenu(this));
if (this instanceof GraphWorkbench) {
popup.add(new EnsembleMenu((GraphWorkbench) this));
}
popup.show(this, e.getX(), e.getY());
}
private void handleMouseReleased(MouseEvent e) {
Object source = e.getSource();
switch (this.workbenchMode) {
case AbstractWorkbench.SELECT_MOVE:
if (source == this) {
finishRubberband();
} else if (source instanceof DisplayNode) {
List dragNodes = this.dragNodes;
if (dragNodes != null && dragNodes.isEmpty()) {
snapSingleNodeFromNegative(source);
snapNodeToGrid((DisplayNode) source);
scrollRectToVisible(((DisplayNode) source).getBounds());
} else if (dragNodes != null) {
snapDragGroupFromNegative();
Rectangle rect = dragNodes.get(0).getBounds();
for (int i = 1; i < dragNodes.size(); i++) {
rect = rect.union(dragNodes.get(i).getBounds());
}
// scrollRectToVisible(rect);
}
}
break;
case AbstractWorkbench.ADD_EDGE:
finishEdge();
break;
}
// if (SearchGraphUtils.isLegalPag(graph).isLegalPag() || doPagColoring) {
// GraphUtils.addPagColoring(new EdgeListGraph(graph));
// }
}
private void handleMouseDragged(MouseEvent e) {
setMouseDragging();
Object source = e.getSource();
Point newPoint = e.getPoint();
switch (this.workbenchMode) {
case AbstractWorkbench.SELECT_MOVE:
dragNodes(source, newPoint, e.isShiftDown());
break;
case AbstractWorkbench.ADD_NODE:
if (source instanceof DisplayNode && getSelectedComponents().isEmpty()) {
dragNodes(source, newPoint, e.isShiftDown());
}
break;
case AbstractWorkbench.ADD_EDGE:
dragNewEdge(source, newPoint);
break;
}
// if (SearchGraphUtils.isLegalPag(graph).isLegalPag() || doPagColoring) {
// GraphUtils.addPagColoring(new EdgeListGraph(graph));
// }
}
private void handleMouseEntered(MouseEvent e) {
Object source = e.getSource();
if (source instanceof DisplayEdge) {
IDisplayEdge displayEdge = (DisplayEdge) source;
Edge edge = displayEdge.getModelEdge();
if (this.graph.containsEdge(edge)) {
// Bootstrapping Distribution
List edgeProb = edge.getEdgeTypeProbabilities();
if (edgeProb != null) {
String endpoint1 = edge.getEndpoint1().toString();
switch (endpoint1) {
case "Tail":
endpoint1 = "-";
break;
case "Arrow":
endpoint1 = "<";
break;
case "Circle":
endpoint1 = "o";
break;
case "Star":
endpoint1 = "*";
break;
case "Null":
endpoint1 = "Null";
break;
}
String endpoint2 = edge.getEndpoint2().toString();
switch (endpoint2) {
case "Tail":
endpoint2 = "-";
break;
case "Arrow":
endpoint2 = ">";
break;
case "Circle":
endpoint2 = "o";
break;
case "Star":
endpoint2 = "*";
break;
case "Null":
endpoint2 = "Null";
break;
}
StringBuilder properties = new StringBuilder();
if (edge.getProperties() != null && edge.getProperties().size() > 0) {
for (Edge.Property property : edge.getProperties()) {
properties.append(" ").append(property.toString());
}
}
StringBuilder text = new StringBuilder("" + edge.getNode1().getName()
+ " " + endpoint1 + "-" + endpoint2 + " "
+ edge.getNode2().getName()
+ properties
+ " ");
String n1 = edge.getNode1().getName();
String n2 = edge.getNode2().getName();
List nodes = new ArrayList<>();
nodes.add(n1);
nodes.add(n2);
Collections.sort(nodes);
for (EdgeTypeProbability edgeTypeProb : edgeProb) {
String _type = "" + edgeTypeProb.getEdgeType();
switch (edgeTypeProb.getEdgeType()) {
case nil:
_type = "no edge";
break;
case ta:
_type = "--?";
_type = nodes.get(0) + " " + _type + " " + nodes.get(1);
break;
case at:
_type = "<--";
_type = nodes.get(0) + " " + _type + " " + nodes.get(1);
break;
case ca:
_type = "o->";
_type = nodes.get(0) + " " + _type + " " + nodes.get(1);
break;
case ac:
_type = "<-o";
_type = nodes.get(0) + " " + _type + " " + nodes.get(1);
break;
case cc:
_type = "o-o";
_type = nodes.get(0) + " " + _type + " " + nodes.get(1);
break;
case aa:
_type = "<->";
_type = nodes.get(0) + " " + _type + " " + nodes.get(1);
break;
case tt:
_type = "---";
_type = nodes.get(0) + " " + _type + " " + nodes.get(1);
break;
default:
break;
}
if (edgeTypeProb.getProbability() > 0) {
properties = new StringBuilder();
if (edgeTypeProb.getProperties() != null && edgeTypeProb.getProperties().size() > 0) {
for (Edge.Property property : edgeTypeProb.getProperties()) {
properties.append(" ").append(property.toString());
}
}
text.append("[").append(_type).append(properties).append("]:").append(String.format("%.4f", edgeTypeProb.getProbability()));
text.append(" ");
}
}
// Commented out by Zhou
// JLabel edgeTypeDistLabel = new JLabel(text);
// edgeTypeDistLabel.setOpaque(true);
// edgeTypeDistLabel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
// setEdgeLabel(edge, edgeTypeDistLabel);
// Use tooltip instead of label - Added by Zhou
setEdgeToolTip(edge, text.toString());
}
}
}
if (source instanceof DisplayNode) {
DisplayNode displayNode = (DisplayNode) source;
Node node = displayNode.getModelNode();
if (this.graph.containsNode(node)) {
Map attributes = node.getAllAttributes();
if (!attributes.isEmpty()) {
StringBuilder attribute = new StringBuilder();
for (String key : attributes.keySet()) {
Object value = attributes.get(key);
attribute.append(key).append(": ").append(value).append(" ");
}
String text = "Node: " + node.getName() + " " + attribute;
setNodeToolTip(node, text);
}
}
}
}
// SInce we use tooltip to show edge type and probablitites,
// we no longer need this call. - Commented out by Zhou
private void snapSingleNodeFromNegative(Object source) {
DisplayNode node = (DisplayNode) source;
int x = node.getLocation().x;
int y = node.getLocation().y;
x = FastMath.max(x, 0);
y = FastMath.max(y, 0);
node.setLocation(x, y);
}
private void snapDragGroupFromNegative() {
int minX = Integer.MAX_VALUE;
int minY = Integer.MAX_VALUE;
List dragNodes = this.dragNodes;
if (dragNodes == null) {
return;
}
for (DisplayNode _node : dragNodes) {
int x = _node.getLocation().x;
int y = _node.getLocation().y;
minX = FastMath.min(minX, x);
minY = FastMath.min(minY, y);
}
minX = FastMath.min(minX, 0);
minY = FastMath.min(minY, 0);
for (DisplayNode _node : dragNodes) {
int x = _node.getLocation().x;
int y = _node.getLocation().y;
_node.setLocation(x - minX, y - minY);
}
}
private void dragNewEdge(Object source, Point newPoint) {
if (source instanceof DisplayNode) {
Point point = ((Component) source).getLocation();
newPoint.translate(point.x, point.y);
}
if (getTrackedEdge() != null) {
getTrackedEdge().updateTrackPoint(newPoint);
}
}
private void dragNodes(Object source, Point newPoint, boolean edgesOnly) {
if (!isAllowNodeDragging()) {
return;
}
if (source instanceof DisplayNode) {
List dragNodes = this.dragNodes;
if (dragNodes == null) {
return;
}
if (!dragNodes.contains(source)) {
moveSingleNode(source, newPoint);
} else {
moveSelectedNodes(source, newPoint);
}
} else if (this.rubberband != null) {
this.rubberband.updateTrackPoint(newPoint);
selectAllInRubberband(this.rubberband, edgesOnly);
}
}
/**
* Move a single, unselected node.
*/
private void moveSingleNode(Object source, Point newPoint) {
DisplayNode node = (DisplayNode) source;
int deltaX = newPoint.x - this.clickPoint.x;
int deltaY = newPoint.y - this.clickPoint.y;
int newX = node.getLocation().x + deltaX;
int newY = node.getLocation().y + deltaY;
node.setLocation(newX, newY);
}
/**
* Move a group of selected nodes together.
*/
private void moveSelectedNodes(Object source, Point newPoint) {
if (!this.dragNodes.contains(source)) {
return;
}
int deltaX = newPoint.x - this.clickPoint.x;
int deltaY = newPoint.y - this.clickPoint.y;
for (DisplayNode _node : this.dragNodes) {
int newX = _node.getLocation().x + deltaX;
int newY = _node.getLocation().y + deltaY;
_node.setLocation(newX, newY);
}
}
/**
* Toggles endpoint A in the following sense: if it's a "o" make it a "-"; if it's a "-", make it a ">"; if it's a
* ">", make it a "-".
*
* @param endpoint 1 for endpoint 1, 2 for endpoint 2.
*/
private void directEdge(IDisplayEdge graphEdge, int endpoint) {
Edge edge = graphEdge.getModelEdge();
if (edge == null) {
throw new IllegalStateException("Graph edge without model edge: " + graphEdge);
}
Edge newEdge;
if (endpoint == 1) {
newEdge = Edges.directedEdge(edge.getNode2(), edge.getNode1());
} else if (endpoint == 2) {
newEdge = Edges.directedEdge(edge.getNode1(), edge.getNode2());
} else {
throw new IllegalArgumentException("Endpoint number should be 1 or 2.");
}
getGraph().removeEdge(edge);
try {
boolean added = getGraph().addEdge(newEdge);
if (!added) {
getGraph().addEdge(edge);
JOptionPane.showMessageDialog(JOptionUtils.centeringComp(),
"Reorienting that edge would violate graph constraints.");
}
} catch (IllegalArgumentException e) {
getGraph().addEdge(edge);
if (doPagColoring) {
GraphUtils.addPagColoring(new EdgeListGraph(graph));
}
JOptionPane.showMessageDialog(JOptionUtils.centeringComp(),
"Reorienting that edge would violate graph constraints.");
}
revalidate();
repaint();
}
private void toggleEndpoint(IDisplayEdge graphEdge, int endpointNumber) {
Edge edge = graphEdge.getModelEdge();
Edge newEdge;
if (endpointNumber == 1) {
Endpoint endpoint = edge.getEndpoint1();
Endpoint nextEndpoint;
if (endpoint == Endpoint.TAIL) {
nextEndpoint = Endpoint.ARROW;
} else if (endpoint == Endpoint.ARROW) {
nextEndpoint = Endpoint.CIRCLE;
} else {
nextEndpoint = Endpoint.TAIL;
}
newEdge = new Edge(edge.getNode1(), edge.getNode2(), nextEndpoint, edge.getEndpoint2());
} else if (endpointNumber == 2) {
Endpoint endpoint = edge.getEndpoint2();
Endpoint nextEndpoint;
if (endpoint == Endpoint.TAIL) {
nextEndpoint = Endpoint.ARROW;
} else if (endpoint == Endpoint.ARROW) {
nextEndpoint = Endpoint.CIRCLE;
} else {
nextEndpoint = Endpoint.TAIL;
}
newEdge = new Edge(edge.getNode1(), edge.getNode2(), edge.getEndpoint1(), nextEndpoint);
} else {
throw new IllegalArgumentException("Endpoint number should be 1 or 2.");
}
getGraph().removeEdge(edge);
try {
boolean added = getGraph().addEdge(newEdge);
if (!added) {
getGraph().addEdge(edge);
if (doPagColoring) {
GraphUtils.addPagColoring(new EdgeListGraph(graph));
}
return;
}
} catch (IllegalArgumentException e) {
getGraph().addEdge(edge);
return;
}
if (doPagColoring) {
GraphUtils.addPagColoring(new EdgeListGraph(graph));
}
revalidate();
repaint();
}
private void setMouseDragging() {
/**
* TEMPORARY bug fix added 4/15/2005. The bug is that in JDK 1.5.0_02
* (without this bug fix) groups of nodes cannot be selected, because if
* you click and drag, an extra mouseClicked event is fired when you
* release the mouse. This is a known bug, #5039416 in Sun's bug
* database. To get around the problem, we set this flag to true when a
* mouseDragged event is fired and ignore the first click (and reset
* this flag to false) on the first mouseClicked event after any
* mouseDragged event. When this bug is fixed in JDK 1.5, this temporary
* bug fix shold be removed. jdramsey 4/15/2005
*/
boolean mouseDragging = true;
}
/**
* True if the user is allowed to add measured variables.
*/
private boolean isAddMeasuredVarsAllowed() {
/**
* True iff the user is allowed to add measured variables.
*/
return true;
}
/**
* @return true if the user is allowed to edit existing meausred variables.
*/
boolean isEditExistingMeasuredVarsAllowed() {
return true;
}
/**
* @return true iff the user is allowed to delete variables.
*/
private boolean isDeleteVariablesAllowed() {
/**
* True iff the user is allowed to delete variables.
*/
return true;
}
public boolean isEnableEditing() {
return this.enableEditing;
}
public void enableEditing(boolean enableEditing) {
this.enableEditing = enableEditing;
setEnabled(enableEditing);
}
public boolean isDoPagColoring() {
return this.doPagColoring;
}
public void setDoPagColoring(boolean doPagColoring) {
this.doPagColoring = doPagColoring;
if (doPagColoring) {
GraphUtils.addPagColoring(graph);
} else {
for (Edge edge : graph.getEdges()) {
edge.getProperties().clear();
}
}
setGraph(graph);
revalidate();
repaint();
}
private void doDoubleClickAction(DisplayNode node) {
deselectAll();
node.doDoubleClickAction(getGraph());
}
private Map getDisplayToLabels() {
return this.displayToLabels;
}
//
// Event handler classes
//
/**
* This is necessary sometimes when the hashcodes of certain objects change (e.g. a node changes its name.)
*/
private void reconstiteMaps() {
this.modelEdgesToDisplay = new HashMap<>(getModelEdgesToDisplay());
this.modelNodesToDisplay = new HashMap<>(getModelNodesToDisplay());
this.displayToModel = new HashMap<>(this.displayToModel);
this.displayToLabels = new HashMap<>(this.displayToLabels);
}
private IDisplayEdge getTrackedEdge() {
return this.trackedEdge;
}
private boolean isNodeEdgeErrorsReported() {
return this.nodeEdgeErrorsReported;
}
protected void setNodeEdgeErrorsReported() {
this.nodeEdgeErrorsReported = true;
}
private boolean isRightClickPopupAllowed() {
return this.rightClickPopupAllowed;
}
protected void setRightClickPopupAllowed(boolean rightClickPopupAllowed) {
this.rightClickPopupAllowed = rightClickPopupAllowed;
}
/**
* This inner class is a simple wrapper for JComponents which are to serve as edge labels in the workbench. Its sole
* function is to make sure the wrapped JComponents stay in the right place in the workbench--that is, halfway along
* their respective edge, slightly off to the side.
*
* @author josephramsey
*/
private static final class GraphEdgeLabel extends JComponent implements PropertyChangeListener {
/**
* The edge that this label should attach to.
*/
private final IDisplayEdge edge;
/**
* The JComponent that serves as the label.
*/
private final JComponent label;
/**
* The distance from the midpoint of the edge to the center of the component along the normal to the edge.
*/
private double normalDistance;
/**
* Constructs a new label wrapper for the given JComponent and edge.
*
* @param edge the edge with which the label is associated.
* @param label the JComponent which serves as the label.
*/
public GraphEdgeLabel(IDisplayEdge edge, JComponent label) {
this.label = label;
this.edge = edge;
setLayout(new BorderLayout());
add(label, BorderLayout.CENTER);
updateLocation(edge.getPointPair());
((Component) edge).addPropertyChangeListener(this);
}
/**
* Listens to "newPointPair" property changes which are emitted by the display edge whenever a new point pair is
* calculated.
*
* @param e the property change event.
*/
public void propertyChange(PropertyChangeEvent e) {
if ((e.getSource() == this.edge) && e.getPropertyName().equals("newPointPair")) {
updateLocation((PointPair) e.getNewValue());
}
}
/**
* Updates the location of the label so that it lies halfway between the two points of the point pair, slightly
* off to the right (if you go from the "from" point to the "to" point).
*
* @param pp the point pair to track.
*/
void updateLocation(PointPair pp) {
if (pp != null) {
moveCenterOutAlongNormal(pp);
// attachCornerToMidpoint(pp, label);
}
}
private void moveCenterOutAlongNormal(PointPair pp) {
// calculate the cross outerProduct of a unit vector from
// pp.from in the direction of pp.to with a unit vector
// perpendicular down into the workbench. this will be a
// unit vector normal to the vector from pp.from to pp.to.
double dx = pp.getFrom().x - pp.getTo().x;
double dy = pp.getFrom().y - pp.getTo().y;
double dist = AbstractWorkbench.distance(pp.getFrom(), pp.getTo());
double normalx = -dy / dist;
double normaly = dx / dist;
// Move the center of the label out from the midpoint in
// the direction of this normal a distance of half the
// diagonal of the label. (Note--a better "distance"
// algorithm needs to be invented for this; a very wide
// but short label gets put way too far from the edge at
// some angles. jdramsey 10/25/01.)
Point midPt = new Point((pp.getFrom().x + pp.getTo().x) / 2, (pp.getFrom().y + pp.getTo().y) / 2);
Point edgeLoc = ((Component) this.edge).getLocation();
int setx = edgeLoc.x + midPt.x;
// Putting the labels directly on the line. (If this works,
// need to modifiy the API to give the user some choice
// in the matter.)
setx += (int) (getNormalDistance() * normalx) / 2;
setx -= getLabel().getWidth() / 2;
int sety = edgeLoc.y + midPt.y;
sety += (int) (getNormalDistance() * normaly) / 2;
sety -= getLabel().getHeight() / 2;
setLocation(setx, sety);
}
public double getNormalDistance() {
return this.normalDistance;
}
public JComponent getLabel() {
return this.label;
}
}
/**
* This inner class is a simple wrapper for JComponents which are to serve as node labels in the workbench. Its sole
* function is to make sure the wrapped JComponents stay in the right place in the workbench--that is, slightly off
* to the right of the display node.
*
* @author josephramsey
*/
private static final class GraphNodeLabel extends JComponent {
/**
* The JComponent that serves as the label.
*/
private final JComponent label;
/**
* The x location of the label relative to the center of the node.
*/
private final int xOffset;
/**
* The y location of the label relative to the center of the node.
*/
private final int yOffset;
/**
* Constructs a new label wrapper for the given JComponent and node.
*
* @param node the node with which the label is associated.
* @param label the JComponent which serves as the label.
*/
public GraphNodeLabel(DisplayNode node, JComponent label, int xOffset, int yOffset) {
this.label = label;
Rectangle rectangle = node.getBounds();
this.xOffset = (int) rectangle.getWidth() / 2 + xOffset;
this.yOffset = (int) rectangle.getHeight() / 2 + yOffset;
setLayout(new BorderLayout());
add(label, BorderLayout.CENTER);
updateLocation(node);
node.addComponentListener(new ComponentAdapter() {
public void componentMoved(ComponentEvent e) {
updateLocation(e.getComponent());
}
});
}
void updateLocation(Component component) {
int x = component.getX() + component.getWidth() / 2 + this.xOffset;
int y = component.getY() + component.getHeight() / 2 + this.yOffset;
setLocation(x, y);
}
public JComponent getLabel() {
return this.label;
}
}
/**
* Handles ComponentEvent
s. We use an inner class instead of the workbench itself since we don't want
* to expose the handler methods on the workbench's public API.
*/
private static final class ComponentHandler extends ComponentAdapter {
private final AbstractWorkbench workbench;
public ComponentHandler(AbstractWorkbench workbench) {
this.workbench = workbench;
}
/**
* Adjusts scrollbars to automatically reflect the position of a component which is being dragged.
*/
@Override
public void componentMoved(ComponentEvent e) {
Component source = (Component) e.getSource();
Rectangle bounds = source.getBounds();
if (source instanceof DisplayNode) {
Node modelNode = (Node) (this.workbench.getDisplayToModel().get(source));
// Adding a null pointer check
if (modelNode == null) {
return;
}
int centerX = bounds.x + bounds.width / 2;
int centerY = bounds.y + bounds.height / 2;
modelNode.setCenterX(centerX);
modelNode.setCenterY(centerY);
this.workbench.adjustPreferredSize();
// This causes wierdness when nodes are dragged off to the
// right. Replacing with a scroll to rect on mouseup.
// jdramsey 4/29/2005
// workbench.scrollRectToVisible(bounds);
}
}
}
private static final class MouseMotionHandler extends MouseMotionAdapter {
private final AbstractWorkbench workbench;
public MouseMotionHandler(AbstractWorkbench workbench) {
this.workbench = workbench;
}
@Override
public void mouseMoved(MouseEvent e) {
this.workbench.currentMouseLocation = e.getPoint();
}
@Override
public void mouseDragged(MouseEvent e) {
this.workbench.handleMouseDragged(e);
}
}
/**
* Handles mouse events and mouse motion events.
*/
private final class MouseHandler extends MouseAdapter {
private final AbstractWorkbench workbench;
public MouseHandler(AbstractWorkbench workbench) {
this.workbench = workbench;
}
@Override
public void mouseClicked(MouseEvent e) {
if (AbstractWorkbench.this.isEnableEditing()) {
this.workbench.handleMouseClicked(e);
}
}
@Override
public void mousePressed(MouseEvent e) {
this.workbench.handleMousePressed(e);
}
@Override
public void mouseReleased(MouseEvent e) {
if (AbstractWorkbench.this.isEnableEditing()) {
this.workbench.handleMouseReleased(e);
}
// Copy the laid out graph to the clipboard.
// new CopyLayoutAction(getWorkbench()).actionPerformed(null);
}
@Override
public void mouseEntered(MouseEvent e) {
if (AbstractWorkbench.this.isEnableEditing()) {
this.workbench.handleMouseEntered(e);
}
}
@Override
public void mouseExited(MouseEvent e) {
// Commented out by Zhou
//workbench.handleMouseExited(e);
}
}
/**
* Handles PropertyChangeEvent
s.
*/
private final class PropertyChangeHandler implements PropertyChangeListener {
private final AbstractWorkbench workbench;
public PropertyChangeHandler(AbstractWorkbench workbench) {
this.workbench = workbench;
}
public void propertyChange(PropertyChangeEvent e) {
String propName = e.getPropertyName();
Object oldValue = e.getOldValue();
Object newValue = e.getNewValue();
if ("nodeAdded".equals(propName)) {
this.workbench.addNode((Node) newValue);
} else if ("nodeRemoved".equals(propName)) {
this.workbench.removeNode((Node) oldValue);
} else if ("edgeAdded".equals(propName)) {
this.workbench.addEdge((Edge) newValue);
} else if ("edgeRemoved".equals(propName)) {
this.workbench.removeEdge((Edge) oldValue);
} else if ("edgeLaunch".equals(propName)) {
System.out.println("Attempt to launch edge.");
} else if ("deleteNode".equals(propName)) {
Object node = e.getSource();
if (node instanceof DisplayNode) {
node = this.workbench.displayToModel.get(node);
}
if (node instanceof GraphNode) {
this.workbench.deselectAll();
this.workbench.selectNode((GraphNode) node);
this.workbench.deleteSelectedObjects();
}
} else if ("cloneMe".equals(propName)) {
AbstractWorkbench.this.firePropertyChange("cloneMe", e.getOldValue(), e.getNewValue());
}
}
}
}