1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2010-2010 - DIGITEO - Clement DAVID <clement.david@scilab.org>
4  * Copyright (C) 2011-2015 - Scilab Enterprises - Clement DAVID
5  *
6  * Copyright (C) 2012 - 2016 - Scilab Enterprises
7  *
8  * This file is hereby licensed under the terms of the GNU GPL v2.0,
9  * pursuant to article 5.3.4 of the CeCILL v.2.1.
10  * This file was originally licensed under the terms of the CeCILL v2.1,
11  * and continues to be available under such terms.
12  * For more information, see the COPYING file which you should have received
13  * along with this program.
14  *
15  */
16 
17 package org.scilab.modules.xcos.graph.swing.handler;
18 
19 import java.awt.Color;
20 import java.awt.event.MouseEvent;
21 import java.util.ArrayList;
22 import java.util.List;
23 
24 import org.scilab.modules.xcos.block.BasicBlock;
25 import org.scilab.modules.xcos.graph.swing.GraphComponent;
26 
27 import com.mxgraph.model.mxICell;
28 import com.mxgraph.model.mxIGraphModel;
29 import com.mxgraph.swing.handler.mxCellMarker;
30 import com.mxgraph.swing.handler.mxConnectPreview;
31 import com.mxgraph.swing.handler.mxConnectionHandler;
32 import com.mxgraph.util.mxEvent;
33 import com.mxgraph.util.mxPoint;
34 import com.mxgraph.view.mxCellState;
35 import com.mxgraph.view.mxGraph;
36 
37 /**
38  * Connection handler used to handle multi point links.
39  */
40 @SuppressWarnings(value = { "serial" })
41 public class ConnectionHandler extends mxConnectionHandler {
42     private boolean multiPointLinkStarted;
43 
44     /**
45      * Default constructor.
46      *
47      * @param graphComponent
48      *            the component
49      */
ConnectionHandler(GraphComponent graphComponent)50     public ConnectionHandler(GraphComponent graphComponent) {
51         super(graphComponent);
52 
53         /*
54          * Same default settings as mxConnectionHandler plus get first free
55          * input port in case of a simple flow connection.
56          */
57 
58         marker = new mxCellMarker(graphComponent) {
59             // Overrides to return cell at location only if valid (so that
60             // there is no highlight for invalid cells that have no error
61             // message when the mouse is released)
62             @Override
63             protected Object getCell(MouseEvent e) {
64                 Object cell = super.getCell(e);
65 
66                 if (isConnecting()) {
67                     if (source != null) {
68                         cell = getFirstValidPort(cell);
69                         error = validateConnection(source.getCell(), cell);
70 
71                         if (error != null && error.length() == 0) {
72                             cell = null;
73 
74                             // Enables create target inside groups
75                             if (createTarget) {
76                                 error = null;
77                             }
78                         }
79                     }
80                 } else if (!isValidSource(cell)) {
81                     cell = null;
82                 }
83 
84                 return cell;
85             }
86 
87             // Sets the highlight color according to isValidConnection
88             @Override
89             protected boolean isValidState(mxCellState state) {
90                 if (isConnecting()) {
91                     return error == null;
92                 } else {
93                     return super.isValidState(state);
94                 }
95             }
96 
97             // Overrides to use marker color only in highlight mode or for
98             // target selection
99             @Override
100             protected Color getMarkerColor(MouseEvent e, mxCellState state, boolean isValid) {
101                 return (isHighlighting() || isConnecting()) ? super.getMarkerColor(e, state, isValid) : null;
102             }
103 
104             // Overrides to use hotspot only for source selection otherwise
105             // intersects always returns true when over a cell
106             @Override
107             protected boolean intersects(mxCellState state, MouseEvent e) {
108                 if (!isHighlighting() || isConnecting()) {
109                     return true;
110                 }
111 
112                 return super.intersects(state, e);
113             }
114 
115             private Object getFirstValidPort(Object o) {
116                 if (!(o instanceof BasicBlock)) {
117                     return o;
118                 }
119                 final BasicBlock block = (BasicBlock) o;
120 
121                 for (int i = 0; i < block.getChildCount(); i++) {
122                     final Object cell = block.getChildAt(i);
123 
124                     final String err = validateConnection(source.getCell(), cell);
125                     if (err == null) {
126                         return cell;
127                     }
128                 }
129 
130                 return o;
131             }
132 
133             @Override
134             public void reset() {
135                 if (markedState != null) {
136                     this.graphComponent.getGraph().addSelectionCell(markedState.getCell());
137                 }
138 
139                 super.reset();
140             }
141         };
142     }
143 
144     /**
145      * @return {@link ConnectPreview} instance
146      * @see com.mxgraph.swing.handler.mxConnectionHandler#createConnectPreview()
147      */
148     @Override
createConnectPreview()149     protected mxConnectPreview createConnectPreview() {
150         return new ConnectPreview((GraphComponent) graphComponent);
151     }
152 
153     /**
154      * Enable or disable the reset handler which reset any action on graph
155      * modification.
156      *
157      * @param status
158      *            the enable status
159      */
setResetEnable(boolean status)160     protected void setResetEnable(boolean status) {
161         final mxIGraphModel model = graphComponent.getGraph().getModel();
162 
163         if (status) {
164             model.addListener(mxEvent.CHANGE, resetHandler);
165         } else {
166             model.removeListener(resetHandler, mxEvent.CHANGE);
167         }
168     }
169 
170     /*
171      * mxMouseAdapter specific reimplementation
172      */
173 
174     /**
175      * Handle first release and click on the empty background during connection.
176      *
177      * @param e
178      *            the event
179      * @see com.mxgraph.swing.handler.mxConnectionHandler#mouseReleased(java.awt.event.MouseEvent)
180      */
181     @Override
mouseReleased(MouseEvent e)182     public void mouseReleased(MouseEvent e) {
183         final boolean isEventValid = error != null && error.isEmpty() && !e.isConsumed();
184         final boolean hasValidState = first != null && connectPreview.isActive() && !marker.hasValidState();
185 
186         if (isEventValid && hasValidState) {
187             final mxGraph graph = graphComponent.getGraph();
188             final double x = graph.snap(e.getX());
189             final double y = graph.snap(e.getY());
190 
191             // we are during a link creation on an invalid area
192             mxICell cell = (mxICell) connectPreview.getPreviewState().getCell();
193 
194             // allocate points if applicable
195             List<mxPoint> points = cell.getGeometry().getPoints();
196             if (points == null) {
197                 points = new ArrayList<mxPoint>();
198                 cell.getGeometry().setPoints(points);
199             }
200 
201             // scale and set the point
202             // extracted from mxConnectPreview#transformScreenPoint
203             final mxPoint tr = graph.getView().getTranslate();
204             final double scale = graph.getView().getScale();
205             points.add(new mxPoint(graph.snap(x / scale - tr.getX()), graph.snap(y / scale - tr.getY())));
206 
207             // update the preview and set the flag
208             connectPreview.update(e, null, x, y);
209             multiPointLinkStarted = true;
210 
211             e.consume();
212         } else {
213             if (marker.hasValidState() && connectPreview.getPreviewState() != null) {
214                 final mxGraph graph = graphComponent.getGraph();
215                 final double x = graph.snap(e.getX());
216                 final double y = graph.snap(e.getY());
217 
218                 // We are ending a link creation on an valid port,
219                 // so sync the points coordinates with the model
220                 mxICell cell = (mxICell) connectPreview.getPreviewState().getCell();
221                 cell.setGeometry(cell.getGeometry());
222             }
223 
224             multiPointLinkStarted = false;
225             super.mouseReleased(e);
226         }
227     }
228 
229     /**
230      * Only chain up when the multi point link feature is disable, drag
231      * otherwise.
232      *
233      * @param e
234      *            the event
235      * @see com.mxgraph.swing.handler.mxConnectionHandler#mouseMoved(java.awt.event.MouseEvent)
236      */
237     @Override
mouseMoved(MouseEvent e)238     public void mouseMoved(MouseEvent e) {
239         if (multiPointLinkStarted) {
240             mouseDragged(e);
241         } else {
242             super.mouseMoved(e);
243         }
244     }
245 
246     /**
247      * Only chain up when multi point link feature is disable.
248      *
249      * This will not update the first point on multi point link creation.
250      *
251      * @param e
252      *            the mouse event
253      * @param state
254      *            the marker valid state
255      * @see com.mxgraph.swing.handler.mxConnectionHandler#start(java.awt.event.MouseEvent,
256      *      com.mxgraph.view.mxCellState)
257      */
258     @Override
start(MouseEvent e, mxCellState state)259     public void start(MouseEvent e, mxCellState state) {
260         if (!multiPointLinkStarted) {
261             super.start(e, state);
262         }
263     }
264 }
265