1/**
2 * Plugin for Freemind import.
3 * See https://github.com/jiangxin/freemind-mmx/tree/master/freemind
4 */
5Draw.loadPlugin(function(ui)
6{
7	var graph = ui.editor.graph;
8
9	// Adds resource for action
10	mxResources.parse('importFreemind=Freemind');
11
12	// Parses Freemind data
13	function importFreemindData(data)
14	{
15		// Gets the default parent for inserting new cells. This
16		// is normally the first child of the root (ie. layer 0).
17		var defaultParent = graph.getDefaultParent();
18		var cells = [];
19
20		var defaultWidth = 80;
21		var defaultHeight = 30;
22		var mainConceptHeight = 40;
23		var defaultHorizontalSpaceBetweenVertex = 40;
24		var defaultVerticalSpaceBetweenVertex = 10;
25
26		var freeMindMainConceptVertexStyle = 'ellipse;whiteSpace=wrap;html=1;align=center;collapsible=0;container=1;recursiveResize=0;';
27		var freeMindBranchVertexStyle = 'whiteSpace=wrap;html=1;shape=partialRectangle;top=0;left=0;bottom=1;right=0;points=[[0,1],[1,1]];strokeColor=#000000;fillColor=none;align=center;verticalAlign=bottom;routingCenterY=0.5;snapToPoint=1;collapsible=0;container=1;recursiveResize=0;autosize=1;';
28		var freeMindConceptVertexStyle = 'whiteSpace=wrap;html=1;rounded=1;arcSize=50;align=center;verticalAlign=middle;collapsible=0;container=1;recursiveResize=0;strokeWidth=1;autosize=1;spacing=4;';
29		var freeMindEdgeStyle = 'edgeStyle=entityRelationEdgeStyle;startArrow=none;endArrow=none;segment=10;curved=1;html=1;';
30
31		// Tells whether or not a node has child ideas
32		var hasChilds = function(node)
33		{
34			for (var i = 0; i < node.childNodes.length; i++)
35			{
36				if (node.childNodes[i].nodeName == 'node')
37				{
38					return true;
39				}
40			}
41
42			return false;
43		};
44
45		// Generates useful info on the nodes to be used later.
46		var generatePreprocessingNodeInfo = function(node)
47		{
48			var childCount = 0;
49			var maxChildsInHierarchy = 0;
50
51			for (var i = 0; i < node.childNodes.length; i++)
52			{
53				var childNode = node.childNodes[i];
54
55				if (childNode.nodeName == 'node')
56				{
57					var maxChilds = generatePreprocessingNodeInfo(childNode);
58					maxChildsInHierarchy = Math.max(maxChildsInHierarchy, maxChilds);
59					childCount++;
60				}
61			}
62
63			node.childCount = childCount;
64			node.maxChilds = Math.max(childCount, maxChildsInHierarchy);
65			return childCount;
66		}
67
68		// Main node generation funcion (recursive)
69		var processFreeMindNode = function(node, nodeParent, x, y)
70		{
71			var mainConcept = false;
72			var vertexStyle = freeMindBranchVertexStyle;
73
74			if (nodeParent == defaultParent)
75			{
76				mainConcept = true;
77				vertexStyle = freeMindMainConceptVertexStyle;
78			}
79			else if (hasChilds(node))
80			{
81				// Concept, style appropiately
82				vertexStyle = freeMindConceptVertexStyle;
83			}
84
85			var nodeName = node.getAttribute('TEXT') || '';
86			var nodeVertex = graph.insertVertex(defaultParent, null, nodeName, x, y, defaultWidth,
87					defaultHeight, vertexStyle);
88			graph.cellLabelChanged(nodeVertex, nodeName, true);
89
90			if (mainConcept)
91			{
92				nodeVertex.geometry.height = mainConceptHeight; // TODO: Maybe set height according to it's width, so it's rounded?
93			}
94
95			if (nodeParent != defaultParent)
96			{
97				// Don't generate an edge for the first node
98				graph.insertEdge(defaultParent, null, '', nodeParent, nodeVertex, freeMindEdgeStyle);
99			}
100
101			cells.push(nodeVertex);
102			// Insert child nodes, on correct positions
103			var childNumber = 0;
104
105			for (var i = 0; i < node.childNodes.length; i++)
106			{
107				var childNode = node.childNodes[i];
108
109				if (childNode.nodeName == 'node')
110				{
111					var childX = x + nodeVertex.geometry.width + defaultHorizontalSpaceBetweenVertex;
112					var childY = y + (defaultHeight + defaultVerticalSpaceBetweenVertex) * childNumber;
113					childNumber += childNode.maxChilds == 0 ? 1 : childNode.maxChilds;
114					processFreeMindNode(childNode, nodeVertex, childX, childY);
115				}
116			}
117		}
118
119		// Makes the import one undoable edit
120		graph.getModel().beginUpdate();
121		try
122		{
123			// Gets point for free space in the graph for insert
124			var pt = graph.getFreeInsertPoint();
125			var freeMindDOM = mxUtils.parseXml(data);
126			var freeMindDOMchilds = freeMindDOM.children[0];
127
128			// Transverse the childs, and generate relevant input
129			for (var i = 0; i < freeMindDOMchilds.childNodes.length; i++)
130			{
131				if (freeMindDOMchilds.childNodes[i].nodeName == 'node')
132				{
133					generatePreprocessingNodeInfo(freeMindDOMchilds.childNodes[i]);
134				}
135			}
136
137			// Generate the nodes
138			for (var i = 0; i < freeMindDOMchilds.childNodes.length; i++)
139			{
140				if (freeMindDOMchilds.childNodes[i].nodeName == 'node')
141				{
142					processFreeMindNode(freeMindDOMchilds.childNodes[i], defaultParent, pt.x, pt.y);
143				}
144			}
145
146			// Applies current styles to new cells (might not be needed)
147			graph.fireEvent(new mxEventObject('cellsInserted', 'cells', cells));
148		}
149		finally
150		{
151			graph.getModel().endUpdate();
152		}
153
154		// Selects new cells and scrolls into view
155		graph.setSelectionCells(cells);
156		graph.scrollCellToVisible(graph.getSelectionCell());
157	};
158
159	// Adds action
160	ui.actions.addAction('importFreemind...', function()
161	{
162		// Only modern browsers for now. We'll move the import
163		// code above to the main codebase later
164		if (Graph.fileSupport)
165		{
166			if (ui.impFMFileInputElt == null)
167			{
168				var input = document.createElement('input');
169				input.setAttribute('type', 'file');
170
171				mxEvent.addListener(input, 'change', function()
172				{
173					if (input.files != null)
174					{
175						// Only one file for now...
176						var reader = new FileReader();
177
178						reader.onload = function(e)
179						{
180							importFreemindData(e.target.result);
181						};
182
183						reader.readAsText(input.files[0]);
184
185			    		// Resets input to force change event for same file (type reset required for IE)
186						input.type = '';
187						input.type = 'file';
188			    		input.value = '';
189					}
190				});
191
192				input.style.display = 'none';
193				document.body.appendChild(input);
194				ui.impFMFileInputElt = input;
195			}
196
197			ui.impFMFileInputElt.click();
198		}
199	});
200
201	// Adds menu
202	ui.menubar.addMenu('Import', function(menu, parent)
203	{
204		ui.menus.addMenuItem(menu, 'importFreemind');
205	});
206
207	// Moves import menu to before help menu
208	ui.menubar.container.insertBefore(ui.menubar.container.lastChild,
209			ui.menubar.container.lastChild.previousSibling.previousSibling.previousSibling);
210});
211