1/**
2 * Mindmaps plugin.
3 *
4 * Todo:
5 * - Make cursor key selection more generic
6 * - Handle single-cell movement on touch
7 * - Move multiple cells without subtrees
8 * - Make offset subtrees more generic
9 */
10Draw.loadPlugin(function(ui)
11{
12	if (ui.editor.isChromelessView())
13	{
14		return;
15	}
16
17	var spacing = 10;
18	var level = 40;
19
20	var graph = ui.editor.graph;
21	var model = graph.getModel();
22
23	// Adds resources for actions
24	mxResources.parse('selectChildren=Select Children');
25	mxResources.parse('selectSiblings=Select Siblings');
26	mxResources.parse('selectSubtree=Select Subtree');
27	mxResources.parse('selectParent=Select Parent');
28
29	function isTreeCell(cell)
30	{
31		var result = false;
32
33		if (cell != null)
34		{
35			graph.traverse(cell, true, function(vertex)
36			{
37				result = vertex.getAttribute('treeRoot') == '1';
38
39				return !result;
40			}, null, null, true);
41		}
42
43		return result;
44	};
45
46	var uiCreatePopupMenu = ui.menus.createPopupMenu;
47	ui.menus.createPopupMenu = function(menu, cell, evt)
48	{
49		uiCreatePopupMenu.apply(this, arguments);
50
51		if (isTreeCell(graph.getSelectionCell()) && graph.getSelectionCount() == 1)
52		{
53			var cell = graph.getSelectionCell();
54			var sib = graph.getOutgoingEdges(cell);
55			menu.addSeparator();
56
57			if (sib != null && sib.length > 0)
58			{
59				this.addMenuItems(menu, ['selectChildren', 'selectSubtree'], null, evt);
60			}
61
62			menu.addSeparator();
63
64			if (cell.getAttribute('treeRoot') != '1')
65			{
66				this.addMenuItems(menu, ['selectSiblings', 'selectParent'], null, evt);
67			}
68		}
69	};
70
71	// Adds actions
72	ui.actions.addAction('selectChildren', function()
73	{
74		if (graph.isEnabled() && graph.getSelectionCount() == 1)
75		{
76			var cell = graph.getSelectionCell();
77			var sib = graph.getOutgoingEdges(cell);
78
79			if (sib != null)
80			{
81				var tmp = [];
82
83				for (var i = 0; i < sib.length; i++)
84				{
85					tmp.push(graph.model.getTerminal(sib[i], false));
86				}
87
88				graph.setSelectionCells(tmp);
89			}
90		}
91	}, null, null, 'Alt+Shift+X');
92
93	// Adds actions
94	ui.actions.addAction('selectSiblings', function()
95	{
96		if (graph.isEnabled() && graph.getSelectionCount() == 1)
97		{
98			var cell = graph.getSelectionCell();
99			var edges = graph.getIncomingEdges(cell);
100
101			if (edges != null && edges.length > 0)
102			{
103				var sib = graph.getOutgoingEdges(graph.model.getTerminal(edges[0], true));
104
105				if (sib != null)
106				{
107					var tmp = [];
108
109					for (var i = 0; i < sib.length; i++)
110					{
111						tmp.push(graph.model.getTerminal(sib[i], false));
112					}
113
114					graph.setSelectionCells(tmp);
115				}
116			}
117		}
118	}, null, null, 'Alt+Shift+S');
119
120	// Adds actions
121	ui.actions.addAction('selectParent', function()
122	{
123		if (graph.isEnabled() && graph.getSelectionCount() == 1)
124		{
125			var cell = graph.getSelectionCell();
126			var edges = graph.getIncomingEdges(cell);
127
128			if (edges != null && edges.length > 0)
129			{
130				graph.setSelectionCell(graph.model.getTerminal(edges[0], true));
131			}
132		}
133	}, null, null, 'Alt+Shift+P');
134
135	ui.actions.addAction('selectSubtree', function()
136	{
137		if (graph.isEnabled() && graph.getSelectionCount() == 1)
138		{
139			var cell = graph.getSelectionCell();
140			// Makes space for new parent
141			var subtree = [];
142
143			graph.traverse(cell, true, function(vertex, edge)
144			{
145				if (edge != null)
146				{
147					subtree.push(edge);
148				}
149
150				subtree.push(vertex);
151
152				return true;
153			});
154
155			graph.setSelectionCells(subtree);
156		}
157	}, null, null, 'Alt+Shift+T');
158
159	/**
160	 * Overriddes
161	 */
162	var graphFoldCells = graph.foldCells;
163
164	graph.foldCells = function(collapse, recurse, cells, checkFoldable, evt)
165	{
166		//console.log('cells', cells, collapse);
167		this.stopEditing();
168
169		this.model.beginUpdate();
170		try
171		{
172			var newCells = cells.splice();
173			var tmp = [];
174
175			for (var i = 0; i < cells.length; i++)
176			{
177				if (isTreeCell(cells[i]))
178				{
179					graph.traverse(cells[i], true, function(vertex, edge)
180					{
181						if (edge != null)
182						{
183							tmp.push(edge);
184						}
185
186						if (vertex != cells[i])
187						{
188							tmp.push(vertex);
189						}
190
191						// Stop traversal on collapsed vertices
192						return vertex == cells[i] || !graph.model.isCollapsed(vertex);
193					});
194
195					graph.model.setCollapsed(cells[i], collapse);
196				}
197			}
198
199			for (var i = 0; i < tmp.length; i++)
200			{
201				graph.model.setVisible(tmp[i], !collapse);
202			}
203
204			cells = newCells;
205			graphFoldCells.apply(this, arguments);
206		}
207		finally
208		{
209			this.model.endUpdate();
210		}
211	};
212
213	var graphRemoveCells = graph.removeCells;
214
215	graph.removeCells = function(cells, includeEdges)
216	{
217		var tmp = [];
218
219		for (var i = 0; i < cells.length; i++)
220		{
221			if (isTreeCell(cells[i]))
222			{
223				graph.traverse(cells[i], true, function(vertex, edge)
224				{
225					if (edge != null)
226					{
227						tmp.push(edge);
228					}
229
230					tmp.push(vertex);
231
232					return true;
233				});
234
235				var edges = graph.getIncomingEdges(cells[i]);
236				cells = cells.concat(edges);
237			}
238			else
239			{
240				tmp.push(cells[i]);
241			}
242		}
243
244		cells = tmp;
245
246		graphRemoveCells.apply(this, arguments);
247	};
248
249	ui.hoverIcons.getStateAt = function(state, x, y)
250	{
251		return (isTreeCell(state.cell)) ? null : this.graph.view.getState(this.graph.getCellAt(x, y));
252	};
253
254	var graphDuplicateCells = graph.duplicateCells;
255
256	graph.duplicateCells = function(cells, append)
257	{
258		cells = (cells != null) ? cells : this.getSelectionCells();
259		var temp = cells.slice(0);
260
261		for (var i = 0; i < temp.length; i++)
262		{
263			var cell = temp[i];
264			var state = graph.view.getState(cell);
265
266			if (state != null && isTreeCell(state.cell))
267			{
268				// Avoids disconnecting subtree by removing all incoming edges
269				var edges = graph.getIncomingEdges(state.cell);
270
271				for (var j = 0; j < edges.length; j++)
272				{
273					mxUtils.remove(edges[j], cells);
274				}
275			}
276		}
277
278		this.model.beginUpdate();
279		try
280		{
281			var result = graphDuplicateCells.call(this, cells, append);
282
283			if (result.length == cells.length)
284			{
285				for (var i = 0; i < cells.length; i++)
286				{
287					if (isTreeCell(cells[i]))
288					{
289						var newEdges = graph.getIncomingEdges(result[i]);
290						var edges = graph.getIncomingEdges(cells[i]);
291
292						if (newEdges.length == 0 && edges.length > 0)
293						{
294							var clone = this.cloneCells([edges[0]])[0];
295							this.addEdge(clone, graph.getDefaultParent(),
296								this.model.getTerminal(edges[0], true), result[i]);
297						}
298					}
299				}
300			}
301		}
302		finally
303		{
304			this.model.endUpdate();
305		}
306
307		return result;
308	};
309
310	var graphMoveCells = graph.moveCells;
311
312	graph.moveCells = function(cells, dx, dy, clone, target, evt, mapping)
313	{
314		var result = null;
315
316		this.model.beginUpdate();
317		try
318		{
319			var newSource = target;
320
321			if (isTreeCell(target))
322			{
323				// Handles only drag from tree or from sidebar with dangling edges
324				for (var i = 0; i < cells.length; i++)
325				{
326					if (isTreeCell(cells[i]) || (graph.model.isEdge(cells[i]) &&
327						graph.model.getTerminal(cells[i], true) == null))
328					{
329						target = null;
330						break;
331					}
332				}
333
334				// Applies distance between previous and current parent for non-sidebar drags
335				if (newSource != null && target == null && this.view.getState(cells[0]) != null)
336				{
337					var edges = graph.getIncomingEdges(cells[0]);
338
339					if (edges.length > 0)
340					{
341						var state1 = graph.view.getState(graph.model.getTerminal(edges[0], true));
342
343						if (state1 != null)
344						{
345							var state2 = graph.view.getState(newSource);
346
347							if (state2 != null)
348							{
349								dx = state2.getCenterX() - state1.getCenterX();
350								dy = state2.getCenterY() - state1.getCenterY();
351							}
352						}
353					}
354				}
355			}
356
357			result = graphMoveCells.apply(this, arguments);
358
359			if (result.length == cells.length)
360			{
361				for (var i = 0; i < result.length; i++)
362				{
363					// Connects all dangling edges from the sidebar when dropped into drop target (not hover icon)
364					if (this.model.isEdge(result[i]))
365					{
366						if (isTreeCell(newSource) && mxUtils.indexOf(result, this.model.getTerminal(result[i], true)) < 0)
367						{
368							this.model.setTerminal(result[i], newSource, true);
369						}
370					}
371					else if (isTreeCell(cells[i]))
372					{
373						var edges = graph.getIncomingEdges(cells[i]);
374
375						if (edges.length > 0)
376						{
377							if (!clone)
378							{
379								if (isTreeCell(newSource) && mxUtils.indexOf(cells, this.model.getTerminal(edges[0], true)) < 0)
380								{
381									this.model.setTerminal(edges[0], newSource, true);
382								}
383							}
384							else
385							{
386								var newEdges = graph.getIncomingEdges(result[i]);
387
388								if (newEdges.length == 0)
389								{
390									var temp = newSource;
391
392									if (temp == null)
393									{
394										temp = graph.model.getTerminal(edges[0], true);
395									}
396
397									var clone = this.cloneCells([edges[0]])[0];
398									this.addEdge(clone, graph.getDefaultParent(), temp, result[i]);
399								}
400							}
401						}
402					}
403				}
404			}
405		}
406		finally
407		{
408			this.model.endUpdate();
409		}
410
411		return result;
412	};
413
414	// Connects all dangling edges from the sidebar (by
415	// default only first dangling edge gets connected)
416	var sidebarDropAndConnect = ui.sidebar.dropAndConnect;
417
418	ui.sidebar.dropAndConnect = function(source, targets, direction, dropCellIndex)
419	{
420		var model = graph.model;
421		var result = null;
422
423		model.beginUpdate();
424		try
425		{
426			result = sidebarDropAndConnect.apply(this, arguments);
427
428			if (isTreeCell(source))
429			{
430				for (var i = 0; i < result.length; i++)
431				{
432					if (model.isEdge(result[i]) && model.getTerminal(result[i], true) == null)
433					{
434						model.setTerminal(result[i], source, true);
435						var geo = graph.getCellGeometry(result[i]);
436						geo.points = null;
437
438						if (geo.getTerminalPoint(true) != null)
439						{
440							geo.setTerminalPoint(null, true);
441						}
442					}
443				}
444			}
445		}
446		finally
447		{
448			model.endUpdate();
449		}
450
451		return result;
452	};
453
454	/**
455	 * Checks source point of incoming edge relative to target terminal.
456	 */
457	function getTreeDirection(cell)
458	{
459		var state = graph.view.getState(cell);
460
461		if (state != null)
462		{
463			var edges = graph.getIncomingEdges(state.cell);
464
465			if (edges.length > 0)
466			{
467				var edgeState = graph.view.getState(edges[0]);
468
469				if (edgeState != null)
470				{
471					var abs = edgeState.absolutePoints;
472
473					if (abs != null && abs.length > 0)
474					{
475						var pt = abs[abs.length - 1];
476
477						if (pt != null)
478						{
479							if (pt.y == state.y && Math.abs(pt.x - state.getCenterX()) < state.width / 2)
480							{
481								return mxConstants.DIRECTION_SOUTH;
482							}
483							else if (pt.y == state.y + state.height && Math.abs(pt.x - state.getCenterX()) < state.width / 2)
484							{
485								return mxConstants.DIRECTION_NORTH;
486							}
487							else if (pt.x > state.getCenterX())
488							{
489								return mxConstants.DIRECTION_WEST;
490							}
491						}
492					}
493				}
494			}
495		}
496
497		return mxConstants.DIRECTION_EAST;
498	};
499
500	function addSibling(cell, after)
501	{
502		after = (after != null) ? after : true;
503
504		graph.model.beginUpdate();
505		try
506		{
507			var edges = graph.getIncomingEdges(cell);
508			var clones = graph.cloneCells([edges[0], cell]);
509			graph.model.setTerminal(clones[0], graph.model.getTerminal(edges[0], true), true);
510
511			var dir = getTreeDirection(cell);
512
513			if (dir == mxConstants.DIRECTION_SOUTH || dir == mxConstants.DIRECTION_NORTH)
514			{
515				clones[1].geometry.x += (after) ? cell.geometry.width + spacing :
516					-clones[1].geometry.width - spacing;
517			}
518			else
519			{
520				clones[1].geometry.y += (after) ? cell.geometry.height + spacing :
521					-clones[1].geometry.height - spacing;
522			}
523
524			if (dir == mxConstants.DIRECTION_WEST)
525			{
526				clones[1].geometry.x = cell.geometry.x + cell.geometry.width - clones[1].geometry.width;
527			}
528
529			// Moves existing siblings
530			var state = graph.view.getState(cell);
531			var s = graph.view.scale;
532
533			if (state != null)
534			{
535				var bbox = mxRectangle.fromRectangle(state);
536
537				if (dir == mxConstants.DIRECTION_SOUTH ||
538					dir == mxConstants.DIRECTION_NORTH)
539				{
540					bbox.x += ((after) ? cell.geometry.width + spacing :
541						-clones[1].geometry.width - spacing) * s;
542				}
543				else
544				{
545					bbox.y += ((after) ? cell.geometry.height + spacing :
546						-clones[1].geometry.height - spacing) * s;
547				}
548
549				var sib = graph.getOutgoingEdges(graph.model.getTerminal(edges[0], true));
550
551				if (sib != null)
552				{
553					var hor = (dir == mxConstants.DIRECTION_SOUTH || dir == mxConstants.DIRECTION_NORTH);
554					var dx = 0;
555					var dy = 0;
556
557					for (var i = 0; i < sib.length; i++)
558					{
559						var temp = graph.model.getTerminal(sib[i], false);
560
561						if (dir == getTreeDirection(temp))
562						{
563							var sibling = graph.view.getState(temp);
564
565							if (temp != cell && sibling != null)
566							{
567								if ((hor && after != sibling.getCenterX() < state.getCenterX()) ||
568									(!hor && after != sibling.getCenterY() < state.getCenterY()))
569								{
570									if (mxUtils.intersects(bbox, sibling))
571									{
572										dx = spacing + Math.max(dx, (Math.min(bbox.x + bbox.width,
573											sibling.x + sibling.width) - Math.max(bbox.x, sibling.x)) / s);
574										dy = spacing + Math.max(dy, (Math.min(bbox.y + bbox.height,
575											sibling.y + sibling.height) - Math.max(bbox.y, sibling.y)) / s);
576									}
577								}
578							}
579						}
580					}
581
582					if (hor)
583					{
584						dy = 0;
585					}
586					else
587					{
588						dx = 0;
589					}
590
591					for (var i = 0; i < sib.length; i++)
592					{
593						var temp = graph.model.getTerminal(sib[i], false);
594
595						if (dir == getTreeDirection(temp))
596						{
597							var sibling = graph.view.getState(temp);
598
599							if (temp != cell && sibling != null)
600							{
601								if ((hor && after != sibling.getCenterX() < state.getCenterX()) ||
602									(!hor && after != sibling.getCenterY() < state.getCenterY()))
603								{
604									var subtree = [];
605
606									graph.traverse(sibling.cell, true, function(vertex, edge)
607									{
608										if (edge != null)
609										{
610											subtree.push(edge);
611										}
612
613										subtree.push(vertex);
614
615										return true;
616									});
617
618									graph.moveCells(subtree, ((after) ? 1 : -1) * dx, ((after) ? 1 : -1) * dy);
619								}
620							}
621						}
622					}
623				}
624			}
625
626			return graph.addCells(clones);
627		}
628		finally
629		{
630			graph.model.endUpdate();
631		}
632	};
633
634	function addParent(cell)
635	{
636		graph.model.beginUpdate();
637		try
638		{
639			var dir = getTreeDirection(cell);
640			var edges = graph.getIncomingEdges(cell);
641			var clones = graph.cloneCells([edges[0], cell]);
642			graph.model.setTerminal(edges[0], clones[1], false);
643			graph.model.setTerminal(clones[0], clones[1], true);
644			graph.model.setTerminal(clones[0], cell, false);
645
646			// Makes space for new parent
647			var subtree = [];
648
649			graph.traverse(cell, true, function(vertex, edge)
650			{
651				if (edge != null)
652				{
653					subtree.push(edge);
654				}
655
656				subtree.push(vertex);
657
658				return true;
659			});
660
661			var dx = cell.geometry.width + level;
662			var dy = cell.geometry.height + level;
663
664			if (dir == mxConstants.DIRECTION_SOUTH)
665			{
666				dx = 0;
667			}
668			else if (dir == mxConstants.DIRECTION_NORTH)
669			{
670				dx = 0;
671				dy = -level;
672			}
673			else if (dir == mxConstants.DIRECTION_WEST)
674			{
675				dx = -level;
676				dy = 0;
677			}
678			else if (dir == mxConstants.DIRECTION_EAST)
679			{
680				dy = 0;
681			}
682
683			graph.moveCells(subtree, dx, dy);
684
685			return graph.addCells(clones);
686		}
687		finally
688		{
689			graph.model.endUpdate();
690		}
691	};
692
693	function addChild(cell)
694	{
695		graph.model.beginUpdate();
696		try
697		{
698			var edges = graph.getIncomingEdges(cell);
699			var clones = graph.cloneCells([edges[0], cell]);
700			graph.model.setTerminal(clones[0], cell, true);
701
702			// Finds free space
703			var edges = graph.getOutgoingEdges(cell);
704			var targets = [];
705
706			for (var i = 0; i < edges.length; i++)
707			{
708				var target = graph.model.getTerminal(edges[i], false);
709
710				if (target != null)
711				{
712					targets.push(target);
713				}
714			}
715
716			var bbox = graph.view.getBounds(targets);
717			var dir = getTreeDirection(cell);
718			var tr = graph.view.translate;
719			var s = graph.view.scale;
720
721			if (dir == mxConstants.DIRECTION_SOUTH)
722			{
723				clones[1].geometry.x = (bbox == null) ? cell.geometry.x + (cell.geometry.width -
724					clones[1].geometry.width) / 2 : (bbox.x + bbox.width) / s - tr.x + spacing;
725				clones[1].geometry.y += cell.geometry.height + level;
726			}
727			else if (dir == mxConstants.DIRECTION_NORTH)
728			{
729				clones[1].geometry.x = (bbox == null) ? cell.geometry.x + (cell.geometry.width -
730						clones[1].geometry.width) / 2 : (bbox.x + bbox.width) / s - tr.x + spacing;
731				clones[1].geometry.y -= clones[1].geometry.height + level;
732			}
733			else if (dir == mxConstants.DIRECTION_WEST)
734			{
735				clones[1].geometry.x -= clones[1].geometry.width + level;
736				clones[1].geometry.y = (bbox == null) ? cell.geometry.y + (cell.geometry.height -
737						clones[1].geometry.height) / 2 : (bbox.y + bbox.height) / s - tr.y + spacing;
738			}
739			else
740			{
741				clones[1].geometry.x += cell.geometry.width + level;
742				clones[1].geometry.y = (bbox == null) ? cell.geometry.y + (cell.geometry.height -
743						clones[1].geometry.height) / 2 : (bbox.y + bbox.height) / s - tr.y + spacing;
744			}
745
746			return graph.addCells(clones);
747		}
748		finally
749		{
750			graph.model.endUpdate();
751		}
752	};
753
754	function getOrderedTargets(cell, horizontal, ref)
755	{
756		var sib = graph.getOutgoingEdges(cell);
757		var state = graph.view.getState(ref);
758		var targets = [];
759
760		if (state != null && sib != null)
761		{
762			for (var i = 0; i < sib.length; i++)
763			{
764				var temp = graph.view.getState(graph.model.getTerminal(sib[i], false));
765
766				if (temp != null && ((!horizontal && (Math.min(temp.x + temp.width,
767					state.x + state.width) >= Math.max(temp.x, state.x))) ||
768					(horizontal && (Math.min(temp.y + temp.height, state.y + state.height) >=
769					Math.max(temp.y, state.y)))))
770				{
771					targets.push(temp);
772				}
773			}
774
775			targets.sort(function(a, b)
776			{
777				return (horizontal) ? a.x + a.width - b.x - b.width : a.y + a.height - b.y - b.height;
778			});
779		}
780
781		return targets;
782	};
783
784	function selectCell(cell, direction)
785	{
786		var dir = getTreeDirection(cell);
787		var h1 = dir == mxConstants.DIRECTION_EAST || dir == mxConstants.DIRECTION_WEST;
788		var h2 = direction == mxConstants.DIRECTION_EAST || direction == mxConstants.DIRECTION_WEST;
789
790		if (h1 == h2 && dir != direction)
791		{
792			ui.actions.get('selectParent').funct();
793		}
794		else if (dir == direction)
795		{
796			var sib = graph.getOutgoingEdges(cell);
797
798			if (sib != null && sib.length > 0)
799			{
800				graph.setSelectionCell(graph.model.getTerminal(sib[0], false));
801			}
802		}
803		else
804		{
805			var edges = graph.getIncomingEdges(cell);
806
807			if (edges != null && edges.length > 0)
808			{
809				var targets = getOrderedTargets(graph.model.getTerminal(edges[0], true), h2, cell);
810				var state = graph.view.getState(cell);
811
812				if (state != null)
813				{
814					var idx = mxUtils.indexOf(targets, state);
815
816					if (idx >= 0)
817					{
818						idx += (direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_WEST) ? -1 : 1;
819
820						if (idx >= 0 && idx <= targets.length - 1)
821						{
822							graph.setSelectionCell(targets[idx].cell);
823						}
824					}
825				}
826			}
827		}
828	};
829
830	// Overrides keyboard shortcuts
831	var altShiftActions = {88: ui.actions.get('selectChildren'), // Alt+Shift+X
832			84: ui.actions.get('selectSubtree'), // Alt+Shift+T
833			80: ui.actions.get('selectParent'), // Alt+Shift+P
834			83: ui.actions.get('selectSiblings')} // Alt+Shift+S
835
836	var editorUiOnKeyDown = ui.onKeyDown;
837
838	ui.onKeyDown = function(evt)
839	{
840		try
841		{
842			if (graph.isEnabled() && !graph.isEditing() && graph.getSelectionCount() == 1 &&
843				isTreeCell(graph.getSelectionCell()))
844			{
845				var cells = null;
846
847				if (graph.getSelectionCell().getAttribute('treeRoot') != '1')
848				{
849					if (evt.which == 9) // Tab adds child
850					{
851						cells = (mxEvent.isShiftDown(evt)) ?
852							addParent(graph.getSelectionCell()) :
853							addChild(graph.getSelectionCell());
854					}
855					else if (evt.which == 13) // Enter adds sibling
856					{
857						cells = addSibling(graph.getSelectionCell(), !mxEvent.isShiftDown(evt));
858					}
859				}
860
861				if (cells != null && cells.length > 0)
862				{
863					if (cells.length == 1 && graph.model.isEdge(cells[0]))
864					{
865						graph.setSelectionCell(graph.model.getTerminal(cells[0], false));
866					}
867					else
868					{
869						graph.setSelectionCell(cells[cells.length - 1]);
870					}
871
872					if (ui.hoverIcons != null)
873					{
874						ui.hoverIcons.update(graph.view.getState(graph.getSelectionCell()));
875					}
876
877					graph.startEditingAtCell(graph.getSelectionCell());
878					mxEvent.consume(evt);
879				}
880				else
881				{
882					if (mxEvent.isAltDown(evt) && mxEvent.isShiftDown(evt))
883					{
884						var action = altShiftActions[evt.keyCode];
885
886						if (action != null)
887						{
888							action.funct(evt);
889							mxEvent.consume(evt);
890						}
891					}
892					else
893					{
894						if (evt.keyCode == 37) // left
895						{
896							selectCell(graph.getSelectionCell(), mxConstants.DIRECTION_WEST);
897							mxEvent.consume(evt);
898						}
899						else if (evt.keyCode == 38) // up
900						{
901							selectCell(graph.getSelectionCell(), mxConstants.DIRECTION_NORTH);
902							mxEvent.consume(evt);
903						}
904						else if (evt.keyCode == 39) // right
905						{
906							selectCell(graph.getSelectionCell(), mxConstants.DIRECTION_EAST);
907							mxEvent.consume(evt);
908						}
909						else if (evt.keyCode == 40) // down
910						{
911							selectCell(graph.getSelectionCell(), mxConstants.DIRECTION_SOUTH);
912							mxEvent.consume(evt);
913						}
914					}
915				}
916			}
917		}
918		catch (e)
919		{
920			console.log('error', e);
921		}
922
923		if (!mxEvent.isConsumed(evt))
924		{
925			editorUiOnKeyDown.apply(this, arguments);
926		}
927	};
928
929	var graphConnectVertex = graph.connectVertex;
930
931	graph.connectVertex = function(source, direction, length, evt, forceClone, ignoreCellAt)
932	{
933		if (isTreeCell(source) && source.getAttribute('treeRoot') != '1')
934		{
935			var dir = getTreeDirection(source);
936			var h1 = dir == mxConstants.DIRECTION_EAST || dir == mxConstants.DIRECTION_WEST;
937			var h2 = direction == mxConstants.DIRECTION_EAST || direction == mxConstants.DIRECTION_WEST;
938
939			if (dir == direction)
940			{
941				return addChild(source);
942			}
943			else if (h1 == h2)
944			{
945				return addParent(source);
946			}
947			else
948			{
949				return addSibling(source, direction != mxConstants.DIRECTION_NORTH &&
950					direction != mxConstants.DIRECTION_WEST);
951			}
952
953			return [];
954		}
955		else
956		{
957			this.model.beginUpdate();
958			try
959			{
960				var cells = graphConnectVertex.call(this, source, direction, length, evt, forceClone,
961					ignoreCellAt || source.getAttribute('treeRoot') == '1');
962
963				// Removes treeRoot flag in clones
964				if (source.getAttribute('treeRoot') == '1')
965				{
966					for (var i = 0; i < cells.length; i++)
967					{
968						if (cells[i].getAttribute('treeRoot') == '1')
969						{
970							graph.setAttributeForCell(cells[i], 'treeRoot', null);
971						}
972					}
973				}
974			}
975			finally
976			{
977				this.model.endUpdate();
978			}
979
980			return cells;
981		}
982	};
983
984	var graphHandlerGetCells = graph.graphHandler.getCells;
985
986	graph.graphHandler.getCells = function(initialCell)
987	{
988		var cells = graphHandlerGetCells.apply(this, arguments);
989		var temp = cells.slice(0);
990
991		// Removes all edges first
992		for (var i = 0; i < temp.length; i++)
993		{
994			if (isTreeCell(temp[i]))
995			{
996				// Avoids disconnecting subtree by removing all incoming edges
997				var edges = graph.getIncomingEdges(temp[i]);
998
999				for (var j = 0; j < edges.length; j++)
1000				{
1001					mxUtils.remove(edges[j], cells);
1002				}
1003			}
1004		}
1005
1006		for (var i = 0; i < temp.length; i++)
1007		{
1008			if (isTreeCell(temp[i]))
1009			{
1010				// Gets the subtree from cell downwards
1011				graph.traverse(temp[i], true, function(vertex, edge)
1012				{
1013					// TODO: Use dictionary to avoid duplicates
1014					if (edge != null && mxUtils.indexOf(cells, edge) < 0)
1015					{
1016						cells.push(edge);
1017					}
1018
1019					if (mxUtils.indexOf(cells, vertex) < 0)
1020					{
1021						cells.push(vertex);
1022					}
1023
1024					return true;
1025				});
1026			}
1027		}
1028
1029		return cells;
1030	};
1031
1032//	var ignoreMove = false;
1033//
1034//	graph.addListener(mxEvent.MOVE_CELLS, function(sender, evt)
1035//	{
1036//		if (!ignoreMove)
1037//		{
1038//			var cells = evt.getProperty('cells');
1039//			var dx = evt.getProperty('dx');
1040//			var dy = evt.getProperty('dy');
1041//			ignoreMove = true;
1042//
1043//			for (var i = 0; i < cells.length; i++)
1044//			{
1045//				var state = graph.view.getState(cells[i]);
1046//
1047//				if (state != null && state.style['mindmapRoot'] == '1')
1048//				{
1049//					// TODO: Move subtree by same dx/dy
1050//					//layout.execute(model.getParent(state.cell), state.cell);
1051//
1052//					// Gets the subtree from cell downwards
1053//					var tmp = [];
1054//					graph.traverse(cells[i], true, function(vertex)
1055//					{
1056//						tmp.push(vertex);
1057//
1058//						return true;
1059//					});
1060//
1061//					mxUtils.remove(cells[i], tmp);
1062//					graph.moveCells(tmp, dx, dy);
1063//				}
1064//			}
1065//
1066//			ignoreMove = false;
1067//		}
1068//	});
1069
1070	// Defines a new class for all icons
1071	function mxIconSet(state)
1072	{
1073		this.images = [];
1074		var graph = state.view.graph;
1075
1076		// Icon1
1077//		var img = mxUtils.createImage('images/handle-connect.png');
1078//		img.setAttribute('title', 'Duplicate');
1079//		img.style.position = 'absolute';
1080//		img.style.cursor = 'pointer';
1081//		img.style.width = '26px';
1082//		img.style.height = '26px';
1083//		img.style.left = (state.x - 13) + 'px';
1084//		img.style.top = (state.getCenterY() - 13) + 'px';
1085//
1086//		mxEvent.addGestureListeners(img,
1087//			mxUtils.bind(this, function(evt)
1088//			{
1089//				var s = graph.gridSize;
1090//				graph.setSelectionCells(graph.moveCells([state.cell], s, s, true));
1091//				mxEvent.consume(evt);
1092//				this.destroy();
1093//			})
1094//		);
1095//
1096//		state.view.graph.container.appendChild(img);
1097//		this.images.push(img);
1098
1099		// Delete
1100		var img = mxUtils.createImage('plugins/trees/handle-move.gif');
1101		img.setAttribute('title', 'Move Cell without Subtree');
1102		img.style.position = 'absolute';
1103		img.style.cursor = 'pointer';
1104		img.style.width = '26px';
1105		img.style.height = '26px';
1106		img.style.left = (state.getCenterX() - 13) + 'px';
1107		img.style.top = (state.getCenterY() - 13) + 'px';
1108
1109		mxEvent.addGestureListeners(img, mxUtils.bind(this, function(evt)
1110		{
1111			graph.stopEditing(false);
1112			ui.hoverIcons.reset();
1113
1114			if (!graph.isCellSelected(state.cell))
1115			{
1116				graph.setSelectionCell(state.cell);
1117			}
1118
1119			graph.graphHandler.start(state.cell, mxEvent.getClientX(evt), mxEvent.getClientY(evt));
1120
1121			graph.graphHandler.cells = [state.cell];
1122			graph.graphHandler.bounds = graph.graphHandler.graph.getView().getBounds(graph.graphHandler.cells);
1123			graph.graphHandler.pBounds = graph.graphHandler.getPreviewBounds(graph.graphHandler.cells);
1124
1125			graph.graphHandler.cellWasClicked = true;
1126			graph.isMouseDown = true;
1127			graph.isMouseTrigger = mxEvent.isMouseEvent(evt);
1128			mxEvent.consume(evt);
1129
1130			// Disables dragging the image
1131			mxEvent.consume(evt);
1132			this.destroy();
1133		}));
1134
1135//		mxEvent.addListener(img, 'click',
1136//			mxUtils.bind(this, function(evt)
1137//			{
1138//				console.log('here', graph.graphHandler.dx);
1139//
1140//				if (Math.abs(graph.graphHandler.currentDx) < graph.tolerance)
1141//				{
1142//					graph.setSelectionCell(state.cell);
1143//				}
1144//			})
1145//		);
1146
1147		state.view.graph.container.appendChild(img);
1148		this.images.push(img);
1149	};
1150
1151	mxIconSet.prototype.destroy = function()
1152	{
1153		if (this.images != null)
1154		{
1155			for (var i = 0; i < this.images.length; i++)
1156			{
1157				var img = this.images[i];
1158				img.parentNode.removeChild(img);
1159			}
1160		}
1161
1162		this.images = null;
1163	};
1164
1165	// Defines the tolerance before removing the icons
1166	var iconTolerance = 20;
1167
1168	// Shows icons if the mouse is over a cell
1169	graph.addMouseListener(
1170	{
1171	    currentState: null,
1172	    currentIconSet: null,
1173	    mouseDown: function(sender, me)
1174	    {
1175	    	// Hides icons on mouse down
1176        	if (this.currentState != null)
1177        	{
1178          		this.dragLeave(me.getEvent(), this.currentState);
1179          		this.currentState = null;
1180        	}
1181
1182        	// TODO: Fix single cell movement on touch devices
1183//        	if (mxEvent.isTouchEvent(me.getEvent()))
1184//        	{
1185//        		this.mouseMove(sender, me);
1186//        	}
1187	    },
1188	    mouseMove: function(sender, me)
1189	    {
1190	    	if (this.currentState != null && (me.getState() == this.currentState ||
1191	    		me.getState() == null))
1192	    	{
1193	    		var tol = iconTolerance;
1194	    		var tmp = new mxRectangle(me.getGraphX() - tol,
1195	    			me.getGraphY() - tol, 2 * tol, 2 * tol);
1196
1197	    		if (mxUtils.intersects(tmp, this.currentState))
1198	    		{
1199	    			return;
1200	    		}
1201	    	}
1202
1203			var tmp = me.getState();
1204
1205	    	// Ignores everything but vertices
1206			if ((graph.isMouseDown && !mxEvent.isTouchEvent(me.getEvent())) ||
1207				graph.isEditing() || (tmp != null &&
1208				(!graph.getModel().isVertex(tmp.cell) || !isTreeCell(me.getCell()))))
1209			{
1210				tmp = null;
1211			}
1212
1213	      	if (tmp != this.currentState)
1214	      	{
1215	        	if (this.currentState != null)
1216	        	{
1217	          		this.dragLeave(me.getEvent(), this.currentState);
1218	        	}
1219
1220        		this.currentState = tmp;
1221
1222	        	if (this.currentState != null)
1223	        	{
1224	          		this.dragEnter(me.getEvent(), this.currentState);
1225	        	}
1226	      	}
1227	    },
1228	    mouseUp: function(sender, me) { },
1229	    dragEnter: function(evt, state)
1230	    {
1231	    	if (this.currentIconSet == null)
1232	    	{
1233    			this.currentIconSet = new mxIconSet(state);
1234	    	}
1235	    },
1236	    dragLeave: function(evt, state)
1237	    {
1238	    	if (this.currentIconSet != null)
1239	    	{
1240    			this.currentIconSet.destroy();
1241    			this.currentIconSet = null;
1242	    	}
1243	    }
1244	});
1245
1246	// Adds sidebar entries
1247	var sb = ui.sidebar;
1248
1249    sb.addPalette('trees', 'Trees', true, function(content)
1250    {
1251        (function()
1252        {
1253	    	var cell = new mxCell('Central Idea', new mxGeometry(0, 20, 100, 40),
1254		    	'ellipse;whiteSpace=wrap;html=1;align=center;' +
1255		    	'collapsible=0;container=1;recursiveResize=0;');
1256		    graph.setAttributeForCell(cell, 'treeRoot', '1');
1257	    	cell.vertex = true;
1258
1259	    	var cell2 = new mxCell('Branch', new mxGeometry(160, 0, 80, 20),
1260	    		'whiteSpace=wrap;html=1;shape=partialRectangle;top=0;left=0;bottom=1;right=0;points=[[0,1],[1,1]];' +
1261	    		'strokeColor=#000000;fillColor=none;align=center;verticalAlign=bottom;routingCenterY=0.5;' +
1262	    		'snapToPoint=1;collapsible=0;container=1;recursiveResize=0;autosize=1;');
1263	    	cell2.vertex = true;
1264
1265	    	var edge = new mxCell('', new mxGeometry(0, 0, 0, 0), 'edgeStyle=entityRelationEdgeStyle;' +
1266				'startArrow=none;endArrow=none;segment=10;curved=1;');
1267			edge.geometry.relative = true;
1268			edge.edge = true;
1269
1270			cell.insertEdge(edge, true);
1271			cell2.insertEdge(edge, false);
1272
1273	    	var cell3 = new mxCell('Sub Topic', new mxGeometry(160, 40, 72, 26),
1274	    		'whiteSpace=wrap;html=1;rounded=1;arcSize=50;align=center;verticalAlign=middle;' +
1275	    		'collapsible=0;container=1;recursiveResize=0;strokeWidth=1;autosize=1;spacing=4;');
1276	    	cell3.vertex = true;
1277
1278	    	var edge2 = new mxCell('', new mxGeometry(0, 0, 0, 0), 'edgeStyle=entityRelationEdgeStyle;' +
1279				'startArrow=none;endArrow=none;segment=10;curved=1;');
1280			edge2.geometry.setTerminalPoint(new mxPoint(-40, 40), true);
1281			edge2.geometry.relative = true;
1282			edge2.edge = true;
1283
1284			cell.insertEdge(edge2, true);
1285			cell3.insertEdge(edge2, false);
1286
1287		    content.appendChild(sb.createVertexTemplateFromCells([edge, edge2, cell, cell2, cell3], 240, 66, 'Mindmap'));
1288        })();
1289
1290        (function()
1291        {
1292	    	var cell = new mxCell('Central Idea', new mxGeometry(0, 0, 100, 40),
1293		    	'ellipse;whiteSpace=wrap;html=1;align=center;' +
1294		    	'collapsible=0;container=1;recursiveResize=0;');
1295		    graph.setAttributeForCell(cell, 'treeRoot', '1');
1296	    	cell.vertex = true;
1297
1298		    content.appendChild(sb.createVertexTemplateFromCells([cell], 100, 40, 'Central Idea'));
1299        })();
1300
1301        (function()
1302        {
1303	    	var cell = new mxCell('Branch', new mxGeometry(0, 0, 80, 20),
1304	    		'whiteSpace=wrap;html=1;shape=partialRectangle;top=0;left=0;bottom=1;right=0;points=[[0,1],[1,1]];' +
1305	    		'strokeColor=#000000;fillColor=none;align=center;verticalAlign=bottom;routingCenterY=0.5;' +
1306	    		'snapToPoint=1;collapsible=0;container=1;recursiveResize=0;autosize=1;');
1307	    	cell.vertex = true;
1308
1309	    	var edge = new mxCell('', new mxGeometry(0, 0, 0, 0), 'edgeStyle=entityRelationEdgeStyle;' +
1310				'startArrow=none;endArrow=none;segment=10;curved=1;');
1311			edge.geometry.setTerminalPoint(new mxPoint(-40, 40), true);
1312			edge.geometry.relative = true;
1313			edge.edge = true;
1314
1315			cell.insertEdge(edge, false);
1316
1317			content.appendChild(sb.createVertexTemplateFromCells([edge, cell], 80, 20, 'Branch'));
1318        })();
1319
1320        (function()
1321        {
1322	    	var cell = new mxCell('Sub Topic', new mxGeometry(0, 0, 72, 26),
1323	    		'whiteSpace=wrap;html=1;rounded=1;arcSize=50;align=center;verticalAlign=middle;' +
1324	    		'collapsible=0;container=1;recursiveResize=0;strokeWidth=1;autosize=1;spacing=4;');
1325	    	cell.vertex = true;
1326
1327	    	var edge = new mxCell('', new mxGeometry(0, 0, 0, 0), 'edgeStyle=entityRelationEdgeStyle;' +
1328				'startArrow=none;endArrow=none;segment=10;curved=1;');
1329			edge.geometry.setTerminalPoint(new mxPoint(-40, 40), true);
1330			edge.geometry.relative = true;
1331			edge.edge = true;
1332
1333			cell.insertEdge(edge, false);
1334
1335			content.appendChild(sb.createVertexTemplateFromCells([edge, cell], 72, 26, 'Sub Topic'));
1336        })();
1337
1338        (function()
1339        {
1340	    	var cell = new mxCell('Organization', new mxGeometry(60, 0, 120, 60),
1341	    		'whiteSpace=wrap;html=1;align=center;' +
1342	        	'collapsible=0;container=1;recursiveResize=0;');
1343		    graph.setAttributeForCell(cell, 'treeRoot', '1');
1344	    	cell.vertex = true;
1345
1346	    	var cell2 = new mxCell('Division', new mxGeometry(0, 100, 100, 60),
1347	    		'whiteSpace=wrap;html=1;align=center;verticalAlign=middle;' +
1348	    		'collapsible=0;container=1;recursiveResize=0;');
1349	    	cell2.vertex = true;
1350
1351	    	var edge = new mxCell('', new mxGeometry(0, 0, 0, 0), 'edgeStyle=elbowEdgeStyle;elbow=vertical;' +
1352				'startArrow=none;endArrow=none;rounded=0;');
1353			edge.geometry.relative = true;
1354			edge.edge = true;
1355
1356			cell.insertEdge(edge, true);
1357			cell2.insertEdge(edge, false);
1358
1359	    	var cell3 = new mxCell('Division', new mxGeometry(140, 100, 100, 60),
1360	    		'whiteSpace=wrap;html=1;align=center;verticalAlign=middle;' +
1361	    		'collapsible=0;container=1;recursiveResize=0;');
1362	    	cell3.vertex = true;
1363
1364	    	var edge2 = new mxCell('', new mxGeometry(0, 0, 0, 0), 'edgeStyle=elbowEdgeStyle;elbow=vertical;' +
1365				'startArrow=none;endArrow=none;rounded=0;');
1366			edge2.geometry.relative = true;
1367			edge2.edge = true;
1368
1369			cell.insertEdge(edge2, true);
1370			cell3.insertEdge(edge2, false);
1371
1372		    content.appendChild(sb.createVertexTemplateFromCells([edge, edge2, cell, cell2, cell3], 240, 160, 'Orgchart'));
1373        })();
1374
1375        (function()
1376        {
1377	    	var cell = new mxCell('Tree Root', new mxGeometry(0, 0, 120, 60),
1378	    		'whiteSpace=wrap;html=1;align=center;' +
1379	        	'collapsible=0;container=1;recursiveResize=0;');
1380		    graph.setAttributeForCell(cell, 'treeRoot', '1');
1381	    	cell.vertex = true;
1382
1383		    content.appendChild(sb.createVertexTemplateFromCells([cell], 120, 60, 'Tree Root'));
1384        })();
1385
1386        (function()
1387        {
1388	    	var cell = new mxCell('Sub Tree', new mxGeometry(0, 0, 100, 60),
1389	    		'whiteSpace=wrap;html=1;align=center;verticalAlign=middle;' +
1390	    		'collapsible=0;container=1;recursiveResize=0;');
1391	    	cell.vertex = true;
1392
1393	    	var edge = new mxCell('', new mxGeometry(0, 0, 0, 0), 'edgeStyle=elbowEdgeStyle;elbow=vertical;' +
1394				'startArrow=none;endArrow=none;rounded=0;');
1395			edge.geometry.setTerminalPoint(new mxPoint(0, -40), true);
1396			edge.geometry.relative = true;
1397			edge.edge = true;
1398
1399			cell.insertEdge(edge, false);
1400
1401			content.appendChild(sb.createVertexTemplateFromCells([edge, cell], 100, 60, 'Sub Tree'));
1402        })();
1403
1404        (function()
1405        {
1406	    	var cell = new mxCell('Sub Section', new mxGeometry(0, 0, 100, 60),
1407	    		'whiteSpace=wrap;html=1;align=center;verticalAlign=middle;' +
1408	    		'collapsible=0;container=1;recursiveResize=0;');
1409	    	cell.vertex = true;
1410
1411	    	var edge = new mxCell('', new mxGeometry(0, 0, 0, 0), 'edgeStyle=orthogonalEdgeStyle;' +
1412				'startArrow=none;endArrow=none;rounded=0;targetPortConstraint=eastwest;sourcePortConstraint=northsouth;');
1413			edge.geometry.setTerminalPoint(new mxPoint(110, -40), true);
1414			edge.geometry.relative = true;
1415			edge.edge = true;
1416
1417			cell.insertEdge(edge, false);
1418
1419	    	var cell2 = new mxCell('Sub Section', new mxGeometry(120, 0, 100, 60),
1420	    		'whiteSpace=wrap;html=1;align=center;verticalAlign=middle;' +
1421	    		'collapsible=0;container=1;recursiveResize=0;');
1422	    	cell2.vertex = true;
1423
1424	    	var edge2 = new mxCell('', new mxGeometry(0, 0, 0, 0), 'edgeStyle=orthogonalEdgeStyle;' +
1425				'startArrow=none;endArrow=none;rounded=0;targetPortConstraint=eastwest;sourcePortConstraint=northsouth;');
1426			edge2.geometry.setTerminalPoint(new mxPoint(110, -40), true);
1427			edge2.geometry.relative = true;
1428			edge2.edge = true;
1429
1430			cell2.insertEdge(edge2, false);
1431
1432			content.appendChild(sb.createVertexTemplateFromCells([edge, edge2, cell, cell2], 220, 60, 'Sub Sections'));
1433        })();
1434    });
1435
1436    // Collapses default sidebar entry and inserts this before
1437    var c = ui.sidebar.container;
1438    var general = c.getElementsByTagName('a')[0];
1439    general.click();
1440    c.insertBefore(c.lastChild.previousSibling, general);
1441    c.insertBefore(c.lastChild, general);
1442});