1/**
2 * Explore plugin.
3 */
4Draw.loadPlugin(function(ui)
5{
6	// Adds resource for action
7	mxResources.parse('exploreFromHere=Explore from here...');
8
9	// Max number of edges per page
10	var pageSize = 20;
11
12	var uiCreatePopupMenu = ui.menus.createPopupMenu;
13	ui.menus.createPopupMenu = function(menu, cell, evt)
14	{
15		uiCreatePopupMenu.apply(this, arguments);
16
17		var graph = ui.editor.graph;
18
19		if (graph.model.isVertex(graph.getSelectionCell()))
20		{
21			this.addMenuItems(menu, ['-', 'exploreFromHere'], null, evt);
22		}
23	};
24
25	//
26	// Main function
27	//
28	function exploreFromHere(selectionCell)
29	{
30		var sourceGraph = ui.editor.graph;
31
32		var container = document.createElement('div');
33		container.style.position = 'absolute';
34		container.style.display = 'block';
35		container.style.background = (Editor.isDarkMode()) ?
36			Editor.darkColor : '#ffffff';
37		container.style.width = '100%';
38		container.style.height = '100%';
39		container.style.left = '0px';
40		container.style.top = '0px';
41		container.style.zIndex = 2;
42
43		var deleteImage = document.createElement('img');
44		deleteImage.setAttribute('src', IMAGE_PATH + '/delete.png');
45		deleteImage.style.position = 'absolute';
46		deleteImage.style.cursor = 'pointer';
47		deleteImage.style.right = '10px';
48		deleteImage.style.top = '10px';
49		container.appendChild(deleteImage);
50
51		var closeLabel = document.createElement('div');
52		closeLabel.style.position = 'absolute';
53		closeLabel.style.cursor = 'pointer';
54		closeLabel.style.right = '38px';
55		closeLabel.style.top = '14px';
56		closeLabel.style.textAlign = 'right';
57		closeLabel.style.verticalAlign = 'top';
58		mxUtils.write(closeLabel, mxResources.get('close'));
59		container.appendChild(closeLabel);
60		document.body.appendChild(container);
61
62		var keyHandler = function(evt)
63		{
64			if (evt.keyCode == 27)
65			{
66				deleteImage.click();
67			}
68		};
69
70		mxEvent.addListener(document, 'keydown', keyHandler);
71
72		// Global variable to make sure each cell in a response has
73		// a unique ID throughout the complete life of the program,
74		// in a real-life setup each cell should have an external
75		// ID on the business object or else the cell ID should be
76		// globally unique for the lifetime of the graph model.
77		var requestId = 0;
78
79		function main(container)
80		{
81			// Checks if browser is supported
82			if (!mxClient.isBrowserSupported())
83			{
84				// Displays an error message if the browser is
85				// not supported.
86				mxUtils.error('Browser is not supported!', 200, false);
87			}
88			else
89			{
90				// Creates the graph inside the given container
91				var graph = new Graph(container);
92				graph.keepEdgesInBackground = true;
93				graph.isCellResizable = function()
94				{
95					return false;
96				};
97				// Workaround to hide custom handles
98				graph.isCellRotatable = function()
99				{
100					return false;
101				};
102
103				// Shows hand cursor for all vertices
104				graph.getCursorForCell = function(cell)
105				{
106					if (this.model.isVertex(cell))
107					{
108						return 'pointer';
109					}
110
111					return null;
112				};
113
114				graph.getFoldingImage = function()
115				{
116					return null;
117				};
118
119				var closeHandler = function()
120				{
121					mxEvent.removeListener(document, 'keydown', keyHandler);
122					container.parentNode.removeChild(container);
123
124					// FIXME: Does not work
125					sourceGraph.scrollCellToVisible(selectionCell);
126				};
127
128				mxEvent.addListener(deleteImage, 'click', closeHandler);
129				mxEvent.addListener(closeLabel, 'click', closeHandler);
130
131				// Disables all built-in interactions
132				graph.setEnabled(false);
133
134				// Handles clicks on cells
135				graph.click = function(me)
136				{
137					var evt = me.getEvent();
138					var cell = me.getCell();
139
140					if (cell != null && cell != graph.rootCell)
141					{
142						load(graph, cell);
143					}
144				};
145
146				// Gets the default parent for inserting new cells. This
147				// is normally the first child of the root (ie. layer 0).
148				var parent = graph.getDefaultParent();
149
150				var cx = graph.container.scrollWidth / 2;
151				var cy = graph.container.scrollHeight / 3;
152
153				graph.model.beginUpdate();
154				var cell = graph.importCells([selectionCell])[0];
155				cell.sourceCellId = selectionCell.id;
156				cell.geometry.x = cx - cell.geometry.width / 2;
157				cell.geometry.y = cy - cell.geometry.height / 2;
158				graph.model.endUpdate();
159
160				// Animates the changes in the graph model
161				graph.getModel().addListener(mxEvent.CHANGE, function(sender, evt)
162				{
163					var changes = evt.getProperty('edit').changes;
164					var prev = mxText.prototype.enableBoundingBox;
165					mxText.prototype.enableBoundingBox = false;
166					graph.labelsVisible = false;
167
168					mxEffects.animateChanges(graph, changes, function()
169					{
170						mxText.prototype.prev = true;
171						graph.labelsVisible = true;
172						graph.refresh();
173						graph.tooltipHandler.hide();
174					});
175				});
176
177				load(graph, cell);
178			}
179		};
180
181		// Loads the links for the given cell into the given graph
182		// by requesting the respective data in the server-side
183		// (implemented for this demo using the server-function)
184		function load(graph, cell)
185		{
186			if (graph.getModel().isVertex(cell))
187			{
188				var cx = graph.container.scrollWidth / 2;
189				var cy = graph.container.scrollHeight / 3;
190
191				// Gets the default parent for inserting new cells. This
192				// is normally the first child of the root (ie. layer 0).
193				var parent = graph.getDefaultParent();
194				graph.rootCell = cell.referenceCell || cell;
195
196				// Adds cells to the model in a single step
197				graph.getModel().beginUpdate();
198				try
199				{
200					var cells = rootChanged(graph, cell);
201
202					// Removes all cells except the new root
203					for (var key in graph.getModel().cells)
204					{
205						var tmp = graph.getModel().getCell(key);
206
207						if (tmp != graph.rootCell && graph.getModel().isVertex(tmp))
208						{
209							graph.removeCells([tmp]);
210						}
211					}
212
213					// Merges the response model with the client model
214					//graph.getModel().mergeChildren(model.getRoot().getChildAt(0), parent);
215					graph.addCells(cells);
216
217					// Moves the given cell to the center
218					var geo = graph.getModel().getGeometry(graph.rootCell);
219
220					if (geo != null)
221					{
222						geo = geo.clone();
223
224						geo.x = cx - geo.width / 2;
225						geo.y = cy - geo.height / 3;
226						graph.getModel().setGeometry(graph.rootCell, geo);
227					}
228
229					// Creates a list of the new vertices, if there is more
230					// than the center vertex which might have existed
231					// previously, then this needs to be changed to analyze
232					// the target model before calling mergeChildren above
233					var vertices = [];
234
235					for (var key in graph.getModel().cells)
236					{
237						var tmp = graph.getModel().getCell(key);
238
239						if (tmp != graph.rootCell && graph.getModel().isVertex(tmp) &&
240							graph.getModel().getParent(tmp) == graph.getDefaultParent())
241						{
242							vertices.push(tmp);
243
244							// Changes the initial location "in-place"
245							// to get a nice animation effect from the
246							// center to the radius of the circle
247							var geo = graph.getModel().getGeometry(tmp);
248
249							if (geo != null)
250							{
251								geo.x = cx - geo.width / 2;
252								geo.y = cy - geo.height / 2;
253							}
254						}
255					}
256
257					// Arranges the response in a circle
258					var cellCount = vertices.length;
259					var phi = 2 * Math.PI / cellCount;
260					var r = Math.min(graph.container.scrollWidth / 3 - 80,
261							graph.container.scrollHeight / 3 - 80);
262
263					for (var i = 0; i < cellCount; i++)
264					{
265						var geo = graph.getModel().getGeometry(vertices[i]);
266
267						if (geo != null)
268						{
269							geo = geo.clone();
270							geo.x += r * Math.sin(i * phi);
271							geo.y += r * Math.cos(i * phi);
272
273							graph.getModel().setGeometry(vertices[i], geo);
274						}
275					}
276
277					// Keeps parallel edges apart
278					var layout = new mxParallelEdgeLayout(graph);
279					layout.spacing = 60;
280					layout.execute(graph.getDefaultParent());
281				}
282				finally
283				{
284					// Updates the display
285					graph.getModel().endUpdate();
286				}
287			}
288		};
289
290		// Gets the edges from the source cell and adds the targets
291		function rootChanged(graph, cell)
292		{
293			// TODO: Keep existing cells, probably best via XML to redirect IDs
294			var realCell = cell.referenceCell || cell;
295			var sourceCell = sourceGraph.model.getCell(realCell.sourceCellId);
296			var edges = sourceGraph.getEdges(sourceCell, null, true, true, false, true);
297			var cells = edges;
298
299			// Paging by selecting a window in the edges array
300			if (cell.startIndex != null || (pageSize > 0 && edges.length > pageSize))
301			{
302				var start = cell.startIndex || 0;
303
304				cells = edges.slice(Math.max(0, start), Math.min(edges.length, start + pageSize));
305			}
306
307			cells = cells.concat(sourceGraph.getOpposites(cells, sourceCell));
308			var clones = graph.cloneCells(cells);
309
310			var edgeStyle = ';curved=1;noEdgeStyle=1;entryX=none;entryY=none;exitX=none;exitY=none;';
311			var btnStyle = 'fillColor=green;fontColor=white;strokeColor=green;';
312
313			for (var i = 0; i < cells.length; i++)
314			{
315				clones[i].sourceCellId = cells[i].id;
316
317				if (graph.model.isEdge(clones[i]))
318				{
319					// Removes waypoints, edge styles, constraints and centers the label
320					clones[i].geometry.x = 0;
321					clones[i].geometry.y = 0;
322					clones[i].geometry.points = null;
323					clones[i].setStyle(clones[i].getStyle() + edgeStyle);
324					clones[i].setTerminal(realCell, clones[i].getTerminal(true) == null);
325				}
326			}
327
328			if (cell.startIndex > 0)
329			{
330				var backCell = graph.createVertex(null, null, 'Back...', 0, 0, 80, 30, btnStyle);
331				backCell.referenceCell = realCell;
332				backCell.startIndex = Math.max(0, (cell.startIndex || 0) - pageSize);
333				clones.splice(0, 0, backCell);
334			}
335
336			if (edges.length > (cell.startIndex || 0) + pageSize)
337			{
338				var moreCell = graph.createVertex(null, null, 'More...', 0, 0, 80, 30, btnStyle);
339				moreCell.referenceCell = realCell;
340				moreCell.startIndex = (cell.startIndex || 0) + pageSize;
341				clones.splice(0, 0, moreCell);
342			}
343
344			return clones;
345		};
346
347		main(container);
348	};
349
350	// Adds action
351	ui.actions.addAction('exploreFromHere', function()
352	{
353		exploreFromHere(ui.editor.graph.getSelectionCell());
354	});
355
356	// Click handler for chromeless mode
357	if (ui.editor.isChromelessView())
358	{
359		ui.editor.graph.click = function(me)
360		{
361			if (ui.editor.graph.model.isVertex(me.getCell()) &&
362				ui.editor.graph.model.getEdgeCount(me.getCell()) > 0 &&
363				this.getLinkForCell(me.getCell()) == null)
364			{
365				exploreFromHere(me.getCell());
366			}
367		};
368	}
369});
370