1/**
2 * Copyright (c) 2017, CTI LOGIC
3 * Copyright (c) 2006-2017, JGraph Ltd
4 * Copyright (c) 2006-2017, Gaudenz Alder
5 *
6 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
9 *
10 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
11 *
12 * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
15 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
16 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
17 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
18 */
19
20//TODO integrate this code in mxGuide (Especially as this is now affecting the other guides)
21(function()
22{
23	var guideMove = mxGuide.prototype.move;
24
25	mxGuide.prototype.move = function (bounds, delta, gridEnabled, clone)
26	{
27	    var yShift = delta.y;
28	    var xShift = delta.x;
29	    var hasHorGuides = false;
30	    var hasVerGuides = false;
31
32	    if (this.states != null && bounds != null && delta != null)
33	    {
34	      var guide = this;
35		  var newState = new mxCellState();
36		  var scale = this.graph.getView().scale;
37		  var tolerance = Math.max(2, this.getGuideTolerance() / 2);
38
39		  newState.x = bounds.x + xShift;
40		  newState.y = bounds.y + yShift;
41		  newState.width = bounds.width;
42		  newState.height = bounds.height;
43		  var verticalCells = [];
44		  var horizontalCells = [];
45
46	      //although states are defined as cellState, it has some mxRectangles!
47	      var states = [];
48
49	      for (var i = 0; i < this.states.length; i++)
50		  {
51	    	  var state = this.states[i];
52	    	  var found = false;
53
54	    	  if (state instanceof mxCellState)
55			  {
56		    	  if (clone || !this.graph.isCellSelected(state.cell))
57				  {
58		    		  if (((newState.x >= state.x && newState.x <= (state.x + state.width))
59		    	              || (state.x >= newState.x && state.x <= (newState.x + newState.width)))
60		    	              && (newState.y > state.y + state.height + 4|| newState.y + newState.height + 4 < state.y)) // + 4 to avoid having dy = 0 considered which cause a bug with 3 cells case
61		    		  {
62			            verticalCells.push(state);
63			          }
64		    		  else if (((newState.y >= state.y && newState.y <= (state.y + state.height))
65				            || (state.y >= newState.y && state.y <= (newState.y + newState.height)))
66				            && (newState.x > state.x + state.width + 4 || newState.x + newState.width + 4 < state.x)) // + 4 to avoid having dy = 0 considered which cause a bug with 3 cells case
67		    		  {
68			            horizontalCells.push(state);
69			          }
70				  }
71			  }
72		  }
73
74	      var eqCy = 0;
75	      var dy = 0;
76	      var fixedDy = 0;
77	      var midDy = 0;
78	      var eqCx = 0;
79	      var dx = 0;
80	      var fixedDx = 0;
81	      var midDx = 0;
82	      var shift = 5 * scale;
83
84	      if (verticalCells.length > 1)
85	      {
86	        verticalCells.push(newState);
87
88	        verticalCells.sort(function(s1, s2)
89			{
90	          return s1.y - s2.y;
91	        });
92
93	        var newStatePassed = false;
94            var firstMoving = newState == verticalCells[0];
95            var lastMoving = newState == verticalCells[verticalCells.length - 1];
96
97            //find the mid space and use it as dy and fixedDy
98            if (!firstMoving && !lastMoving)
99        	{
100            	for (var i = 1; i < verticalCells.length - 1; i++)
101    	  	  	{
102            		if (newState == verticalCells[i])
103        			{
104            			var s1 = verticalCells[i - 1];
105            			var s3 = verticalCells[i + 1];
106            			midDy = (s3.y - s1.y - s1.height - newState.height) / 2;
107            			dy = midDy;
108            			fixedDy = dy;
109            			break;
110        			}
111    	  	  	}
112        	}
113
114	        for (var i = 0; i < verticalCells.length - 1; i++)
115	  	  	{
116	            var s1 = verticalCells[i];
117	            var s2 = verticalCells[i + 1];
118	            var isMovingOne = newState == s1 || newState == s2;
119	            var curDy = s2.y - s1.y - s1.height;
120
121	            newStatePassed |= newState == s1;
122
123	            if (dy == 0 && eqCy == 0)
124	            {
125	              dy = curDy;
126	              eqCy = 1;
127	            }
128                else if (Math.abs(dy - curDy) <= (isMovingOne || (i == 1 && newStatePassed)? tolerance : 0)) //non-moving cells must have exact same dy, must handle the case of having the first cell moving so we allow tolerance for second cell (until fixedDy is non-zero)
129	            {
130	              eqCy += 1;
131	            }
132	            else if (eqCy > 1 && newStatePassed) //stop and ignore the following cells
133            	{
134	              verticalCells = verticalCells.slice(0, i + 1);
135	              break;
136            	}
137	            else if (verticalCells.length - i >= 3 && !newStatePassed) //reset and start counting again
138            	{
139	      	      eqCy = 0;
140	    	      dy = midDy != 0? midDy : 0;
141	    	      fixedDy = dy;
142	    	      verticalCells.splice(0, i == 0? 1 : i);
143	    	      i = -1;
144            	}
145	            else
146            	{
147		          break;
148            	}
149
150	            if (fixedDy == 0 && !isMovingOne)
151	        	{
152	            	fixedDy = curDy;
153	            	//Update dy such that following cells shows equal distance guides without tolerance
154	            	dy = fixedDy;
155	        	}
156	  	  	}
157
158            if (verticalCells.length == 3 && verticalCells[1] == newState)
159        	{
160              fixedDy = 0;
161        	}
162	      }
163
164	      if (horizontalCells.length > 1)
165	      {
166	        horizontalCells.push(newState)
167
168	        horizontalCells.sort(function(s1, s2)
169			{
170	          return s1.x - s2.x;
171	        });
172
173	        var newStatePassed = false;
174            var firstMoving = newState == horizontalCells[0];
175            var lastMoving = newState == horizontalCells[horizontalCells.length - 1];
176
177            //find the mid space and use it as dx and fixedDx
178            if (!firstMoving && !lastMoving)
179        	{
180            	for (var i = 1; i < horizontalCells.length - 1; i++)
181    	  	  	{
182            		if (newState == horizontalCells[i])
183        			{
184            			var s1 = horizontalCells[i - 1];
185            			var s3 = horizontalCells[i + 1];
186            			midDx = (s3.x - s1.x - s1.width - newState.width) / 2;
187            			dx = midDx;
188            			fixedDx = dx;
189            			break;
190        			}
191    	  	  	}
192        	}
193
194	        for (var i = 0; i < horizontalCells.length - 1; i++)
195	  	  	{
196	            var s1 = horizontalCells[i];
197	            var s2 = horizontalCells[i + 1];
198	            var isMovingOne = newState == s1 || newState == s2;
199	            var curDx = s2.x - s1.x - s1.width;
200
201	            newStatePassed |= newState == s1;
202
203	            if (dx == 0 && eqCx == 0)
204	            {
205	              dx = curDx;
206	              eqCx = 1;
207	            }
208                else if (Math.abs(dx - curDx) <= (isMovingOne || (i == 1 && newStatePassed)? tolerance : 0))
209	            {
210	              eqCx += 1;
211	            }
212	            else if (eqCx > 1 && newStatePassed) //stop and ignore the following cells
213            	{
214	              horizontalCells = horizontalCells.slice(0, i + 1);
215	              break;
216            	}
217	            else if (horizontalCells.length - i >= 3 && !newStatePassed) //reset and start counting again
218            	{
219	      	      eqCx = 0;
220	    	      dx = midDx != 0? midDx : 0;
221	    	      fixedDx = dx;
222	    	      horizontalCells.splice(0, i == 0? 1 : i);
223	    	      i = -1;
224            	}
225	            else
226            	{
227		          break;
228            	}
229
230	            if (fixedDx == 0 && !isMovingOne)
231	        	{
232	            	fixedDx = curDx;
233	            	//Update dx such that following cells shows equal distance guides without tolerance
234	            	dx = fixedDx;
235	        	}
236	  	  	}
237	        if (horizontalCells.length == 3 && horizontalCells[1] == newState)
238        	{
239              fixedDx = 0;
240        	}
241	      }
242
243	      var createEqGuide = function(p1, p2, curGuide, isVer)
244	      {
245	        var points = [];
246	        var dx = 0
247	        var dy = 0;
248
249	        if (isVer)
250	        {
251	          dx = shift;
252	          dy = 0;
253	        }
254	        else
255	        {
256	          dx = 0;
257	          dy = shift;
258	        }
259
260	        points.push(new mxPoint(p1.x - dx, p1.y - dy));
261	        points.push(new mxPoint(p1.x + dx, p1.y + dy));
262	        points.push(p1);
263	        points.push(p2);
264	        points.push(new mxPoint(p2.x - dx, p2.y - dy));
265	        points.push(new mxPoint(p2.x + dx, p2.y + dy));
266
267	        if (curGuide != null)
268	        {
269	          curGuide.points = points;
270	          return curGuide;
271	        }
272	        else
273	        {
274	          var guideEq = new mxPolyline(points, mxConstants.GUIDE_COLOR, mxConstants.GUIDE_STROKEWIDTH);
275	          guideEq.dialect = mxConstants.DIALECT_SVG;
276	          guideEq.pointerEvents = false;
277	          guideEq.init(guide.graph.getView().getOverlayPane());
278	          return guideEq;
279	        }
280	      };
281
282	      var hideEqGuides = function(horizontal, vertical)
283	      {
284	    	  if (horizontal && guide.guidesArrHor != null)
285	    	  {
286		    	  for (var i = 0; i < guide.guidesArrHor.length; i++)
287		          {
288		    		  guide.guidesArrHor[i].node.style.visibility = "hidden";
289		          }
290	    	  }
291
292	    	  if (vertical && guide.guidesArrVer != null)
293    		  {
294		    	  for (var i = 0; i < guide.guidesArrVer.length; i++)
295		          {
296		    		  guide.guidesArrVer[i].node.style.visibility = "hidden";
297		          }
298    		  }
299	      };
300
301	      if (eqCx > 1 && eqCx == horizontalCells.length - 1)
302	      {
303	        var guidesArr = [];
304	        var curArr = guide.guidesArrHor;
305	        var hPoints = [];
306	        var newX = 0;
307
308	        //If the newState (moving cell) is the first one, use the next one for x coordinate such that the guide doesn't move with the cell
309	        var firstI = horizontalCells[0] == newState? 1 : 0;
310	        var firstY = horizontalCells[firstI].y + horizontalCells[firstI].height;
311
312	        if (fixedDx > 0)
313			{
314	            for (var i = 0; i < horizontalCells.length - 1; i++)
315	      	  	{
316	                var s1 = horizontalCells[i];
317	                var s2 = horizontalCells[i + 1];
318
319	        		if (newState == s1)
320	    			{
321	        			newX = s2.x - s1.width - fixedDx;
322	        			hPoints.push(new mxPoint(newX + s1.width + shift, firstY));
323	                    hPoints.push(new mxPoint(s2.x - shift, firstY));
324	    			}
325	        		else if (newState == s2)
326	    			{
327	        			hPoints.push(new mxPoint(s1.x + s1.width + shift, firstY));
328	        			newX = s1.x + s1.width + fixedDx;
329	        			hPoints.push(new mxPoint(newX - shift, firstY));
330	    			}
331	        		else
332	    			{
333	        			hPoints.push(new mxPoint(s1.x + s1.width + shift, firstY));
334	                    hPoints.push(new mxPoint(s2.x - shift, firstY));
335	    			}
336	            }
337			}
338	        else //this is the case when there are 3 cells and the middle one is moving
339	    	{
340	        	var s1 = horizontalCells[0];
341	            var s3 = horizontalCells[2];
342				newX = s1.x + s1.width + (s3.x - s1.x - s1.width - newState.width) / 2;
343				hPoints.push(new mxPoint(s1.x + s1.width + shift, firstY));
344	            hPoints.push(new mxPoint(newX - shift, firstY));
345				hPoints.push(new mxPoint(newX + newState.width + shift, firstY));
346	            hPoints.push(new mxPoint(s3.x - shift, firstY));
347	    	}
348
349	        for (var i = 0; i < hPoints.length; i += 2)
350	        {
351	          var p1 = hPoints[i];
352	          var p2 = hPoints[i+1];
353	          var guideEq = createEqGuide(p1, p2, curArr != null ? curArr[i/2] : null);
354	          guideEq.node.style.visibility = "visible";
355	          guideEq.redraw();
356	          guidesArr.push(guideEq);
357	        }
358
359	        //destroy old non-recycled guides
360	        for (var i = hPoints.length / 2; curArr != null && i < curArr.length; i ++)
361	        {
362	        	curArr[i].destroy();
363	        }
364
365	        guide.guidesArrHor = guidesArr;
366
367	        xShift = newX - bounds.x;
368	        hasHorGuides = true;
369	      }
370	      else
371	      {
372	    	  hideEqGuides(true);
373	      }
374
375	      if (eqCy > 1 && eqCy == verticalCells.length - 1)
376	      {
377	        var guidesArr = [];
378	        var curArr = guide.guidesArrVer;
379	        var vPoints = [];
380	        var newY = 0;
381
382	        //If the newState (moving cell) is the first one, use the next one for x coordinate such that the guide doesn't move with the cell
383	        var firstI = verticalCells[0] == newState? 1 : 0;
384	        var firstX = verticalCells[firstI].x + verticalCells[firstI].width;
385
386	        if (fixedDy > 0)
387			{
388		        for (var i = 0; i < verticalCells.length - 1; i++)
389		  	  	{
390		        	var s1 = verticalCells[i];
391		            var s2 = verticalCells[i + 1];
392
393	        		if (newState == s1)
394	    			{
395	        			newY = s2.y - s1.height - fixedDy;
396	        			vPoints.push(new mxPoint(firstX, newY + s1.height + shift));
397						vPoints.push(new mxPoint(firstX, s2.y - shift));
398	    			}
399	        		else if (newState == s2)
400	    			{
401						vPoints.push(new mxPoint(firstX, s1.y + s1.height + shift));
402	        			newY = s1.y + s1.height + fixedDy;
403	        			vPoints.push(new mxPoint(firstX, newY - shift));
404	    			}
405	        		else
406	    			{
407						vPoints.push(new mxPoint(firstX, s1.y + s1.height + shift));
408						vPoints.push(new mxPoint(firstX, s2.y - shift));
409	    			}
410	    		}
411			}
412	    	else //this is the case when there are 3 cells and the middle one is moving
413			{
414	        	var s1 = verticalCells[0];
415	            var s3 = verticalCells[2];
416				newY = s1.y + s1.height + (s3.y - s1.y - s1.height - newState.height) / 2;
417				vPoints.push(new mxPoint(firstX, s1.y + s1.height + shift));
418				vPoints.push(new mxPoint(firstX, newY - shift));
419				vPoints.push(new mxPoint(firstX, newY + newState.height + shift));
420				vPoints.push(new mxPoint(firstX, s3.y - shift));
421			}
422
423	        for (var i = 0; i < vPoints.length; i += 2)
424	        {
425	          var p1 = vPoints[i];
426	          var p2 = vPoints[i+1];
427	          var guideEq = createEqGuide(p1, p2, curArr != null ? curArr[i/2] : null, true);
428	          guideEq.node.style.visibility = "visible";
429	          guideEq.redraw();
430	          guidesArr.push(guideEq);
431	        }
432
433	        //destroy old non-recycled guides
434	        for (var i = vPoints.length / 2; curArr != null && i < curArr.length; i ++)
435	        {
436	        	curArr[i].destroy();
437	        }
438
439	        guide.guidesArrVer = guidesArr;
440
441	        yShift = newY - bounds.y;
442	        hasVerGuides = true;
443	      }
444	      else
445	      {
446	    	  hideEqGuides(false, true);
447	      }
448	    }
449
450	    if (hasHorGuides || hasVerGuides)
451    	{
452	    	var eqPoint = new mxPoint(xShift, yShift);
453	    	var newPoint = guideMove.call(this, bounds, eqPoint, gridEnabled, clone);
454
455	    	//Adjust our point to match non-conflicting other guides
456	    	if (hasHorGuides && !hasVerGuides)
457			{
458	    		eqPoint.y = newPoint.y;
459			}
460	    	else if (hasVerGuides && !hasHorGuides)
461			{
462	    		eqPoint.x = newPoint.x;
463			}
464
465    		//Hide other guide if this guide overrides them
466    		if (newPoint.y != eqPoint.y)
467    		{
468    			if (this.guideY != null && this.guideY.node != null)
469    			{
470    				this.guideY.node.style.visibility = 'hidden';
471    			}
472    		}
473    		if (newPoint.x != eqPoint.x)
474    		{
475    			if (this.guideX != null && this.guideX.node != null)
476    			{
477    				this.guideX.node.style.visibility = 'hidden';
478    			}
479    		}
480
481	    	return eqPoint;
482    	}
483	    else
484    	{
485	    	hideEqGuides(true, true);
486	    	return guideMove.apply(this, arguments);
487    	}
488	};
489
490	var guideSetVisible = mxGuide.prototype.setVisible;
491
492	mxGuide.prototype.setVisible = function (visible)
493	{
494		var guide = this;
495		guideSetVisible.call(guide, visible);
496
497	    var guidesArrVer = guide.guidesArrVer;
498	    var guidesArrHor = guide.guidesArrHor;
499
500	    if (guidesArrVer != null)
501	    {
502	      for (var i = 0; i < guidesArrVer.length; i++)
503		  {
504	    	  guidesArrVer[i].node.style.visibility = visible? "visible" : "hidden";
505		  }
506	    }
507
508	    if (guidesArrHor != null)
509	    {
510	      for (var i = 0; i < guidesArrHor.length; i++)
511	  	  {
512	    	  guidesArrHor[i].node.style.visibility = visible? "visible" : "hidden";
513	  	  }
514	    }
515	};
516
517	var guideDestroy = mxGuide.prototype.destroy;
518
519	mxGuide.prototype.destroy = function()
520	{
521		guideDestroy.call(this);
522	    var guidesArrVer = this.guidesArrVer;
523	    var guidesArrHor = this.guidesArrHor;
524
525	    if (guidesArrVer != null)
526	    {
527	    	for (var i = 0; i < guidesArrVer.length; i++)
528	    	{
529	    		guidesArrVer[i].destroy();
530	    	}
531	    	this.guidesArrVer = null;
532	    }
533
534	    if (guidesArrHor != null)
535	    {
536	    	for (var i = 0; i < guidesArrHor.length; i++)
537	    	{
538	    		guidesArrHor[i].destroy();
539	    	}
540	    	this.guidesArrHor = null;
541	    }
542	};
543})();