1 /**
  2  * <pre>
  3  * Creates a new MindMapModel. 
  4  * 
  5  * This object represents the underlying mind map model and provides access 
  6  * to the document, the mind map and the currently selected node.
  7  * 
  8  * All changes to the mind map pass through this object, either through calling
  9  * methods directly or using the executeAction() method to perform NodeActions.
 10  * </pre>
 11  * 
 12  * @constructor
 13  * @param {mindmaps.EventBus} eventBus
 14  * @param {mindmaps.CommandRegistry} commandRegistry
 15  */
 16 mindmaps.MindMapModel = function(eventBus, commandRegistry) {
 17 	var self = this;
 18 	this.document = null;
 19 	this.selectedNode = null;
 20 
 21 	/**
 22 	 * Gets the current document.
 23 	 * 
 24 	 * @returns {mindmaps.Document} the current document.
 25 	 */
 26 	this.getDocument = function() {
 27 		return this.document;
 28 	};
 29 
 30 	/**
 31 	 * Sets the current document and will publish a DOCUMENT_OPENED or
 32 	 * DOCUMENT_CLOSED event.
 33 	 * 
 34 	 * @param {mindmaps.Document} doc or pass null to close the document
 35 	 */
 36 	this.setDocument = function(doc) {
 37 		this.document = doc;
 38 		if (doc) {
 39 			eventBus.publish(mindmaps.Event.DOCUMENT_OPENED, doc);
 40 		} else {
 41 			eventBus.publish(mindmaps.Event.DOCUMENT_CLOSED);
 42 		}
 43 	};
 44 
 45 	/**
 46 	 * Gets the current mind map associated with the document.
 47 	 * 
 48 	 * @returns {mindmaps.MindMap} the mind map or null
 49 	 */
 50 	this.getMindMap = function() {
 51 		if (this.document) {
 52 			return this.document.mindmap;
 53 		}
 54 		return null;
 55 	};
 56 
 57 	/**
 58 	 * Initialise.
 59 	 * 
 60 	 * @private
 61 	 */
 62 	this.init = function() {
 63 		var createCommand = commandRegistry.get(mindmaps.CreateNodeCommand);
 64 		createCommand.setHandler(this.createNode.bind(this));
 65 
 66 		var deleteCommand = commandRegistry.get(mindmaps.DeleteNodeCommand);
 67 		deleteCommand.setHandler(this.deleteNode.bind(this));
 68 
 69 		eventBus.subscribe(mindmaps.Event.DOCUMENT_CLOSED, function() {
 70 			createCommand.setEnabled(false);
 71 			deleteCommand.setEnabled(false);
 72 		});
 73 
 74 		eventBus.subscribe(mindmaps.Event.DOCUMENT_OPENED, function() {
 75 			createCommand.setEnabled(true);
 76 			deleteCommand.setEnabled(true);
 77 		});
 78 	};
 79 
 80 	/**
 81 	 * Deletes a node or the currently selected one if no argument is passed.
 82 	 * 
 83 	 * @param {mindmaps.Node} [node] defaults to currently selected.
 84 	 */
 85 	this.deleteNode = function(node) {
 86 		if (!node) {
 87 			node = this.selectedNode;
 88 		}
 89 		var map = this.getMindMap();
 90 		var action = new mindmaps.action.DeleteNodeAction(node, map);
 91 		this.executeAction(action);
 92 	};
 93 
 94 	/**
 95 	 * Attaches a new node the mind map. If invoked without arguments, it will
 96 	 * add a new child to the selected node with an automatically generated
 97 	 * position.
 98 	 * 
 99 	 * @param {mindmaps.Node} node the new node
100 	 * @param {mindmaps.Node} parent
101 	 */
102 	this.createNode = function(node, parent) {
103 		var map = this.getMindMap();
104 		if (!(node && parent)) {
105 			parent = this.selectedNode;
106 			var action = new mindmaps.action.CreateAutoPositionedNodeAction(
107 					parent, map);
108 		} else {
109 			var action = new mindmaps.action.CreateNodeAction(node, parent, map);
110 		}
111 
112 		this.executeAction(action);
113 	};
114 
115 	/**
116 	 * Sets the node as the currently selected.
117 	 * 
118 	 * @param {mindmaps.Node} node
119 	 */
120 	this.selectNode = function(node) {
121 		if (node === this.selectedNode) {
122 			return;
123 		}
124 
125 		var oldSelected = this.selectedNode;
126 		this.selectedNode = node;
127 		eventBus.publish(mindmaps.Event.NODE_SELECTED, node, oldSelected);
128 	};
129 
130 	/**
131 	 * Changes the caption for the passed node or for the selected one if node
132 	 * is null.
133 	 * 
134 	 * @param {mindmaps.Node} node
135 	 * @param {String} caption
136 	 */
137 	this.changeNodeCaption = function(node, caption) {
138 		if (!node) {
139 			node = this.selectedNode;
140 		}
141 
142 		var action = new mindmaps.action.ChangeNodeCaptionAction(node, caption);
143 		this.executeAction(action);
144 	};
145 
146 	/**
147 	 * Executes a node action. An executed action might raise an event over the
148 	 * event bus and cause an undo event to be emitted via MindMapModel#undoAction.
149 	 * 
150 	 * @param {mindmaps.Action} action
151 	 */
152 	this.executeAction = function(action) {
153 		var executed = action.execute();
154 
155 		// cancel action if false was returned
156 		if (executed !== undefined && !executed) {
157 			return false;
158 		}
159 
160 		// publish event
161 		if (action.event) {
162 			if (!Array.isArray(action.event)) {
163 				action.event = [ action.event ];
164 			}
165 			eventBus.publish.apply(eventBus, action.event);
166 		}
167 
168 		// register undo function if available
169 		if (action.undo) {
170 			var undoFunc = function() {
171 				self.executeAction(action.undo());
172 			};
173 
174 			// register redo function
175 			var redoFunc = null;
176 			if (action.redo) {
177 				redoFunc = function() {
178 					self.executeAction(action.redo());
179 				};
180 			}
181 
182 			// emit undo event
183 			if (this.undoEvent) {
184 				this.undoEvent(undoFunc, redoFunc);
185 			}
186 		}
187 	};
188 
189 	/**
190 	 * Event that is fired when a new undo operation should be recorded.
191 	 * 
192 	 * @event
193 	 * @param {Function} undoFunc
194 	 * @param {Function} [redoFunc]
195 	 */
196 	this.undoEvent = function(undoFunc, redoFunc) {
197 	};
198 
199 	this.init();
200 };