Built motion from commit bcd50b9.|0.0.29
[motion.git] / public / assets / plugins / jscripty / js / EditorUi.js
1 /**
2  * $Id: EditorUi.js,v 1.21 2013/03/14 20:46:36 david Exp $
3  * Copyright (c) 2006-2012, JGraph Ltd
4  */
5 /**
6  * Constructs a new graph editor
7  */
8 EditorUi = function(editor, container) {
9         this.editor = editor || new Editor();
10         this.container = container || document.getElementById('geEditor');
11         var graph = editor.graph;
12         var self = this;
13
14         // Disables scrollbars
15         this.container.style.overflow = 'hidden';
16
17         // Pre-fetches submenu image
18         new Image().src = mxPopupMenu.prototype.submenuImage;
19
20         // Pre-fetches connect image
21         if (mxConnectionHandler.prototype.connectImage != null) {
22                 new Image().src = mxConnectionHandler.prototype.connectImage.src;
23         }
24
25         // Creates the user interface
26         this.actions = new Actions(this);
27         this.menus = new Menus(this);
28         this.createDivs();
29         this.refresh();
30         this.createUi();
31
32         // Disables HTML and text selection
33         var textEditing = mxUtils.bind(this, function(evt) {
34                 if (evt == null) {
35                         evt = window.event;
36                 }
37
38                 if (this.isSelectionAllowed(evt)) {
39                         return true;
40                 }
41
42                 return graph.isEditing();
43         });
44
45         // Disables text selection while not editing and no dialog visible
46         if (this.container == document.body) {
47                 this.menubarContainer.onselectstart = textEditing;
48                 this.menubarContainer.onmousedown = textEditing;
49                 this.toolbarContainer.onselectstart = textEditing;
50                 this.toolbarContainer.onmousedown = textEditing;
51                 this.diagramContainer.onselectstart = textEditing;
52                 this.diagramContainer.onmousedown = textEditing;
53                 this.sidebarContainer.onselectstart = textEditing;
54                 this.sidebarContainer.onmousedown = textEditing;
55                 this.footerContainer.onselectstart = textEditing;
56                 this.footerContainer.onmousedown = textEditing;
57         }
58
59         // And uses built-in context menu while editing
60         if (mxClient.IS_IE && (typeof(document.documentMode) === 'undefined' ||
61                         document.documentMode < 9)) {
62                 mxEvent.addListener(this.diagramContainer, 'contextmenu', textEditing);
63                 mxEvent.addListener(this.sidebarContainer, 'contextmenu', textEditing);
64         } else {
65                 // Allows browser context menu outside of diagram and sidebar
66                 this.diagramContainer.oncontextmenu = textEditing;
67                 this.sidebarContainer.oncontextmenu = textEditing;
68         }
69
70         // Contains the main graph instance inside the given panel
71         graph.init(this.diagramContainer);
72         graph.refresh();
73
74         // Enables scrollbars and sets cursor style for the container
75         graph.container.setAttribute('tabindex', '0');
76         // graph.container.style.overflow = (touchStyle) ? 'hidden' : 'auto';
77         graph.container.style.cursor = 'default';
78         graph.container.style.backgroundImage = 'url(' + editor.gridImage + ')';
79         graph.container.style.backgroundPosition = '-1px -1px';
80         graph.container.focus();
81
82         // Keeps graph container focused on mouse down
83         var graphFireMouseEvent = graph.fireMouseEvent;
84         graph.fireMouseEvent = function(evtName, me, sender) {
85                 if (evtName == mxEvent.MOUSE_DOWN) {
86                         this.container.focus();
87                 }
88
89                 graphFireMouseEvent.apply(this, arguments);
90         };
91
92         // Giuseppe Careri
93         // Defines invalid connections along with the error messages that they produce.
94
95         this.createMultiplicities(graph.multiplicities, ['start', 'end', 'input',
96                 'question', 'email'
97         ], [
98                 [1, 1],
99                 [0, 0],
100                 [1, 1],
101                 [1, 'n'],
102                 [1, 'n']
103         ], [
104                 [0, 0],
105                 [1, 'n'],
106                 [1, 'n'],
107                 [1, 'n'],
108                 [1, 'n']
109
110         ]);
111
112
113         // Giuseppe Careri
114         // Processes a doubleclick on an optional cell and fires a <dblclick> event.
115         // The event is fired initially.
116         // If the graph is enabled and the event has not been consumed, then <edit> is called with the given cell.
117         // The event is ignored if no cell was specified.
118         graph.dblClick = function(evt, cell) {
119                 var edges = ['input', 'question'];
120                 var mxe = new mxEventObject(mxEvent.DOUBLE_CLICK, 'event', evt, 'cell',
121                         cell);
122                 this.fireEvent(mxe);
123
124                 if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed()) {
125                         // Override dblClick Vertices and Edges
126                         if (this.getModel().isEdge(cell)) {
127                                 if (edges.indexOf(cell.source.value.tagName) >= 0)
128                                         this.startEditingAtCell(cell);
129                         } else if (this.getModel().isVertex(cell)) {
130                                 console.log(self);
131                                 console.log(cell);
132                                 self.showDialog(new GeneralDialog(self, cell).container, 320, 280, true,
133                                         true);
134                         }
135                         mxe.consume();
136                 }
137         }
138
139         // Configures automatic expand on mouseover
140         graph.panningHandler.autoExpand = true;
141
142         // Installs context menu
143         graph.panningHandler.factoryMethod = mxUtils.bind(this, function(menu, cell,
144                 evt) {
145                 this.menus.createPopupMenu(menu, cell, evt);
146         });
147
148         // Initializes the outline
149         editor.outline.init(this.outlineContainer);
150
151         // Hides context menu
152         mxEvent.addGestureListeners(document, mxUtils.bind(this, function(evt) {
153                 graph.panningHandler.hideMenu();
154         }));
155
156         // Adds gesture handling (pinch to zoom)
157         if (mxClient.IS_TOUCH) {
158                 mxEvent.addListener(graph.container, 'gesturechange',
159                         mxUtils.bind(this, function(evt) {
160                                 graph.view.getDrawPane().setAttribute('transform', 'scale(' + evt.scale +
161                                         ')');
162                                 graph.view.getOverlayPane().style.visibility = 'hidden';
163                         })
164                 );
165
166                 mxEvent.addListener(graph.container, 'gestureend',
167                         mxUtils.bind(this, function(evt) {
168                                 graph.view.getDrawPane().removeAttribute('transform');
169                                 graph.zoomToCenter = true;
170                                 graph.zoom(evt.scale);
171                                 graph.view.getOverlayPane().style.visibility = 'visible';
172                         })
173                 );
174         }
175
176         // Create handler for key events
177         var keyHandler = this.createKeyHandler(editor);
178
179         // Getter for key handler
180         this.getKeyHandler = function() {
181                 return keyHandler;
182         };
183
184         // Updates the editor UI after the window has been resized
185         mxEvent.addListener(window, 'resize', mxUtils.bind(this, function() {
186                 this.refresh();
187                 graph.sizeDidChange();
188                 this.editor.outline.update(false);
189                 this.editor.outline.outline.sizeDidChange();
190         }));
191
192         // Updates action and menu states
193         this.init();
194         this.open();
195 };
196
197 /**
198  * Specifies the size of the split bar.
199  */
200 EditorUi.prototype.splitSize = (mxClient.IS_TOUCH) ? 16 : 8;
201
202 /**
203  * Specifies the height of the menubar. Default is 34.
204  */
205 EditorUi.prototype.menubarHeight = 33;
206
207 /**
208  * Specifies the height of the toolbar. Default is 36.
209  */
210 EditorUi.prototype.toolbarHeight = 36;
211
212 /**
213  * Specifies the height of the footer. Default is 28.
214  */
215 EditorUi.prototype.footerHeight = 28;
216
217 /**
218  * Specifies the position of the horizontal split bar. Default is 212.
219  */
220 EditorUi.prototype.hsplitPosition = 204;
221
222 /**
223  * Specifies the position of the vertical split bar. Default is 190.
224  */
225 EditorUi.prototype.vsplitPosition = 190;
226
227 /**
228  * Installs the listeners to update the action states.
229  */
230 EditorUi.prototype.init = function() {
231         // Updates action states
232         this.addUndoListener();
233         this.addSelectionListener();
234
235         // Overrides clipboard to update paste action state
236         var paste = this.actions.get('paste');
237
238         var updatePaste = function() {
239                 paste.setEnabled(!mxClipboard.isEmpty());
240         };
241
242         var mxClipboardCut = mxClipboard.cut;
243         mxClipboard.cut = function() {
244                 mxClipboardCut.apply(this, arguments);
245                 updatePaste();
246         };
247
248         var mxClipboardCopy = mxClipboard.copy;
249         mxClipboard.copy = function() {
250                 mxClipboardCopy.apply(this, arguments);
251                 updatePaste();
252         };
253 };
254
255 /**
256  * Hook for allowing selection and context menu for certain events.
257  */
258 EditorUi.prototype.isSelectionAllowed = function(evt) {
259         return false;
260 };
261
262 /**
263  * Opens the current diagram via the window.opener if one exists.
264  */
265 EditorUi.prototype.open = function() {
266         // Cross-domain window access is not allowed in FF, so if we
267         // were opened from another domain then this will fail.
268         try {
269                 if (window.opener != null && window.opener.openFile != null) {
270                         window.opener.openFile.setConsumer(mxUtils.bind(this, function(xml,
271                                 filename) {
272                                 try {
273                                         var doc = mxUtils.parseXml(xml);
274                                         this.editor.setGraphXml(doc.documentElement);
275                                         this.editor.modified = false;
276                                         this.editor.undoManager.clear();
277
278                                         if (filename != null) {
279                                                 this.editor.filename = filename;
280                                         }
281                                 } catch (e) {
282                                         mxUtils.alert(mxResources.get('invalidOrMissingFile') + ': ' + e.message);
283                                 }
284                         }));
285                 }
286         } catch (e) {
287                 // ignore
288         }
289 };
290
291 /**
292  * Giuseppe Careri
293  * Opens the current diagram via string.
294  */
295 EditorUi.prototype.openString = function(xml, filename, data) {
296         try {
297                 var doc = mxUtils.parseXml(xml);
298                 this.editor.setGraphXml(doc.documentElement);
299                 this.editor.modified = false;
300                 this.editor.undoManager.clear();
301
302                 if (filename != null) {
303                         this.editor.filename = filename;
304                 }
305
306                 if (data != null) {
307                         this.editor.data = data;
308                 }
309         } catch (e) {
310                 mxUtils.alert(mxResources.get('invalidOrMissingFile') + ': ' + e.message);
311         }
312 };
313
314 /**
315  * Saves As the current graph under the given project name.
316  */
317 EditorUi.prototype.new = function(name) {
318         var editor = this.editor;
319
320         if (name != null) {
321                 var xml = mxUtils.getPrettyXml(this.editor.getGraphXml());
322                 try {
323                         if (useLocalStorage) {
324                                 if (localStorage.getItem(name) != null &&
325                                         !mxUtils.confirm(mxResources.get('replace', [name]))) {
326                                         return;
327                                 }
328
329                                 localStorage.setItem(name, xml);
330                                 this.editor.setStatus(mxResources.get('saved') + ' ' + new Date());
331                         } else {
332                                 console.log(xml.length);
333                                 console.log(MAX_REQUEST_SIZE);
334                                 if (xml.length < MAX_REQUEST_SIZE) {
335                                         xml = encodeURIComponent(xml);
336                                         name = encodeURIComponent(name);
337
338                                         var xhr = new XMLHttpRequest();
339                                         xhr.open("POST", SAVE_URL, true);
340                                         xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
341                                         xhr.setRequestHeader('Authorization', 'Bearer ' + editor.data.token);
342                                         xhr.onload = function(e) {
343                                                 if (xhr.readyState === 4) {
344                                                         if (xhr.status === 201) {
345                                                                 editor.setStatus('Project ' + JSON.parse(xhr.response).name +
346                                                                         ' is opening in new window!');
347                                                                 setTimeout(function() {
348                                                                         window.open('jscripty/project/' + JSON.parse(xhr.response).id,
349                                                                                 '_blank');
350                                                                 }, 1500);
351                                                         } else {
352                                                                 editor.setStatus('Error creating project: ' +
353                                                                         JSON.parse(xhr.response).errors[0].message);
354                                                         }
355                                                 }
356                                         };
357                                         xhr.onerror = function(e) {
358                                                 mxUtils.alert(xhr.statusText);
359                                         };
360                                         xhr.send('description=project_new&name=' + name);
361                                 } else {
362                                         mxUtils.alert(mxResources.get('drawingTooLarge'));
363                                         mxUtils.popup(xml);
364
365                                         return;
366                                 }
367                         }
368
369                         this.editor.filename = name;
370                         this.editor.modified = false;
371                 } catch (e) {
372                         this.editor.setStatus('Error creating project');
373                 }
374         } else {
375                 this.editor.setStatus('Error creating project');
376         }
377 };
378
379
380 /**
381  * Saves the current graph under the given project name.
382  */
383 EditorUi.prototype.save = function(name) {
384         var editor = this.editor;
385
386         if (name != null) {
387                 var xml = mxUtils.getPrettyXml(this.editor.getGraphXml());
388                 try {
389                         if (useLocalStorage) {
390                                 if (localStorage.getItem(name) != null &&
391                                         !mxUtils.confirm(mxResources.get('replace', [name]))) {
392                                         return;
393                                 }
394
395                                 localStorage.setItem(name, xml);
396                                 this.editor.setStatus(mxResources.get('saved') + ' ' + new Date());
397                         } else {
398                                 console.log(xml.length);
399                                 console.log(MAX_REQUEST_SIZE);
400                                 if (xml.length < MAX_REQUEST_SIZE) {
401                                         xml = encodeURIComponent(xml);
402
403                                         var xhr = new XMLHttpRequest();
404                                         xhr.open("PUT", SAVE_URL + this.editor.data.id, true);
405                                         xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
406                                         xhr.setRequestHeader('Authorization', 'Bearer ' + editor.data.token);
407                                         xhr.onload = function(e) {
408                                                 if (xhr.readyState === 4) {
409                                                         if (xhr.status === 200) {
410                                                                 editor.setStatus('Updated at ' + JSON.parse(xhr.response).updatedAt +
411                                                                         ' (' + xhr.status + ' ' + xhr.statusText + ')');
412                                                         } else {
413                                                                 editor.setStatus('Error saving project: ' +
414                                                                         JSON.parse(xhr.response).errors[0].message);
415                                                         }
416                                                 }
417                                         };
418                                         xhr.onerror = function(e) {
419                                                 mxUtils.alert(xhr.statusText);
420                                         };
421                                         xhr.send('draft=' + xml);
422                                 } else {
423                                         mxUtils.alert(mxResources.get('drawingTooLarge'));
424                                         mxUtils.popup(xml);
425
426                                         return;
427                                 }
428                         }
429
430                         this.editor.filename = name;
431                         this.editor.modified = false;
432                 } catch (e) {
433                         this.editor.setStatus('Error saving file');
434                 }
435         } else {
436                 this.editor.setStatus('Error saving file');
437         }
438 };
439
440 /**
441  * Saves As the current graph under the given project name.
442  */
443 EditorUi.prototype.saveAs = function(name) {
444         var editor = this.editor;
445         console.log(name);
446         if (name != null) {
447                 var xml = mxUtils.getPrettyXml(this.editor.getGraphXml());
448                 try {
449                         if (useLocalStorage) {
450                                 if (localStorage.getItem(name) != null &&
451                                         !mxUtils.confirm(mxResources.get('replace', [name]))) {
452                                         return;
453                                 }
454
455                                 localStorage.setItem(name, xml);
456                                 this.editor.setStatus(mxResources.get('saved') + ' ' + new Date());
457                         } else {
458                                 console.log(xml.length);
459                                 console.log(MAX_REQUEST_SIZE);
460                                 if (xml.length < MAX_REQUEST_SIZE) {
461                                         xml = encodeURIComponent(xml);
462                                         name = encodeURIComponent(name);
463
464                                         var xhr = new XMLHttpRequest();
465                                         xhr.open("POST", SAVE_URL, true);
466                                         xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
467                                         xhr.setRequestHeader('Authorization', 'Bearer ' + editor.data.token);
468                                         xhr.onload = function(e) {
469                                                 if (xhr.readyState === 4) {
470                                                         if (xhr.status === 201) {
471                                                                 editor.setStatus('Project ' + JSON.parse(xhr.response).name +
472                                                                         ' is opening in new window!');
473                                                                 setTimeout(function() {
474                                                                         window.open('jscripty/project/' + JSON.parse(xhr.response).id,
475                                                                                 '_blank');
476                                                                 }, 1500);
477                                                         } else {
478                                                                 editor.setStatus('Error cloning project: ' +
479                                                                         JSON.parse(xhr.response).errors[0].message);
480                                                         }
481                                                 }
482                                         };
483                                         xhr.onerror = function(e) {
484                                                 mxUtils.alert(xhr.statusText);
485                                         };
486                                         xhr.send('description=project_cloned&name=' + name +
487                                                 '&draft=' + xml + '&production=' + xml);
488                                 } else {
489                                         mxUtils.alert(mxResources.get('drawingTooLarge'));
490                                         mxUtils.popup(xml);
491
492                                         return;
493                                 }
494                         }
495
496                         this.editor.filename = name;
497                         this.editor.modified = false;
498                 } catch (e) {
499                         this.editor.setStatus('Error saving file');
500                 }
501         } else {
502                 this.editor.setStatus('Error saving file');
503         }
504 };
505
506 /**
507  * Plush the current graph under the given project name.
508  */
509 EditorUi.prototype.validate = function(a, b) {
510         var graph = this.editor.graph;
511         a = null != a ? a : graph.model.getRoot();
512         b = null != b ? b : {};
513         for (var c = !0, d = graph.model.getChildCount(a), e = 0; e < d; e++) {
514                 var f = graph.model.getChildAt(a, e),
515                         g = b;
516                 graph.isValidRoot(f) && (g = {});
517                 g = this.validate(f, g);
518                 null != g ? graph.setCellWarning(f, g.replace(/\n/g, "\x3cbr\x3e")) : graph.setCellWarning(f, null);
519                 c = c && null == g
520         }
521         d = "";
522         if (graph.model.isVertex(a)) {
523                 switch (a.value.nodeName) {
524                         case 'question':
525                                 d = (a.value.getAttribute('question') !== "") ? "" :
526                                         'Content is empty' + "\n";
527                                 break;
528                         case 'form':
529                                 d = (a.value.getAttribute('question') !== "") ? "" :
530                                         'Content is empty' + "\n";
531                                 break;
532                         case 'start':
533                                 d = this.validateBlock('start') ? '' : "Only one Start block is allowed" + "\n";
534                                 break;
535                 }
536         }
537
538         if (graph.model.isEdge(a)) {
539                 var z = graph.model.getCell(a.source.getId());
540                 switch (z.value.nodeName) {
541                         case 'question':
542                                 var patt = /^[a-zA-Z0-9][a-zA-Z0-9\s]*$/;
543                                 var str = String(a.getValue());
544                                 if ((str == 'undefined') || (str == 'null')) {
545                                         str = ' ';
546                                 }
547                                 var res = str.split(",");
548                                 res.forEach(function(entry) {
549                                         d = d + ((patt.test(entry.trim())) ? "" : 'Can not be empty' + "\n");
550                                 });
551                                 break;
552
553                 }
554         }
555
556         graph.isCellCollapsed(a) && !c && (d += (mxResources.get(graph.containsValidationErrorsResource) || graph.containsValidationErrorsResource) + "\n");
557         d = graph.model.isEdge(a) ? d + (graph.getEdgeValidationError(a, graph.model.getTerminal(a, !0), graph.model.getTerminal(a, !1)) || "") : d + (graph.getCellValidationError(a) || "");
558         e = graph.validateCell(a, b);
559         null != e && (d += e);
560         null == graph.model.getParent(a) && graph.view.validate();
561         return 0 < d.length || !c ? d : null
562 }
563
564 EditorUi.prototype.validateBlock = function(blockName) {
565         var graph = this.editor.graph;
566         var parent = graph.getDefaultParent();
567         var count = 0;
568         // var model = graph.getModel();
569         //We can also use model.getChildren(parent)
570         parent.children.forEach(function(n) {
571                 if (graph.model.isVertex(n) && n.value.nodeName == blockName) {
572                         count++;
573                 }
574         });
575         return count > 1 ? false : true;
576 }
577
578 EditorUi.prototype.publish = function(name) {
579
580         var editor = this.editor;
581
582         if (name != null) {
583
584                 var response = this.validate();
585                 if (response === null) {
586                         var xml = mxUtils.getPrettyXml(this.editor.getGraphXml());
587                         try {
588                                 if (useLocalStorage) {
589                                         if (localStorage.getItem(name) != null &&
590                                                 !mxUtils.confirm(mxResources.get('replace', [name]))) {
591                                                 return;
592                                         }
593
594                                         localStorage.setItem(name, xml);
595                                         this.editor.setStatus(mxResources.get('saved') + ' ' + new Date());
596                                 } else {
597                                         console.log(xml.length);
598                                         console.log(MAX_REQUEST_SIZE);
599                                         if (xml.length < MAX_REQUEST_SIZE) {
600                                                 xml = encodeURIComponent(xml);
601
602                                                 var xhr = new XMLHttpRequest();
603                                                 xhr.open("PUT", SAVE_URL + this.editor.data.id, true);
604                                                 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
605                                                 xhr.setRequestHeader('Authorization', 'Bearer ' + editor.data.token);
606                                                 xhr.onload = function(e) {
607                                                         if (xhr.readyState === 4) {
608                                                                 if (xhr.status === 200) {
609                                                                         editor.setStatus('Published at ' + JSON.parse(xhr.response).updatedAt +
610                                                                                 ' (' + xhr.status + ' ' + xhr.statusText + ')');
611                                                                 } else {
612                                                                         editor.setStatus('Error saving project: ' +
613                                                                                 JSON.parse(xhr.response).errors[0].message);
614                                                                 }
615                                                         }
616                                                 };
617                                                 xhr.onerror = function(e) {
618                                                         mxUtils.alert(xhr.statusText);
619                                                 };
620                                                 xhr.send('draft=' + xml + '&production=' + xml);
621                                         } else {
622                                                 mxUtils.alert(mxResources.get('drawingTooLarge'));
623                                                 mxUtils.popup(xml);
624
625                                                 return;
626                                         }
627                                 }
628
629                                 this.editor.filename = name;
630                                 this.editor.modified = false;
631                         } catch (e) {
632                                 this.editor.setStatus('Error publishing file');
633                         }
634                 } else {
635                         editor.setStatus('Error publishing project: ');
636                 }
637         } else {
638                 this.editor.setStatus('Error publishing file');
639         }
640         // }
641
642
643
644 };
645
646 /**
647  * Create variable
648  */
649 EditorUi.prototype.variable = function(name) {
650         var editor = this.editor;
651
652         if (name != null) {
653                 try {
654
655                         var xhr = new XMLHttpRequest();
656                         xhr.open("POST", VARIABLE_URL, true);
657                         xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
658                         xhr.setRequestHeader('Authorization', 'Bearer ' + editor.data.token);
659                         xhr.onload = function(e) {
660                                 if (xhr.readyState === 4) {
661                                         if (xhr.status === 201) {
662                                                 editor.setStatus('Variable created at ' + JSON.parse(xhr.response).createdAt +
663                                                         ' (' + xhr.status + ' ' + xhr.statusText + ')');
664                                         } else {
665                                                 editor.setStatus('Error cloning project: ' +
666                                                         JSON.parse(xhr.response).errors[0].message);
667                                         }
668                                 }
669                         };
670                         xhr.onerror = function(e) {
671                                 mxUtils.alert(xhr.statusText);
672                         };
673                         xhr.send('name=' + name);
674                 } catch (e) {
675                         this.editor.setStatus('Error creating variable');
676                 }
677         } else {
678                 this.editor.setStatus('Error creating variable');
679         }
680 };
681
682 /**
683  * Returns the URL for a copy of this editor with no state.
684  */
685 EditorUi.prototype.getUrl = function(pathname) {
686         var href = (pathname != null) ? pathname : window.location.pathname;
687         var parms = (href.indexOf('?') > 0) ? 1 : 0;
688
689         // Removes template URL parameter for new blank diagram
690         for (var key in urlParams) {
691                 if (parms == 0) {
692                         href += '?';
693                 } else {
694                         href += '&';
695                 }
696
697                 href += key + '=' + urlParams[key];
698                 parms++;
699         }
700
701         return href;
702 };
703
704 /**
705  * Updates the states of the given undo/redo items.
706  */
707 EditorUi.prototype.addUndoListener = function() {
708         var undo = this.actions.get('undo');
709         var redo = this.actions.get('redo');
710
711         var undoMgr = this.editor.undoManager;
712
713         var undoListener = function() {
714                 undo.setEnabled(undoMgr.canUndo());
715                 redo.setEnabled(undoMgr.canRedo());
716         };
717
718         undoMgr.addListener(mxEvent.ADD, undoListener);
719         undoMgr.addListener(mxEvent.UNDO, undoListener);
720         undoMgr.addListener(mxEvent.REDO, undoListener);
721         undoMgr.addListener(mxEvent.CLEAR, undoListener);
722
723         // Updates the button states once
724         undoListener();
725 };
726
727 /**
728  * Updates the states of the given toolbar items based on the selection.
729  */
730 EditorUi.prototype.addSelectionListener = function() {
731         var selectionListener = mxUtils.bind(this, function() {
732                 var graph = this.editor.graph;
733                 var selected = !graph.isSelectionEmpty();
734                 var vertexSelected = false;
735                 var edgeSelected = false;
736
737                 var cells = graph.getSelectionCells();
738
739                 if (cells != null) {
740                         for (var i = 0; i < cells.length; i++) {
741                                 var cell = cells[i];
742
743                                 if (graph.getModel().isEdge(cell)) {
744                                         edgeSelected = true;
745                                 }
746
747                                 if (graph.getModel().isVertex(cell)) {
748                                         vertexSelected = true;
749                                 }
750
751                                 if (edgeSelected && vertexSelected) {
752                                         break;
753                                 }
754                         }
755                 }
756
757                 // Updates action states
758                 var actions = ['cut', 'copy', 'delete', 'duplicate', 'bold', 'italic',
759                         'style', 'underline', 'toFront', 'toBack', 'dashed', 'rounded',
760                         'shadow',
761                         'tilt', 'autosize'
762                 ];
763
764                 for (var i = 0; i < actions.length; i++) {
765                         this.actions.get(actions[i]).setEnabled(selected);
766                 }
767
768                 this.actions.get('curved').setEnabled(edgeSelected);
769                 this.actions.get('rotation').setEnabled(vertexSelected);
770                 this.actions.get('wordWrap').setEnabled(vertexSelected);
771                 this.actions.get('group').setEnabled(graph.getSelectionCount() > 1);
772                 this.actions.get('ungroup').setEnabled(graph.getSelectionCount() == 1 &&
773                         graph.getModel().getChildCount(graph.getSelectionCell()) > 0);
774                 var oneVertexSelected = vertexSelected && graph.getSelectionCount() == 1;
775                 this.actions.get('removeFromGroup').setEnabled(oneVertexSelected &&
776                         graph.getModel().isVertex(graph.getModel().getParent(graph.getSelectionCell()))
777                 );
778
779                 // Updates menu states
780                 var menus = ['fontFamily', 'fontSize', 'alignment', 'position', 'text',
781                         'format',
782                         'arrange', 'linewidth', 'spacing'
783                 ];
784
785                 for (var i = 0; i < menus.length; i++) {
786                         this.menus.get(menus[i]).setEnabled(selected);
787                 }
788
789                 menus = ['line', 'lineend', 'linestart'];
790
791                 for (var i = 0; i < menus.length; i++) {
792                         this.menus.get(menus[i]).setEnabled(edgeSelected);
793                 }
794
795                 this.actions.get('setAsDefaultEdge').setEnabled(edgeSelected);
796
797                 this.menus.get('align').setEnabled(graph.getSelectionCount() > 1);
798                 this.menus.get('direction').setEnabled(vertexSelected || (edgeSelected &&
799                         graph.isLoop(graph.view.getState(graph.getSelectionCell()))));
800                 this.menus.get('navigation').setEnabled(graph.foldingEnabled && ((graph.view
801                                 .currentRoot != null) ||
802                         (graph.getSelectionCount() == 1 && graph.isValidRoot(graph.getSelectionCell()))
803                 ));
804                 this.actions.get('home').setEnabled(graph.view.currentRoot != null);
805                 this.actions.get('exitGroup').setEnabled(graph.view.currentRoot != null);
806                 var groupEnabled = graph.getSelectionCount() == 1 && graph.isValidRoot(
807                         graph.getSelectionCell());
808                 this.actions.get('enterGroup').setEnabled(groupEnabled);
809                 this.actions.get('expand').setEnabled(groupEnabled);
810                 this.actions.get('collapse').setEnabled(groupEnabled);
811                 this.actions.get('editLink').setEnabled(graph.getSelectionCount() == 1);
812                 this.actions.get('openLink').setEnabled(graph.getSelectionCount() == 1 &&
813                         graph.getLinkForCell(graph.getSelectionCell()) != null);
814         });
815
816         this.editor.graph.getSelectionModel().addListener(mxEvent.CHANGE,
817                 selectionListener);
818         selectionListener();
819 };
820
821 /**
822  * Refreshes the viewport.
823  */
824 EditorUi.prototype.refresh = function() {
825         var quirks = mxClient.IS_IE && (document.documentMode == null || document.documentMode ==
826                 5);
827         var w = this.container.clientWidth;
828         var h = this.container.clientHeight;
829
830         if (this.container == document.body) {
831                 w = document.body.clientWidth || document.documentElement.clientWidth;
832                 h = (quirks) ? document.body.clientHeight || document.documentElement.clientHeight :
833                         document.documentElement.clientHeight;
834         }
835
836         var effHsplitPosition = Math.max(0, Math.min(this.hsplitPosition, w - this.splitSize -
837                 20));
838         var effVsplitPosition = Math.max(0, Math.min(this.vsplitPosition, h - this.menubarHeight -
839                 this.toolbarHeight - this.footerHeight - this.splitSize - 1));
840
841         this.menubarContainer.style.height = this.menubarHeight + 'px';
842         this.toolbarContainer.style.top = this.menubarHeight + 'px';
843         this.toolbarContainer.style.height = this.toolbarHeight + 'px';
844
845         var tmp = this.menubarHeight + this.toolbarHeight;
846
847         if (!mxClient.IS_QUIRKS) {
848                 tmp += 1;
849         }
850
851         this.sidebarContainer.style.top = tmp + 'px';
852         this.sidebarContainer.style.width = effHsplitPosition + 'px';
853         this.outlineContainer.style.width = effHsplitPosition + 'px';
854         this.outlineContainer.style.height = effVsplitPosition + 'px';
855         this.outlineContainer.style.bottom = this.footerHeight + 'px';
856         this.diagramContainer.style.left = (effHsplitPosition + this.splitSize) +
857                 'px';
858         this.diagramContainer.style.top = this.sidebarContainer.style.top;
859         this.footerContainer.style.height = this.footerHeight + 'px';
860         this.footerContainer.style.display = 'none';
861         this.hsplit.style.top = this.sidebarContainer.style.top;
862         this.hsplit.style.bottom = this.outlineContainer.style.bottom;
863         this.hsplit.style.left = effHsplitPosition + 'px';
864         this.vsplit.style.width = this.sidebarContainer.style.width;
865         this.vsplit.style.bottom = (effVsplitPosition + this.footerHeight) + 'px';
866
867         if (quirks) {
868                 this.menubarContainer.style.width = w + 'px';
869                 this.toolbarContainer.style.width = this.menubarContainer.style.width;
870                 var sidebarHeight = (h - effVsplitPosition - this.splitSize - this.footerHeight -
871                         this.menubarHeight - this.toolbarHeight);
872                 this.sidebarContainer.style.height = sidebarHeight + 'px';
873                 this.diagramContainer.style.width = (w - effHsplitPosition - this.splitSize) +
874                         'px';
875                 var diagramHeight = (h - this.footerHeight - this.menubarHeight - this.toolbarHeight);
876                 this.diagramContainer.style.height = diagramHeight + 'px';
877                 this.footerContainer.style.width = this.menubarContainer.style.width;
878                 this.hsplit.style.height = diagramHeight + 'px';
879         } else {
880                 this.sidebarContainer.style.bottom = (effVsplitPosition + this.splitSize +
881                         this.footerHeight) + 'px';
882                 this.diagramContainer.style.bottom = this.outlineContainer.style.bottom;
883         }
884 };
885
886 /**
887  * Creates the required containers.
888  */
889 EditorUi.prototype.createDivs = function() {
890         this.menubarContainer = this.createDiv('geMenubarContainer');
891         this.toolbarContainer = this.createDiv('geToolbarContainer');
892         this.sidebarContainer = this.createDiv('geSidebarContainer');
893         this.outlineContainer = this.createDiv('geOutlineContainer');
894         this.diagramContainer = this.createDiv('geDiagramContainer');
895         this.footerContainer = this.createDiv('geFooterContainer');
896         this.hsplit = this.createDiv('geHsplit');
897         this.vsplit = this.createDiv('geVsplit');
898
899         // Sets static style for containers
900         this.menubarContainer.style.top = '0px';
901         this.menubarContainer.style.left = '0px';
902         this.menubarContainer.style.right = '0px';
903         this.toolbarContainer.style.left = '0px';
904         this.toolbarContainer.style.right = '0px';
905         this.sidebarContainer.style.left = '0px';
906         this.outlineContainer.style.left = '0px';
907         this.diagramContainer.style.right = '0px';
908         this.footerContainer.style.left = '0px';
909         this.footerContainer.style.right = '0px';
910         this.footerContainer.style.bottom = '0px';
911         this.vsplit.style.left = '0px';
912         this.vsplit.style.height = this.splitSize + 'px';
913         this.hsplit.style.width = this.splitSize + 'px';
914 };
915
916 /**
917  * Creates the required containers.
918  */
919 EditorUi.prototype.createUi = function() {
920         // Creates menubar
921         this.menubar = this.menus.createMenubar(this.createDiv('geMenubar'));
922         this.menubarContainer.appendChild(this.menubar.container);
923
924         // Creates toolbar
925         this.toolbar = this.createToolbar(this.createDiv('geToolbar'));
926         this.toolbarContainer.appendChild(this.toolbar.container);
927
928         // Creates the sidebar
929         this.sidebar = this.createSidebar(this.sidebarContainer);
930
931         // Creates the footer
932         this.footerContainer.appendChild(this.createFooter());
933
934         // Adds status bar in menubar
935         this.statusContainer = this.createStatusContainer();
936
937         // Connects the status bar to the editor status
938         this.editor.addListener('statusChanged', mxUtils.bind(this, function() {
939                 this.setStatusText(this.editor.getStatus());
940         }));
941
942         this.setStatusText(this.editor.getStatus());
943         this.menubar.container.appendChild(this.statusContainer);
944
945         // Inserts into DOM
946         this.container.appendChild(this.menubarContainer);
947         this.container.appendChild(this.toolbarContainer);
948         this.container.appendChild(this.sidebarContainer);
949         this.container.appendChild(this.outlineContainer);
950         this.container.appendChild(this.diagramContainer);
951         this.container.appendChild(this.footerContainer);
952         this.container.appendChild(this.hsplit);
953         this.container.appendChild(this.vsplit);
954
955         // HSplit
956         this.addSplitHandler(this.hsplit, true, 0, mxUtils.bind(this, function(
957                 value) {
958                 this.hsplitPosition = value;
959                 this.refresh();
960                 this.editor.graph.sizeDidChange();
961                 this.editor.outline.update(false);
962                 this.editor.outline.outline.sizeDidChange();
963         }));
964
965         // VSplit
966         this.addSplitHandler(this.vsplit, false, this.footerHeight, mxUtils.bind(
967                 this,
968                 function(value) {
969                         this.vsplitPosition = value;
970                         this.refresh();
971                         this.editor.outline.update(false);
972                         this.editor.outline.outline.sizeDidChange();
973                 }));
974 };
975
976 /**
977  * Creates a new toolbar for the given container.
978  */
979 EditorUi.prototype.createStatusContainer = function() {
980         var container = document.createElement('a');
981         container.className = 'geItem geStatus';
982
983         return container;
984 };
985
986 /**
987  * Creates a new toolbar for the given container.
988  */
989 EditorUi.prototype.createStatusContainer = function() {
990         var container = document.createElement('a');
991         container.className = 'geItem geStatus';
992
993         return container;
994 };
995
996 /**
997  * Creates a new toolbar for the given container.
998  */
999 EditorUi.prototype.setStatusText = function(value) {
1000         this.statusContainer.innerHTML = value;
1001 };
1002
1003 /**
1004  * Creates a new toolbar for the given container.
1005  */
1006 EditorUi.prototype.createToolbar = function(container) {
1007         return new Toolbar(this, container);
1008 };
1009
1010 /**
1011  * Creates a new sidebar for the given container.
1012  */
1013 EditorUi.prototype.createSidebar = function(container) {
1014         return new Sidebar(this, container);
1015 };
1016
1017 /**
1018  * Creates and returns a new footer.
1019  */
1020 EditorUi.prototype.createFooter = function() {
1021         return this.createDiv('geFooter');
1022 };
1023
1024 /**
1025  * Creates the actual toolbar for the toolbar container.
1026  */
1027 EditorUi.prototype.createDiv = function(classname) {
1028         var elt = document.createElement('div');
1029         elt.className = classname;
1030
1031         return elt;
1032 };
1033
1034 /**
1035  * Creates the custom header.
1036  */
1037 EditorUi.prototype.createHeader = function(classname) {
1038         var elt = document.createElement(classname);
1039         return elt;
1040 };
1041
1042 /**
1043  * Creates the custom header.
1044  */
1045 EditorUi.prototype.createForm = function(classname) {
1046         var elt = document.createElement('form');
1047         elt.className = classname;
1048
1049         return elt;
1050 };
1051
1052 /**
1053  * Updates the states of the given undo/redo items.
1054  */
1055 EditorUi.prototype.addSplitHandler = function(elt, horizontal, dx, onChange) {
1056         var start = null;
1057         var initial = null;
1058
1059         function getValue() {
1060                 return parseInt(((horizontal) ? elt.style.left : elt.style.bottom));
1061         };
1062
1063         function moveHandler(evt) {
1064                 if (start != null) {
1065                         var pt = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));
1066                         onChange(Math.max(0, initial + ((horizontal) ? (pt.x - start.x) : (start.y -
1067                                 pt.y)) - dx));
1068                         mxEvent.consume(evt);
1069                 }
1070         };
1071
1072         function dropHandler(evt) {
1073                 moveHandler(evt);
1074                 start = null;
1075                 initial = null;
1076         };
1077
1078         mxEvent.addGestureListeners(elt, function(evt) {
1079                 start = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));
1080                 initial = getValue();
1081                 mxEvent.consume(evt);
1082         });
1083
1084         mxEvent.addListener(document, 'mousemove', moveHandler);
1085         mxEvent.addListener(document, 'touchmove', moveHandler);
1086         mxEvent.addListener(document, 'mouseup', dropHandler);
1087         mxEvent.addListener(document, 'touchend', dropHandler);
1088 };
1089
1090 /**
1091  * Displays a print dialog.
1092  */
1093 EditorUi.prototype.showDialog = function(elt, w, h, modal, closable, onClose) {
1094         this.hideDialog();
1095         this.dialog = new Dialog(this, elt, w, (mxClient.IS_VML) ? h - 12 : h,
1096                 modal,
1097                 closable, onClose);
1098 };
1099
1100 /**
1101  * Displays a print dialog.
1102  */
1103 EditorUi.prototype.hideDialog = function() {
1104         if (this.dialog != null) {
1105                 this.dialog.close();
1106                 this.dialog = null;
1107                 this.editor.graph.container.focus();
1108         }
1109 };
1110
1111 /**
1112  * Adds the label menu items to the given menu and parent.
1113  */
1114 EditorUi.prototype.saveFile = function(forceDialog) {
1115         if (!forceDialog && this.editor.filename != null) {
1116                 this.save(this.editor.getOrCreateFilename());
1117         } else {
1118                 this.showDialog(new SaveDialog(this).container, 300, 100, true, true);
1119         }
1120 };
1121
1122 /**
1123  * Adds the label menu items to the given menu and parent.
1124  */
1125 EditorUi.prototype.publishFile = function(forceDialog) {
1126         if (!forceDialog && this.editor.filename != null) {
1127                 this.publish(this.editor.getOrCreateFilename());
1128         } else {
1129                 //this.showDialog(new SaveDialog(this).container, 300, 100, true, true);
1130         }
1131 };
1132
1133 /**
1134  * Executes the given layout.
1135  */
1136 EditorUi.prototype.executeLayout = function(layout, animate, ignoreChildCount) {
1137         var graph = this.editor.graph;
1138         var cell = graph.getSelectionCell();
1139
1140         // Allow global overridding of animation
1141         animate = this.animate != null ? this.animate : animate;
1142
1143         graph.getModel().beginUpdate();
1144         try {
1145                 layout.execute(graph.getDefaultParent(), cell);
1146         } catch (e) {
1147                 throw e;
1148         } finally {
1149                 // Animates the changes in the graph model except
1150                 // for Camino, where animation is too slow
1151                 if (animate && navigator.userAgent.indexOf('Camino') < 0) {
1152                         // New API for animating graph layout results asynchronously
1153                         var morph = new mxMorphing(graph);
1154                         morph.addListener(mxEvent.DONE, mxUtils.bind(this, function() {
1155                                 graph.getModel().endUpdate();
1156                         }));
1157
1158                         morph.startAnimation();
1159                 } else {
1160                         graph.getModel().endUpdate();
1161                 }
1162         }
1163 };
1164
1165 /**
1166  * Creates the keyboard event handler for the current graph and history.
1167  */
1168 EditorUi.prototype.createKeyHandler = function(editor) {
1169         var graph = this.editor.graph;
1170         var keyHandler = new mxKeyHandler(graph);
1171
1172         // Routes command-key to control-key on Mac
1173         keyHandler.isControlDown = function(evt) {
1174                 return mxEvent.isControlDown(evt) || (mxClient.IS_MAC && evt.metaKey);
1175         };
1176
1177         // Helper function to move cells with the cursor keys
1178         function nudge(keyCode) {
1179                 if (!graph.isSelectionEmpty()) {
1180                         var dx = 0;
1181                         var dy = 0;
1182
1183                         if (keyCode == 37) {
1184                                 dx = -1;
1185                         } else if (keyCode == 38) {
1186                                 dy = -1;
1187                         } else if (keyCode == 39) {
1188                                 dx = 1;
1189                         } else if (keyCode == 40) {
1190                                 dy = 1;
1191                         }
1192
1193                         graph.moveCells(graph.getSelectionCells(), dx, dy);
1194                         graph.scrollCellToVisible(graph.getSelectionCell());
1195                 }
1196         };
1197
1198         // Binds keystrokes to actions
1199         var bindAction = mxUtils.bind(this, function(code, control, key, shift) {
1200                 var action = this.actions.get(key);
1201
1202                 if (action != null) {
1203                         var f = function() {
1204                                 if (action.enabled) {
1205                                         action.funct();
1206                                 }
1207                         };
1208
1209                         if (control) {
1210                                 if (shift) {
1211                                         keyHandler.bindControlShiftKey(code, f);
1212                                 } else {
1213                                         keyHandler.bindControlKey(code, f);
1214                                 }
1215                         } else {
1216                                 if (shift) {
1217                                         keyHandler.bindShiftKey(code, f);
1218                                 } else {
1219                                         keyHandler.bindKey(code, f);
1220                                 }
1221                         }
1222                 }
1223         });
1224
1225         var ui = this;
1226         var keyHandleEscape = keyHandler.escape;
1227         keyHandler.escape = function(evt) {
1228                 ui.hideDialog();
1229                 keyHandleEscape.apply(this, arguments);
1230         };
1231
1232         // Ignores enter keystroke. Remove this line if you want the
1233         // enter keystroke to stop editing.
1234         keyHandler.enter = function() {};
1235         keyHandler.bindKey(8, function() {
1236                 graph.foldCells(true);
1237         }); // Backspace
1238         keyHandler.bindKey(13, function() {
1239                 graph.foldCells(false);
1240         }); // Enter
1241         keyHandler.bindKey(33, function() {
1242                 graph.exitGroup();
1243         }); // Page Up
1244         keyHandler.bindKey(34, function() {
1245                 graph.enterGroup();
1246         }); // Page Down
1247         keyHandler.bindKey(36, function() {
1248                 graph.home();
1249         }); // Home
1250         keyHandler.bindKey(35, function() {
1251                 graph.refresh();
1252         }); // End
1253         keyHandler.bindKey(37, function() {
1254                 nudge(37);
1255         }); // Left arrow
1256         keyHandler.bindKey(38, function() {
1257                 nudge(38);
1258         }); // Up arrow
1259         keyHandler.bindKey(39, function() {
1260                 nudge(39);
1261         }); // Right arrow
1262         keyHandler.bindKey(40, function() {
1263                 nudge(40);
1264         }); // Down arrow
1265         keyHandler.bindKey(113, function() {
1266                 graph.startEditingAtCell();
1267         });
1268         bindAction(46, false, 'delete'); // Delete
1269         bindAction(82, true, 'tilt'); // Ctrl+R
1270         bindAction(83, true, 'save'); // Ctrl+S
1271         bindAction(83, true, 'saveAs', true); // Ctrl+Shift+S
1272         bindAction(107, false, 'zoomIn'); // Add
1273         bindAction(109, false, 'zoomOut'); // Subtract
1274         bindAction(65, true, 'selectAll'); // Ctrl+A
1275         bindAction(86, true, 'selectVertices', true); // Ctrl+Shift+V
1276         bindAction(69, true, 'selectEdges', true); // Ctrl+Shift+E
1277         bindAction(69, true, 'export'); // Ctrl+Shift+E
1278         bindAction(66, true, 'toBack'); // Ctrl+B
1279         bindAction(70, true, 'toFront'); // Ctrl+F
1280         bindAction(68, true, 'duplicate'); // Ctrl+D
1281         bindAction(90, true, 'undo'); // Ctrl+Z
1282         bindAction(89, true, 'redo'); // Ctrl+Y
1283         bindAction(88, true, 'cut'); // Ctrl+X
1284         bindAction(67, true, 'copy'); // Ctrl+C
1285         bindAction(81, true, 'connect'); // Ctrl+Q
1286         bindAction(86, true, 'paste'); // Ctrl+V
1287         bindAction(71, true, 'group'); // Ctrl+G
1288         bindAction(71, true, 'grid', true); // Ctrl+Shift+G
1289         bindAction(85, true, 'ungroup'); // Ctrl+U
1290         bindAction(112, false, 'about'); // F1
1291         bindAction(80, true, 'publish', true); // Ctrl+Shift+P
1292
1293         return keyHandler;
1294 };
1295
1296 /**
1297  * Giuseppe Careri
1298  * Creates a new toolbar for the given container.
1299  */
1300 EditorUi.prototype.createMultiplicities = function(graph, cells, source,
1301         target) {
1302         for (var i = 0; i < cells.length; i++) {
1303                 graph.push(new mxMultiplicity(true, cells[i], null, null, source[i][0],
1304                         source[i][1], null, mxResources.get(cells[i]) + ' must have ' + source[i][0] + ' outgoing edge.', null));
1305                 graph.push(new mxMultiplicity(false, cells[i], null, null, target[i][0],
1306                         target[i][1], null, mxResources.get(cells[i]) + ' must have ' + target[i][0] + ' incoming edge.', null));
1307         };
1308 };