1/// @brief The SketchCanvas class accepts a canvas object to draw on.
2/// @param canvas The canvas to draw the figure to.
3/// @param options An initialization parameters table that contains following items:
4///                editmode: The canvas is used for editing when true.
5///                debug: A function with one argument to output debug string.
6///
7/// Make sure to invoke this class's constructor with "new" prepended
8/// and keep the returned object to some variable.
9///
10/// It has following event handlers that can be assigned as this object's method.
11///
12/// function onLocalChange();
13/// This event is invoked when the canvas saves its contents into localStorage of the
14/// browser.  Use this event to update list of locally saved figures.
15///
16/// function onUpdateServerList(list);
17/// This event is invoked when the object requests the server to refresh figure list
18/// and receives response.
19///
20/// function onUpdateData(data);
21/// This event is invoked when the contents of figure data is modified.
22function SketchCanvas(canvas, options){
23'use strict';
24var editmode = options && options.editmode;
25var scale = options && options.scale ? options.scale : 1;
26
27// Obtain the browser's preferred language.
28var currentLanguage = (window.navigator.language || window.navigator.userLanguage || window.navigator.userLanguage);
29currentLanguage = currentLanguage.substr(0, 2);
30
31i18n.init({lng: currentLanguage, fallbackLng: 'en', resStore: resources, getAsync: false});
32
33var dobjs; // Drawing objects
34var dhistory; // Drawing object history (for undoing)
35var selectobj = [];
36
37var handleSize = 4;
38var gridEnable = false;
39var gridSize = 8;
40var toolButtonInterval = 36;
41
42// Called at the end of the constructor
43function onload(){
44
45  // Check existence of canvas element and treating not compatible browsers
46  if ( ! canvas || ! canvas.getContext ) {
47    return false;
48  }
49
50	// Ignore mouse events if it's non-edit mode.
51	if(editmode){
52		canvas.onclick = mouseLeftClick;
53		canvas.onmousedown = mouseDown;
54		canvas.onmouseup = mouseUp;
55		canvas.onmousemove = mouseMove;
56		canvas.onmouseout = mouseleave;
57		canvas.setAttribute("tabindex", 0); // Make sure the canvas can have a key focus
58		canvas.onkeydown = keyDown;
59	}
60	else
61		canvas.onclick = viewModeClick;
62
63  // 2D context
64  ctx = canvas.getContext('2d');
65	// Set a placeholder function to ignore setLineDash method for browsers that don't support it.
66	// The method is not compatible with many browsers, but we don't care if it's not supported
67	// because it's only used for selected object designation.
68	if(!ctx.setLineDash) {
69		ctx.setLineDash = function(){};
70	}
71
72	var aw = 20; // Arrow width
73
74	// Previous toolbar page button
75	buttons.push(new Button(mx0, my0, mx0 + aw, my0 + mh0, function(){
76		ctx.fillStyle = 'rgb(127,127,127)';
77		ctx.beginPath();
78		ctx.moveTo(mx0, my0 + mh0 / 2);
79		ctx.lineTo(mx0 + aw, my0);
80		ctx.lineTo(mx0 + aw, my0 + mh0);
81		ctx.closePath();
82		ctx.fill();
83	}, function(){
84		debug("clicked left");
85		toolbarPage = Math.max(toolbarPage - 1, 0);
86		toolbar = toolbars[toolbarPage];
87		cur_tool = toolbar[0];
88		draw();
89	}));
90
91	// Next toolbar page button
92	buttons.push(new Button(mx0 + mw0 - aw, my0, mx0 + mw0, my0 + mh0, function(){
93		ctx.beginPath();
94		ctx.moveTo(mx0 + mw0, my0 + mh0 / 2);
95		ctx.lineTo(mx0 + mw0 - aw, my0);
96		ctx.lineTo(mx0 + mw0 - aw, my0 + mh0);
97		ctx.closePath();
98		ctx.fill();
99	}, function(){
100		debug("clicked right");
101		toolbarPage = Math.min(toolbarPage + 1, toolbars.length-1);
102		toolbar = toolbars[toolbarPage];
103		cur_tool = toolbar[0];
104		draw();
105	}));
106
107  resizeCanvas();
108  draw();
109
110  // Draw objects
111  dobjs = [];
112
113  // And the history of operations
114  dhistory = [];
115}
116
117var datadir = "data";
118
119function draw() {
120	if(!editmode)
121		return;
122	// Draw a rectangle
123	ctx.beginPath();
124	ctx.strokeStyle = 'rgb(192, 192, 77)'; // yellow
125		ctx.font = i18n.t("14px 'Courier'");
126	ctx.strokeText('SketchCanvas Editor v0.1.3', 420, 10);
127	ctx.rect(x0, y0, w0, h0);
128	ctx.rect(x1, y1, w1, h1);
129	ctx.closePath();
130	ctx.stroke();
131
132	// Background for the page text
133	ctx.fillStyle = 'rgb(255,255,255)';
134	ctx.fillRect(x0 + 1, y0 + 1, x1 - 2, y0 + h0 - 2);
135
136	// Text indicating current toolbar page
137	ctx.fillStyle = 'rgb(0,0,0)';
138	var text = (toolbarPage + 1) + "/" + (toolbars.length);
139	var width = ctx.measureText(text).width;
140	ctx.fillText(text, mx0 + mw0 / 2 - width / 2, my0 + mh0 / 2);
141
142	// menu
143	drawMenu();
144	drawTBox();
145	drawButtons();
146	drawCBox(cur_col);
147	drawHBox(cur_thin);
148}
149
150// draw coord(for Debug)
151function drawPos(x, y) {
152  ctx.strokeText('X='+x+' Y='+y, x, y);
153}
154
155// Menu
156function drawMenu() {
157	for(var i=0;i<menus.length;i++) {
158		ctx.fillStyle = 'rgb(100, 200, 100)'; // green
159		ctx.fillRect(mx1+(i+0)*(mw1+10), my0, mw1, mh0);
160		//ctx.strokeStyle = 'rgb(50, 192, 177)'; // cyan
161		ctx.strokeStyle = 'rgb(250, 250, 250)'; // white
162		ctx.strokeText(menus[i].text, mx1+10+(i+0)*(mw1+10), my0+20);
163	}
164}
165
166/// Returns bounding box of a toolbar button.
167function getTBox(i){
168	return {minx: mx0, miny: my0 + toolButtonInterval + toolButtonInterval*i,
169		maxx: mx0 + mw0, maxy: my0 + toolButtonInterval + toolButtonInterval*i+mh0};
170}
171
172// Tool Box
173function drawTBox() {
174	ctx.fillStyle = 'rgb(255,255,255)';
175	ctx.fillRect(x0 + 1, my0 + mh0 + 1, x1 - 2, my0 + h0 - 2);
176
177	for(var i=0;i<toolbar.length;i++) {
178		if (cur_tool === toolbar[i])
179			ctx.fillStyle = 'rgb(255, 80, 77)'; // red
180		else
181			ctx.fillStyle = 'rgb(192, 80, 77)'; // red
182		var r = getTBox(i);
183		ctx.fillRect(r.minx, r.miny, r.maxx - r.minx, r.maxy - r.miny);
184		ctx.strokeStyle = 'rgb(250, 250, 250)'; // white
185		drawParts(toolbar[i], mx0+10, my0 + toolButtonInterval + 10 + (mh0+8)*i);
186	}
187}
188
189function drawButtons(){
190	for(var i = 0; i < buttons.length; i++){
191		if("ondraw" in buttons[i])
192			buttons[i].ondraw();
193	}
194}
195
196// Color Palette
197function drawCBox(no) {
198	for(var i=0;i<5;i++) {
199		ctx.beginPath();
200		ctx.fillStyle = colstr[i];
201		var x = mx2+(mw2+10)*i;
202		ctx.fillRect(x, my0, mw2, mh0);
203		ctx.stroke();
204		if (4==i) {		// border line if white
205			ctx.beginPath();
206			//ctx.lineWidth = 1;
207			ctx.strokeStyle = gray;
208			ctx.rect(x, my0, mw2, mh0);
209			ctx.stroke();
210		}
211		if (no == i+31) {
212			ctx.beginPath();
213			ctx.strokeStyle = gray;
214			ctx.strokeText('○', x+9, my0+20);
215		}
216	}
217}
218
219// Thin Box
220function drawHBox(no) {
221	for(var i=0;i<3;i++) {
222		ctx.beginPath();
223		if (no == i+41)
224			ctx.fillStyle = 'rgb(255, 80, 77)'; // red
225		else
226			ctx.fillStyle = 'rgb(192, 80, 77)'; // red
227		ctx.fillRect(mx3+(mw2+10)*i, my0, mw2, mh0);
228		ctx.beginPath();
229		ctx.strokeStyle = white;
230		ctx.lineWidth = i + 1;
231		ctx.moveTo(mx3+(mw2+10)*i+10, my0+15);
232		ctx.lineTo(mx3+(mw2+10)*i+25, my0+15);
233		ctx.stroke();
234	}
235	ctx.lineWidth = 1;
236}
237
238// draw toolbox
239function drawParts(tool, x, y) {
240	if(tool.drawTool)
241		tool.drawTool(x, y);
242	else
243		ctx.strokeText(i18n.t('Unimplemented'), x, y);
244}
245
246// Returns bounding box for a drawing object.
247function objBounds(obj, rawvalue){
248	var ret = obj.getBoundingRect(rawvalue);
249	if(!rawvalue){
250		ret.minx += offset.x;
251		ret.maxx += offset.x;
252		ret.miny += offset.y;
253		ret.maxy += offset.y;
254	}
255	return ret;
256}
257
258// Expand given rectangle by an offset
259function expandRect(r, offset){
260	return {minx: r.minx - offset, miny : r.miny - offset,
261		maxx: r.maxx + offset, maxy: r.maxy + offset};
262}
263
264// Check if a point intersects with a rectangle
265function hitRect(r, x, y){
266	return r.minx < x && x < r.maxx && r.miny < y && y < r.maxy;
267}
268
269// Check if two rectangles intersect each other
270function intersectRect(r1, r2){
271	return r1.minx < r2.maxx && r2.minx < r1.maxx && r1.miny < r2.maxy && r2.miny < r1.maxy;
272}
273
274function mouseLeftClick(e) {
275	if (3 == e.which) mouseRightClick(e);
276	else {
277		var clrect = canvas.getBoundingClientRect();
278		var mx = e.clientX - clrect.left;
279		var my = e.clientY - clrect.top;
280
281		for(var i = 0; i < buttons.length; i++){
282			if(hitRect(buttons[i], mx, my)){
283				buttons[i].onclick();
284				return;
285			}
286		}
287
288		if(choiceTBox(mx, my))
289			return;
290
291		var menuno = checkMenu(mx, my);
292		debug(menuno);
293		if (menuno < 0) {		// draw area
294			if(mx < offset.x || my < offset.y)
295				return;
296			if(cur_tool.name === "delete"){ // delete
297				for(var i = 0; i < dobjs.length; i++){
298					// For the time being, we use the bounding boxes of the objects
299					// to determine the clicked object.  It may be surprising
300					// when a diagonal line gets deleted by clicking on seemingly
301					// empty space, but we could fix it in the future.
302					var bounds = expandRect(objBounds(dobjs[i]), 10);
303					if(hitRect(bounds, mx, my)){
304						// If any dobj hits, save the current state to the undo buffer and delete the object.
305						dhistory.push(cloneObject(dobjs));
306						// Delete from selected object list, too.
307						for(var j = 0; j < selectobj.length;){
308							if(selectobj[j] === dobjs[i])
309								selectobj.splice(j, 1);
310							else
311								j++;
312						}
313						dobjs.splice(i, 1);
314						redraw(dobjs);
315						updateDrawData();
316						return;
317					}
318				}
319				return;
320			}
321			else if(cur_tool.name !== "select" && cur_tool.name !== "pathedit")
322				cur_tool.appendPoint(mx, my);
323		}
324		else if (menuno < 10) {
325			drawMenu();
326			menus[menuno].onclick();
327		}
328		else if (menuno <= 40) {
329			drawCBox(menuno);
330			cur_col = colnames[menuno-31];
331			if(0 < selectobj.length){
332				dhistory.push(cloneObject(dobjs));
333				for(var i = 0; i < selectobj.length; i++)
334					selectobj[i].color = cur_col;
335				updateDrawData();
336				redraw(dobjs);
337			}
338		}
339		else {
340			drawHBox(menuno);
341			cur_thin = menuno - 40;
342			if(0 < selectobj.length){
343				dhistory.push(cloneObject(dobjs));
344				for(var i = 0; i < selectobj.length; i++)
345					selectobj[i].width = cur_thin;
346				updateDrawData();
347				redraw(dobjs);
348			}
349		}
350		//if (1 == menuno) debug('x='+arr[0].x + 'y='+arr[0].y);
351	}
352}
353
354function mouseRightClick(e) {
355	//drawPos(e.pageX, e.pageY);
356	//cood = { x:e.pageX, y:e.pageY }
357	//arr[idx] = cood;
358	//idx++;
359	debug('idx='+idx);
360}
361
362var movebase = [0,0];
363var dragstart = [0,0];
364var dragend = [0,0];
365var moving = false;
366var sizing = null; // Reference to object being resized. Null if no resizing is in progress.
367var sizedir = 0;
368var pointMoving = null;
369var pointMovingIdx = 0;
370var pointMovingCP = ""; ///< Control point for moving, "": vertex, "c": first control point, "d": second control point
371var boxselecting = false;
372function pathEditMouseDown(e){
373	var clrect = canvas.getBoundingClientRect();
374	var mx = e.clientX - clrect.left;
375	var my = e.clientY - clrect.top;
376
377	// Prioritize path editing over shape selection.
378
379	// Check to see if we are dragging any of control points.
380	for(var n = 0; n < selectobj.length; n++){
381		// Do not try to change shape of non-sizeable object.
382		if(!selectobj[n].isSizeable())
383			continue;
384		var pts = selectobj[n].points;
385		// Grab a control point uder the mouse cursor to move it.
386		for(var i = 0; i < pts.length; i++){
387			var p = pts[i];
388
389			// Function to check if we should start dragging one of vertices or
390			// control points.  Written as a local function to clarify logic.
391			var checkVertexHandle = function(self){
392				if(!hitRect(pointHandle(p.x, p.y), mx - offset.x, my - offset.y))
393					return false;
394				// Check control points for only the "path" shapes.
395				// Pressing Ctrl key before dragging the handle pulls out the
396				// control point, even if the vertex didn't have one.
397				if(selectobj[n].tool === "path" && e.ctrlKey){
398					if(0 < i && !("dx" in p))
399						pointMovingCP = "d";
400					else if(i+1 < pts.length && !("cx" in pts[i+1])){
401						pointMovingCP = "c";
402						pointMoving = selectobj[n];
403						pointMovingIdx = i+1;
404						return true;
405					}
406					else
407						return false;
408				}
409				else
410					pointMovingCP = "";
411				pointMoving = selectobj[n];
412				pointMovingIdx = i;
413
414				// Remember clicked vertex for showing control handles
415				var needsRedraw = self.selectPointShape !== selectobj[n] || self.selectPointIdx !== pointMovingIdx;
416				self.selectPointShape = selectobj[n];
417				self.selectPointIdx = pointMovingIdx;
418				if(needsRedraw) // Redraw only if the selected
419					redraw(dobjs);
420
421				dhistory.push(cloneObject(dobjs));
422				return true;
423			}
424
425			// do{ ... }while(false) statement could do a similar trick, but we prefer
426			// local functions for the ease of debugging and more concise expression.
427			if(checkVertexHandle(this))
428				return;
429
430			// Check for existing control points
431			// Select only the control points around the selected vertex
432			if(this.selectPointShape !== selectobj[n] || i < this.selectPointIdx || this.selectPointIdx + 1 < i)
433				continue;
434			if("cx" in p && "cy" in p && hitRect(pointHandle(p.cx, p.cy), mx - offset.x, my - offset.y)){
435				pointMoving = selectobj[n];
436				pointMovingIdx = i;
437				pointMovingCP = "c";
438				dhistory.push(cloneObject(dobjs));
439				return;
440			}
441			if("dx" in p && "dy" in p && hitRect(pointHandle(p.dx, p.dy), mx - offset.x, my - offset.y)){
442				pointMoving = selectobj[n];
443				pointMovingIdx = i;
444				pointMovingCP = "d";
445				dhistory.push(cloneObject(dobjs));
446				return;
447			}
448		}
449	}
450
451	selectCommonMouseDown(mx, my);
452}
453
454/// A common method to select and pathedit tools.
455/// The pathedit tool can select objects, not to mention the select tool.
456function selectCommonMouseDown(mx, my){
457
458	var menuno = checkMenu(mx, my);
459	if(0 <= menuno) // If we are clicking on a menu button, ignore this event
460		return null;
461
462	// First, check if we clicked on one of already selected shapes.
463	// if so, do not clear the selection and get ready to manipulate already selected shapes.
464	for(var i = 0; i < selectobj.length; i++){
465		var bounds = expandRect(objBounds(selectobj[i]), 10);
466		if(hitRect(bounds, mx, my))
467			return true;
468	}
469
470	// Second, check if we clicked other shapes.
471	// If so, clear the selection and select the new shape.
472	for(var i = 0; i < dobjs.length; i++){
473		// For the time being, we use the bounding boxes of the objects
474		// to determine the clicked object.  It may be surprising
475		// when a diagonal line gets deleted by clicking on seemingly
476		// empty space, but we could fix it in the future.
477		var bounds = expandRect(objBounds(dobjs[i]), 10);
478		if(hitRect(bounds, mx, my)){
479			// If we haven't selected an object but clicked on an object, select it.
480			// Single click always selects a single shape.
481			selectobj = [dobjs[i]];
482			// Forget point selection in pathedit tool if shape selection is changed.
483			toolmap.pathedit.selectPointIdx = -1;
484			redraw(dobjs);
485			return true;
486		}
487	}
488
489	return false;
490}
491
492function selectMouseDown(e){
493	var clrect = canvas.getBoundingClientRect();
494	var mx = e.clientX - clrect.left;
495	var my = e.clientY - clrect.top;
496
497	// Prevent the select tool from clearing selection even if the mouse is on the
498	// toolbar or the menu bar.
499	if(mx < offset.x || my < offset.y)
500		return;
501
502	// Enter sizing, moving or box-selecting mode only with select tool.
503	// The pathedit tool should not bother interfering with these modes.
504
505	// Check to see if we are dragging any of scaling handles.
506	for(var n = 0; n < selectobj.length; n++){
507		// Do not try to change size of non-sizeable object.
508		if(!selectobj[n].isSizeable())
509			continue;
510		var bounds = objBounds(selectobj[n]);
511		// Do not enter sizing mode if the object is point sized.
512		if(1 <= Math.abs(bounds.maxx - bounds.minx) && 1 <= Math.abs(bounds.maxy - bounds.miny)){
513			for(var i = 0; i < 8; i++){
514				if(hitRect(getHandleRect(bounds, i), mx, my)){
515					sizedir = i;
516					sizing = selectobj[n];
517					dhistory.push(cloneObject(dobjs));
518					return;
519				}
520			}
521		}
522	}
523
524	var pointOnSelection = selectCommonMouseDown(mx, my);
525
526	// If we're starting dragging on a selected object, enter moving mode.
527	if(pointOnSelection){
528		var mx = gridEnable ? Math.round(mx / gridSize) * gridSize : mx;
529		var my = gridEnable ? Math.round(my / gridSize) * gridSize : my;
530		movebase = [mx, my];
531		moving = true;
532		dhistory.push(cloneObject(dobjs));
533	}
534	else{
535		// If no object is selected and dragging is started, it's box selection mode.
536		boxselecting = true;
537		selectobj = [];
538		dragstart = [mx, my];
539	}
540}
541
542function mouseDown(e){
543	if(cur_tool)
544		cur_tool.mouseDown(e);
545}
546
547function mouseUp(e){
548	if(cur_tool)
549		cur_tool.mouseUp(e);
550}
551
552function selectMouseMove(e){
553	var clrect = canvas.getBoundingClientRect();
554	var mx = (gridEnable ? Math.round(e.clientX / gridSize) * gridSize : e.clientX) - clrect.left;
555	var my = (gridEnable ? Math.round(e.clientY / gridSize) * gridSize : e.clientY) - clrect.top;
556
557	if(0 < selectobj.length){
558		if(moving){
559			var dx = mx - movebase[0];
560			var dy = my - movebase[1];
561			for(var n = 0; n < selectobj.length; n++){
562				var obj = selectobj[n];
563				for(var i = 0; i < obj.points.length; i++){
564					var pt = obj.points[i];
565					pt.x += dx;
566					pt.y += dy;
567					// Move control points, too
568					if("cx" in pt && "cy" in pt)
569						pt.cx += dx, pt.cy += dy;
570					if("dx" in pt && "dy" in pt)
571						pt.dx += dx, pt.dy += dy;
572				}
573			}
574			movebase = [mx, my];
575			redraw(dobjs);
576		}
577		else if(sizing){
578			/* Definition of handle index and position
579				0 --- 1 --- 2
580				|           |
581				7           3
582				|           |
583				6 --- 5 --- 4
584			*/
585			mx -= offset.x;
586			my -= offset.y;
587			var bounds = objBounds(sizing, true);
588			var ux = [-1,0,1,1,1,0,-1,-1][sizedir];
589			var uy = [-1,-1,-1,0,1,1,1,0][sizedir];
590			var xscale = ux === 0 ? 1 : (ux === 1 ? mx - bounds.minx : bounds.maxx - mx) / (bounds.maxx - bounds.minx);
591			var yscale = uy === 0 ? 1 : (uy === 1 ? my - bounds.miny : bounds.maxy - my) / (bounds.maxy - bounds.miny);
592			var obj = sizing;
593			for(var i = 0; i < obj.points.length; i++){
594				var pt = obj.points[i];
595				if(ux !== 0 && xscale !== 0){
596					// Scale control points, too
597					var props = ["x", "cx", "dx"];
598					for(var j = 0; j < props.length; j++){
599						var prop = props[j];
600						if(!(prop in pt))
601							continue;
602						pt[prop] = ux === 1 ?
603							(pt[prop] - bounds.minx) * xscale + bounds.minx :
604							(pt[prop] - bounds.maxx) * xscale + bounds.maxx;
605					}
606				}
607				if(uy !== 0 && yscale !== 0){
608					// Scale control points, too
609					var props = ["y", "cy", "dy"];
610					for(var j = 0; j < props.length; j++){
611						var prop = props[j];
612						if(!(prop in pt))
613							continue;
614						pt[prop] = uy === 1 ?
615							(pt[prop] - bounds.miny) * yscale + bounds.miny :
616							(pt[prop] - bounds.maxy) * yscale + bounds.maxy;
617					}
618				}
619			}
620			// Invert handle selection when the handle is dragged to the other side to enable mirror scaling.
621			if(ux !== 0 && xscale < 0)
622				sizedir = [2,1,0,7,6,5,4,3][sizedir];
623			if(uy !== 0 && yscale < 0)
624				sizedir = [6,5,4,3,2,1,0,7][sizedir];
625			redraw(dobjs);
626		}
627	}
628
629	// We could use e.buttons to check if it's supported by all the browsers,
630	// but it seems not much trusty.
631	if(!moving && !sizing && boxselecting){
632		dragend = [mx, my];
633		var box = {
634			minx: Math.min(dragstart[0], mx),
635			maxx: Math.max(dragstart[0], mx),
636			miny: Math.min(dragstart[1], my),
637			maxy: Math.max(dragstart[1], my),
638		}
639		selectobj = [];
640		// Select all intersecting objects with the dragged box.
641		for(var i = 0; i < dobjs.length; i++){
642			var bounds = expandRect(objBounds(dobjs[i]), 10);
643			if(intersectRect(bounds, box))
644				selectobj.push(dobjs[i]);
645		}
646		redraw(dobjs);
647	}
648}
649
650function pathEditMouseMove(e){
651	var clrect = canvas.getBoundingClientRect();
652	var mx = (gridEnable ? Math.round(e.clientX / gridSize) * gridSize : e.clientX) - clrect.left;
653	var my = (gridEnable ? Math.round(e.clientY / gridSize) * gridSize : e.clientY) - clrect.top;
654
655	if(0 < selectobj.length){
656		if(pointMoving){
657			mx -= offset.x;
658			my -= offset.y;
659
660			// Relatively move a control point if it exists
661			var relmove = function(name, idx, dx, dy){
662				if(pointMovingIdx + idx < 0 || pointMoving.points.length <= pointMovingIdx + idx)
663					return;
664				var p1 = pointMoving.points[pointMovingIdx + idx];
665				if((name + "x") in p1) p1[name + "x"] += dx;
666				if((name + "y") in p1) p1[name + "y"] += dy;
667			}
668
669			if(pointMovingCP === ""){
670				// When moving a vertex, Bezier control points associated with that vertex
671				// should move as well.  We remember delta of position change and apply it
672				// to the control points later.
673				var dx = mx - pointMoving.points[pointMovingIdx].x;
674				var dy = my - pointMoving.points[pointMovingIdx].y;
675				pointMoving.points[pointMovingIdx].x = mx;
676				pointMoving.points[pointMovingIdx].y = my;
677				// If it's the last vertex, there's no control point toward the next vertex.
678				if(pointMovingIdx + 1 < pointMoving.points.length)
679					relmove("c", 1, dx, dy);
680				relmove("d", 0, dx, dy);
681			}
682			else{
683				var curpoint = pointMoving.points[pointMovingIdx];
684				curpoint[pointMovingCP + "x"] = mx;
685				curpoint[pointMovingCP + "y"] = my;
686
687				// Move the control point in the opposite side of the vertex so that the
688				// curve seems smooth.  A line between a cubic Bezier spline curve's control point
689				// and its vertex is tangent to the curve, so keeping the control points parallel
690				// makes the connected Bezier curves look smooth.  This could be done by hand-editing,
691				// but it's very cumbersome to do everytime even single control point of spline is
692				// edited.
693				// Note that SVG's "S" or "s" path command can reflect the Bezier control point to
694				// achieve similar effect, but the commands cannot specify distinct lengths for each
695				// control points from the central vertex.  So we keep using "C" command.
696				// Sometimes the user wants to make a non-smooth path, so this functionality can be
697				// disabled by pressing ALT key while dragging.
698				if(!e.altKey && pointMovingCP === "d" && pointMovingIdx + 1 < pointMoving.points.length){
699					var nextpoint = pointMoving.points[pointMovingIdx + 1];
700					if("cx" in nextpoint && "cy" in nextpoint){
701						var dx = nextpoint.cx - curpoint.x;
702						var dy = nextpoint.cy - curpoint.y;
703						var len = Math.sqrt(dx * dx + dy * dy);
704						var angle = Math.atan2(dy, dx);
705						var newangle = Math.atan2(curpoint.y - curpoint.dy, curpoint.x - curpoint.dx);
706						nextpoint.cx = curpoint.x + len * Math.cos(newangle);
707						nextpoint.cy = curpoint.y + len * Math.sin(newangle);
708					}
709				}
710				else if(!e.altKey && pointMovingCP === "c" && 0 <= pointMovingIdx - 1){
711					var prevpoint = pointMoving.points[pointMovingIdx - 1];
712					if("dx" in prevpoint && "dy" in prevpoint){
713						var dx = prevpoint.dx - prevpoint.x;
714						var dy = prevpoint.dy - prevpoint.y;
715						var len = Math.sqrt(dx * dx + dy * dy);
716						var angle = Math.atan2(dy, dx);
717						var newangle = Math.atan2(prevpoint.y - curpoint.cy, prevpoint.x - curpoint.cx);
718						prevpoint.dx = prevpoint.x + len * Math.cos(newangle);
719						prevpoint.dy = prevpoint.y + len * Math.sin(newangle);
720					}
721				}
722			}
723			redraw(dobjs);
724		}
725	}
726}
727
728function mouseMove(e){
729	if(cur_tool.mouseMove)
730		cur_tool.mouseMove(e);
731}
732
733function mouseleave(e){
734	moving = false;
735	sizing = null;
736	boxselecting = false;
737}
738
739/// Mouse click event handler for view mode.
740/// Some shapes respond to the event in view mode.
741function viewModeClick(e){
742	var clrect = canvas.getBoundingClientRect();
743	var mx = (gridEnable ? Math.round(e.clientX / gridSize) * gridSize : e.clientX) - clrect.left;
744	var my = (gridEnable ? Math.round(e.clientY / gridSize) * gridSize : e.clientY) - clrect.top;
745	var m = {x: mx / scale, y: my / scale}; /// Convert to the logical coordinate system before hit test
746
747	// Check to see if we are dragging any of scaling handles.
748	for(var i = 0; i < dobjs.length; i++){
749		if(!("viewModeClick" in dobjs[i]))
750			continue;
751		var bounds = expandRect(objBounds(dobjs[i]), 10);
752		if(hitRect(bounds, m.x, m.y)){
753			if(dobjs[i].viewModeClick(e))
754				return true;
755		}
756	}
757
758}
759
760function keyDown(e){
761	e = e || window.event;
762	if(cur_tool.keyDown)
763		cur_tool.keyDown(e);
764}
765
766/// Delete given shapes from the canvas.
767/// Also deletes from selection list if matched.
768/// Passing selectobj variable itself as the argument clears the whole selection.
769function deleteShapes(shapes){
770	for (var i = 0; i < shapes.length; i++) {
771		var s = shapes[i];
772		for (var j = 0; j < dobjs.length; j++) {
773			if(dobjs[j] === s){
774				dobjs.splice(j, 1);
775				break;
776			}
777		}
778		if(shapes !== selectobj){
779			for (var j = 0; j < selectobj.length; j++) {
780				if(selectobj[j] === s){
781					selectobj.splice(j, 1);
782					break;
783				}
784			}
785		}
786	}
787	if(shapes === selectobj)
788		selectobj = [];
789}
790
791/// Constrain coordinate values if the grid is enabled.
792function constrainCoord(coord){
793	if(gridEnable)
794		return { x: Math.round(coord.x / gridSize) * gridSize, y: Math.round(coord.y / gridSize) * gridSize };
795	else
796		return { x: coord.x, y: coord.y };
797}
798
799/// Convert canvas coordinates to the source coordinates.
800/// These coordinates can differ when offset is not zero.
801function canvasToSrc(coord){
802	return { x: (coord.x - offset.x) / scale, y: (coord.y - offset.y) / scale };
803}
804
805// A local function to set font size with the global scaling factor in mind.
806function setFont(baseSize) {
807	ctx.font = baseSize + "px 'Noto Sans Japanese', sans-serif";
808}
809
810/// Draw a shape on the canvas.
811function drawCanvas(obj) {
812	var tool = toolmap[obj.tool];
813	var numPoints = tool.points;
814
815	tool.setColor(coltable[obj.color]);
816	tool.setWidth(obj.width);
817	if(numPoints <= obj.points.length)
818		if(tool.draw(obj))
819			return;
820}
821
822function getHandleRect(bounds, i){
823	var x, y;
824	switch(i){
825	case 0: x = bounds.minx, y = bounds.miny; break;
826	case 1: x = (bounds.minx+bounds.maxx)/2, y = bounds.miny; break;
827	case 2: x = bounds.maxx, y = bounds.miny; break;
828	case 3: x = bounds.maxx, y = (bounds.miny+bounds.maxy)/2; break;
829	case 4: x = bounds.maxx, y = bounds.maxy; break;
830	case 5: x = (bounds.minx+bounds.maxx)/2, y = bounds.maxy; break;
831	case 6: x = bounds.minx, y = bounds.maxy; break;
832	case 7: x = bounds.minx, y = (bounds.miny+bounds.maxy)/2; break;
833	default: return;
834	}
835	return pointHandle(x, y);
836}
837
838/// Returns a small rectangle surrounding the given point which is suitable for mouse-picking.
839function pointHandle(x, y){
840	return {minx: x - handleSize, miny: y - handleSize, maxx: x + handleSize, maxy: y + handleSize};
841}
842
843// Resize the canvas so that it fits the contents.
844// Note that the canvas contents are also cleared, so we might need to redraw everything.
845function resizeCanvas(){
846	if(!editmode){
847		// Resize the canvas so that the figure fits the size of canvas.
848		// It's only done in view mode because we should show the toolbar and the menu bar
849		// in edit mode.
850		canvas.width = metaObj.size[0] * scale;
851		canvas.height = metaObj.size[1] * scale;
852		x1 = 0;
853		y1 = 0;
854		offset = {x:0, y:0};
855	}
856	else{
857		canvas.width = 1024;
858		canvas.height = 640;
859		x1 = 90;
860		y1 = 50;
861		offset = {x:x1, y:y1};
862	}
863}
864
865/// Draw a handle rectangle or a circle around a vertex or a control point
866function drawHandle(x, y, color, circle){
867	var r = pointHandle(x, y);
868	ctx.fillStyle = color;
869	if(!circle)
870		ctx.fillRect(r.minx, r.miny, r.maxx - r.minx, r.maxy-r.miny);
871	ctx.beginPath();
872	ctx.strokeStyle = '#000';
873	if(circle){
874		ctx.arc((r.minx + r.maxx) / 2., (r.miny + r.maxy) / 2, handleSize, 0, 2 * Math.PI, false);
875		ctx.fill();
876	}
877	else
878		ctx.rect(r.minx, r.miny, r.maxx - r.minx, r.maxy-r.miny);
879	ctx.stroke();
880}
881
882// redraw
883function redraw(pt) {
884
885	clearCanvas();
886
887	// Clip the drawing region when in edit mode in order to prevent shapes from
888	// contaminate the menu bar and the toolbar.
889	if(editmode){
890		ctx.save();
891		ctx.beginPath();
892		ctx.rect(x1,y1, w1, h1);
893		ctx.clip();
894	}
895
896	if(gridEnable){
897		ctx.fillStyle = "#000";
898		for(var ix = Math.ceil(x1 / gridSize); ix < (x1 + w1) / gridSize; ix++){
899			for(var iy = Math.ceil(y1 / gridSize); iy < (y1 + h1) / gridSize; iy++){
900				ctx.fillRect(ix * gridSize, iy * gridSize, 1, 1);
901			}
902		}
903	}
904
905	// Translate and scale the coordinates by applying matrices before invoking drawCanvas().
906	ctx.save();
907	ctx.translate(offset.x, offset.y);
908	ctx.scale(scale, scale);
909	for (var i=0; i<pt.length; i++)
910		drawCanvas(pt[i]);
911
912	if(cur_shape)
913		drawCanvas(cur_shape);
914
915	ctx.restore();
916
917	if(boxselecting){
918		ctx.beginPath();
919		ctx.lineWidth = 1;
920		ctx.strokeStyle = '#000';
921		ctx.setLineDash([5]);
922		ctx.rect(dragstart[0], dragstart[1], dragend[0] - dragstart[0], dragend[1] - dragstart[1]);
923		ctx.stroke();
924		ctx.setLineDash([]);
925	}
926
927	for(var n = 0; n < selectobj.length; n++)
928		cur_tool.selectDraw(selectobj[n]);
929
930	if(editmode)
931		ctx.restore();
932}
933
934// When pathedit tool is selected, draw handles on the shape's control points
935// and the path that connecting control points, but not the
936// bounding boxes and scaling handles, which could be annoying
937// because they're nothing to do with path editing.
938function pathEditSelectDraw(shape){
939	var pts = shape.points;
940	ctx.beginPath();
941	ctx.lineWidth = 1;
942	ctx.strokeStyle = '#000';
943	if(toolmap[shape.tool].isArc){
944		ctx.setLineDash([5]);
945		ctx.moveTo(pts[0].x + offset.x, pts[0].y + offset.y);
946		for(var i = 1; i < pts.length; i++)
947			ctx.lineTo(pts[i].x + offset.x, pts[i].y + offset.y);
948		ctx.stroke();
949		ctx.setLineDash([]);
950	}
951
952	// Draws dashed line that connects a control point and its associated vertex
953	function drawGuidingLine(pt0, name){
954		ctx.setLineDash([5]);
955		ctx.beginPath();
956		ctx.moveTo(pt0.x + offset.x, pt0.y + offset.y);
957		ctx.lineTo(pt[name + "x"] + offset.x, pt[name + "y"] + offset.y);
958		ctx.stroke();
959		ctx.setLineDash([]);
960	}
961
962	for(var i = 0; i < pts.length; i++){
963		var pt = pts[i];
964		// Indicate currently selected vertex with blue handle, otherwise gray
965		drawHandle(pt.x + offset.x, pt.y + offset.y,
966			shape === this.selectPointShape && i === this.selectPointIdx ? '#7f7fff' : '#7f7f7f');
967
968		// Draw handles for control point only around the selected vertex
969		if(shape !== this.selectPointShape || i < this.selectPointIdx || this.selectPointIdx + 1 < i)
970			continue;
971
972		// Handles and guiding lines for the control points
973		if(0 < i && "cx" in pt && "cy" in pt){
974			drawHandle(pt.cx + offset.x, pt.cy + offset.y, '#ff7f7f', true);
975			drawGuidingLine(pts[i-1], "c");
976		}
977		if("dx" in pt && "dy" in pt){
978			drawHandle(pt.dx + offset.x, pt.dy + offset.y, '#ff7f7f', true);
979			drawGuidingLine(pt, "d");
980		}
981	}
982}
983
984// ==================== Shape class definition ================================= //
985function Shape(){
986	this.tool = "line";
987	this.color = "black";
988	this.width = 1;
989	this.points = [];
990}
991//inherit(Shape, Object);
992
993Shape.prototype.serialize = function(){
994	function set_default(t,k,v,def){
995		if(v !== def)
996			t[k] = v;
997	}
998
999	// send parts to server
1000	var dat = "";
1001	for (var i=0; i<this.points.length; i++){
1002		if(i !== 0) dat += ":";
1003		// Prepend control points if this vertex has them.
1004		if("cx" in this.points[i] && "cy" in this.points[i])
1005			dat += this.points[i].cx+","+this.points[i].cy+",";
1006		if("dx" in this.points[i] && "dy" in this.points[i])
1007			dat += this.points[i].dx+","+this.points[i].dy+",";
1008		dat += this.points[i].x+","+this.points[i].y;
1009	}
1010	var alldat = {
1011		type: this.tool,
1012		points: dat
1013	};
1014	// Values with defaults needs not assigned a value when saved.
1015	// This will save space if the drawn element properties use many default values.
1016	set_default(alldat, "color", this.color, "black");
1017	set_default(alldat, "width", this.width, 1);
1018	return alldat;
1019};
1020
1021Shape.prototype.isSizeable = function(){
1022	return true;
1023}
1024
1025Shape.prototype.deserialize = function(obj){
1026	this.color = obj.color || "black";
1027	this.width = obj.width || 1;
1028	if("points" in obj){
1029		var pt1 = obj.points.split(":");
1030		var arr = [];
1031		for(var j = 0; j < pt1.length; j++){
1032			var pt2 = pt1[j].split(",");
1033			var pt = {};
1034			if(6 <= pt2.length){
1035				pt.cx = parseFloat(pt2[0]);
1036				pt.cy = parseFloat(pt2[1]);
1037			}
1038			if(4 <= pt2.length){
1039				pt.dx = parseFloat(pt2[pt2.length-4]);
1040				pt.dy = parseFloat(pt2[pt2.length-3]);
1041			}
1042			pt.x = parseFloat(pt2[pt2.length-2]);
1043			pt.y = parseFloat(pt2[pt2.length-1]);
1044			arr.push(pt);
1045		}
1046		this.points = arr;
1047	}
1048};
1049
1050Shape.prototype.getBoundingRect = function(){
1051	// Get bounding box of the object
1052	var maxx, maxy, minx, miny;
1053	for(var j = 0; j < this.points.length; j++){
1054		var x = this.points[j].x;
1055		if(maxx === undefined || maxx < x)
1056			maxx = x;
1057		if(minx === undefined || x < minx)
1058			minx = x;
1059		var y = this.points[j].y;
1060		if(maxy === undefined || maxy < y)
1061			maxy = y;
1062		if(miny === undefined || y < miny)
1063			miny = y;
1064	}
1065	return {minx: minx, miny: miny, maxx: maxx, maxy: maxy};
1066};
1067// ==================== Shape class definition end ================================= //
1068
1069// ==================== PointShape class definition ================================= //
1070function PointShape(){
1071	Shape.call(this);
1072}
1073inherit(PointShape, Shape);
1074
1075PointShape.prototype.isSizeable = function(){
1076	return false;
1077}
1078
1079PointShape.prototype.getBoundingRect = function(){
1080	var height = 20;
1081	var width = 20;
1082	return {minx: this.points[0].x, miny: this.points[0].y,
1083		maxx: this.points[0].x + width, maxy: this.points[0].y + height};
1084}
1085// ==================== PointOject class definition end ================================= //
1086
1087// ==================== TextShape class definition ================================= //
1088function TextShape(){
1089	Shape.call(this);
1090	this.text = "";
1091	this.link = "";
1092}
1093inherit(TextShape, Shape);
1094
1095TextShape.prototype.serialize = function(){
1096	var alldat = Shape.prototype.serialize.call(this);
1097	alldat.text = this.text;
1098	if(this.link) alldat.link = this.link; // Do not serialize blank URL
1099	return alldat;
1100}
1101
1102TextShape.prototype.deserialize = function(obj){
1103	Shape.prototype.deserialize.call(this, obj);
1104	if (undefined !== obj.text) this.text = obj.text;
1105	if(undefined !== obj.link) this.link = obj.link;
1106};
1107
1108TextShape.prototype.isSizeable = function(){
1109	return false;
1110}
1111
1112TextShape.prototype.getBoundingRect = function(){
1113	var height = this.width === 1 ? 14 : this.width === 2 ? 16 : 20;
1114	var oldfont = ctx.font;
1115	ctx.font = setFont(height);
1116	var width = ctx.measureText(this.text).width;
1117	ctx.font = oldfont;
1118	return {minx: this.points[0].x, miny: this.points[0].y - height,
1119		maxx: this.points[0].x + width, maxy: this.points[0].y};
1120}
1121
1122/// Click event in view mode
1123TextShape.prototype.viewModeClick = function(e){
1124	if("link" in this && this.link){
1125		location.href = this.link;
1126		return true;
1127	}
1128	return false;
1129}
1130// ==================== TextShape class definition end ================================= //
1131
1132// ==================== PathShape class definition ================================= //
1133function PathShape(){
1134	Shape.call(this);
1135}
1136inherit(PathShape, Shape);
1137
1138/// Custom serializer for SVG-compatible path data
1139PathShape.prototype.serialize = function(){
1140	var alldat = Shape.prototype.serialize.call(this);
1141	var src = "";
1142	for (var i=0; i<this.points.length; i++){
1143		var pt = this.points[i];
1144		if(i !== 0 && ("cx" in pt && "cy" in pt || "dx" in pt && "dy" in pt)){
1145			src += "C"; // Bezier curve command. 'S' command is not yet supported.
1146			// Prepend control points if this vertex has them.
1147			if("cx" in pt && "cy" in pt)
1148				src += pt.cx+","+pt.cy+" ";
1149			else
1150				src += this.points[i-1].x+","+this.points[i-1].y+" ";
1151			if("dx" in pt && "dy" in pt)
1152				src += pt.dx+","+pt.dy+" ";
1153			else
1154				src += pt.x+","+pt.y+" ";
1155		}
1156		else if(i === 0)
1157			src += "M"; // Moveto command
1158		else
1159			src += "L"; // Lineto command
1160		src += pt.x+","+pt.y;
1161	}
1162	alldat.d = src;
1163	// Set point data source to src and forget about old field
1164	delete alldat.points;
1165	if("arrow" in this)
1166		alldat.arrow = set2seq(this.arrow);
1167	return alldat;
1168}
1169
1170/// Custom deserializer for SVG-compatible path data
1171PathShape.prototype.deserialize = function(obj){
1172	Shape.prototype.deserialize.call(this, obj);
1173	if("arrow" in obj)
1174		this.arrow = seq2set(obj.arrow);
1175	if(!("d" in obj))
1176		return;
1177	var arr = [];
1178	var src = obj.d;
1179	var pt2 = [];
1180	var last = 0;
1181
1182	function process(){
1183		if(k <= last)
1184			return;
1185		var ssrc = src.slice(last+1, k);
1186		pt2 = ssrc.split(/[, \t]/);
1187		var pt = {};
1188		if(6 <= pt2.length){
1189			pt.cx = parseFloat(pt2[0]);
1190			pt.cy = parseFloat(pt2[1]);
1191		}
1192		if(4 <= pt2.length){
1193			pt.dx = parseFloat(pt2[pt2.length-4]);
1194			pt.dy = parseFloat(pt2[pt2.length-3]);
1195		}
1196		pt.x = parseFloat(pt2[pt2.length-2]);
1197		pt.y = parseFloat(pt2[pt2.length-1]);
1198		arr.push(pt);
1199		last = k;
1200	}
1201
1202	for(var k = 0; k < src.length; k++){
1203		if("MCLS".indexOf(src.charAt(k)) >= 0)
1204			process();
1205	}
1206	process();
1207
1208	this.points = arr;
1209};
1210// ==================== PathShape class definition end ================================= //
1211
1212
1213function serialize(dobjs){
1214	var ret = [metaObj];
1215	for(var i = 0; i < dobjs.length; i++)
1216		ret.push(dobjs[i].serialize());
1217	return ret;
1218}
1219
1220function deserialize(dat){
1221	// Reset the metaObj before deserialization
1222	metaObj = cloneObject(defaultMetaObj);
1223	var ret = [];
1224	for (var i=0; i<dat.length; i++) {
1225		var obj = dat[i];
1226		if(obj.type === 'meta'){
1227			metaObj = obj;
1228			continue;
1229		}
1230		if(!(obj.type in toolmap))
1231			continue;
1232		var robj = new toolmap[obj.type].objctor();
1233		robj.tool = obj.type;
1234		robj.deserialize(obj);
1235		ret.push(robj);
1236	}
1237	return ret;
1238}
1239
1240this.loadData = function(value){
1241	try{
1242		dobjs = deserialize(jsyaml.safeLoad(value));
1243		selectobj = []; // Clear the selection explicitly
1244		resizeCanvas();
1245		draw();
1246		redraw(dobjs);
1247	} catch(e){
1248		console.log(e);
1249	}
1250}
1251
1252this.saveAsImage = function(img){
1253	var reset = editmode;
1254	if(editmode){
1255		editmode = false;
1256		resizeCanvas();
1257		redraw(dobjs);
1258	}
1259	img.src = canvas.toDataURL();
1260	if(reset){
1261		editmode = true;
1262		resizeCanvas();
1263		draw();
1264		redraw(dobjs);
1265	}
1266}
1267
1268/// @brief Loads a data from local storage of the browser
1269/// @param name The name of the sketch in the local storage to load.
1270this.loadLocal = function(name){
1271	try{
1272		var origData = localStorage.getItem("canvasDrawData");
1273		if(origData === null)
1274			return;
1275		var selData = jsyaml.safeLoad(origData);
1276		dobjs = deserialize(jsyaml.safeLoad(selData[name]));
1277		selectobj = []; // Clear the selection explicitly
1278		updateDrawData();
1279		redraw(dobjs);
1280	} catch(e){
1281		debug(e);
1282	}
1283}
1284
1285// Downloads the file list from the server.
1286function downloadList(){
1287	// Asynchronous request for getting figure data in the server.
1288	var xmlHttp = createXMLHttpRequest();
1289	if(xmlHttp){
1290		// The event handler is assigned here because xmlHttp is a free variable
1291		// implicitly passed to the anonymous function without polluting the
1292		// global namespace.
1293		xmlHttp.onreadystatechange = function(){
1294			if(xmlHttp.readyState !== 4 || xmlHttp.status !== 200)
1295				return;
1296			try{
1297				var selData = xmlHttp.responseText;
1298				if(!selData)
1299					return;
1300				if(self.onUpdateServerList)
1301					self.onUpdateServerList(selData.split("\n"));
1302			}
1303			catch(e){
1304				console.log(e);
1305			}
1306		};
1307		xmlHttp.open("GET", "list.php", true);
1308		xmlHttp.send();
1309	}
1310}
1311
1312this.listServer = downloadList;
1313
1314this.requestServerFile = function(item, hash){
1315	// Asynchronous request for getting figure data in the server.
1316	var xmlHttp = createXMLHttpRequest();
1317	if(xmlHttp){
1318		var request;
1319		// The event handler is assigned here because xmlHttp is a free variable
1320		// implicitly passed to the anonymous function without polluting the
1321		// global namespace.
1322		xmlHttp.onreadystatechange = function(){
1323			if(xmlHttp.readyState !== 4 || xmlHttp.status !== 200)
1324				return;
1325			try{
1326				var selData = xmlHttp.responseText;
1327				if(!selData)
1328					return;
1329				if(hash){
1330					var firstLine = selData.split("\n", 1)[0];
1331					if(firstLine !== "succeeded")
1332						throw "Failed to obtain revision " + selData;
1333					selData = selData.substr(selData.indexOf("\n")+1);
1334				}
1335				dobjs = deserialize(jsyaml.safeLoad(selData));
1336				selectobj = [];
1337				updateDrawData();
1338				resizeCanvas();
1339				draw();
1340				redraw(dobjs);
1341			}
1342			catch(e){
1343				debug(e);
1344			}
1345		};
1346		if(!hash)
1347			request = datadir + "/" + encodeURI(item);
1348		else
1349			request = "history.php?fname=" + encodeURI(item) + "&hash=" + encodeURI(hash);
1350		xmlHttp.open("GET", request, true);
1351		xmlHttp.send();
1352	}
1353}
1354
1355this.requestServerFileHistory = function(item, responseCallback){
1356	var historyQuery = createXMLHttpRequest();
1357	if(historyQuery){
1358		historyQuery.onreadystatechange = function(){
1359			if(historyQuery.readyState !== 4 || historyQuery.status !== 200)
1360				return;
1361			try{
1362				var res = historyQuery.responseText;
1363				if(!res)
1364					return;
1365				var historyData = res.split("\n");
1366				if(historyData[0] !== "succeeded")
1367					return;
1368				historyData = historyData.splice(1);
1369				responseCallback(historyData);
1370			}
1371			catch(e){
1372				console.log(e);
1373			}
1374		};
1375		historyQuery.open("GET", "history.php?fname=" + encodeURI(item), true);
1376		historyQuery.send();
1377	}
1378}
1379
1380// clear canvas
1381function clearCanvas() {
1382	if(editmode){
1383		// Fill outside of valid figure area defined by metaObj.size with gray color.
1384		ctx.fillStyle = '#7f7f7f';
1385		ctx.fillRect(x1,y1, w1, h1);
1386	}
1387	ctx.fillStyle = white;
1388	ctx.fillRect(x1, y1, Math.min(w1, metaObj.size[0]), Math.min(h1, metaObj.size[1]));
1389}
1390
1391// Sets the size of the canvas
1392function setSize(sx, sy){
1393	metaObj.size[0] = sx;
1394	metaObj.size[1] = sy;
1395	updateDrawData();
1396	redraw(dobjs);
1397}
1398
1399// check all menu
1400function checkMenu(x, y) {
1401	var no = choiceMenu(x, y);
1402	if (no >= 0) return no;
1403	no = choiceCBox(x, y);
1404	if (no > 0) return no;
1405	no = choiceHBox(x, y);
1406	if (no > 0) return no;
1407
1408	if (x > w0 || y > h0) no = -1;
1409	return no;
1410}
1411
1412function choiceMenu(x, y) {
1413	// menu
1414	if (y < my0 || y > my0+mh0) return -1;
1415	for(var i=0;i<menus.length;i++) {
1416		if (x >= mx1+(mw1+10)*i && x <= mx1+mw0+(mw1+10)*i) return i;
1417	}
1418
1419	return -1;
1420}
1421
1422/// Selects a toolbar button. Returns true if the coordinates hit a button.
1423function choiceTBox(x, y) {
1424	// ToolBox
1425	if (x < mx0 || x > mx0+mw0) return false;
1426	for(var i=0;i<toolbar.length;i++) {
1427		var r = getTBox(i);
1428		if(hitRect(r, x, y)){
1429			cur_tool = toolbar[i];
1430			drawTBox();
1431			cur_shape = null;
1432			redraw(dobjs);
1433			return true;
1434		}
1435	}
1436
1437	return false;
1438}
1439
1440	// Color Parett
1441function choiceCBox(x, y) {
1442	if (y < my0 || y > my0+mh0) return -1;
1443	for(var i=0;i<5;i++) {
1444		if (x >= mx2+(mw2+10)*i && x <= mx2+mw2+(mw2+10)*i) return i+31;
1445	}
1446
1447	return -1;
1448}
1449	// Thin Box
1450function choiceHBox(x, y) {
1451	if (y < my0 || y > my0+mh0) return -1;
1452	for(var i=0;i<3;i++) {
1453		if (x >= mx3+(mw2+10)*i && x <= mx3+mw2+(mw2+10)*i) return i+41;
1454	}
1455
1456	return -1;
1457}
1458
1459/// @brief Save a sketch data to a local storage entry with name
1460/// @param name Name of the sketch which can be used in loadLocal() to restore
1461this.saveLocal = function(name){
1462	if(typeof(Storage) !== "undefined"){
1463		var str = localStorage.getItem("canvasDrawData");
1464		var origData = str === null ? {} : jsyaml.safeLoad(str);
1465		var newEntry = !(name in origData);
1466		origData[name] = jsyaml.safeDump(serialize(dobjs));
1467		localStorage.setItem("canvasDrawData", jsyaml.safeDump(origData));
1468		// If the named sketch didn't exist, fire up the event of local storage change.
1469		if(newEntry && ('onLocalChange' in this) && this.onLocalChange)
1470			this.onLocalChange();
1471		return true;
1472	}
1473	return false;
1474};
1475
1476/// @brief Returns list of sketches saved in local storage
1477/// @returns An array with sketch names in each element
1478this.listLocal = function() {
1479
1480	if(typeof(Storage) !== "undefined"){
1481		var str = localStorage.getItem("canvasDrawData");
1482		var origData = str === null ? {} : jsyaml.safeLoad(str);
1483
1484		// Enumerating keys array would be simpler if we could use Object.keys(),
1485		// but the method won't work for IE6, 7, 8.
1486		// If we'd repeat this procedure, we may be able to use a shim in:
1487		// https://github/com/es-shims/es5-shim/blob/v2.0.5/es5-shim.js
1488		var keys = [];
1489		for(var name in origData)
1490			keys.push(name);
1491
1492		return keys;
1493	}
1494
1495}
1496
1497/// Custom inheritance function that prevents the super class's constructor
1498/// from being called on inehritance.
1499/// Also assigns constructor property of the subclass properly.
1500/// Inheriting is closely related to cloneObject()
1501function inherit(subclass,base){
1502	// If the browser or ECMAScript supports Object.create, use it
1503	// (but don't remember to redirect constructor pointer to subclass)
1504	if(Object.create){
1505		subclass.prototype = Object.create(base.prototype);
1506	}
1507	else{
1508		var sub = function(){};
1509		sub.prototype = base.prototype;
1510		subclass.prototype = new sub;
1511	}
1512	subclass.prototype.constructor = subclass;
1513}
1514
1515/// Copy all properties in src to target, retaining target's existing properties
1516/// that have different names from src's.
1517function mixin(target, src){
1518	for(var k in src){
1519		target[k] = src[k];
1520	}
1521}
1522
1523// Create a deep clone of objects
1524function cloneObject(obj) {
1525	if (obj === null || typeof obj !== 'object') {
1526		return obj;
1527	}
1528
1529	// give temp the original obj's constructor
1530	// this 'new' is important in case obj is user-defined class which was created by
1531	// 'new ClassName()', no matter inherited or not.
1532	// I wonder why Array and Object (built-in classes) don't need new operator.
1533	var temp = new obj.constructor();
1534	for (var key in obj) {
1535		temp[key] = cloneObject(obj[key]);
1536	}
1537
1538	return temp;
1539}
1540
1541function updateDrawData(){
1542	try{
1543		var text = jsyaml.safeDump(serialize(dobjs), {flowLevel: 2});
1544		if(('onUpdateData' in self) && self.onUpdateData)
1545			self.onUpdateData(text);
1546	} catch(e){
1547		console.log(e);
1548	}
1549}
1550
1551// clear data
1552function ajaxclear() {
1553	dobjs = [];
1554	selectobj = [];
1555	updateDrawData();
1556	clearCanvas();
1557}
1558
1559// undo
1560function ajaxundo() {
1561	if(dhistory.length < 1)
1562		return;
1563	dobjs = dhistory[dhistory.length-1];
1564	dhistory.pop();
1565	selectobj = [];
1566	updateDrawData();
1567	redraw(dobjs);
1568}
1569
1570var createXMLHttpRequest = this.createXMLHttpRequest;
1571
1572/// @brief Posts a sketch data to the server
1573/// @param fname The file name of the added sketch.
1574/// @param target The target URL for posting.
1575/// @param requestDelete If true, it will post delete request instead of new data.
1576this.postData = function(fname, target, requestDelete){
1577	var data = jsyaml.safeDump(serialize(dobjs), {flowLevel: 2});
1578	// Asynchronous request for getting figure data in the server.
1579	var xmlHttp = createXMLHttpRequest();
1580	if(xmlHttp){
1581		// The event handler is assigned here because xmlHttp is a free variable
1582		// implicitly passed to the anonymous function without polluting the
1583		// global namespace.
1584		xmlHttp.onreadystatechange = function(){
1585			if(xmlHttp.readyState !== 4 || xmlHttp.status !== 200)
1586				return;
1587			debug(xmlHttp.responseText);
1588			downloadList();
1589		};
1590		xmlHttp.open("POST", target, true);
1591		xmlHttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
1592		var request = "fname=" + encodeURI(fname);
1593		if(requestDelete)
1594			request += "&action=delete";
1595		else
1596			request += "&drawdata=" + encodeURI(data);
1597		xmlHttp.send(request);
1598	}
1599};
1600
1601/// @brief Posts a request to server to pull from remote (respective to the server)
1602/// @param remoteName The remote name (defined in the server)
1603this.pull = function(remoteName){
1604	// Asynchronous request for pulling.
1605	var xmlHttp = createXMLHttpRequest();
1606	if(xmlHttp){
1607		// The event handler is assigned here because xmlHttp is a free variable
1608		// implicitly passed to the anonymous function without polluting the
1609		// global namespace.
1610		xmlHttp.onreadystatechange = function(){
1611			if(xmlHttp.readyState !== 4 || xmlHttp.status !== 200)
1612				return;
1613			debug(xmlHttp.responseText);
1614			downloadList();
1615		};
1616		xmlHttp.open("GET", "pull.php?remote=" + encodeURI(remoteName), true);
1617		xmlHttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
1618		xmlHttp.send();
1619	}
1620}
1621
1622/// @brief Posts a request to server to push to remote (respective to the server)
1623/// @param remoteName The remote name (defined in the server)
1624this.push = function(remoteName){
1625	// Asynchronous request for pulling.
1626	var xmlHttp = createXMLHttpRequest();
1627	if(xmlHttp){
1628		// The event handler is assigned here because xmlHttp is a free variable
1629		// implicitly passed to the anonymous function without polluting the
1630		// global namespace.
1631		xmlHttp.onreadystatechange = function(){
1632			if(xmlHttp.readyState !== 4 || xmlHttp.status !== 200)
1633				return;
1634			debug(xmlHttp.responseText);
1635			downloadList();
1636		};
1637		xmlHttp.open("GET", "push.php?remote=" + encodeURI(remoteName), true);
1638		xmlHttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
1639		xmlHttp.send();
1640	}
1641}
1642
1643//------------------------ debug ------------------------
1644function debug(msg) {
1645	if(options && options.debug)
1646		options.debug(msg);
1647}
1648
1649// ==================== Button class definition ================================= //
1650/// An abstract button class.
1651/// SVG or canvas graphics libraries like EaselJS would do this sort of thing
1652/// much better, but I don't feel like to port to it at once.
1653function Button(minx, miny, maxx, maxy, ondraw, onclick){
1654	this.minx = minx;
1655	this.miny = miny;
1656	this.maxx = maxx;
1657	this.maxy = maxy;
1658	this.ondraw = ondraw;
1659	this.onclick = onclick;
1660}
1661
1662// ==================== Menu class definition ================================= //
1663function MenuItem(text, onclick){
1664	this.text = i18n.t(text);
1665	this.onclick = onclick;
1666}
1667
1668// init --------------------------------------------------
1669//window.captureEvents(Event.click);
1670//onclick=mouseLeftClick;
1671var ctx;
1672var menus = [
1673	new MenuItem("Grid", function(){
1674		gridEnable = !gridEnable;
1675		redraw(dobjs);
1676	}),
1677	new MenuItem("Grid+", function(){
1678		if(gridSize < 32)
1679			gridSize *= 2;
1680		if(gridEnable)
1681			redraw(dobjs);
1682	}),
1683	new MenuItem("Grid-", function(){
1684		if(4 < gridSize)
1685			gridSize /= 2;
1686		if(gridEnable)
1687			redraw(dobjs);
1688	}),
1689	new MenuItem("Clear", function(){	// clear
1690		clearCanvas();
1691		ajaxclear();
1692	}),
1693	new MenuItem("Redraw", function(){redraw(dobjs);}),// redraw
1694	new MenuItem("Undo", function(){	// undo
1695		clearCanvas();
1696		ajaxundo();
1697	}),
1698	new MenuItem("Size", function(){
1699		// Show size input layer on top of the canvas because the canvas cannot have
1700		// a text input element.
1701		if(!sizeLayer){
1702			sizeLayer = document.createElement('div');
1703			var lay = sizeLayer;
1704			lay.id = 'bookingLayer';
1705			lay.style.position = 'absolute';
1706			lay.style.padding = '5px 5px 5px 5px';
1707			lay.style.borderStyle = 'solid';
1708			lay.style.borderColor = '#cf0000';
1709			lay.style.borderWidth = '2px';
1710			// Drop shadow to make it distinguishable from the figure contents.
1711			lay.style.boxShadow = '0px 0px 20px grey';
1712			lay.style.background = '#cfffcf';
1713			lay.innerHTML = i18n.t('Input image size in pixels') + ':<br>'
1714				+ 'x:<input id="sizeinputx" type="text">'
1715				+ 'y:<input id="sizeinputy" type="text">';
1716			var okbutton = document.createElement('input');
1717			okbutton.type = 'button';
1718			okbutton.value = 'OK';
1719			okbutton.onclick = function(s){
1720				lay.style.display = 'none';
1721				setSize(parseFloat(document.getElementById('sizeinputx').value),
1722					parseFloat(document.getElementById('sizeinputy').value));
1723			}
1724			var cancelbutton = document.createElement('input');
1725			cancelbutton.type = 'button';
1726			cancelbutton.value = 'Cancel';
1727			cancelbutton.onclick = function(s){
1728				lay.style.display = 'none';
1729			}
1730			lay.appendChild(document.createElement('br'));
1731			lay.appendChild(okbutton);
1732			lay.appendChild(cancelbutton);
1733			// Append as the body element's child because style.position = "absolute" would
1734			// screw up in deeply nested DOM tree (which may have a positioned ancestor).
1735			document.body.appendChild(lay);
1736		}
1737		else // Just show the created layer in the second invocation.
1738			sizeLayer.style.display = 'block';
1739		var canvasRect = canvas.getBoundingClientRect();
1740		// Cross-browser scroll position query
1741		var scrollX = document.documentElement.scrollLeft || document.body.scrollLeft;
1742		var scrollY = document.documentElement.scrollTop || document.body.scrollTop;
1743		// getBoundingClientRect() returns coordinates relative to view, which means we have to
1744		// add scroll position into them.
1745		sizeLayer.style.left = (canvasRect.left + scrollX + 150) + 'px';
1746		sizeLayer.style.top = (canvasRect.top + scrollY + 50) + 'px';
1747		document.getElementById('sizeinputx').value = metaObj.size[0];
1748		document.getElementById('sizeinputy').value = metaObj.size[1];
1749	}), // size
1750];
1751
1752var buttons = [];
1753
1754
1755/// @brief Converts a sequence (array) to a set (object)
1756///
1757/// We wish if we could use YAML's !!set type (which is described in
1758/// http://yaml.org/type/set.html ), but JavaScript doesn't natively
1759/// support set type and js-yaml library converts YAML's !!set type
1760/// into an object with property names as keys in the set.
1761/// The values associated with the keys are not meaningful and
1762/// null is assigned by the library.
1763/// If we serialize the object, it would be an !!map instead of a !!set.
1764/// For example, a !!set {head, tail} would be serialized as
1765/// "{head: null, tail: null}", which is far from beautiful.
1766///
1767/// So we had to pretend as if !!set is encoded as !!seq in the serialized
1768/// YAML document by converting JavaScript objects into arrays.
1769/// It would be "[head, tail]" in the case of the former example.
1770///
1771/// The seq2set function converts sequence from YAML parser into
1772/// a set-like object, while set2seq does the opposite.
1773///
1774/// Note that these functions work only for sets of strings.
1775///
1776/// @sa set2seq
1777function seq2set(seq){
1778	var ret = {};
1779	for(var i = 0; i < seq.length; i++){
1780		if(seq[i] === "")
1781			continue;
1782		ret[seq[i]] = null;
1783	}
1784	return ret;
1785}
1786
1787/// @brief Converts a set (object) to a sequence (array)
1788/// @sa seq2set
1789function set2seq(set){
1790	var ret = [];
1791	for(var i in set){
1792		if(i === "")
1793			continue;
1794		ret.push(i);
1795	}
1796	return ret;
1797}
1798
1799// ==================== Tool class definition ================================= //
1800
1801// A mapping of tool names and tool objects. Automatically updated in the Tool class's constructor.
1802var toolmap = {};
1803
1804/// @brief A class that represents a tool in the toolbar.
1805/// @param name Name of the tool, used in serialized text
1806/// @param points Number of points which are used to describe points
1807/// @param params A table of initialization parameters:
1808///         objctor: The constructor function that is used to create Shape, stands for OBJect ConsTructOR
1809///         drawTool: A function(x, y) to draw icon on the toolbar.
1810///         setColor: A function(color) that is called before drawing.
1811///         setWidth: A function(width) that is called before drawing.
1812///         draw: A function(mode,str) to actually draw a shape.
1813function Tool(name, points, params){
1814	this.name = name;
1815	this.points = points || 1;
1816	this.objctor = params && params.objctor || Shape;
1817	this.drawTool = params && params.drawTool;
1818	mixin(this, params);
1819	// The path tool shares the same shape type identifier among multiple
1820	// tools, so duplicate assignments should be avoided.
1821	if(!(name in toolmap))
1822		toolmap[name] = this;
1823}
1824
1825Tool.prototype.setColor = function(color){
1826	ctx.strokeStyle = color;
1827};
1828
1829function setColorFill(color){
1830	ctx.fillStyle = color;
1831}
1832
1833Tool.prototype.setWidth = function(width){
1834	ctx.lineWidth = width;
1835};
1836
1837function nop(){}
1838
1839Tool.prototype.draw = nop;
1840
1841/// Append a point to the shape by mouse click
1842Tool.prototype.appendPoint = function(x, y) {
1843	function addPoint(x, y){
1844		if (cur_shape.points.length === cur_tool.points){
1845			dhistory.push(cloneObject(dobjs));
1846			dobjs.push(cur_shape);
1847			updateDrawData();
1848			cur_shape = null;
1849			redraw(dobjs);
1850			return true;
1851		}
1852		else{
1853			cur_shape.points.push(canvasToSrc(constrainCoord({x:x, y:y})));
1854			return false;
1855		}
1856	}
1857
1858	if(!cur_shape){
1859		var obj = new cur_tool.objctor();
1860		obj.tool = cur_tool.name;
1861		obj.color = cur_col;
1862		obj.width = cur_thin;
1863		cur_shape = obj;
1864		// Add two points for previewing shapes that consist of multiple points,
1865		// but single-point shapes will finish with just single click.
1866		if(!addPoint(x, y))
1867			addPoint(x, y);
1868	}
1869	else{
1870		addPoint(x, y);
1871	}
1872}
1873
1874Tool.prototype.mouseDown = function(e){
1875	// Do nothing by default
1876}
1877
1878Tool.prototype.mouseMove = function(e){
1879	if(cur_shape && 0 < cur_shape.points.length){
1880		var clrect = canvas.getBoundingClientRect();
1881		var mx = (gridEnable ? Math.round(e.clientX / gridSize) * gridSize : e.clientX) - clrect.left;
1882		var my = (gridEnable ? Math.round(e.clientY / gridSize) * gridSize : e.clientY) - clrect.top;
1883		// Live preview of the shape being added.
1884		var coord = {x: mx, y: my};
1885		cur_shape.points[cur_shape.points.length-1] = canvasToSrc(constrainCoord(coord));
1886		redraw(dobjs);
1887	}
1888}
1889
1890Tool.prototype.mouseUp = function(e){
1891	moving = false;
1892	sizing = null;
1893	pointMoving = null;
1894	var needsRedraw = boxselecting;
1895	boxselecting = false;
1896	if(needsRedraw) // Redraw to clear selection box
1897		redraw(dobjs);
1898}
1899
1900Tool.prototype.keyDown = function(e){
1901	var code = e.keyCode || e.which;
1902	if(code === 46){ // Delete key
1903		dhistory.push(cloneObject(dobjs)); // Push undo buffer
1904		// Delete all selected objects
1905		deleteShapes(selectobj);
1906		updateDrawData();
1907		redraw(dobjs);
1908	}
1909}
1910
1911// All other tools than the pathedit, draw scaling handles and
1912// bounding boxes around selected objects.
1913// Aside from the select tool, the user cannot grab the scaling handles,
1914// but they're useful to visually appeal that the object is selected.
1915Tool.prototype.selectDraw = function(shape){
1916	var bounds = objBounds(shape);
1917
1918	ctx.beginPath();
1919	ctx.lineWidth = 1;
1920	ctx.strokeStyle = '#000';
1921	ctx.setLineDash([5]);
1922	ctx.rect(bounds.minx, bounds.miny, bounds.maxx-bounds.minx, bounds.maxy-bounds.miny);
1923	ctx.stroke();
1924	ctx.setLineDash([]);
1925
1926	ctx.beginPath();
1927	ctx.strokeStyle = '#000';
1928	for(var i = 0; i < 8; i++){
1929		var r = getHandleRect(bounds, i);
1930		ctx.fillStyle = sizing === shape && i === sizedir ? '#7fff7f' : '#ffff7f';
1931		ctx.fillRect(r.minx, r.miny, r.maxx - r.minx, r.maxy-r.miny);
1932		ctx.rect(r.minx, r.miny, r.maxx - r.minx, r.maxy-r.miny);
1933	}
1934	ctx.stroke();
1935}
1936
1937// ==================== Tool class definition end ============================= //
1938
1939
1940// List of tools in the toolbar.
1941var toolbar = [
1942	new Tool("select", 1, {drawTool: function(x, y){
1943			ctx.beginPath();
1944			ctx.moveTo(x, y-5);
1945			ctx.lineTo(x, y+10);
1946			ctx.lineTo(x+4, y+7);
1947			ctx.lineTo(x+6, y+11);
1948			ctx.lineTo(x+8, y+9);
1949			ctx.lineTo(x+6, y+5);
1950			ctx.lineTo(x+10, y+3);
1951			ctx.closePath();
1952			ctx.stroke();
1953			ctx.strokeText('1', x+45, y+10);
1954		},
1955		mouseDown: selectMouseDown,
1956		mouseMove: selectMouseMove,
1957		mouseUp: function(e){
1958			if(0 < selectobj.length && (moving || sizing))
1959				updateDrawData();
1960			Tool.prototype.mouseUp.call(this, e);
1961		}
1962	}),
1963	new Tool("pathedit", 1, {
1964		// The path editing tool. This is especially useful when editing
1965		// existing curves.  The icon is identical to that of select tool,
1966		// except that the mouse cursor graphic is filled.
1967		drawTool: function(x, y){
1968			ctx.fillStyle = 'rgb(250, 250, 250)';
1969			ctx.beginPath();
1970			ctx.moveTo(x, y-5);
1971			ctx.lineTo(x, y+10);
1972			ctx.lineTo(x+4, y+7);
1973			ctx.lineTo(x+6, y+11);
1974			ctx.lineTo(x+8, y+9);
1975			ctx.lineTo(x+6, y+5);
1976			ctx.lineTo(x+10, y+3);
1977			ctx.closePath();
1978			ctx.fill();
1979			ctx.strokeText('1', x+45, y+10);
1980		},
1981		mouseDown: pathEditMouseDown,
1982		mouseMove: pathEditMouseMove,
1983		mouseUp: function(e){
1984			if(0 < selectobj.length && pointMoving)
1985				updateDrawData();
1986			Tool.prototype.mouseUp.call(this, e);
1987		},
1988		keyDown: function(e){
1989			var code = e.keyCode || e.which;
1990			if(code === 46){ // Delete key
1991				// Pathedit tool's delete key just delete single vertex in a path
1992				if(this.selectPointShape && this.selectPointShape.tool === "path" && 0 <= this.selectPointIdx){
1993					dhistory.push(cloneObject(dobjs)); // Push undo buffer
1994					if(this.selectPointShape.points.length <= 2){
1995						deleteShapes([this.selectPointShape]);
1996						this.selectPointShape = null;
1997					}
1998					else
1999						this.selectPointShape.points.splice(this.selectPointIdx, 1);
2000					updateDrawData();
2001					redraw(dobjs);
2002				}
2003			}
2004		},
2005		selectDraw: pathEditSelectDraw,
2006		selectPointShape: null, // Default value for the variable
2007		selectPointIdx: -1, // Default value for the variable
2008	}),
2009	new Tool("line", 2, {
2010		drawTool: function(x, y){
2011			ctx.beginPath();
2012			ctx.moveTo(x, y);
2013			ctx.lineTo(x+40, y+10);
2014			ctx.stroke();
2015			ctx.strokeText('2', x+45, y+10);
2016		},
2017		draw: function(obj){
2018			var arr = obj.points;
2019			ctx.beginPath();
2020			ctx.moveTo(arr[0].x, arr[0].y);
2021			ctx.lineTo(arr[1].x, arr[1].y);
2022			ctx.stroke();
2023			ctx.lineWidth = 1;
2024		},
2025	}),
2026	new Tool("arrow", 2, {
2027		drawTool: function(x, y){
2028			ctx.beginPath();
2029			l_arrow(ctx, [{x:x, y:y+5}, {x:x+40, y:y+5}]);
2030			ctx.strokeText('2', x+45, y+10);
2031		},
2032		draw: function(obj){
2033			var arr = obj.points;
2034			ctx.beginPath();
2035			l_arrow(ctx, arr);
2036			ctx.lineWidth = 1;
2037		}
2038	}),
2039	new Tool("barrow", 2, {
2040		drawTool: function(x, y){
2041			ctx.beginPath();
2042			l_tarrow(ctx, [{x:x, y:y+5}, {x:x+40, y:y+5}]);
2043			ctx.strokeText('2', x+45, y+10);
2044		},
2045		draw: function(obj){
2046			var arr = obj.points;
2047			ctx.beginPath();
2048			l_tarrow(ctx, arr);
2049			ctx.lineWidth = 1;
2050		}
2051	}),
2052	new Tool("darrow", 2, {
2053		drawTool: function(x, y){
2054			ctx.beginPath();
2055			ctx.moveTo(x, y+3);
2056			ctx.lineTo(x+39, y+3);
2057			ctx.moveTo(x, y+7);
2058			ctx.lineTo(x+39, y+7);
2059			ctx.moveTo(x+35, y);
2060			ctx.lineTo(x+40, y+5);
2061			ctx.lineTo(x+35, y+10);
2062			ctx.stroke();
2063			ctx.strokeText('2', x+45, y+10);
2064		},
2065		draw: function(obj){
2066			var arr = obj.points;
2067			ctx.beginPath();
2068			l_darrow(ctx, arr);
2069			ctx.lineWidth = 1;
2070		}
2071	}),
2072	new Tool("arc", 3, {
2073		isArc: true,
2074		drawTool: function(x, y){
2075			ctx.beginPath();
2076			ctx.moveTo(x, y);
2077			ctx.quadraticCurveTo(x+20, y+20, x+40, y);
2078			ctx.stroke();
2079			ctx.strokeText('3', x+45, y+10);
2080		},
2081		draw: function(obj){
2082			var arr = obj.points;
2083			ctx.beginPath();
2084			ctx.moveTo(arr[0].x, arr[0].y);
2085			ctx.quadraticCurveTo(arr[1].x, arr[1].y, arr[2].x, arr[2].y);
2086			ctx.stroke();
2087			ctx.lineWidth = 1;
2088		}
2089	}),
2090	new Tool("arcarrow", 3, {
2091		isArc: true,
2092		drawTool: function(x, y){
2093			ctx.beginPath();
2094			ctx.moveTo(x, y);
2095			ctx.quadraticCurveTo(x+20, y+20, x+40, y);
2096			l_hige(ctx, [{x:x+20, y:y+20}, {x:x+40, y:y}]);
2097			ctx.strokeText('3', x+45, y+10);
2098		},
2099		draw: function(obj){
2100			var arr = obj.points;
2101			ctx.beginPath();
2102			ctx.moveTo(arr[0].x, arr[0].y);
2103			ctx.quadraticCurveTo(arr[1].x, arr[1].y, arr[2].x, arr[2].y);
2104			l_hige(ctx, [arr[1], arr[2]]);
2105			ctx.lineWidth = 1;
2106		}
2107	}),
2108	new Tool("arcbarrow", 3, {
2109		isArc: true,
2110		drawTool: function(x, y){
2111			ctx.beginPath();
2112			ctx.moveTo(x, y);
2113			ctx.quadraticCurveTo(x+20, y+20, x+40, y);
2114			var a = [{x:x+20, y:y+20}, {x:x+40, y:y}];
2115			l_hige(ctx, a);
2116			//a[0] = {x:x+10, y:y+10};
2117			a[1] = {x:x, y:y};
2118			l_hige(ctx, a);
2119			ctx.strokeText('3', x+45, y+10);
2120		},
2121		draw: function(obj){
2122			var arr = obj.points;
2123			ctx.beginPath();
2124			ctx.moveTo(arr[0].x, arr[0].y);
2125			ctx.quadraticCurveTo(arr[1].x, arr[1].y, arr[2].x, arr[2].y);
2126			var a = new Array(2);
2127			a[0] = arr[1];
2128			a[1] = arr[2];
2129			l_hige(ctx, a);
2130			a[1] = arr[0];
2131			l_hige(ctx, a);
2132			ctx.lineWidth = 1;
2133		}
2134	}),
2135	new Tool("rect", 2, {
2136		drawTool: function(x, y){
2137			ctx.beginPath();
2138			ctx.rect(x, y, 40, 10);
2139			ctx.stroke();
2140			ctx.strokeText('2', x+45, y+10);
2141		},
2142		draw: function(obj){
2143			var arr = obj.points;
2144			ctx.beginPath();
2145			ctx.rect(arr[0].x, arr[0].y, arr[1].x-arr[0].x, arr[1].y-arr[0].y);
2146			ctx.stroke();
2147			ctx.lineWidth = 1;
2148		}
2149	}),
2150	new Tool("ellipse", 2, {
2151		drawTool: function(x, y){
2152			ctx.beginPath();
2153			ctx.scale(1.0, 0.5);		// vertically half
2154			ctx.arc(x+20, (y+5)*2, 20, 0, 2 * Math.PI, false);
2155			ctx.stroke();
2156			ctx.scale(1.0, 2.0);
2157			ctx.strokeText('2', x+45, y+10);
2158		},
2159		draw: function(obj){
2160			var arr = obj.points;
2161			ctx.beginPath();
2162			l_elipse(ctx, arr);
2163			ctx.lineWidth = 1;
2164		}
2165	}),
2166	new Tool("rectfill", 2, {
2167		drawTool: function(x, y){
2168			ctx.beginPath();
2169			ctx.fillStyle = 'rgb(250, 250, 250)';
2170			ctx.fillRect(x, y, 40, 10);
2171			ctx.strokeText('2', x+45, y+10);
2172		},
2173		setColor: setColorFill,
2174		setWidth: nop,
2175		draw: function(obj){
2176			var arr = obj.points;
2177			ctx.beginPath();
2178			ctx.fillRect(arr[0].x, arr[0].y, arr[1].x-arr[0].x, arr[1].y-arr[0].y);
2179		}
2180	}),
2181	new Tool("ellipsefill", 2, {drawTool: function(x, y){
2182			ctx.beginPath();
2183			ctx.fillStyle = 'rgb(250, 250, 250)';
2184			ctx.scale(1.0, 0.5);		// vertically half
2185			ctx.arc(x+20, (y+5)*2, 20, 0, 2 * Math.PI, false);
2186			ctx.fill();
2187			ctx.scale(1.0, 2.0);
2188			ctx.strokeText('2', x+45, y+10);
2189		},
2190		setColor: setColorFill,
2191		setWidth: nop,
2192		draw: function(obj){
2193			var arr = obj.points;
2194			ctx.beginPath();
2195			l_elipsef(ctx, arr);
2196			ctx.lineWidth = 1;
2197		}
2198	}),
2199	new Tool("star", 1, {objctor: PointShape,
2200		drawTool: function(x, y){
2201			ctx.beginPath();
2202			ctx.moveTo(x+8, y-3);
2203			ctx.lineTo(x+14, y+13);
2204			ctx.lineTo(x, y+2);
2205			ctx.lineTo(x+16, y+2);
2206			ctx.lineTo(x+2, y+13);
2207			ctx.closePath();
2208			ctx.stroke();
2209			ctx.strokeText('1', x+45, y+10);
2210		},
2211		draw: function(obj){
2212			var arr = obj.points;
2213			ctx.beginPath();
2214			//ctx.lineWidth = cur_thin - 40;
2215			l_star(ctx, arr);
2216			ctx.lineWidth = 1;
2217		}
2218	}),
2219	new Tool("check", 1, {objctor: PointShape,
2220		drawTool: function(x, y){
2221			ctx.beginPath();
2222			ctx.moveTo(x, y);
2223			ctx.lineTo(x+5, y+7);
2224			ctx.lineTo(x+20, y);
2225			ctx.stroke();
2226			ctx.strokeText('1', x+45, y+10);
2227		},
2228		setWidth: nop,
2229		draw: function(obj){
2230			var arr = obj.points;
2231			ctx.beginPath();
2232			l_check(ctx, arr);
2233		}
2234	}),
2235	new Tool("text", 1, {objctor: TextShape,
2236		drawTool: function(x, y){
2237			ctx.beginPath();
2238			ctx.strokeText(i18n.t('Text'), x+3, y+10);
2239			ctx.strokeText('1', x+45, y+10);
2240		},
2241		setColor: setColorFill,
2242		draw: function(obj){
2243			var str = obj.text;
2244			if (null == str) {		// cancel
2245//					idx = 0;
2246				return;
2247			}
2248			ctx.beginPath();
2249			if (1 == obj.width) setFont(14);
2250			else if (2 == obj.width) setFont(16);
2251			else setFont(20);
2252			ctx.fillText(str, obj.points[0].x, obj.points[0].y);
2253
2254			// Draw blue underline for linked text
2255			if(obj.link){
2256				ctx.strokeStyle = '#0000ff';
2257				ctx.beginPath();
2258				ctx.moveTo(obj.points[0].x, obj.points[0].y + 4);
2259				ctx.lineTo(obj.points[0].x + ctx.measureText(str).width, obj.points[0].y + 4);
2260				ctx.stroke();
2261			}
2262
2263			ctx.font = setFont(14);
2264		},
2265		appendPoint: function(x, y){
2266			var textTool = this;
2267			// Show size input layer on top of the canvas because the canvas cannot have
2268			// a text input element.
2269			if(!textLayer){
2270				textLayer = document.createElement('div');
2271				// Create field for remembering position of text being inserted.
2272				// Free variables won't work well.
2273				textLayer.canvasPos = {x:0, y:0};
2274				var lay = textLayer;
2275				lay.id = 'textLayer';
2276				lay.style.position = 'absolute';
2277				lay.style.padding = '5px 5px 5px 5px';
2278				lay.style.borderStyle = 'solid';
2279				lay.style.borderColor = '#cf0000';
2280				lay.style.borderWidth = '2px';
2281				// Drop shadow to make it distinguishable from the figure contents.
2282				lay.style.boxShadow = '0px 0px 20px grey';
2283				lay.style.background = '#cfffcf';
2284
2285				// Create and assign the input element to a field of the textLayer object
2286				// to keep track of the input element after this function is exited.
2287				lay.textInput = document.createElement('input');
2288				lay.textInput.id = "textinput";
2289				lay.textInput.type = "text";
2290				lay.textInput.style.width = "30em";
2291				lay.textInput.onkeyup = function(e){
2292					// Convert enter key event to OK button click
2293					if(e.keyCode === 13)
2294						okbutton.onclick();
2295				};
2296				lay.appendChild(lay.textInput);
2297
2298				// Add a text area for link
2299				lay.appendChild(document.createElement('br'));
2300				lay.linkInput = document.createElement('input');
2301				lay.linkInput.id = "linkinput";
2302				lay.linkInput.type = "text";
2303				lay.linkInput.onkeyup = lay.textInput.onkeyup;
2304				var linkdiv = document.createElement('div');
2305				linkdiv.innerHTML = "Link:";
2306				linkdiv.appendChild(lay.linkInput);
2307				lay.appendChild(linkdiv);
2308
2309				var okbutton = document.createElement('input');
2310				okbutton.type = 'button';
2311				okbutton.value = 'OK';
2312				okbutton.onclick = function(e){
2313					lay.style.display = 'none';
2314					// Ignore blank text
2315					if(lay.textInput.value == '')
2316						return;
2317					dhistory.push(cloneObject(dobjs));
2318					// If a shape is clicked, alter its value instead of adding a new one.
2319					if(lay.dobj){
2320						lay.dobj.text = lay.textInput.value;
2321						lay.dobj.link = lay.linkInput.value;
2322					}
2323					else{
2324						var obj = new textTool.objctor();
2325						obj.tool = cur_tool.name;
2326						obj.color = cur_col;
2327						obj.width = cur_thin;
2328						obj.points.push({x: lay.canvasPos.x, y: lay.canvasPos.y});
2329						obj.text = lay.textInput.value;
2330						obj.link = lay.linkInput.value;
2331						dobjs.push(obj);
2332					}
2333					updateDrawData();
2334					redraw(dobjs);
2335				}
2336				var cancelbutton = document.createElement('input');
2337				cancelbutton.type = 'button';
2338				cancelbutton.value = 'Cancel';
2339				cancelbutton.onclick = function(s){
2340					lay.style.display = 'none';
2341				}
2342//				lay.appendChild(document.createElement('br')); // It seems to add an extra line break
2343				lay.appendChild(okbutton);
2344				lay.appendChild(cancelbutton);
2345				// Append as the body element's child because style.position = "absolute" would
2346				// screw up in deeply nested DOM tree (which may have a positioned ancestor).
2347				document.body.appendChild(lay);
2348			}
2349			else
2350				textLayer.style.display = 'block';
2351			var coord = canvasToSrc(constrainCoord({x:x, y:y}));
2352			textLayer.canvasPos.x = coord.x;
2353			textLayer.canvasPos.y = coord.y;
2354
2355			// Find if any TextShape is under the mouse cursor.
2356			textLayer.dobj = null;
2357			textLayer.textInput.value = "";
2358			for (var i = 0; i < dobjs.length; i++) {
2359				if(dobjs[i] instanceof TextShape && hitRect(objBounds(dobjs[i], true), coord.x, coord.y)){
2360					textLayer.dobj = dobjs[i]; // Remember the shape being clicked on.
2361					textLayer.textInput.value = dobjs[i].text; // Initialized the input buffer with the previous content.
2362					textLayer.linkInput.value = dobjs[i].link;
2363					break;
2364				}
2365			}
2366
2367			// Adjust the text area's width so that two of them uses the div element's space efficiently.
2368			var textInputRect = textLayer.textInput.getBoundingClientRect();
2369			var linkInputRect = textLayer.linkInput.getBoundingClientRect();
2370			textLayer.linkInput.style.width = textInputRect.width - (linkInputRect.left - textInputRect.left) + "px";
2371
2372			var canvasRect = canvas.getBoundingClientRect();
2373			// Cross-browser scroll position query
2374			var scrollX = document.documentElement.scrollLeft || document.body.scrollLeft;
2375			var scrollY = document.documentElement.scrollTop || document.body.scrollTop;
2376			// getBoundingClientRect() returns coordinates relative to view, which means we have to
2377			// add scroll position into them.
2378			textLayer.style.left = (canvasRect.left + scrollX + offset.x + coord.x) + 'px';
2379			textLayer.style.top = (canvasRect.top + scrollY + offset.y + coord.y) + 'px';
2380			// focus() should be called after textLayer is positioned, otherwise the page may
2381			// unexpectedly scroll to somewhere.
2382			textLayer.textInput.focus();
2383
2384			// Reset the point buffer
2385//				idx = 0;
2386			return true; // Skip registration
2387		}
2388	})
2389];
2390
2391var toolbars = [toolbar,
2392	[
2393		toolmap.select,
2394		toolmap.pathedit,
2395		new Tool("delete", 1, {
2396			drawTool: function(x, y){
2397				ctx.beginPath();
2398				ctx.moveTo(x, y);
2399				ctx.lineTo(x+10, y+10);
2400				ctx.moveTo(x, y+10);
2401				ctx.lineTo(x+10, y);
2402				ctx.stroke();
2403				ctx.strokeText('1', x+45, y+10);
2404			}
2405		}),
2406		new Tool("done", 1, {objctor: PointShape,
2407			drawTool: function(x, y){
2408				ctx.beginPath();
2409				ctx.strokeText(i18n.t('Done'), x+3, y+10);
2410				ctx.beginPath();
2411				ctx.arc(x+9, y+5, 8, 0, 6.28, false);
2412				ctx.stroke();
2413				ctx.strokeText('1', x+45, y+10);
2414			},
2415			setWidth: nop,
2416			draw: function(obj){
2417				var arr = obj.points;
2418				ctx.beginPath();
2419				l_complete(ctx, arr);
2420			}
2421		}),
2422		new Tool("path", 2, {
2423			objctor: PathShape,
2424			drawTool: function(x, y){
2425				ctx.beginPath();
2426				ctx.moveTo(x, y);
2427				ctx.bezierCurveTo(x+10, y+20, x+20, y-10, x+30, y+10);
2428				ctx.stroke();
2429				ctx.strokeText('n', x+45, y+10);
2430			},
2431			draw: function(obj){
2432				var arr = obj.points;
2433				if(arr.length < 2)
2434					return;
2435				ctx.beginPath();
2436				ctx.moveTo(arr[0].x, arr[0].y);
2437				for(var i = 1; i < arr.length; i++){
2438					if("cx" in arr[i] && "cy" in arr[i]){
2439						if("dx" in arr[i] && "dy" in arr[i])
2440							ctx.bezierCurveTo(arr[i].cx, arr[i].cy, arr[i].dx, arr[i].dy, arr[i].x, arr[i].y);
2441						else
2442							ctx.bezierCurveTo(arr[i].cx, arr[i].cy, arr[i].x, arr[i].y, arr[i].x, arr[i].y);
2443					}
2444					else if("dx" in arr[i] && "dy" in arr[i])
2445						ctx.bezierCurveTo(arr[i-1].x, arr[i-1].y, arr[i].dx, arr[i].dy, arr[i].x, arr[i].y);
2446					else
2447						ctx.lineTo(arr[i].x, arr[i].y);
2448				}
2449				ctx.stroke();
2450				if("arrow" in obj && "head" in obj.arrow && 1 < arr.length){
2451					var first = arr[0], first2 = arr[1], a = [];
2452					if("cx" in first2 && "cy" in first2 && (first2.cx !== first.x || first2.cy !== first.y))
2453						a[0] = {x: first2.cx, y: first2.cy};
2454					else if("dx" in first2 && "dy" in first2 && (first2.dx !== first.x || first2.dy !== first.y))
2455						a[0] = {x: first2.dx, y: first2.dy};
2456					else
2457						a[0] = first2;
2458					a[1] = first;
2459					ctx.beginPath();
2460					l_hige(ctx, a);
2461				}
2462				if("arrow" in obj && "tail" in obj.arrow && 1 < arr.length){
2463					var last = arr[arr.length-1], last2 = arr[arr.length-2], a = [];
2464					if("dx" in last && "dy" in last && (last.dx !== last.x || last.dy !== last.y))
2465						a[0] = {x: last.dx, y: last.dy};
2466					else if("cx" in last && "cy" in last && (last.cx !== last.x || last.cy !== last.y))
2467						a[0] = {x: last.cx, y: last.cy};
2468					else
2469						a[0] = last2;
2470					a[1] = last;
2471					ctx.beginPath();
2472					l_hige(ctx, a);
2473				}
2474				ctx.lineWidth = 1;
2475
2476				// Draws dashed line that connects a control point and its associated vertex
2477				// This one does not take offset into account since the transformation is done
2478				// before this function.
2479				function drawGuidingLineNoOffset(pt0, pt, name){
2480					ctx.setLineDash([5]);
2481					ctx.beginPath();
2482					ctx.moveTo(pt0.x, pt0.y);
2483					ctx.lineTo(pt[name + "x"], pt[name + "y"]);
2484					ctx.stroke();
2485					ctx.setLineDash([]);
2486				}
2487
2488				if(obj === cur_shape){
2489					var last = arr[arr.length-1];
2490					drawGuidingLineNoOffset(last ,last, "d");
2491					drawHandle(last.dx, last.dy, "#ff7f7f", true);
2492					var next = {x: 2 * last.x - last.dx, y: 2 * last.y - last.dy};
2493					drawGuidingLineNoOffset(last, next, "");
2494					drawHandle(next.x, next.y, "#ff7f7f", true);
2495				}
2496			},
2497			onNewShape: function(shape){}, /// Virtual event handler on creation of a new shape
2498			appendPoint: function(x, y){
2499				function addPoint(){
2500					var pts = cur_shape.points;
2501					pts.push(cloneObject(d));
2502				}
2503
2504				var d = canvasToSrc(constrainCoord({x:x, y:y}));
2505				if(!cur_shape){
2506					var obj = new cur_tool.objctor();
2507					obj.tool = cur_tool.name;
2508					obj.color = cur_col;
2509					obj.width = cur_thin;
2510					this.onNewShape(obj);
2511					cur_shape = obj;
2512					addPoint();
2513					addPoint();
2514				}
2515				else{
2516					var pts = cur_shape.points;
2517					if(pts.length !== 1){
2518						var prev = pts[pts.length-2];
2519						if(d.x === prev.x && d.y === prev.y){
2520							cur_shape.points.pop();
2521							dhistory.push(cloneObject(dobjs));
2522							dobjs.push(cur_shape);
2523							updateDrawData();
2524							cur_shape = null;
2525							redraw(dobjs);
2526							return true;
2527						}
2528					}
2529					if(0 < pts.length){
2530						var prev = pts[pts.length-1];
2531						// Mirror position of control point for smooth curve
2532						if("dx" in prev && "dy" in prev){
2533							d.cx = 2 * prev.x - prev.dx;
2534							d.cy = 2 * prev.y - prev.dy;
2535						}
2536					}
2537					addPoint();
2538				}
2539			},
2540			mouseDown: function(e){
2541				if(cur_shape){
2542					var clrect = canvas.getBoundingClientRect();
2543					var mx = e.clientX - clrect.left;
2544					var my = e.clientY - clrect.top;
2545					// Remember mouse stat to this (Path tool) object for later use
2546					// in mouseMove().
2547					this.lastx = mx;
2548					this.lasty = my;
2549					this.lastPoint = cur_shape.points[cur_shape.points.length-1];
2550				}
2551			},
2552			mouseMove: function(e){
2553				if(!cur_shape)
2554					return;
2555				var clrect = canvas.getBoundingClientRect();
2556				var mx = e.clientX - clrect.left;
2557				var my = e.clientY - clrect.top;
2558				if(this.lastPoint){
2559					var pt = this.lastPoint;
2560					var d = canvasToSrc(constrainCoord({x:mx, y:my}));
2561					if(0 < cur_shape.points.length && (pt.x !== d.x && pt.y !== d.y)){
2562						var prev = cur_shape.points[cur_shape.points.length-2];
2563						// Extend control point by mouse dragging.
2564						pt.dx = 2 * pt.x - d.x;
2565						pt.dy = 2 * pt.y - d.y;
2566					}
2567					redraw(dobjs);
2568				}
2569				else if(0 < cur_shape.points.length){
2570					// Live preview of the shape being added.
2571					var coord = canvasToSrc(constrainCoord({x: mx, y: my}));
2572					var pt = cur_shape.points[cur_shape.points.length-1];
2573					var dx = coord.x - pt.x;
2574					var dy = coord.y - pt.y;
2575					pt.x = coord.x;
2576					pt.y = coord.y;
2577					// Only move dx and dy along with x, y.
2578					// cx and cy should be moved with the previous vertex.
2579					if("dx" in pt && "dy" in pt)
2580						pt.dx += dx, pt.dy += dy;
2581					redraw(dobjs);
2582				}
2583			},
2584			mouseUp: function(e){
2585				this.lastPoint = null;
2586			},
2587		}),
2588		new Tool("path", 2, {
2589			objctor: PathShape,
2590			drawTool: function(x, y){
2591				ctx.beginPath();
2592				ctx.moveTo(x, y);
2593				ctx.bezierCurveTo(x+10, y+20, x+20, y-10, x+30, y+10);
2594				ctx.stroke();
2595				l_hige(ctx, [{x: x+20, y: y-10}, {x: x+30, y: y+10}]);
2596				ctx.strokeText('n', x+45, y+10);
2597			},
2598			draw: toolmap.path.draw,
2599			onNewShape: function(shape){shape.arrow = {"tail": null}},
2600			appendPoint: toolmap.path.appendPoint,
2601			mouseDown: toolmap.path.mouseDown,
2602			mouseMove: toolmap.path.mouseMove,
2603			mouseUp: toolmap.path.mouseUp,
2604		}),
2605		new Tool("path", 2, {
2606			objctor: PathShape,
2607			drawTool: function(x, y){
2608				ctx.beginPath();
2609				ctx.moveTo(x, y);
2610				ctx.bezierCurveTo(x+10, y+20, x+20, y-10, x+30, y+10);
2611				ctx.stroke();
2612				l_hige(ctx, [{x: x+10, y: y+20}, {x: x, y: y}]);
2613				l_hige(ctx, [{x: x+20, y: y-10}, {x: x+30, y: y+10}]);
2614				ctx.strokeText('n', x+45, y+10);
2615			},
2616			draw: toolmap.path.draw,
2617			onNewShape: function(shape){shape.arrow = {head: null, "tail": null}},
2618			appendPoint: toolmap.path.appendPoint,
2619			mouseDown: toolmap.path.mouseDown,
2620			mouseMove: toolmap.path.mouseMove,
2621			mouseUp: toolmap.path.mouseUp,
2622		})
2623	]
2624];
2625var toolbarPage = 0;
2626
2627var white = "rgb(255, 255, 255)";
2628var black = "rgb(0, 0, 0)";
2629var blue = "rgb(0, 100, 255)";
2630var green = "rgb(0, 255, 0)";
2631var red = "rgb(255, 0, 0)";
2632var gray = "rgb(150, 150, 150)";
2633var colstr = new Array(black,blue,red,green,white);
2634var colnames = ["black", "blue", "red", "green", "white"];
2635var coltable = {"black": black, "blue": blue, "red": red, "green": green, "white": white};
2636var x0 = 0, y0 = 0, w0 = 1024, h0 = 640;
2637var x1 = 90, y1 = 50, w1 = 930, h1 = 580;
2638var mx0 = 10, mx1 = x1, mx2 = 600, mx3 = 820;
2639var mw0 = 70, mw1 = 60, mw2 = 30, my0 = 20, mh0 = 28;
2640var cur_tool = toolmap.select, cur_col = "black", cur_thin = 1;
2641var cur_shape = null;
2642var offset = editmode ? {x:x1, y:y1} : {x:0, y:0};
2643
2644// The layer to show input controls for width and height sizes of the figure.
2645// It's kept as a member variable in order to reuse in the second and later invocations.
2646var sizeLayer = null;
2647
2648// The layer to input text.
2649// It used to be prompt() function, but it's not beautiful.
2650var textLayer = null;
2651
2652// The default metaObj values used for resetting.
2653var defaultMetaObj = {type: "meta", size: [1024-x1, 640-y1]};
2654
2655// The meta object is always the first element in the serialized figure text,
2656// but is not an element of dobjs array.
2657// It's automatically loaded when deserialized and included when serialized.
2658var metaObj = cloneObject(defaultMetaObj);
2659
2660// A pseudo-this pointer that can be used in private methods.
2661// Private methods mean local functions in this constructor, which
2662// don't share this pointer with the constructor itself unless the
2663// form "function.call(this)" is used.
2664var self = this;
2665
2666onload();
2667}
2668
2669// Create and return a XMLHttpRequest object or ActiveXObject for IE6-
2670SketchCanvas.prototype.createXMLHttpRequest = function(){
2671	var xmlHttp = null;
2672	try{
2673		// for IE7+, Fireforx, Chrome, Opera, Safari
2674		xmlHttp = new XMLHttpRequest();
2675	}
2676	catch(e){
2677		try{
2678			// for IE6, IE5 (canvas element wouldn't work from the start, though)
2679			xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
2680		}
2681		catch(e){
2682			try{
2683				xmlHttp = new ActiveXObject("Microsoft.XMLHttp");
2684			}
2685			catch(e){
2686				return null;
2687			}
2688		}
2689	}
2690	return xmlHttp;
2691}
2692
2693