Browse Source

5.6.0.4 release

Gaudenz Alder 9 years ago
parent
commit
884146df91

+ 14 - 0
ChangeLog

@@ -1,3 +1,17 @@
+12-SEP-2016: 5.6.0.4
+
+- Uses mxGraph 3.6.0.1
+- Adds reset for undo history of in-place editor
+- Fixes inconsistent selection check for cursor keys
+- Adds hash listener for pages with url parameters
+- Fixes PDF, image with XML export for multiple pages
+- File ID has precedence over create, url parameters
+- Adds validation for grid size in page setup dialog
+- Fixes fullscreen toggle in embed mode for Kennedy
+- Adds keyboard shortcuts in toolbar tooltips
+- Uses mxfile wrapper for embedded HTML files
+- Adds optional callback argument in App.main
+
 07-SEP-2016: 5.6.0.3
 
 - Uses mxGraph 3.6

+ 1 - 1
README.md

@@ -21,4 +21,4 @@ The simplest way to run draw.io initially is to fork this project, [publish the
 
 Supported Browsers
 ------------------
-draw.io supports IE 9+, Chrome 30+, Firefox 31+, Safari versions actively patched by Apple (6.2.x, 7.1.x, 8.0.x and 9.x at time of writing), Opera 20+, Native Android browser 5.x+, the default browser in the current and previous major iOS versions (e.g. 9.x and 8.x) and Edge 20+.
+draw.io supports IE 9+, Chrome 30+, Firefox 31+, Safari versions actively patched by Apple (6.2.x, 7.1.x, 8.0.x and 9.x at time of writing), Opera 20+, Native Android browser 5.x+, the default browser in the current and previous major iOS versions (e.g. 9.x and 8.x) and Edge 20+.

+ 1 - 1
VERSION

@@ -1 +1 @@
-5.6.0.3
+5.6.0.4

+ 1 - 0
etc/build/build.xml

@@ -488,6 +488,7 @@
 				<exclude name="glyphicons_blogger.png"/>
 				<exclude name="glyphicons_google.png"/>
 				<exclude name="glyphicons_twitter.png"/>
+				<exclude name="glyphicons_github.png"/>
 				<exclude name="stop-flat-icon-80.png"/>
 				<exclude name="1x1.png"/>
 				<exclude name="2x2.png"/>

File diff suppressed because it is too large
+ 10 - 10
etc/mxgraph/mxClient.js


+ 0 - 0
etc/sandstorm/build.sh


+ 0 - 0
etc/sandstorm/stage.sh


+ 1 - 1
src/com/mxgraph/io/gliffy/importer/GliffyDiagramConverter.java

@@ -331,7 +331,7 @@ public class GliffyDiagramConverter {
 				GliffyImage image = graphic.getImage();
 				cell.setVertex(true);
 				style.append("shape=" + StencilTranslator.translate(gliffyObject.uid)).append(";");
-				style.append("image=" + image.getUrl());
+				style.append("image=" + image.getUrl()).append(";");
 
 				text = gliffyObject.getText();
 			}

+ 2 - 1
war/cache.manifest

@@ -1,7 +1,7 @@
 CACHE MANIFEST
 
 # THIS FILE WAS GENERATED. DO NOT MODIFY!
-# 09/07/2016 03:32 PM
+# 09/12/2016 12:53 PM
 
 /app.html
 /index.html?offline=1
@@ -49,6 +49,7 @@ CACHE MANIFEST
 /images/glyphicons_google.png
 /images/glyphicons_facebook.png
 /images/glyphicons_twitter.png
+/images/glyphicons_github.png
 /js/jscolor/arrow.gif
 /js/jscolor/hs.png
 /js/jscolor/cross.gif

BIN
war/images/glyphicons_github.png


+ 4 - 0
war/index.html

@@ -353,6 +353,10 @@
     	<a href="https://www.twitter.com/drawio" title="draw.io on Twitter" target="_blank">
     		<img border="0" width="24" height="24" src="images/glyphicons_twitter.png" alt="draw.io on Twitter"/>
     	</a>
+    	&nbsp;
+    	<a href="https://github.com/jgraph/draw.io" title="draw.io on GitHub" target="_blank">
+    		<img border="0" width="24" height="24" src="images/glyphicons_github.png" alt="draw.io on GitHub"/>
+    	</a>
 	</div>
 	<table align="center">
 		<tr>

File diff suppressed because it is too large
+ 572 - 571
war/js/app.min.js


File diff suppressed because it is too large
+ 46 - 46
war/js/atlas-viewer.min.js


File diff suppressed because it is too large
+ 5779 - 5778
war/js/atlas.min.js


+ 131 - 162
war/js/diagramly/App.js

@@ -353,8 +353,10 @@ App.getStoredMode = function()
 
 /**
  * Program flow starts here.
+ * 
+ * Optional callback is called with the app instance.
  */
-App.main = function()
+App.main = function(callback)
 {
 	var lastErrorMessage = null;
 	
@@ -539,6 +541,11 @@ App.main = function()
 			}
 		}
 		
+		if (callback != null)
+		{
+			callback(ui);
+		}
+		
 		/**
 		 * For developers only
 		 */
@@ -777,18 +784,18 @@ App.prototype.init = function()
 					this.drive.addListener('userChanged', mxUtils.bind(this, function()
 					{
 						// Changes the footer ads for Google Accounts
-//						if (this.updateAd != null)
-//						{
-//							this.adsHtml = ['<a title="Quick start video" href="https://www.youtube.com/watch?v=8OaMWa4R1SE&t=1" target="_blank">' +
-//											'<img border="0" align="absmiddle" style="margin-top:-4px;" src="images/glyphicons_star.png"/>&nbsp;&nbsp;Quick start video</a>',
-//											'<a title="Google Docs Add-on" href="https://chrome.google.com/webstore/detail/drawio-diagrams/clpbjldiohnnmfmkngmaohehlnfkmoea" target="_blank">' +
-//											'<img border="0" align="absmiddle" style="margin-top:-4px;" src="images/glyphicons_star.png"/>&nbsp;&nbsp;Google Docs Add-on</a>',
-//											'<a title="Google Chrome App" href="https://chrome.google.com/webstore/detail/drawio-desktop/pebppomjfocnoigkeepgbmcifnnlndla" target="_blank">' +
-//											'<img border="0" align="absmiddle" style="margin-top:-4px;" src="images/glyphicons_star.png"/>&nbsp;&nbsp;Google Chrome App</a>',
-//											'<a title="Please help us to 5 stars" href="https://chrome.google.com/webstore/detail/drawio-pro/onlkggianjhjenigcpigpjehhpplldkc/reviews" target="_blank">' +
-//											'<img border="0" align="absmiddle" style="margin-top:-4px;" src="images/glyphicons_star.png"/>&nbsp;&nbsp;Please help us to 5 stars</a>'];
-//							this.updateAd(this.adsHtml.length - 1);
-//						}
+						if (this.updateAd != null)
+						{
+							this.adsHtml = ['<a title="Quick start video" href="https://www.youtube.com/watch?v=8OaMWa4R1SE&t=1" target="_blank">' +
+											'<img border="0" align="absmiddle" style="margin-top:-4px;" src="images/glyphicons_star.png"/>&nbsp;&nbsp;Quick start video</a>',
+											'<a title="Google Docs Add-on" href="https://chrome.google.com/webstore/detail/drawio-diagrams/clpbjldiohnnmfmkngmaohehlnfkmoea" target="_blank">' +
+											'<img border="0" align="absmiddle" style="margin-top:-4px;" src="images/glyphicons_star.png"/>&nbsp;&nbsp;Google Docs Add-on</a>',
+											'<a title="Google Chrome App" href="https://chrome.google.com/webstore/detail/drawio-desktop/pebppomjfocnoigkeepgbmcifnnlndla" target="_blank">' +
+											'<img border="0" align="absmiddle" style="margin-top:-4px;" src="images/glyphicons_star.png"/>&nbsp;&nbsp;Google Chrome App</a>',
+											'<a title="Please help us to 5 stars" href="https://chrome.google.com/webstore/detail/drawio-pro/onlkggianjhjenigcpigpjehhpplldkc/reviews" target="_blank">' +
+											'<img border="0" align="absmiddle" style="margin-top:-4px;" src="images/glyphicons_star.png"/>&nbsp;&nbsp;Please help us to 5 stars</a>'];
+							this.updateAd(this.adsHtml.length - 1);
+						}
 						
 						this.updateUserElement();
 						this.restoreLibraries();
@@ -1596,11 +1603,7 @@ App.prototype.createFileData = function(node, graph, file, url, forceXml, forceS
 		redirect = editLink;
 	}
 		
-	if (!forceSvg && !forceXml && (forceHtml || (file != null && /(\.html)$/i.test(file.getTitle()))))
-	{
-		return this.getHtml2(node, graph, file.getTitle(), editLink, redirect, ignoreSelection);
-	}
-	else if (node == null)
+	if (node == null)
 	{
 		return '';
 	}
@@ -1608,7 +1611,7 @@ App.prototype.createFileData = function(node, graph, file, url, forceXml, forceS
 	{
 		var fileNode = node;
 
-		// Ignores case for for possible HTML or XML nodes
+		// Ignores case for possible HTML or XML nodes
 		if (fileNode.nodeName.toLowerCase() != 'mxfile')
 		{
 			// Removes control chars in input for correct roundtrip check
@@ -1643,9 +1646,14 @@ App.prototype.createFileData = function(node, graph, file, url, forceXml, forceS
 		}
 				
 		var xml = mxUtils.getXml(fileNode);
-
+		
+		// Writes the file as an embedded HTML file
+		if (!forceSvg && !forceXml && (forceHtml || (file != null && /(\.html)$/i.test(file.getTitle()))))
+		{
+			xml = this.getHtml2(fileNode, graph, file.getTitle(), editLink, redirect, ignoreSelection);
+		}
 		// Maps the XML data to the content attribute in the SVG node 
-		if (forceSvg || (!forceXml && file != null && /(\.svg)$/i.test(file.getTitle())))
+		else if (forceSvg || (!forceXml && file != null && /(\.svg)$/i.test(file.getTitle())))
 		{
 			if (file != null && (file.getMode() == App.MODE_DEVICE || file.getMode() == App.MODE_BROWSER))
 			{
@@ -1665,9 +1673,11 @@ App.prototype.createFileData = function(node, graph, file, url, forceXml, forceS
  * @param {number} dx X-coordinate of the translation.
  * @param {number} dy Y-coordinate of the translation.
  */
-App.prototype.getFileData = function(forceXml, forceSvg, forceHtml, embeddedCallback, ignoreSelection)
+App.prototype.getFileData = function(forceXml, forceSvg, forceHtml, embeddedCallback, ignoreSelection, currentPage)
 {
 	ignoreSelection = (ignoreSelection != null) ? ignoreSelection : true;
+	currentPage = (currentPage != null) ? currentPage : false;
+	
 	var node = this.editor.getGraphXml(ignoreSelection);
 		
 	if (ignoreSelection && this.fileNode != null && this.currentPage != null)
@@ -1676,28 +1686,35 @@ App.prototype.getFileData = function(forceXml, forceSvg, forceHtml, embeddedCall
 		mxUtils.setTextContent(this.currentPage.node, data);
 		node = this.fileNode.cloneNode(false);
 		
-		// Restores order of pages
-		for (var i = 0; i < this.pages.length; i++)
+		if (currentPage)
 		{
-			var mapping = this.pages[i].mapping;
-			
-			// Updates XML of all pages for realtime
-			if (this.currentPage != this.pages[i] && mapping != null && mapping.needsUpdate)
+			node.appendChild(this.currentPage.node);
+		}
+		else
+		{
+			// Restores order of pages
+			for (var i = 0; i < this.pages.length; i++)
 			{
-				var enc = new mxCodec(mxUtils.createXmlDocument());
-				var temp = enc.encode(mapping.graphModel);
-			
-				// Uses the graph state from the realtime model
-				mapping.writeRealtimeToNode(temp);					
-
-				var data = this.editor.graph.compress(this.editor.graph.zapGremlins(mxUtils.getXml(temp)));
-				mxUtils.setTextContent(this.pages[i].node, data);
+				var mapping = this.pages[i].mapping;
 				
-				// Marks the page as up-to-date
-				mapping.needsUpdate = false;
+				// Updates XML of all pages for realtime
+				if (this.currentPage != this.pages[i] && mapping != null && mapping.needsUpdate)
+				{
+					var enc = new mxCodec(mxUtils.createXmlDocument());
+					var temp = enc.encode(mapping.graphModel);
+				
+					// Uses the graph state from the realtime model
+					mapping.writeRealtimeToNode(temp);					
+	
+					var data = this.editor.graph.compress(this.editor.graph.zapGremlins(mxUtils.getXml(temp)));
+					mxUtils.setTextContent(this.pages[i].node, data);
+					
+					// Marks the page as up-to-date
+					mapping.needsUpdate = false;
+				}
+				
+				node.appendChild(this.pages[i].node);
 			}
-			
-			node.appendChild(this.pages[i].node);
 		}
 	}
 	
@@ -1858,10 +1875,7 @@ App.prototype.getDiagramId = function()
 };
 
 /**
- * Main function. Program starts here.
- * 
- * @param {number} dx X-coordinate of the translation.
- * @param {number} dy Y-coordinate of the translation.
+ * Opens any file specified in the URL parameters.
  */
 App.prototype.open = function()
 {
@@ -2123,7 +2137,24 @@ App.prototype.start = function()
 		}), onerror, /(\.png)($|\?)/i.test(url));
 	});
 	
-	if (urlParams['url'] != null && this.spinner.spin(document.body, mxResources.get('loading')))
+	// Listens to changes of the hash if not in embed or client mode
+	if (urlParams['client'] != '1' && urlParams['embed'] != '1')
+	{
+		// KNOWN: Does not work in quirks mode
+		mxEvent.addListener(window, 'hashchange', mxUtils.bind(this, function(evt)
+		{
+			var id = this.getDiagramId();
+			var file = this.getCurrentFile();
+			
+			if (file == null || file.getHash() != id)
+			{
+				this.loadFile(id, true);
+			}
+		}));
+	}
+	
+	if ((window.location.hash == null || window.location.hash.length <= 1) &&
+		urlParams['url'] != null && this.spinner.spin(document.body, mxResources.get('loading')))
 	{
 		try
 		{
@@ -2308,7 +2339,8 @@ App.prototype.start = function()
 		
 		var value = decodeURIComponent(urlParams['create'] || '');
 		
-		if (value != null && value.length > 0 && this.spinner.spin(document.body, mxResources.get('loading')))
+		if ((window.location.hash == null || window.location.hash.length <= 1) &&
+			value != null && value.length > 0 && this.spinner.spin(document.body, mxResources.get('loading')))
 		{
 			var reconnect = mxUtils.bind(this, function()
 			{
@@ -2422,19 +2454,6 @@ App.prototype.start = function()
 			}
 			else
 			{
-				// Listens to changes of the hash
-				// Known: Does not work in quirks mode
-				mxEvent.addListener(window, 'hashchange', mxUtils.bind(this, function(evt)
-				{
-					var id = this.getDiagramId();
-					var file = this.getCurrentFile();
-					
-					if (file == null || file.getHash() != id)
-					{
-						this.loadFile(id, true);
-					}
-				}));
-				
 				done();
 			}
 		}
@@ -3141,79 +3160,47 @@ App.prototype.fileCreated = function(file, libs, replace, done)
 		{
 			this.spinner.stop();
 			
-			if (this.mode == null && (urlParams['create'] != null || urlParams['url'] != null))
+			var fn2 = mxUtils.bind(this, function()
 			{
-				if (file.constructor == LocalFile)
+				window.openFile = null;
+				this.fileLoaded(file);
+				
+				if (libs != null)
 				{
-					// LATER: Remove create, title and mode URL params
-					this.fileLoaded(file);
+					this.sidebar.showEntries(libs);
 				}
-				else if (this.spinner.spin(document.body, mxResources.get('inserting')))
+
+				if (done != null)
 				{
-					// Makes sure the file is not loaded when the hash changes
-					this.setCurrentFile(file);
-					window.location.hash = file.getHash();
-					
-					// Removes create URL parameter and reloads page
-					window.location.search = this.getSearch(['create', 'title', 'notitle', 'mode', 'url']);
+					done();
 				}
-			}
-			else
+			});
+	
+			// Updates the file if it has been overwritten
+			if (!replace && this.getCurrentFile() != null && this.mode != null)
 			{
-				var fn2 = mxUtils.bind(this, function()
+				// Opens local file in a new window
+				if (file.constructor == LocalFile)
 				{
-					// Replaces current URL for non-local files if user wants to open in same window
-					if (file.constructor != LocalFile && (urlParams['create'] != null || urlParams['url'] != null))
-					{
-						this.setCurrentFile(file);
-						window.location.hash = file.getHash();
-						
-						// Removes URL parameters and reloads page
-						window.location.search = this.getSearch(['create', 'title', 'notitle', 'mode', 'url']);
-					}
-					else
+					window.openFile = new OpenFile(function()
 					{
 						window.openFile = null;
-						this.fileLoaded(file);
+					});
 						
-						if (libs != null)
-						{
-							this.sidebar.showEntries(libs);
-						}
-					}
-
-					if (done != null)
-					{
-						done();
-					}
-				});
-		
-				// Updates the file if it has been overwritten
-				if (!replace && (this.getCurrentFile() != null && (decodeURIComponent(this.getDiagramId()) !=
-					decodeURIComponent(file.getHash()) || file.constructor == LocalFile)))
-				{
-					// Opens local file in a new window
-					if (file.constructor == LocalFile)
-					{
-						window.openFile = new OpenFile(function()
-						{
-							window.openFile = null;
-						});
-							
-						window.openFile.setData(file.getData(), file.getTitle());
-					}
-
-					window.openWindow(url, null, fn2);
-				}
-				else
-				{
-					fn2();
+					window.openFile.setData(file.getData(), file.getTitle());
 				}
+
+				window.openWindow(url, null, fn2);
+			}
+			else
+			{
+				fn2();
 			}
 		});
 		
-		// Updates data in memory for local files
-		if (file.constructor == LocalFile)
+		// Updates data in memory for local files and save is implicit
+		// via start of realtime for DriveFiles
+		if (file.constructor == LocalFile || file.constructor == DriveFile)
 		{
 			fn();
 		}
@@ -4087,8 +4074,10 @@ App.prototype.fileLoaded = function(file)
 	{
 		try
 		{
-			file.open();
+			// Order is significant, current file needed for correct
+			// file format for initial save after starting realtime
 			this.setCurrentFile(file);
+			file.open();
 			this.diagramContainer.style.visibility = '';
 			this.formatContainer.style.visibility = '';
 			
@@ -4560,44 +4549,6 @@ EditorUi.prototype.exportSvg = function(scale, transparentBackground, ignoreSele
 	}
 };
 
-/**
- * Translates this point by the given vector.
- * 
- * @param {number} dx X-coordinate of the translation.
- * @param {number} dy Y-coordinate of the translation.
- */
-App.prototype.getOrCreateVoiceButton = function()
-{
-	if (this.voiceButton == null)
-	{
-		this.voiceButton = document.createElement('div');
-		this.voiceButton.className = 'geBtn';
-		this.voiceButton.style.width = '140px';
-		this.voiceButton.style.minWidth = '140px';
-		this.voiceButton.style.textOverflow = 'ellipsis';
-		this.voiceButton.style.overflowX = 'hidden';
-		this.voiceButton.style.fontWeight = 'bold';
-		this.voiceButton.style.textAlign = 'center';
-		this.voiceButton.style.display = 'inline-block';
-		this.voiceButton.style.padding = '0 10px 0 10px';
-		this.voiceButton.style.marginTop = '-4px';
-		this.voiceButton.style.height = '28px';
-		this.voiceButton.style.lineHeight = '28px';
-		this.voiceButton.style.color = '#235695';
-		
-		if (this.buttonContainer.firstChild != null)
-		{
-			this.buttonContainer.insertBefore(this.voiceButton, this.buttonContainer.firstChild);
-		}
-		else
-		{
-			this.buttonContainer.appendChild(this.voiceButton);
-		}
-	}
-	
-	return this.voiceButton;
-};
-
 /**
  * Translates this point by the given vector.
  * 
@@ -5420,7 +5371,12 @@ App.prototype.downloadFile = function(format, nonCompressed, addShadow, ignoreSe
 		else
 		{
 			var bounds = this.editor.graph.getGraphBounds();
-			var data = this.getFileData(true, null, null, null, ignoreSelection);
+			
+			// Exports only current page for PDF since it does not contain file data, but for
+			// the other formats with XML included we need to send the complete data and use
+			// the from/to URL parameters to specify the page to be exported.
+			var data = this.getFileData(true, null, null, null, ignoreSelection, format != 'xmlpng');
+			var range = '';
 			
 			if (bounds.width * bounds.height <= MAX_AREA && data.length <= MAX_REQUEST_SIZE)
 			{
@@ -5431,11 +5387,24 @@ App.prototype.downloadFile = function(format, nonCompressed, addShadow, ignoreSe
 		       		embed = '1';
 		       		format = 'png';
 		       		filename = basename + '.' + format;
+		       		
+		       		// Finds the current page number
+		       		if (this.pages != null && this.currentPage != null)
+		       		{
+		       			for (var i = 0; i < this.pages.length; i++)
+		       			{
+		       				if (this.pages[i] == this.currentPage)
+		       				{
+		       					range = '&from=' + i;
+		       					break;
+		       				}
+		       			}
+		       		}
 		       	}
 		       	
 				this.saveRequest(data, filename, format, function(newTitle, base64)
 				{
-					return new mxXmlRequest(EXPORT_URL, 'format=' + format +
+					return new mxXmlRequest(EXPORT_URL, 'format=' + format + range +
 						'&base64=' + base64 + '&embedXml=' + embed + '&xml=' +
 						encodeURIComponent(data) + ((newTitle != null) ?
 						'&filename=' + encodeURIComponent(newTitle) : ''));
@@ -5902,7 +5871,7 @@ App.prototype.updateHeader = function()
 		this.toggleFormatElement.style.position = 'absolute';
 		this.toggleFormatElement.style.display = 'inline-block';
 		this.toggleFormatElement.style.top = '5px';
-		this.toggleFormatElement.style.right = '26px';
+		this.toggleFormatElement.style.right = (uiTheme != 'atlas' && urlParams['embed'] != '1') ? '26px' : '10px';
 		this.toggleFormatElement.style.padding = '2px';
 		this.toggleFormatElement.style.fontSize = '14px';
 		this.toggleFormatElement.className = (uiTheme != 'atlas') ? 'geButton' : '';
@@ -5935,7 +5904,7 @@ App.prototype.updateHeader = function()
 		this.fullscreenElement.style.position = 'absolute';
 		this.fullscreenElement.style.display = 'inline-block';
 		this.fullscreenElement.style.top = '5px';
-		this.fullscreenElement.style.right = '42px';
+		this.fullscreenElement.style.right = (uiTheme != 'atlas' && urlParams['embed'] != '1') ? '42px' : '26px';
 		this.fullscreenElement.style.padding = '2px';
 		this.fullscreenElement.style.fontSize = '14px';
 		this.fullscreenElement.className = (uiTheme != 'atlas') ? 'geButton' : '';
@@ -5951,7 +5920,7 @@ App.prototype.updateHeader = function()
 
 		mxEvent.addListener(this.fullscreenElement, 'click', mxUtils.bind(this, function(evt)
 		{
-			if (uiTheme != 'atlas')
+			if (uiTheme != 'atlas' && urlParams['embed'] != '1')
 			{
 				this.toggleCompactMode(!collapsed);
 			}

+ 1 - 1
war/js/diagramly/EditorUi.js

@@ -5195,7 +5195,7 @@
 			
 			this.toolbar.container.appendChild(div);
 			this.toolbar.staticElements.push(div);
-			div.style.right = '42px';
+			div.style.right = (uiTheme != 'atlas') ? '52px' : '42px';
 		}
 	};
 

File diff suppressed because it is too large
+ 10 - 10
war/js/embed-static.min.js


+ 7 - 1
war/js/mxgraph/Dialogs.js

@@ -687,6 +687,12 @@ var PageSetupDialog = function(editorUi)
 	gridSizeInput.value = graph.getGridSize();
 	td.appendChild(gridSizeInput);
 	
+	mxEvent.addListener(gridSizeInput, 'change', function()
+	{
+		var value = parseInt(gridSizeInput.value);
+		gridSizeInput.value = Math.max(1, (isNaN(value)) ? graph.getGridSize() : value);
+	});
+	
 	row.appendChild(td);
 	tbody.appendChild(row);
 	
@@ -787,7 +793,7 @@ var PageSetupDialog = function(editorUi)
 		
 		if (graph.gridSize !== gridSizeInput.value)
 		{
-			graph.setGridSize(parseFloat(gridSizeInput.value));
+			graph.setGridSize(parseInt(gridSizeInput.value));
 		}
 	});
 	applyBtn.className = 'geBtn gePrimaryBtn';

+ 62 - 63
war/js/mxgraph/EditorUi.js

@@ -3399,69 +3399,6 @@ EditorUi.prototype.createKeyHandler = function(editor)
 		return mxEvent.isControlDown(evt) || (mxClient.IS_MAC && evt.metaKey);
 	};
 
-	// Overridden to handle special alt+shift+cursor keyboard shortcuts
-	var directions = {37: mxConstants.DIRECTION_WEST, 38: mxConstants.DIRECTION_NORTH,
-			39: mxConstants.DIRECTION_EAST, 40: mxConstants.DIRECTION_SOUTH};
-	
-	var keyHandlerGetFunction = keyHandler.getFunction;
-
-	mxKeyHandler.prototype.getFunction = function(evt)
-	{
-		if (directions[evt.keyCode] != null)
-		{
-			var cell = graph.getSelectionCell();
-			
-			if (graph.model.isVertex(cell))
-			{
-				if (mxEvent.isShiftDown(evt) && mxEvent.isAltDown(evt))
-				{
-					return function()
-					{
-						var cells = graph.connectVertex(cell, directions[evt.keyCode], graph.defaultEdgeLength, evt, true);
-		
-						if (cells != null && cells.length > 0)
-						{
-							if (cells.length == 1 && graph.model.isEdge(cells[0]))
-							{
-								graph.setSelectionCell(graph.model.getTerminal(cells[0], false));
-							}
-							else
-							{
-								graph.setSelectionCell(cells[cells.length - 1]);
-							}
-							
-							if (editorUi.hoverIcons != null)
-							{
-								editorUi.hoverIcons.update(graph.view.getState(graph.getSelectionCell()));
-							}
-						}
-					};
-				}
-				else
-				{
-					// Avoids consuming event if no vertex is selected by returning null below
-					// Cursor keys move and resize (ctrl) cells
-					if (this.isControlDown(evt))
-					{
-						return function()
-						{
-							nudge(evt.keyCode, (mxEvent.isShiftDown(evt)) ? graph.gridSize : null, true);
-						};
-					}
-					else
-					{
-						return function()
-						{
-							nudge(evt.keyCode, (mxEvent.isShiftDown(evt)) ? graph.gridSize : null);
-						};
-					}
-				}
-			}
-		}
-
-		return keyHandlerGetFunction.apply(this, arguments);
-	};
-
 	var queue = [];
 	var thread = null;
 	
@@ -3601,6 +3538,68 @@ EditorUi.prototype.createKeyHandler = function(editor)
 		}, 200);
 	};
 	
+	// Overridden to handle special alt+shift+cursor keyboard shortcuts
+	var directions = {37: mxConstants.DIRECTION_WEST, 38: mxConstants.DIRECTION_NORTH,
+			39: mxConstants.DIRECTION_EAST, 40: mxConstants.DIRECTION_SOUTH};
+	
+	var keyHandlerGetFunction = keyHandler.getFunction;
+
+	mxKeyHandler.prototype.getFunction = function(evt)
+	{
+		if (directions[evt.keyCode] != null && !graph.isSelectionEmpty())
+		{
+			if (mxEvent.isShiftDown(evt) && mxEvent.isAltDown(evt))
+			{
+				if (graph.model.isVertex(graph.getSelectionCell()))
+				{
+					return function()
+					{
+						var cells = graph.connectVertex(graph.getSelectionCell(), directions[evt.keyCode],
+							graph.defaultEdgeLength, evt, true);
+		
+						if (cells != null && cells.length > 0)
+						{
+							if (cells.length == 1 && graph.model.isEdge(cells[0]))
+							{
+								graph.setSelectionCell(graph.model.getTerminal(cells[0], false));
+							}
+							else
+							{
+								graph.setSelectionCell(cells[cells.length - 1]);
+							}
+							
+							if (editorUi.hoverIcons != null)
+							{
+								editorUi.hoverIcons.update(graph.view.getState(graph.getSelectionCell()));
+							}
+						}
+					};
+				}
+			}
+			else
+			{
+				// Avoids consuming event if no vertex is selected by returning null below
+				// Cursor keys move and resize (ctrl) cells
+				if (this.isControlDown(evt))
+				{
+					return function()
+					{
+						nudge(evt.keyCode, (mxEvent.isShiftDown(evt)) ? graph.gridSize : null, true);
+					};
+				}
+				else
+				{
+					return function()
+					{
+						nudge(evt.keyCode, (mxEvent.isShiftDown(evt)) ? graph.gridSize : null);
+					};
+				}
+			}
+		}
+
+		return keyHandlerGetFunction.apply(this, arguments);
+	};
+
 	// Binds keystrokes to actions
 	keyHandler.bindAction = mxUtils.bind(this, function(code, control, key, shift)
 	{

+ 7 - 18
war/js/mxgraph/Sidebar.js

@@ -1731,24 +1731,13 @@ Sidebar.prototype.createThumb = function(cells, width, height, parent, title, sh
 	this.graph.labelsVisible = (showLabel == null || showLabel);
 	var fo = mxClient.NO_FO;
 	mxClient.NO_FO = Editor.prototype.originalNoForeignObject;
-	
-	// Paints faster by using the known width and height
-	if (false && realWidth != null && realHeight != null)
-	{
-		var s = Math.floor(Math.min((width - 2 * this.thumbBorder) / realWidth, (height - 2 * this.thumbBorder) / realHeight) * 100) / 100;
-		this.graph.view.scaleAndTranslate(s, Math.floor((width - realWidth * s) / 2 / s), Math.floor((height - realHeight * s) / 2 / s));
-		this.graph.addCells(cells);
-	}
-	else
-	{
-		this.graph.view.scaleAndTranslate(1, 0, 0);
-		this.graph.addCells(cells);
-		var bounds = this.graph.getGraphBounds();
-		var s = Math.floor(Math.min((width - 2 * this.thumbBorder) / bounds.width, (height - 2 * this.thumbBorder)
-			/ bounds.height) * 100) / 100;
-		this.graph.view.scaleAndTranslate(s, Math.floor((width - bounds.width * s) / 2 / s - bounds.x),
-				Math.floor((height - bounds.height * s) / 2 / s - bounds.y));
-	}
+	this.graph.view.scaleAndTranslate(1, 0, 0);
+	this.graph.addCells(cells);
+	var bounds = this.graph.getGraphBounds();
+	var s = Math.floor(Math.min((width - 2 * this.thumbBorder) / bounds.width, (height - 2 * this.thumbBorder)
+		/ bounds.height) * 100) / 100;
+	this.graph.view.scaleAndTranslate(s, Math.floor((width - bounds.width * s) / 2 / s - bounds.x),
+			Math.floor((height - bounds.height * s) / 2 / s - bounds.y));
 	
 	var node = null;
 	

+ 8 - 1
war/js/mxgraph/Toolbar.js

@@ -723,7 +723,14 @@ Toolbar.prototype.addItem = function(sprite, key, c, ignoreDisabled)
 	
 	if (action != null)
 	{
-		elt = this.addButton(sprite, action.label, action.funct, c);
+		var tooltip = action.label;
+		
+		if (action.shortcut != null)
+		{
+			tooltip += ' (' + action.shortcut + ')';
+		}
+		
+		elt = this.addButton(sprite, tooltip, action.funct, c);
 
 		if (!ignoreDisabled)
 		{

File diff suppressed because it is too large
+ 10 - 10
war/js/reader.min.js


File diff suppressed because it is too large
+ 46 - 46
war/js/viewer.min.js


+ 34 - 2
war/plugins/voice.js

@@ -90,7 +90,39 @@ Draw.loadPlugin(function(ui) {
 	mxResources.parse('speechQuit=Quit');
 	
 	// Installs footer click handler
-	var td = ui.getOrCreateVoiceButton();
+	function getOrCreateVoiceButton(ui)
+	{
+		if (ui.voiceButton == null)
+		{
+			ui.voiceButton = document.createElement('div');
+			ui.voiceButton.className = 'geBtn';
+			ui.voiceButton.style.width = '140px';
+			ui.voiceButton.style.minWidth = '140px';
+			ui.voiceButton.style.textOverflow = 'ellipsis';
+			ui.voiceButton.style.overflowX = 'hidden';
+			ui.voiceButton.style.fontWeight = 'bold';
+			ui.voiceButton.style.textAlign = 'center';
+			ui.voiceButton.style.display = 'inline-block';
+			ui.voiceButton.style.padding = '0 10px 0 10px';
+			ui.voiceButton.style.marginTop = '-4px';
+			ui.voiceButton.style.height = '28px';
+			ui.voiceButton.style.lineHeight = '28px';
+			ui.voiceButton.style.color = '#235695';
+			
+			if (ui.buttonContainer.firstChild != null)
+			{
+				ui.buttonContainer.insertBefore(ui.voiceButton, ui.buttonContainer.firstChild);
+			}
+			else
+			{
+				ui.buttonContainer.appendChild(ui.voiceButton);
+			}
+		}
+		
+		return ui.voiceButton;
+	};
+
+	var td = getOrCreateVoiceButton(ui);
 	
 	if (td != null)
 	{
@@ -232,7 +264,7 @@ Draw.loadPlugin(function(ui) {
 					
 					if (index == currentVoice)
 					{
-						ui.menus.addCheckmark(item);
+						menu.addCheckmark(item, Editor.checkmarkImage);
 					}
 				})(i);
 			}