1 /**
  2  * Creates a NavigatorView. This view shows a minified version of the mindmap +
  3  * controls for adjusting the zoom.
  4  * 
  5  * @constructor
  6  */
  7 mindmaps.NavigatorView = function() {
  8 	var self = this;
  9 
 10 	var $content = $("#template-navigator").tmpl();
 11 	var $contentActive = $content.children(".active").hide();
 12 	var $contentInactive = $content.children(".inactive").hide();
 13 	var $dragger = $("#navi-canvas-overlay", $content);
 14 	var $canvas = $("#navi-canvas", $content);
 15 
 16 	/**
 17 	 * Returns the content.
 18 	 * 
 19 	 * @returns {jQuery}
 20 	 */
 21 	this.getContent = function() {
 22 		return $content;
 23 	};
 24 
 25 	/**
 26 	 * Shows the active content.
 27 	 */
 28 	this.showActiveContent = function() {
 29 		$contentInactive.hide();
 30 		$contentActive.show();
 31 	};
 32 
 33 	/**
 34 	 * Shows the inactive content.
 35 	 */
 36 	this.showInactiveContent = function() {
 37 		$contentActive.hide();
 38 		$contentInactive.show();
 39 	};
 40 
 41 	/**
 42 	 * Adjusts the size of the red rectangle.
 43 	 * 
 44 	 * @param {Number} width
 45 	 * @param {Nubmer} height
 46 	 */
 47 	this.setDraggerSize = function(width, height) {
 48 		$dragger.css({
 49 			width : width,
 50 			height : height
 51 		});
 52 	};
 53 
 54 	/**
 55 	 * Sets the position of the dragger rectangle.
 56 	 * 
 57 	 * @param {Number} x
 58 	 * @param {Number} y
 59 	 */
 60 	this.setDraggerPosition = function(x, y) {
 61 		$dragger.css({
 62 			left : x,
 63 			top : y
 64 		});
 65 	};
 66 
 67 	/**
 68 	 * Sets the height of the mini canvas.
 69 	 * 
 70 	 * @param {Number} height
 71 	 */
 72 	this.setCanvasHeight = function(height) {
 73 		$("#navi-canvas", $content).css({
 74 			height : height
 75 		});
 76 	};
 77 
 78 	/**
 79 	 * Gets the width of the mini canvas.
 80 	 * 
 81 	 * @returns {Number}
 82 	 */
 83 	this.getCanvasWidth = function() {
 84 		return $("#navi-canvas", $content).width();
 85 	};
 86 
 87 	this.init = function(canvasSize) {
 88 		$("#navi-slider", $content).slider({
 89 			// TODO remove magic numbers. get values from presenter
 90 			min : 0,
 91 			max : 11,
 92 			step : 1,
 93 			value : 3,
 94 			slide : function(e, ui) {
 95 				if (self.sliderChanged) {
 96 					self.sliderChanged(ui.value);
 97 				}
 98 			}
 99 		});
100 
101 		$("#button-navi-zoom-in", $content).button({
102 			text : false,
103 			icons : {
104 				primary : "ui-icon-zoomin"
105 			}
106 		}).click(function() {
107 			if (self.buttonZoomInClicked) {
108 				self.buttonZoomInClicked();
109 			}
110 		});
111 
112 		$("#button-navi-zoom-out", $content).button({
113 			text : false,
114 			icons : {
115 				primary : "ui-icon-zoomout"
116 			}
117 		}).click(function() {
118 			if (self.buttonZoomOutClicked) {
119 				self.buttonZoomOutClicked();
120 			}
121 		});
122 
123 		// make draggable
124 		$dragger.draggable({
125 			containment : "parent",
126 			start : function(e, ui) {
127 				if (self.dragStart) {
128 					self.dragStart();
129 				}
130 			},
131 			drag : function(e, ui) {
132 				if (self.dragging) {
133 					var x = ui.position.left;
134 					var y = ui.position.top;
135 					self.dragging(x, y);
136 				}
137 			},
138 			stop : function(e, ui) {
139 				if (self.dragStop) {
140 					self.dragStop();
141 				}
142 			}
143 		});
144 	};
145 
146 	/**
147 	 * Draws the complete mindmap onto the mini canvas.
148 	 * 
149 	 * @param {mindmaps.MindMap} mindmap
150 	 * @param {Number} scaleFactor
151 	 */
152 	this.draw = function(mindmap, scaleFactor) {
153 		var root = mindmap.root;
154 		var canvas = $canvas[0];
155 		var width = canvas.width;
156 		var height = canvas.height;
157 		var ctx = canvas.getContext("2d");
158 		ctx.clearRect(0, 0, width, height);
159 		ctx.lineWidth = 1.8;
160 
161 		drawNode(root, width / 2, height / 2);
162 
163 		// draw rect for root
164 		ctx.fillRect(width / 2 - 4, height / 2 - 2, 8, 4);
165 
166 		function scale(value) {
167 			return value / scaleFactor;
168 		}
169 
170 		function drawNode(node, x, y) {
171 			ctx.save();
172 			ctx.translate(x, y);
173 
174 			if (!node.collapseChildren) {
175 				node.forEachChild(function(child) {
176 					ctx.beginPath();
177 					ctx.strokeStyle = child.branchColor;
178 					ctx.moveTo(0, 0);
179 					var posX = scale(child.offset.x);
180 					var posY = scale(child.offset.y);
181 					// var textWidth =
182 					// ctx.measureText(child.getCaption()).width;
183 					textWidth = 5;
184 
185 					/**
186 					 * draw two lines: one going up to the node, and a second
187 					 * horizontal line for the node caption. if node is left of
188 					 * the parent (posX < 0), we shorten the first line and draw
189 					 * the rest horizontally to arrive at the node's offset
190 					 * position. in the other case, we draw the line to the
191 					 * node's offset and draw another for the text.
192 					 */
193 					if (posX < 0) {
194 						var firstStop = posX + textWidth;
195 						var secondStop = posX;
196 					} else {
197 						var firstStop = posX;
198 						var secondStop = posX + textWidth;
199 					}
200 					ctx.lineTo(firstStop, posY);
201 					ctx.lineTo(secondStop, posY);
202 
203 					ctx.stroke();
204 					drawNode(child, secondStop, posY);
205 				});
206 			}
207 			ctx.restore();
208 		}
209 	};
210 
211 	/**
212 	 * Shows the zoom level as percentage.
213 	 * 
214 	 * @param {String} zoom
215 	 */
216 	this.showZoomLevel = function(zoom) {
217 		$("#navi-zoom-level").text(zoom);
218 	};
219 
220 	/**
221 	 * Sets the value of the zoom slider.
222 	 * 
223 	 * @param {Integer} value
224 	 */
225 	this.setSliderValue = function(value) {
226 		$("#navi-slider").slider("value", value);
227 	};
228 };
229 
230 /**
231  * Creates a new NavigatorPresenter.
232  * 
233  * @constructor
234  * @param {mindmaps.EventBus} eventBus
235  * @param {mindmaps.NavigatorView} view
236  * @param {mindmaps.CanvasContainer} container
237  * @param {mindmaps.ZoomController} zoomController
238  */
239 mindmaps.NavigatorPresenter = function(eventBus, view, container,
240 		zoomController) {
241 	var self = this;
242 	var $container = container.getContent();
243 	var viewDragging = false;
244 	var scale = zoomController.DEFAULT_ZOOM;
245 	var canvasSize = new mindmaps.Point();
246 	var docSize = null;
247 	var mindmap = null;
248 
249 	/**
250 	 * Calculates and sets the size of the dragger element.
251 	 */
252 	function calculateDraggerSize() {
253 		var cw = $container.width() / scale;
254 		var ch = $container.height() / scale;
255 		// doc.x / container.x = canvas.x / dragger.x
256 		var width = (cw * canvasSize.x) / docSize.x;
257 		var height = (ch * canvasSize.y) / docSize.y;
258 
259 		// limit size to bounds of canvas
260 		if (width > canvasSize.x) {
261 			width = canvasSize.x;
262 		}
263 
264 		if (height > canvasSize.y) {
265 			height = canvasSize.y;
266 		}
267 
268 		view.setDraggerSize(width, height);
269 	}
270 
271 	/**
272 	 * Calculates and sets the size of the mini canvas.
273 	 */
274 	function calculateCanvasSize() {
275 		var width = view.getCanvasWidth();
276 		var _scale = docSize.x / width;
277 		var height = docSize.y / _scale;
278 
279 		view.setCanvasHeight(height);
280 
281 		canvasSize.x = width;
282 		canvasSize.y = height;
283 	}
284 
285 	/**
286 	 * Calculates and sets the possition of the dragger element.
287 	 */
288 	function calculateDraggerPosition() {
289 		var sl = $container.scrollLeft() / scale;
290 		var st = $container.scrollTop() / scale;
291 
292 		// sl / dox = dl / cw
293 		// dl = sl * cw / dox
294 		var left = sl * canvasSize.x / docSize.x;
295 		var top = st * canvasSize.y / docSize.y;
296 		view.setDraggerPosition(left, top);
297 	}
298 
299 	/**
300 	 * Calculates and sets the zoom level.
301 	 */
302 	function calculateZoomLevel() {
303 		var zoomlevel = scale * 100 + " %";
304 		view.showZoomLevel(zoomlevel);
305 	}
306 
307 	/**
308 	 * Calculates and sets the slider value for the zoom level.
309 	 */
310 	function calculateSliderValue() {
311 		var val = scale / zoomController.ZOOM_STEP - 1;
312 		view.setSliderValue(val);
313 	}
314 
315 	/**
316 	 * Initialize view when a document was opened.
317 	 */
318 	function documentOpened(doc) {
319 		docSize = doc.dimensions;
320 		mindmap = doc.mindmap;
321 
322 		calculateCanvasSize();
323 		calculateDraggerPosition();
324 		calculateDraggerSize();
325 		calculateZoomLevel();
326 		calculateSliderValue();
327 		renderView();
328 
329 		view.showActiveContent();
330 
331 		// move dragger when container was scrolled
332 		$container.bind("scroll.navigator-view", function() {
333 			if (!viewDragging) {
334 				calculateDraggerPosition();
335 			}
336 		});
337 	}
338 
339 	/**
340 	 * Update the canvas of the view component.
341 	 */
342 	function renderView() {
343 		// draw canvas
344 		var scale = docSize.x / canvasSize.x;
345 		view.draw(mindmap, scale);
346 	}
347 
348 	/**
349 	 * Reset when document was closed.
350 	 */
351 	function documentClosed() {
352 		docSize = null;
353 		mindmap = null;
354 		scale = 1;
355 		// clean up
356 		// remove listeners
357 		$container.unbind("scroll.navigator-view");
358 
359 		view.showInactiveContent();
360 	}
361 
362 	/**
363 	 * View callbacks.
364 	 * 
365 	 * @ignore
366 	 */
367 
368 	view.dragStart = function() {
369 		viewDragging = true;
370 	};
371 
372 	// scroll container when the dragger is dragged
373 	view.dragging = function(x, y) {
374 		var scrollLeft = scale * docSize.x * x / canvasSize.x;
375 		var scrollTop = scale * docSize.y * y / canvasSize.y;
376 		$container.scrollLeft(scrollLeft).scrollTop(scrollTop);
377 	};
378 
379 	view.dragStop = function() {
380 		viewDragging = false;
381 	};
382 
383 	view.buttonZoomInClicked = function() {
384 		zoomController.zoomIn();
385 	};
386 
387 	view.buttonZoomOutClicked = function() {
388 		zoomController.zoomOut();
389 	};
390 
391 	view.sliderChanged = function(value) {
392 		zoomController.zoomTo((value + 1) * zoomController.ZOOM_STEP);
393 	};
394 
395 	// set dragger size when container was resized
396 	container.subscribe(mindmaps.CanvasContainer.Event.RESIZED, function() {
397 		if (docSize) {
398 			calculateDraggerSize();
399 		}
400 	});
401 
402 	// document events
403 	eventBus.subscribe(mindmaps.Event.DOCUMENT_OPENED, documentOpened);
404 	eventBus.subscribe(mindmaps.Event.DOCUMENT_CLOSED, documentClosed);
405 
406 	// node events
407 	eventBus.subscribe(mindmaps.Event.NODE_MOVED, renderView);
408 	eventBus.subscribe(mindmaps.Event.NODE_BRANCH_COLOR_CHANGED, renderView);
409 	eventBus.subscribe(mindmaps.Event.NODE_CREATED, renderView);
410 	eventBus.subscribe(mindmaps.Event.NODE_DELETED, renderView);
411 	eventBus.subscribe(mindmaps.Event.NODE_OPENED, renderView);
412 	eventBus.subscribe(mindmaps.Event.NODE_CLOSED, renderView);
413 
414 	eventBus.subscribe(mindmaps.Event.ZOOM_CHANGED, function(zoomFactor) {
415 		scale = zoomFactor;
416 		calculateDraggerPosition();
417 		calculateDraggerSize();
418 		calculateZoomLevel();
419 		calculateSliderValue();
420 	});
421 
422 	this.go = function() {
423 		view.init();
424 		view.showInactiveContent();
425 	};
426 };