diff --git a/app/app.xhtml b/app/app.xhtml
index 0f8d5221..5cf29cff 100644
--- a/app/app.xhtml
+++ b/app/app.xhtml
@@ -211,6 +211,8 @@
+
+
diff --git a/app/index.js b/app/index.js
index ecbca651..a1f5539f 100644
--- a/app/index.js
+++ b/app/index.js
@@ -96,11 +96,13 @@ app.on("window-all-closed", function() {
app.on('ready', function() {
protocol.registerBufferProtocol("ref", function(request, callback) {
var path = request.url.substr(6);
- console.log("PATH", path);
fs.readFile(path, function (err, data) {
- console.log("Got data: ", data.length);
- callback({mimeType: "image/jpeg", data: new Buffer(data)});
+ if (err) {
+ callback({mimeType: "text/html", data: new Buffer("Not found")});
+ } else {
+ callback({mimeType: "image/jpeg", data: new Buffer(data)});
+ }
});
}, function (error, scheme) {
diff --git a/app/package.json b/app/package.json
index c6d87426..9b1e14ca 100644
--- a/app/package.json
+++ b/app/package.json
@@ -2,7 +2,7 @@
"name": "Pencil",
"productName": "Pencil",
"description": "An open-source GUI prototyping tool that is available for ALL platforms.",
- "version": "3.0.1",
+ "version": "3.0.2",
"author": {
"name": "Evolus",
"url": "http://evolus.vn",
@@ -43,20 +43,22 @@
"adm-zip": "^0.4.7",
"archive-type": "^3.2.0",
"archiver": "^1.3.0",
+ "decompress": "^4.0.0",
+ "decompress-targz": "^4.0.0",
"easy-zip2": "^1.0.0",
"electron-log": "^1.3.0",
"electron-updater": "^1.4.1",
"less": "~2.7.1",
"lodash": "^4.13.1",
+ "md5": "^2.2.1",
"moment": "^2.13.0",
"nugget": "^2.0.0",
"q": "^1.4.1",
"rimraf": "^2.5.4",
+ "tar": "https://github.com/dgthanhan/node-tar/archive/6086b1ea82137c61eea4efe882dd514590e5b7a8.tar.gz",
"tar.gz": "^1.0.5",
"tmp": "0.0.31",
- "unzip2": "^0.2.5",
- "decompress": "^4.0.0",
- "decompress-targz": "^4.0.0"
+ "unzip2": "^0.2.5"
},
"private": true
}
diff --git a/app/pencil-core/behavior/SVGTextLayout.js b/app/pencil-core/behavior/SVGTextLayout.js
index 92445670..eac3c872 100644
--- a/app/pencil-core/behavior/SVGTextLayout.js
+++ b/app/pencil-core/behavior/SVGTextLayout.js
@@ -260,10 +260,13 @@ SVGHTMLRenderer.HANDLERS = {
}
};
+SVGHTMLRenderer.STYLE_NAME_MAP = {
+ fill: "color"
+}
SVGHTMLRenderer.prototype.importDefaultStyleFromNode = function (node) {
for (var name in this.defaultStyle) {
var value = node.style[name];
- if (value) this.defaultStyle[name] = value;
+ if (value) this.defaultStyle[SVGHTMLRenderer.STYLE_NAME_MAP[name] || name] = value;
}
};
SVGHTMLRenderer.prototype.renderHTML = function (html, container, view) {
@@ -272,6 +275,7 @@ SVGHTMLRenderer.prototype.renderHTML = function (html, container, view) {
var div = doc.createElementNS(PencilNamespaces.html, "div");
div.style.position = "absolute";
div.style.display = "none";
+ div.style.textAlign = ["left", "center", "right"][this.hAlign || 0];
doc.body.appendChild(div);
for (var styleName in this.defaultStyle) {
@@ -296,7 +300,7 @@ SVGHTMLRenderer.prototype.render = function (nodes, container, view) {
var target = container;
if (vAlign > 0) {
var last = layouts[layouts.length - 1];
- var height = last.y + last.height;
+ var height = last.y + last.height - (view.y || 0);
dy = Math.round((this.height - height) * vAlign / 2);
var target = container.ownerDocument.createElementNS(PencilNamespaces.svg, "g");
target.setAttribute("transform", "translate(0," + dy + ")");
diff --git a/app/pencil-core/behavior/commonBehaviors.js b/app/pencil-core/behavior/commonBehaviors.js
index 36dc7c5c..30d29957 100644
--- a/app/pencil-core/behavior/commonBehaviors.js
+++ b/app/pencil-core/behavior/commonBehaviors.js
@@ -37,8 +37,7 @@ Pencil.behaviors.Color = function (color) {
Svg.setStyle(this, "fill", color.toRGBString());
Svg.setStyle(this, "fill-opacity", color.a);
} else {
- Svg.setStyle(this, "color", color ? color.toRGBString() : null);
- Svg.setStyle(this, "opacity", color ? color.a : null);
+ Svg.setStyle(this, "color", color ? color.toRGBAString() : null);
}
};
Pencil.behaviors.StrokeColor = function (color) {
@@ -646,8 +645,25 @@ Pencil.behaviors.NPatchDomContent = function (nPatch, dim) {
Pencil.behaviors.NPatchDomContentFromImage = function (imageData, dim, xAnchorMaps, yAnchorMaps) {
//sorting
var xCells = imageData.xCells;
- if (!xCells || xCells.length == 0) xCells = [{from: 0, to: imageData.w}];
var yCells = imageData.yCells;
+
+ if ((!xCells || xCells.length == 0) && (!yCells || yCells.length == 0)) {
+ Dom.empty(this);
+
+ this.setAttribute("width", dim.w)
+ this.setAttribute("height", dim.h)
+ this.setAttribute("style", "line-height: 1px;");
+
+ this.appendChild(Dom.newDOMElement({
+ _name: "img",
+ _uri: PencilNamespaces.html,
+ style: new CSS().set("width", dim.w + "px").set("height", dim.h + "px").toString(),
+ src: ImageData.refStringToUrl(imageData.data) || imageData.data
+ }));
+ return;
+ }
+
+ if (!xCells || xCells.length == 0) xCells = [{from: 0, to: imageData.w}];
if (!yCells || yCells.length == 0) yCells = [{from: 0, to: imageData.h}];
xCells = [].concat(xCells);
diff --git a/app/pencil-core/canvasHelper/canvasImpl.js b/app/pencil-core/canvasHelper/canvasImpl.js
index 1d01b4b5..4086d3ee 100644
--- a/app/pencil-core/canvasHelper/canvasImpl.js
+++ b/app/pencil-core/canvasHelper/canvasImpl.js
@@ -1,6 +1,8 @@
var CanvasImpl = {};
CanvasImpl.setupGrid = function () {
+ CanvasImpl.drawMargin.call(this);
+
if (this.gridContainer) {
Dom.empty(this.gridContainer);
} else {
@@ -42,3 +44,37 @@ CanvasImpl.setupGrid = function () {
}
}
};
+CanvasImpl.drawMargin = function () {
+ var unzommedMargin = Pencil.controller.getDocumentPageMargin();
+ if (!unzommedMargin) {
+ if (this.marginPath) this.marginPath.parentNode.removeChild(this.marginPath);
+ this.marginPath = null;
+ return;
+ }
+
+ var margin = unzommedMargin * this.zoom;
+ var color = Config.get(Config.DEV_PAGE_MARGIN_COLOR);
+
+ if (!this.marginPatternDef) {
+ // this.marginPatternDef = Dom.newDOMElement({
+ //
+ // });
+ // this.bgLayer.appendChild(this.marginPatternDef);
+ }
+
+ if (!this.marginPath) {
+ this.marginPath = document.createElementNS(PencilNamespaces.svg, "svg:path");
+ this.marginPath.setAttributeNS(PencilNamespaces.p, "p:name", "margins");
+ this.marginPath.setAttribute("stroke", "none");
+ this.marginPath.setAttribute("fill", color);
+ this.bgLayer.appendChild(this.marginPath);
+ }
+
+ var x = 5;
+ var width = this.width * this.zoom;
+ var height = this.height * this.zoom;
+ this.marginPath.setAttribute("d", [
+ M(0 - x, 0 - x), L(0 - x, height + x), L(width + x, height + x), L(width + x, 0 - x), z,
+ M(margin, margin), L(width - margin, margin), L(width - margin, height - margin), L(margin, height - margin), z
+ ].join(" "));
+};
diff --git a/app/pencil-core/canvasHelper/snappingHelper.js b/app/pencil-core/canvasHelper/snappingHelper.js
index 6fe06d57..a943bd27 100644
--- a/app/pencil-core/canvasHelper/snappingHelper.js
+++ b/app/pencil-core/canvasHelper/snappingHelper.js
@@ -111,6 +111,23 @@ SnappingHelper.prototype.rebuildSnappingGuide = function () {
}
}
}
+
+ var margin = Pencil.controller.getDocumentPageMargin();
+ if (margin) {
+ var uid = Util.newUUID();
+ this.snappingGuide[uid] = {
+ vertical: [
+ new SnappingData("MarginSnap", margin, "Left", true, uid),
+ new SnappingData("MarginSnap", this.canvas.width - margin, "Right", true, uid)
+ ],
+ horizontal: [
+ new SnappingData("MarginSnap", margin, "Top", false, uid),
+ new SnappingData("MarginSnap", this.canvas.height - margin, "Bottom", false, uid)
+ ]
+ };
+
+ }
+
this.sortData();
};
SnappingHelper.prototype.updateSnappingGuide = function (controller, remove) {
diff --git a/app/pencil-core/common/Canvas.js b/app/pencil-core/common/Canvas.js
index fa560627..03a7527c 100644
--- a/app/pencil-core/common/Canvas.js
+++ b/app/pencil-core/common/Canvas.js
@@ -178,6 +178,7 @@ function Canvas(element) {
thiz.handleClick(event);
}, false);
this.svg.addEventListener("mousedown", function (event) {
+ thiz.movementDisabled = Pencil.controller.movementDisabled || event.ctrlKey;
// document.commandDispatcher.advanceFocus();
thiz.focus();
thiz.handleMouseDown(event);
@@ -338,6 +339,10 @@ function Canvas(element) {
CanvasImpl.setupGrid.apply(this);
}
}.bind(this));
+ window.globalEventBus.listen("doc-options-change", function (data) {
+ CanvasImpl.drawMargin.apply(this);
+ this.snappingHelper.rebuildSnappingGuide();
+ }.bind(this));
}
@@ -1148,6 +1153,8 @@ Canvas.prototype.handleMouseMove = function (event, fake) {
if (!fake) {
event.preventDefault();
event.stopPropagation();
+
+ if (this.movementDisabled) return;
}
if (this.currentController.markAsMoving)
@@ -1386,7 +1393,7 @@ Canvas.prototype.handleKeyPress = function (event) {
event.preventDefault();
}
} else if (event.keyCode == DOM_VK_F2) {
- if (this.currentController) {
+ if (this.currentController && !event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey) {
Dom.emitEvent("p:TextEditingRequested", this.element, {
controller : this.currentController
});
@@ -2520,21 +2527,42 @@ Canvas.prototype.sizeToContent = function (hPadding, vPadding) {
return newSize;
};
Canvas.prototype.sizeToContent__ = function (hPadding, vPadding) {
+ var pageMargin = Pencil.controller.getDocumentPageMargin() || 0;
+
+ hPadding += pageMargin;
+ vPadding += pageMargin;
this.zoomTo(1.0);
var thiz = this;
var maxBox = null;
+ function out(name, rect) {
+ console.log(name, "left: ", rect.left, "top: ", rect.top, "width: ", rect.width, "height: ", rect.height);
+ }
+ out("this.svg", this.svg.getBoundingClientRect());
Dom.workOn("./svg:g[@p:type]", this.drawingLayer, function (node) {
try {
var controller = thiz.createControllerFor(node);
+ if (controller.def && controller.def.meta.excludeSizeCalculation) return;
var bbox = controller.getBoundingRect();
+ //HACK: inspect the strokeWidth attribute to fix half stroke bbox issue
var box = {
x1 : bbox.x,
y1 : bbox.y,
x2 : bbox.x + bbox.width,
y2 : bbox.y + bbox.height
};
+
+ if (controller.getGeometry) {
+ var geo = controller.getGeometry();
+ if (geo.ctm && geo.ctm.a == 1 && geo.ctm.b == 0 && geo.ctm.c == 0 && geo.ctm.d == 1 && geo.dim) {
+ box.x1 = geo.ctm.e;
+ box.y1 = geo.ctm.f;
+ box.x2 = box.x1 + geo.dim.w;
+ box.y2 = box.y1 + geo.dim.h;
+ }
+ }
+
if (maxBox == null) {
maxBox = box;
} else {
@@ -2556,6 +2584,10 @@ Canvas.prototype.sizeToContent__ = function (hPadding, vPadding) {
Util.getMessage("button.cancel.close"));
return;
}
+ maxBox.x1 = Math.floor(maxBox.x1);
+ maxBox.x2 = Math.ceil(maxBox.x2);
+ maxBox.y1 = Math.floor(maxBox.y1);
+ maxBox.y2 = Math.ceil(maxBox.y2);
var width = maxBox.x2 - maxBox.x1 + 2 * hPadding;
var height = maxBox.y2 - maxBox.y1 + 2 * vPadding;
diff --git a/app/pencil-core/common/EpgzHandler.js b/app/pencil-core/common/EpgzHandler.js
index a42e4717..f454bb05 100644
--- a/app/pencil-core/common/EpgzHandler.js
+++ b/app/pencil-core/common/EpgzHandler.js
@@ -28,6 +28,7 @@ EpgzHandler.prototype.loadDocument = function(filePath) {
EpgzHandler.prototype.saveDocument = function (documentPath) {
return new Promise(function (resolve, reject) {
+ var path = null;
var targz = require('tar.gz');
new targz({}, {fromBase: true}).compress(Pencil.documentHandler.tempDir.name, documentPath)
.then(resolve).catch(reject);
diff --git a/app/pencil-core/common/config.js b/app/pencil-core/common/config.js
index d01d2f72..b764dd40 100644
--- a/app/pencil-core/common/config.js
+++ b/app/pencil-core/common/config.js
@@ -51,3 +51,19 @@ try {
}
Config._load();
+
+
+
+//Specific configuration schema management
+Config.define = function (name, defaultValue) {
+ if (Config.get(name, null) === null) {
+ Config.set(name, defaultValue);
+ }
+
+ return name;
+};
+
+Config.DEV_PAGE_MARGIN_SIZE = Config.define("dev.pageMargin.size", Config.DEV_DEFAULT_PAGE_MARGIN);
+Config.DEV_PAGE_MARGIN_COLOR = Config.define("dev.pageMargin.color", "rgba(0, 0, 0, 0.2)");
+
+Config.DEV_ENABLE_DISABLED_IN_PROP_PAGE = Config.define("dev.enable_disabled_in_property_page", false);
diff --git a/app/pencil-core/common/controller.js b/app/pencil-core/common/controller.js
index 83eecae1..0a56eb6f 100644
--- a/app/pencil-core/common/controller.js
+++ b/app/pencil-core/common/controller.js
@@ -28,7 +28,7 @@ Controller.prototype.makeSubDir = function (sub) {
return fullPath;
};
Controller.prototype.getDocumentName = function () {
- return this.documentPath ? path.basename(this.documentPath).replace(/\.epz$/, "") : "* Unsaved document";
+ return this.documentPath ? path.basename(this.documentPath).replace(/\.[a-z]+$/, "") : "* Unsaved document";
};
// Controller.prototype.newDocument = function () {
// var thiz = this;
@@ -1057,6 +1057,15 @@ Controller.prototype.copyAsRef = function (sourcePath, callback) {
rd.pipe(wr);
};
+Controller.prototype.generateCollectionResourceRefId = function (collection, resourcePath) {
+ var id = "collection " + collection.id + " " + resourcePath;
+ var md5 = require("md5");
+ id = md5(id) + path.extname(resourcePath);
+
+ id = id.replace(/[^a-z\-0-9]+/gi, "_");
+
+ return id;
+};
Controller.prototype.collectionResourceAsRefSync = function (collection, resourcePath) {
var parts = resourcePath.split("/");
sourcePath = collection.installDirPath;
@@ -1069,8 +1078,7 @@ Controller.prototype.collectionResourceAsRefSync = function (collection, resourc
return null;
}
- var id = "collection " + collection.id + " " + resourcePath;
- id = id.replace(/[^a-z\-0-9]+/gi, "_");
+ var id = this.generateCollectionResourceRefId(collection, resourcePath);
var filePath = path.join(this.makeSubDir(Controller.SUB_REFERENCE), id);
@@ -1414,6 +1422,18 @@ Controller.prototype.exportAsLayout = function () {
});
};
+Controller.prototype.getDocumentPageMargin = function () {
+ if (!StencilCollectionBuilder.isDocumentConfiguredAsStencilCollection()) {
+ return null;
+ }
+ var options = StencilCollectionBuilder.getCurrentDocumentOptions();
+ if (!options) {
+ return null;
+ }
+
+ return options.pageMargin || Config.get(Config.DEV_PAGE_MARGIN_SIZE) || 40;
+};
+
window.onbeforeunload = function (event) {
// Due to a change of Chrome 51, returning non-empty strings or true in beforeunload handler now prevents the page to unload
diff --git a/app/pencil-core/common/renderer.js b/app/pencil-core/common/renderer.js
index 58e09b8b..a1c1158c 100644
--- a/app/pencil-core/common/renderer.js
+++ b/app/pencil-core/common/renderer.js
@@ -132,28 +132,42 @@ module.exports = function () {
};
}
+ var initialized = false;
+
function init() {
+ if (rendererWindow) {
+ try {
+ rendererWindow.destroy();
+ } catch (e) {}
+ }
+
rendererWindow = new BrowserWindow({x: 0, y: 0, useContentSize: true, enableLargerThanScreen: true, show: false, frame: false, autoHideMenuBar: true, transparent: true, webPreferences: {webSecurity: false, defaultEncoding: "UTF-8"}});
// rendererWindow.webContents.openDevTools();
- ipcMain.on("render-request", function (event, data) {
- queueHandler.submit(createRenderTask(event, data));
- });
+ queueHandler.tasks = [];
- ipcMain.on("render-rendered", function (event, data) {
- setTimeout(function () {
- if (currentRenderHandler) currentRenderHandler(event, data);
- }, 100);
- });
+ if (!initialized) {
+ ipcMain.on("render-request", function (event, data) {
+ queueHandler.submit(createRenderTask(event, data));
+ });
- ipcMain.on("font-loading-request", function (event, data) {
- fontFaceCSS = sharedUtil.buildFontFaceCSS(data.faces);
- event.sender.send(data.id, {});
- });
+ ipcMain.on("render-rendered", function (event, data) {
+ setTimeout(function () {
+ if (currentRenderHandler) currentRenderHandler(event, data);
+ }, 100);
+ });
+ ipcMain.on("font-loading-request", function (event, data) {
+ fontFaceCSS = sharedUtil.buildFontFaceCSS(data.faces);
+ event.sender.send(data.id, {});
+ });
+ console.log("RENDERER started.");
+ } else {
+ console.log("RENDERER re-started.");
+ }
- console.log("RENDERER started.");
+ initialized = true;
}
function initOutProcessCanvasBasedRenderer() {
var canvasWindow = new BrowserWindow({x: 0, y: 0, enableLargerThanScreen: true, show: false, autoHideMenuBar: true, webPreferences: {webSecurity: false, defaultEncoding: "UTF-8"}});
@@ -195,6 +209,9 @@ module.exports = function () {
ipcMain.once("render-init", function (event, data) {
init();
});
+ ipcMain.on("render-restart", function (event, data) {
+ init();
+ });
ipcMain.once("canvas-render-init", function (event, data) {
initOutProcessCanvasBasedRenderer();
});
diff --git a/app/pencil-core/common/svgRasterizer.js b/app/pencil-core/common/svgRasterizer.js
index 38f22612..48b4eba9 100644
--- a/app/pencil-core/common/svgRasterizer.js
+++ b/app/pencil-core/common/svgRasterizer.js
@@ -20,6 +20,8 @@ Rasterizer.prototype.getImageDataFromUrl = function (url, callback) {
};
Rasterizer.ipcBasedBackend = {
+ TIME_OUT: 10000,
+ pendingWorkMap: {},
init: function () {
ipcRenderer.send("render-init", {});
},
@@ -27,7 +29,12 @@ Rasterizer.ipcBasedBackend = {
var id = Util.newUUID();
ipcRenderer.once(id, function (event, data) {
+ var work = Rasterizer.ipcBasedBackend.pendingWorkMap[id];
+ if (!work) return;
+
callback(parseLinks ? data : data.url);
+ window.clearTimeout(work.timeoutId);
+ delete Rasterizer.ipcBasedBackend.pendingWorkMap[id];
});
w = width * scale;
@@ -42,6 +49,20 @@ Rasterizer.ipcBasedBackend = {
var xml = Controller.serializer.serializeToString(svgNode);
ipcRenderer.send("render-request", {svg: xml, width: w, height: h, scale: 1, id: id, processLinks: parseLinks});
+
+ var work = {};
+ work.timeoutId = window.setTimeout(function () {
+ var work = Rasterizer.ipcBasedBackend.pendingWorkMap[id];
+ if (!work) return;
+ callback("");
+ delete Rasterizer.ipcBasedBackend.pendingWorkMap[id];
+
+ console.log("Rasterizer seems to be crashed, restarting now!!!");
+ ipcRenderer.send("render-restart", {});
+
+ }, Rasterizer.ipcBasedBackend.TIME_OUT);
+
+ Rasterizer.ipcBasedBackend.pendingWorkMap[id] = work;
}
};
Rasterizer.outProcessCanvasBasedBackend = {
@@ -162,7 +183,23 @@ Rasterizer.prototype.rasterizePageToUrl = function (page, callback, scale, parse
var s = (typeof (scale) == "undefined") ? 1 : scale;
var f = function () {
svg.setAttribute("page", page.name);
- thiz.getBackend().rasterize(svg, page.width, page.height, s, callback, parseLinks);
+ var m = Pencil.controller.getDocumentPageMargin() || 0;
+ var w = page.width;
+ var h = page.height;
+ if (m) {
+ var g = svg.ownerDocument.createElementNS(PencilNamespaces.svg, "g");
+ g.setAttribute("transform", translate(0 - m, 0 - m));
+ while (svg.firstChild) {
+ g.appendChild(svg.removeChild(svg.firstChild));
+ }
+ svg.appendChild(g);
+
+ w -= 2 * m;
+ h -= 2 * m;
+ svg.setAttribute("width", w);
+ svg.setAttribute("height", h);
+ }
+ thiz.getBackend().rasterize(svg, w, h, s, callback, parseLinks);
};
if (page.backgroundPage) {
diff --git a/app/pencil-core/common/util.js b/app/pencil-core/common/util.js
index bf33bf21..1893f27b 100644
--- a/app/pencil-core/common/util.js
+++ b/app/pencil-core/common/util.js
@@ -657,6 +657,10 @@ Dom.parseToNode = function (xml, dom) {
return node;
}
Dom.parseDocument = function (xml) {
+ if (xml && xml.charCodeAt(0) === 0xFEFF) {
+ xml = xml.substr(1);
+ }
+
var dom = Dom.parser.parseFromString(xml, "text/xml");
return dom;
};
@@ -1091,7 +1095,21 @@ Svg.getHeight = function (dom) {
}
return 0;
};
-
+Svg.SYMBOL_NAME_ATTR = "symbolName";
+Svg.getSymbolName = function (node) {
+ if (node.hasAttributeNS(PencilNamespaces.p, Svg.SYMBOL_NAME_ATTR)) {
+ return node.getAttributeNS(PencilNamespaces.p, Svg.SYMBOL_NAME_ATTR);
+ } else {
+ return null;
+ }
+};
+Svg.setSymbolName = function (node, name) {
+ if (typeof(name) === "undefined" || name === null) {
+ node.remoteAttributeNS(PencilNamespaces.p, Svg.SYMBOL_NAME_ATTR);
+ } else {
+ return node.setAttributeNS(PencilNamespaces.p, Svg.SYMBOL_NAME_ATTR, name);
+ }
+};
var Local = {};
Local.getInstalledFonts = function () {
@@ -2136,6 +2154,43 @@ Util.importSandboxFunctions = function () {
pencilSandbox[f.name] = f;
}
};
+Util.workOnListAsync = function (list, worker, callback) {
+ var index = -1;
+ var next = function () {
+ index ++;
+ if (!list || index >= list.length) {
+ if (callback) callback();
+ return;
+ }
+
+ var item = list[index];
+ worker(item, index, next);
+ }
+ next();
+};
+Util.compareVersion = function (version1, version2) {
+ var a = version1.split(/\./);
+ var b = version2.split(/\./);
+
+ for (var i = 0; i < Math.min(a.length, b.length); i ++) {
+ var n1 = parseInt(a[i], 10);
+ var n2 = parseInt(b[i], 10);
+
+ if (isNaN(n1) || isNaN(n2)) {
+ n1 = a[i];
+ n2 = b[i];
+ }
+
+ if (n1 > n2) return 1;
+ if (n1 < n2) return -1;
+ }
+
+ if (a.length > b.length) return 1;
+ if (a.length < b.length) return -1;
+
+ return 0;
+};
+
function pEval (expression, extra, codeLocation) {
var result = null;
@@ -2147,7 +2202,7 @@ function pEval (expression, extra, codeLocation) {
result = eval(expression)
}
} catch (ex) {
- if (expression.length < 1000) error("Problematic code: " + expression);
+ if (expression.length < 2000) error("Problematic code: " + expression);
if (codeLocation) error("Code location: " + codeLocation);
Console.dumpError(ex);
}
@@ -2341,7 +2396,7 @@ function copyFileSync(source, target) {
fs.writeFileSync(targetFile, fs.readFileSync(source));
}
-function copyFolderRecursiveSync( source, target ) {
+function copyFolderRecursiveSync(source, target) {
var files = [];
//check if folder needs to be created or integrated
@@ -2391,4 +2446,31 @@ function _after(fn, after) {
};
}
+function getRequiredValue(input, message, pattern) {
+ var value = input.value;
+ var valid = pattern ? value.match(pattern) : value.trim().length > 0;
+ if (!valid) {
+ var e = new Error(message || "Please enter a valid value.");
+ e._input = input;
+ e._isValidationError = true;
+ throw e;
+ }
+
+ return value;
+}
+function handleCommonValidationError(e) {
+ if (e._isValidationError) {
+ Dialog.error(e.message, "", function () {
+ setTimeout(function () {
+ e._input.focus();
+ e._input.select();
+ }, 250);
+ });
+
+ return false;
+ } else {
+ throw e;
+ }
+}
+
Util.importSandboxFunctions(geo_buildQuickSmoothCurve, geo_buildSmoothCurve, geo_getRotatedPoint, geo_pointAngle, geo_rotate, geo_translate, geo_vectorAngle, geo_vectorLength, geo_findIntersection);
diff --git a/app/pencil-core/definition/collectionManager.js b/app/pencil-core/definition/collectionManager.js
index 127e1dbf..65a01e57 100644
--- a/app/pencil-core/definition/collectionManager.js
+++ b/app/pencil-core/definition/collectionManager.js
@@ -152,8 +152,12 @@ CollectionManager._loadUserDefinedStencilsIn = function (stencilDir, excluded, i
continue;
}
var folderPath = path.join(stencilDir, definitionFile);
- if (CollectionManager._loadStencil(folderPath, parser, isSystem ? true : false, isDeveloperStencil ? true : false)) {
- count++;
+ try {
+ if (CollectionManager._loadStencil(folderPath, parser, isSystem ? true : false, isDeveloperStencil ? true : false)) {
+ count++;
+ }
+ } catch (e) {
+ console.error(e);
}
}
} catch (e) {
diff --git a/app/pencil-core/definition/collectionRepository.js b/app/pencil-core/definition/collectionRepository.js
index 69837689..8d3dd88d 100644
--- a/app/pencil-core/definition/collectionRepository.js
+++ b/app/pencil-core/definition/collectionRepository.js
@@ -5,14 +5,13 @@ const STENCILS_REPO_URL = "https://raw.githubusercontent.com/mbrainiac/stencils-
var CollectionRepository = {
};
-CollectionRepository.loadCollections = function() {
+CollectionRepository.loadCollections = function(url) {
return QP.Promise(function(resolve, reject) {
var nugget = require("nugget");
var tempDir = tmp.dirSync({ keep: false, unsafeCleanup: true }).name;
var filename = "repository-" + new Date().getTime() + ".xml";
- console.log('Downloading', STENCILS_REPO_URL, 'to', tempDir, filename);
var nuggetOpts = {
target: filename,
dir: tempDir,
@@ -20,7 +19,7 @@ CollectionRepository.loadCollections = function() {
quiet: true
};
- nugget(STENCILS_REPO_URL, nuggetOpts, function (errors) {
+ nugget(url || STENCILS_REPO_URL, nuggetOpts, function (errors) {
if (errors) {
var error = errors[0] // nugget returns an array of errors but we only need 1st because we only have 1 url
if (error.message.indexOf('404') === -1) {
@@ -75,6 +74,15 @@ CollectionRepository.parse = function(dom, url) {
collections.push(CollectionRepository.parseCollection(node));
});
+ //the one with uppercase "C" is intended for newer version with support for version checking
+ Dom.workOn("./p:Collection", collectionsNode, function (node) {
+ var minVersion = node.getAttribute("required-version");
+ if (minVersion) {
+ if (Util.compareVersion(pkgInfo.version, minVersion) < 0) return;
+ }
+ collections.push(CollectionRepository.parseCollection(node));
+ });
+
_.forEach(collections, function(c) {
var existed = _.find(CollectionManager.shapeDefinition.collections, function(e) {
return e.id == c.id;
diff --git a/app/pencil-core/definition/shapeDef.js b/app/pencil-core/definition/shapeDef.js
index 362c5551..51ca2e63 100644
--- a/app/pencil-core/definition/shapeDef.js
+++ b/app/pencil-core/definition/shapeDef.js
@@ -21,6 +21,35 @@ ShapeDef.prototype.toString = function () {
ShapeDef.prototype.getProperty = function (name) {
return this.propertyMap[name];
};
+ShapeDef.prototype.removeProperty = function (name) {
+ var found = false;
+ for (var group of this.propertyGroups) {
+ for (var i = 0; i < group.properties.length; i ++) {
+ var property = group.properties[i];
+ if (property.name == name) {
+ group.properties.splice(i, 1);
+ found = true;
+ break;
+ }
+ }
+
+ if (found) break;
+ }
+
+ if (found) {
+ delete this.propertyMap[name];
+ }
+};
+ShapeDef.prototype.removeAction = function (id) {
+ for (var i = 0; i < this.actions.length; i ++) {
+ var action = this.actions[i];
+ if (action.id == id) {
+ this.actions.splice(i, 1);
+ delete this.actionMap[id];
+ break;
+ }
+ }
+};
ShapeDef.prototype.isPropertyAffectedBy = function (target, source, checkedProperties) {
if (target == source) return true;
@@ -51,6 +80,15 @@ PropertyGroup.prototype.toString = function () {
return "[PropertyGroup: " + this.name + "]";
};
+PropertyGroup.prototype.clone = function () {
+ var group = new PropertyGroup();
+ group.name = this.name;
+ for (var prop of this.properties) {
+ group.properties.push(prop.clone());
+ }
+
+ return group;
+};
function Property() {
this.name = null;
@@ -64,6 +102,29 @@ function Property() {
Property.prototype.toString = function () {
return "[Property: " + this.name + "]";
};
+Property.prototype.clone = function () {
+ var property = new Property();
+ property.name = this.name;
+ property.displayName = this.displayName;
+ property.type = this.type;
+ property.initialValue = this.initialValue ? this.type.fromString(this.initialValue.toString()) : null;
+ property.initialValueExpression = this.initialValueExpression;
+
+ for (var name in this.relatedTargets) {
+ property.relatedTargets[name] = this.relatedTargets[name];
+ }
+
+ property.relatedProperties = {};
+ for (var name in this.relatedProperties) {
+ property.relatedProperties[name] = this.relatedProperties[name];
+ }
+
+ for (var name in this.meta) {
+ property.meta[name] = this.meta[name];
+ }
+
+ return property;
+};
Property.prototype.isSimilarTo = function (property) {
return this.name == property.name &&
this.type == property.type;
@@ -99,7 +160,7 @@ function BehaviorItemArg(literal, shapeDef, currentTarget, type) {
this.literal = this.literal.replace(/\$([a-z][a-z0-9]*)/gi, function (zero, one) {
var property = shapeDef.getProperty(one);
if (!property) {
- throw Util.getMessage("invalid.property.reference", one);
+ throw Util.getMessage("invalid.property.reference", one) + " (" + shapeDef.id + ")";
}
property.relatedTargets[currentTarget] = true;
return "properties." + one;
@@ -127,13 +188,3 @@ function Shortcut() {
this.shape = null;
this.propertyMap = {};
}
-
-
-
-
-
-
-
-
-
-
diff --git a/app/pencil-core/definition/shapeDefCollectionParser.js b/app/pencil-core/definition/shapeDefCollectionParser.js
index f576f4f2..1b379cd0 100644
--- a/app/pencil-core/definition/shapeDefCollectionParser.js
+++ b/app/pencil-core/definition/shapeDefCollectionParser.js
@@ -138,7 +138,7 @@ ShapeDefCollectionParser.getCollectionPropertyConfigName = function (collectionI
ShapeDefCollectionParser.prototype.loadCustomLayout = function (installDirPath) {
var layoutUri = path.join(installDirPath, "Layout.xhtml");
if (!fs.existsSync(layoutUri)) return null;
-
+
try {
var html = fs.readFileSync(layoutUri, {encoding: "utf8"});
if (!html) return null;
@@ -332,18 +332,51 @@ ShapeDefCollectionParser.prototype.loadCustomLayout = function (installDirPath)
shapeDef.displayName = shapeDefNode.getAttribute("displayName");
shapeDef.system = shapeDefNode.getAttribute("system") == "true";
shapeDef.collection = collection;
+ var inherits = shapeDefNode.getAttribute("inherits");
+ if (inherits) {
+ if (inherits.indexOf(":") < 0) inherits = collection.id + ":" + inherits;
+ var parentShapeDef = collection.shapeDefMap[inherits];
+ if (parentShapeDef) {
+ shapeDef.parentShapeDef = parentShapeDef;
+ this.processInheritance(shapeDef);
+ }
+ }
+
var iconPath = shapeDefNode.getAttribute("icon");
// if (iconPath.indexOf("data:image") != 0) {
// iconPath = collection.url.substring(0, collection.url.lastIndexOf("/") + 1) + iconPath;
// }
shapeDef.iconPath = iconPath;
+ // adding shapeDef meta
+ shapeDef.meta = {};
+ Dom.workOn("./@p:*", shapeDefNode, function (metaAttribute) {
+ var metaValue = metaAttribute.nodeValue;
+ metaValue = metaValue.replace(/\$([a-z][a-z0-9]*)/gi, function (zero, one) {
+ property.relatedProperties[one] = true;
+ return "properties." + one;
+ });
+ shapeDef.meta[metaAttribute.localName] = metaValue;
+ });
+
var parser = this;
//parse properties
Dom.workOn("./p:Properties/p:PropertyGroup", shapeDefNode, function (propGroupNode) {
- var group = new PropertyGroup;
- group.name = propGroupNode.getAttribute("name");
+ //find existing property group to support duplicate inherited groups
+ var groupName = propGroupNode.getAttribute("name");
+ var group = null;
+ for (var g of shapeDef.propertyGroups) {
+ if (g.name == groupName) {
+ group = g;
+ break;
+ }
+ }
+ if (!group) {
+ group = new PropertyGroup();
+ group.name = groupName;
+ shapeDef.propertyGroups.push(group);
+ }
Dom.workOn("./p:Property", propGroupNode, function (propNode) {
var property = new Property();
@@ -389,11 +422,13 @@ ShapeDefCollectionParser.prototype.loadCustomLayout = function (installDirPath)
property.meta[metaAttribute.localName] = metaValue;
});
+ if (shapeDef.propertyMap[property.name]) {
+ shapeDef.removeProperty(property.name);
+ }
+
group.properties.push(property);
shapeDef.propertyMap[property.name] = property;
});
-
- shapeDef.propertyGroups.push(group);
});
/*/ styles
@@ -514,6 +549,10 @@ ShapeDefCollectionParser.prototype.loadCustomLayout = function (installDirPath)
Console.dumpError(e);
}
+ if (shapeDef.actionMap[action.id]) {
+ shapeDef.removeAction(action.id);
+ }
+
shapeDef.actionMap[action.id] = action;
shapeDef.actions.push(action);
@@ -529,8 +568,52 @@ ShapeDefCollectionParser.prototype.loadCustomLayout = function (installDirPath)
node.removeAttribute("id");
});
+
+ var parentContentPlaceHolder = Dom.getSingle(".//p:ParentContent", shapeDef.contentNode);
+ if (parentContentPlaceHolder && shapeDef.parentShapeDef && shapeDef.parentShapeDef.contentNode) {
+ var f = shapeDef.contentNode.ownerDocument.createDocumentFragment();
+ for (var i = 0; i < shapeDef.parentShapeDef.contentNode.childNodes.length; i ++) {
+ var child = shapeDef.parentShapeDef.contentNode.childNodes[i];
+ child = shapeDef.contentNode.ownerDocument.importNode(child, true);
+ f.appendChild(child);
+ }
+
+ parentContentPlaceHolder.parentNode.replaceChild(f, parentContentPlaceHolder);
+ }
+
return shapeDef;
};
+
+/* public ShapeDef */ ShapeDefCollectionParser.prototype.processInheritance = function (shapeDef) {
+// this.contentNode = null;
+// this.propertyGroups = [];
+// this.behaviors = [];
+// this.actions = [];
+//
+// this.propertyMap = {};
+// this.behaviorMap = {};
+// this.actionMap = {};
+
+ shapeDef.propertyGroups = [].concat();
+ for (var group of shapeDef.parentShapeDef.propertyGroups) {
+ var clonedGroup = group.clone();
+ shapeDef.propertyGroups.push(clonedGroup);
+
+ for (var prop of clonedGroup.properties) {
+ shapeDef.propertyMap[prop.name] = prop;
+ }
+ }
+
+ shapeDef.behaviors = [].concat(shapeDef.parentShapeDef.behaviors);
+ for (var name in shapeDef.parentShapeDef.behaviorMap) {
+ shapeDef.behaviorMap[name] = shapeDef.parentShapeDef.behaviorMap[name];
+ }
+
+ shapeDef.actions = [].concat(shapeDef.parentShapeDef.actions);
+ for (var name in shapeDef.parentShapeDef.actionMap) {
+ shapeDef.actionMap[name] = shapeDef.parentShapeDef.actionMap[name];
+ }
+};
/* public Shortcut */ ShapeDefCollectionParser.prototype.parseShortcut = function (shortcutNode, collection) {
var shortcut = new Shortcut();
diff --git a/app/pencil-core/editor/handleEditor.js b/app/pencil-core/editor/handleEditor.js
index 4c9dfd20..58cec8dd 100644
--- a/app/pencil-core/editor/handleEditor.js
+++ b/app/pencil-core/editor/handleEditor.js
@@ -289,7 +289,6 @@ HandleEditor.prototype.handleMoveTo = function (x, y, event) {
this.lastMatchedOutlet = null;
}
- console.log("move handle");
};
HandleEditor.prototype.getPropertyConstraints = function (handle) {
if (!this.currentHandle) return {};
diff --git a/app/pencil-core/propertyType/bound.js b/app/pencil-core/propertyType/bound.js
index 4b10138c..846fe7af 100644
--- a/app/pencil-core/propertyType/bound.js
+++ b/app/pencil-core/propertyType/bound.js
@@ -37,7 +37,7 @@ Bound.prototype.narrowed = function (x, y) {
};
Bound.prototype.shifted = function (dx, dy) {
return new Bound(this.x - dx, this.y - dy, this.w, this.h);
-
+
};
pencilSandbox.Bound = {
@@ -47,4 +47,4 @@ pencilSandbox.Bound = {
};
for (var p in Bound) {
pencilSandbox.Bound[p] = Bound[p];
-};
\ No newline at end of file
+};
diff --git a/app/pencil-core/propertyType/color.js b/app/pencil-core/propertyType/color.js
index 174248d4..fd56fbf4 100644
--- a/app/pencil-core/propertyType/color.js
+++ b/app/pencil-core/propertyType/color.js
@@ -174,6 +174,41 @@ Color.prototype.transparent = function () {
return color;
};
+Color.prototype.generateTransformTo = function (other) {
+ if (!other) return null;
+ var hsv0 = Color.RGB2HSV(this);
+ var hsv1 = Color.RGB2HSV(other);
+
+ const ALLOWED_DELTA = 5;
+ if (Math.abs(hsv0.hue - hsv1.hue) < ALLOWED_DELTA && Math.abs(hsv0.saturation - hsv1.saturation) < ALLOWED_DELTA) {
+ var transform = "";
+
+ if (Math.abs(hsv0.value - hsv1.value) >= ALLOWED_DELTA / 3) {
+ if (hsv0.value == 0) return null;
+ transform += ".shaded(" + (1 - (hsv1.value / hsv0.value)) + ")";
+ }
+
+ if (Math.abs(this.a - other.a) >= 0.05) {
+ if (this.a == 0) return null;
+ transform += ".hollowed(" + (1 - (other.a / this.a)) + ")";
+ }
+
+ return transform;
+ }
+
+ return null;
+}
+Color.prototype.getDiff = function (other) {
+ if (!other) return 1;
+ var hsv0 = Color.RGB2HSV(this);
+ var hsv1 = Color.RGB2HSV(other);
+
+ return (Math.abs(hsv0.hue - hsv1.hue) / (255 * 4))
+ + (Math.abs(hsv0.saturation - hsv1.saturation) / (255 * 4))
+ + (Math.abs(hsv0.value - hsv1.value) / (100 * 4))
+ + (Math.abs(this.a - other.a) / 4);
+}
+
pencilSandbox.Color = {
newColor: function () {
return new Color();
diff --git a/app/pencil-core/propertyType/font.js b/app/pencil-core/propertyType/font.js
index 2783b6fe..0795621d 100644
--- a/app/pencil-core/propertyType/font.js
+++ b/app/pencil-core/propertyType/font.js
@@ -48,6 +48,44 @@ Font.prototype.getFamilies = function () {
}
return families;
}
+Font.prototype.bold = function (yes) {
+ var font = Font.fromString(this.toString());
+ font.weight = (typeof(yes) == "undefined" || yes) ? "bold" : "normal";
+
+ return font;
+};
+Font.prototype.italic = function (yes) {
+ var font = Font.fromString(this.toString());
+ font.style = (typeof(yes) == "undefined" || yes) ? "italic" : "normal";
+
+ return font;
+};
+Font.prototype.resized = function (delta) {
+ var font = Font.fromString(this.toString());
+ if (typeof(delta) == "string" && delta.match(/^(.+)%$/)) {
+ font.size = Math.round(this.getPixelHeight() * (1 + parseFloat(RegExp.$1) / 100)) + "px";
+ } else if (typeof(delta) == "number") {
+ font.size = Math.round(this.getPixelHeight() * (1 + delta)) + "px";
+ }
+
+ return font;
+};
+Font.prototype.generateTransformTo = function (other) {
+ if (this.family != other.family) return null;
+
+ var transform = "";
+ if (this.weight != other.weight) {
+ transform += ".bold(" + (this.weight != "bold") + ")";
+ }
+ if (this.style != other.style) {
+ transform += ".italic(" + (this.style != "italic") + ")";
+ }
+ if (this.size != other.size && this.getPixelHeight() > 0) {
+ transform += ".resized(" + ((other.getPixelHeight() / this.getPixelHeight()) - 1) + ")";
+ }
+
+ return transform;
+};
pencilSandbox.Font = {
newFont: function () {
diff --git a/app/pencil-core/propertyType/imageData.js b/app/pencil-core/propertyType/imageData.js
index b2aa8987..f93f091b 100644
--- a/app/pencil-core/propertyType/imageData.js
+++ b/app/pencil-core/propertyType/imageData.js
@@ -136,12 +136,12 @@ ImageData.refStringToUrl = function (refString) {
return Pencil.controller.refIdToUrl(id);
};
-ImageData.prompt = function (callback) {
- dialog.showOpenDialog({
+ImageData.prompt = function (callback, ext) {
+ dialog.showOpenDialog(remote.getCurrentWindow(), {
title: "Select Image",
defaultPath: os.homedir(),
filters: [
- { name: "Image files", extensions: ["png", "jpg", "jpeg", "gif", "bmp", "svg"] }
+ { name: "Image files", extensions: ext || ["png", "jpg", "jpeg", "gif", "bmp", "svg"] }
]
}, function (filenames) {
@@ -217,6 +217,27 @@ ImageData.prototype.toString = function () {
return [this.w, this.h, ImageData.generateCellString(this.xCells), ImageData.generateCellString(this.yCells), this.data].join(",");
}
};
+ImageData.SVG_IMAGE_DATA_PREFIX = "data:image/svg+xml";
+ImageData.prototype.getDataAsXML = function () {
+ var url = this.data;
+ if (!url) return null;
+
+ if (url.startsWith(ImageData.SVG_IMAGE_DATA_PREFIX)) {
+ var commaIndex = url.indexOf(",");
+ if (commaIndex < ImageData.SVG_IMAGE_DATA_PREFIX.length || commaIndex > ImageData.SVG_IMAGE_DATA_PREFIX.length + 10) return null;
+ var svg = url.substring(commaIndex + 1);
+
+ return svg;
+ } else if (url.match(/^ref:\/\//)) {
+ var id = ImageData.refStringToId(url);
+ if (!id) {
+ return null;
+ }
+ var filePath = Pencil.controller.refIdToFilePath(id);
+ return fs.readFileSync(filePath, "utf8");
+ }
+ return null;
+};
window.addEventListener("load", function () {
var iframe = document.createElementNS(PencilNamespaces.html, "html:iframe");
diff --git a/app/pencil-core/propertyType/strokeStyle.js b/app/pencil-core/propertyType/strokeStyle.js
index 91c049e1..350e92db 100644
--- a/app/pencil-core/propertyType/strokeStyle.js
+++ b/app/pencil-core/propertyType/strokeStyle.js
@@ -1,6 +1,6 @@
function StrokeStyle(w, array) {
- this.w = w ? w : 1;
- this.array = array ? array : null;
+ this.w = (typeof(w) == "number") ? w : 1;
+ this.array = typeof(array) != "undefined" ? array : null;
}
StrokeStyle.REG_EX = /^([0-9]+)\|([0-9 \,]*)$/;
StrokeStyle.fromString = function(literal) {
@@ -17,8 +17,24 @@ StrokeStyle.prototype.toString = function () {
return this.w + "|" + this.array;
};
StrokeStyle.prototype.condensed = function (ratio) {
- return new StrokeStyle(this.w * (1 + ratio), this.array);
+ return new StrokeStyle(Math.round(this.w * (1 + ratio)), this.array);
};
+StrokeStyle.prototype.styled = function (array) {
+ return new StrokeStyle(this.w, array);
+};
+StrokeStyle.prototype.generateTransformTo = function (other) {
+ var transform = "";
+
+ if (this.w != other.w && this.w > 0) {
+ transform += ".condensed(" + ((other.w / this.w) - 1) + ")";
+ }
+ if (this.array != other.array) {
+ transform += ".styled(" + JSON.stringify(other.array) + ")";
+ }
+
+ return transform;
+};
+
pencilSandbox.StrokeStyle = {
newStrokeStyle: function (w, array) {
diff --git a/app/pencil-core/target/group.js b/app/pencil-core/target/group.js
index 32a21db4..e3fdc20b 100644
--- a/app/pencil-core/target/group.js
+++ b/app/pencil-core/target/group.js
@@ -86,7 +86,7 @@ Group.prototype.getProperty = function (name) {
//TODO: add additonal info to indicate sameness
return firstValue;
-
+
// if (!firstValue) return null;
// var same = true;
// for (var i = 1; i < this.targets.length; i ++) {
@@ -523,3 +523,9 @@ Group.openSizingPolicyDialog = function (target) {
});
};
+Group.prototype.getSymbolName = function () {
+ return Svg.getSymbolName(this.svg);
+};
+Group.prototype.setSymbolName = function (name) {
+ return Svg.setSymbolName(this.svg, name);
+};
diff --git a/app/pencil-core/target/shape.js b/app/pencil-core/target/shape.js
index 34c69eb6..87c62838 100644
--- a/app/pencil-core/target/shape.js
+++ b/app/pencil-core/target/shape.js
@@ -1,4 +1,4 @@
-function Shape(canvas, svg) {
+function Shape(canvas, svg, forcedDefinition) {
this.svg = svg;
this.canvas = canvas;
@@ -9,7 +9,7 @@ function Shape(canvas, svg) {
this.id = this.svg.getAttribute("id");
var defId = this.canvas.getType(svg);
- this.def = CollectionManager.shapeDefinition.locateDefinition(defId);
+ this.def = forcedDefinition || CollectionManager.shapeDefinition.locateDefinition(defId);
if (!this.def) {
throw Util.getMessage("shape.definition.not.found", defId);
}
@@ -49,6 +49,7 @@ Shape.prototype.getPropertyGroups = function () {
Shape.prototype.setInitialPropertyValues = function (overridingValueMap) {
this._evalContext = {collection: this.def.collection};
+ F._target = this.svg;
var hasPostProcessing = false;
@@ -133,6 +134,48 @@ Shape.prototype.repairShapeProperties = function () {
this.applyBehaviorForProperty(name);
}
};
+Shape.prototype.renewTargetProperties = function () {
+ this._evalContext = {collection: this.def.collection};
+ F._target = this.svg;
+
+ var renewed = false;
+
+ for (var name in this.def.propertyMap) {
+ var prop = this.def.propertyMap[name];
+ if (!prop || !prop.meta || prop.meta.renew != "true") continue;
+
+ var propNode = this.locatePropertyNode(name);
+ if (!propNode) continue;
+
+ var value = null;
+
+ var currentCollection = this.def.connection;
+
+ if (prop.initialValueExpression) {
+ value = this.evalExpression(prop.initialValueExpression);
+ } else {
+ value = prop.initialValue;
+ }
+
+ if (prop.type.performIntialProcessing) {
+ var newValue = prop.type.performIntialProcessing(value, this.def, currentCollection);
+ if (newValue) {
+ value = newValue;
+ }
+ }
+
+ this.storeProperty(name, value);
+ renewed = true;
+ }
+
+ if (!renewed) return false;
+
+ for (name in this.def.propertyMap) {
+ this.applyBehaviorForProperty(name);
+ }
+
+ return true;
+};
Shape.prototype.applyBehaviorForProperty = function (name, dontValidateRelatedProperties) {
var propertyDef = this.def.propertyMap[name];
if (!propertyDef) return;
@@ -336,7 +379,7 @@ Shape.prototype.setProperty = function (name, value, nested) {
Shape.prototype.getProperty = function (name) {
var propNode = this.locatePropertyNode(name);
if (!propNode) {
- return null;
+ //return null;
var prop = this.def.getProperty(name);
if (!this._evalContext) {
this._evalContext = {collection: this.def.collection};
@@ -344,6 +387,10 @@ Shape.prototype.getProperty = function (name) {
this._evalContext.collection = this.def.collection;
}
+ if (!prop) {
+ return null;
+ }
+
if (prop.initialValueExpression) {
return this.evalExpression(prop.initialValueExpression);
} else {
@@ -1111,6 +1158,12 @@ Shape.prototype.invalidateInboundConnections = function () {
Shape.prototype.invalidateOutboundConnections = function () {
Connector.invalidateOutboundConnectionsForShapeTarget(this);
};
+Shape.prototype.getSymbolName = function () {
+ return Svg.getSymbolName(this.svg);
+};
+Shape.prototype.setSymbolName = function (name) {
+ return Svg.setSymbolName(this.svg, name);
+};
Shape.prototype.generateShortcutXML = function () {
new PromptDialog().open({
title: "Shortcut",
@@ -1199,3 +1252,28 @@ Shape.prototype.generateShortcutXML = function () {
}.bind(this)
});
};
+
+Shape.prototype.getContentEditActions = function (event) {
+ var actions = [];
+ var editInfo = this.getTextEditingInfo(event);
+ if (editInfo) {
+ actions.push({
+ type: "text",
+ editInfo: editInfo
+ });
+ }
+ for (var action of this.def.actions) {
+ if (action.meta["content-action"] == "true") {
+ actions.push({
+ type: "action",
+ actionId: action.id
+ });
+ }
+ }
+
+ return actions;
+};
+Shape.prototype.handleOtherContentEditAction = function (action) {
+ if (action.type != "action") return;
+ this.performAction(action.actionId);
+};
diff --git a/app/pencil-core/xferHelper/shapeXferHelper.js b/app/pencil-core/xferHelper/shapeXferHelper.js
index 2a2a0dd9..f083cf97 100644
--- a/app/pencil-core/xferHelper/shapeXferHelper.js
+++ b/app/pencil-core/xferHelper/shapeXferHelper.js
@@ -36,6 +36,18 @@ ShapeXferHelper.prototype.handleData = function (dom) {
this.canvas.drawingLayer.appendChild(shape);
this.canvas.selectShape(shape);
+ if (this.canvas.currentController.renewTargetProperties) {
+ try {
+ var renewed = this.canvas.currentController.renewTargetProperties();
+ if (renewed) {
+ this.canvas.selectNone();
+ this.canvas.selectShape(shape);
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ }
+
var rect = this.canvas.currentController.getBoundingRect();
var mx = 0;
var my = 0;
@@ -57,6 +69,7 @@ ShapeXferHelper.prototype.handleData = function (dom) {
this.canvas.snappingHelper.updateSnappingGuide(this.canvas.currentController);
}, this, Util.getMessage("action.create.shape", this.canvas.createControllerFor(shape).getName()));
+
this.canvas.invalidateEditors();
};
diff --git a/app/stencils/Common/Definition.xml b/app/stencils/Common/Definition.xml
index 251fae74..ae6cf7da 100644
--- a/app/stencils/Common/Definition.xml
+++ b/app/stencils/Common/Definition.xml
@@ -1088,7 +1088,7 @@
180,90
-
+
diff --git a/app/views/ApplicationPane.js b/app/views/ApplicationPane.js
index 324abe4f..6fdd9cdb 100644
--- a/app/views/ApplicationPane.js
+++ b/app/views/ApplicationPane.js
@@ -299,3 +299,7 @@ ApplicationPane.prototype.toggleLeftPane = function () {
this.leftSidePane.openLast();
}
};
+
+ApplicationPane.prototype.setContentVisible = function (visible) {
+ this.contentBody.style.visibility = visible ? "visible" : "hidden";
+};
diff --git a/app/views/EditPageNoteDialog.js b/app/views/EditPageNoteDialog.js
index dd2ed916..0159a494 100644
--- a/app/views/EditPageNoteDialog.js
+++ b/app/views/EditPageNoteDialog.js
@@ -106,7 +106,7 @@ function EditPageNoteDialog () {
return n.getAttribute && n.getAttribute("command");
});
if (!node) return;
- var command = node.getAttribute("command");ns
+ var command = node.getAttribute("command");
var arg = node.hasAttribute("arg") ? node.getAttribute("arg") : undefined;
if(command == "createlink") {
var sel = window.document.getSelection();
@@ -203,7 +203,7 @@ function EditPageNoteDialog () {
}
thiz.runEditorCommand("formatBlock", value);
}, false);
-
+
}
__extend(Dialog, EditPageNoteDialog);
diff --git a/app/views/StartUpDocumentView.js b/app/views/StartUpDocumentView.js
index 18ae63dd..6946bb44 100644
--- a/app/views/StartUpDocumentView.js
+++ b/app/views/StartUpDocumentView.js
@@ -36,7 +36,8 @@ function StartUpDocumentView() {
if (doc.thumbPath) {
handler(null, doc.thumbPath);
} else {
- Pencil.documentHandler.parseDocumentThumbnail(filePath, handler);
+ handler(null, "");
+ //Pencil.documentHandler.parseDocumentThumbnail(filePath, handler);
}
}
diff --git a/app/views/collections/CollectionResourceBrowserDialog.js b/app/views/collections/CollectionResourceBrowserDialog.js
index 85f81b15..2bf8260d 100644
--- a/app/views/collections/CollectionResourceBrowserDialog.js
+++ b/app/views/collections/CollectionResourceBrowserDialog.js
@@ -17,18 +17,51 @@ function CollectionResourceBrowserDialog (collection, options) {
this.prefixCombo.setItems(this.prefixes);
+ this.collectionCombo.renderer = function (item) {
+ return item.displayName;
+ };
+ var availableCollections = [collection];
+ var selectedCollection = collection;
+ for (var c of CollectionManager.shapeDefinition.collections) {
+ if (c.id == collection.id || !c.RESOURCE_LIST) continue;
+
+ if (this.options.type) {
+ var found = false;
+ for (var resource of c.RESOURCE_LIST) {
+ if (!resource.type || resource.type == this.options.type) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) continue;
+ }
+
+ availableCollections.push(c);
+ if (CollectionResourceBrowserDialog.lastCollectionId == c.id) {
+ selectedCollection = c;
+ }
+ }
+
+ this.collectionCombo.setItems(availableCollections);
+ this.collectionCombo.selectItem(selectedCollection);
+
this.searchTimeout = null;
var thiz = this;
this.bind("input", function () {
if (this.searchTimeout) {
window.clearTimeout(this.searchTimeout);
+ this.searchTimeout = null;
}
this.searchTimeout = window.setTimeout(function () {
thiz.search();
}, 500);
}, this.filterInput);
+ this.bind("keydown", this.handleFilterKeyDown, this.filterInput);
+
this.bind("p:ItemSelected", this.search, this.prefixCombo);
+ this.bind("p:ItemSelected", this.changeCollection, this.collectionCombo);
var ensureVisibleItemsContentFunction = function() {
this.revealTimeout = null;
@@ -43,15 +76,16 @@ function CollectionResourceBrowserDialog (collection, options) {
}, this.resultContainer);
this.bind("dblclick", this.handleItemDblClick, this.resultContainer);
+ this.bind("keydown", this.handleListKeyDown, this.resultContainer);
+ this.resultContainer.addEventListener("focus", this.handleListFocus.bind(this), true);
- var optionCache = CollectionResourceBrowserDialog.optionCache[this.collection.id];
- if (optionCache) {
- this.filterInput.value = optionCache.keyword || "";
- for (var p of this.prefixes) {
- if (p.prefix == optionCache.prefix) {
- this.prefixCombo.selectItem(p);
- break;
- }
+ this.invalidatePrefixList();
+
+ this.filterInput.value = CollectionResourceBrowserDialog.lastKeyword || "";
+ for (var p of this.prefixCombo.items) {
+ if (p.prefix == CollectionResourceBrowserDialog.lastPrefix) {
+ this.prefixCombo.selectItem(p);
+ break;
}
}
}
@@ -60,6 +94,7 @@ __extend(Dialog, CollectionResourceBrowserDialog);
CollectionResourceBrowserDialog.TYPE_SVG = "svg";
CollectionResourceBrowserDialog.TYPE_BITMAP = "bitmap";
CollectionResourceBrowserDialog.RETURN_IMAGEDATA = "ImageData";
+CollectionResourceBrowserDialog.RETURN_IMAGEDATA_SVG_GROUP = "ImageDataSVGGroup";
CollectionResourceBrowserDialog.RETURN_CONTENT= "Content";
CollectionResourceBrowserDialog.RETURN_DOMCONTENT= "Document";
@@ -71,8 +106,8 @@ CollectionResourceBrowserDialog.prototype.getDialogActions = function () {
{
type: "accept", title: "Select",
run: function () {
- if (this.callback) this.callback();
- return true;
+ if (this.selectedView) this.returnDataInSelectedView();
+ return false;
}
}
]
@@ -80,19 +115,40 @@ CollectionResourceBrowserDialog.prototype.getDialogActions = function () {
CollectionResourceBrowserDialog.prototype.onShown = function () {
this.filterInput.focus();
+ this.filterInput.select();
this.search();
};
+CollectionResourceBrowserDialog.prototype.invalidatePrefixList = function () {
+ var collection = this.collectionCombo.getSelectedItem();
+ var prefixes = [];
+ if (collection.id == this.collection.id) {
+ prefixes = this.prefixes;
+ } else {
+ for (var resource of collection.RESOURCE_LIST) {
+ if (!resource.type || resource.type == this.options.type) {
+ prefixes.push(resource);
+ }
+ }
+ }
+ this.prefixCombo.setItems(prefixes);
+};
+CollectionResourceBrowserDialog.prototype.changeCollection = function () {
+ this.invalidatePrefixList();
+ this.focusResult = true;
+ this.search();
+};
CollectionResourceBrowserDialog.prototype.search = function () {
+ this.searchTimeout = null;
var keyword = this.filterInput.value.trim();
var items = [];
- var dirPath = this.collection.installDirPath;
+ var collection = this.collectionCombo.getSelectedItem();
+ var dirPath = collection.installDirPath;
this.prefix = this.prefixCombo.getSelectedItem().prefix;
- CollectionResourceBrowserDialog.optionCache[this.collection.id] = {
- keyword: keyword,
- prefix: this.prefix
- };
+ CollectionResourceBrowserDialog.lastCollectionId = collection.id;
+ CollectionResourceBrowserDialog.lastKeyword = keyword;
+ CollectionResourceBrowserDialog.lastPrefix = this.prefix;
if (this.prefix) {
var parts = this.prefix.trim().split("/");
@@ -105,19 +161,32 @@ CollectionResourceBrowserDialog.prototype.search = function () {
.then(function (matched) {
Dom.empty(this.resultContainer);
var count = 0;
+ var previousview = null;
for (var item of matched) {
- //if (count ++ > 50) break;
var view = Dom.newDOMElement({
_name: "div",
_uri: PencilNamespaces.html,
title: path.basename(item.name),
+ tabindex: "" + count,
"class": "Item",
_children: [
//imageViewSpec
]
});
view._data = item;
+ view._index = count;
+
+ if (previousview) previousview._next = view;
+ view._prev = previousview;
+ previousview = view;
+
this.resultContainer.appendChild(view);
+ count ++;
+ }
+
+ if (this.focusResult) {
+ if (this.resultContainer.firstChild && this.resultContainer.firstChild.focus) this.resultContainer.firstChild.focus();
+ this.focusResult = false;
}
this.ensureVisibleItemsContent();
@@ -126,6 +195,95 @@ CollectionResourceBrowserDialog.prototype.search = function () {
console.error(err);
});
};
+CollectionResourceBrowserDialog.prototype.handleListFocus = function (e) {
+ var view = Dom.findUpwardForNodeWithData(e.target, "_data");
+ if (!view) return;
+ this.selectedView = view;
+};
+CollectionResourceBrowserDialog.prototype.returnData = function (data) {
+ var collection = this.collectionCombo.getSelectedItem();
+
+ if (this.returnType == CollectionResourceBrowserDialog.RETURN_IMAGEDATA) {
+ var id = Pencil.controller.collectionResourceAsRefSync(collection, data.relativePath);
+ this.close(new ImageData(data.size.w, data.size.h, ImageData.idToRefString(id)));
+
+ } else if (this.returnType == CollectionResourceBrowserDialog.RETURN_IMAGEDATA_SVG_GROUP) {
+ var svg = Dom.parseFile(data.path);
+ var g = svg.createElementNS(PencilNamespaces.svg, "g");
+ while (svg.documentElement.firstChild) {
+ var child = svg.documentElement.firstChild;
+ svg.documentElement.removeChild(child);
+ g.appendChild(child);
+ }
+
+ var xmlData = CollectionResourceBrowserDialog.SVG_IMAGE_DATA_PREFIX + Dom.serializeNode(g);
+ this.close(new ImageData(data.size.w, data.size.h, xmlData));
+ } else if (this.returnType == CollectionResourceBrowserDialog.RETURN_CONTENT) {
+ this.close({
+ size: data.size,
+ content: new PlainText(fs.readFileSync(data.path))
+ });
+ } else if (this.returnType == CollectionResourceBrowserDialog.RETURN_DOMCONTENT) {
+ this.close({
+ size: data.size,
+ document: Dom.parseFile(data.path)
+ });
+ }
+};
+CollectionResourceBrowserDialog.prototype.returnDataInSelectedView = function () {
+ if (!this.selectedView || !this.selectedView._data) return;
+ this.returnData(this.selectedView._data);
+};
+CollectionResourceBrowserDialog.prototype.handleFilterKeyDown = function (e) {
+ if (e.keyCode == DOM_VK_ENTER || e.keyCode == DOM_VK_RETURN) {
+ if (this.searchTimeout) {
+ this.focusResult = true;
+ } else {
+ if (this.resultContainer.firstChild && this.resultContainer.firstChild.focus) this.resultContainer.firstChild.focus();
+ }
+
+ e.stopPropagation();
+ e.preventDefault();
+ }
+};
+CollectionResourceBrowserDialog.prototype.handleListKeyDown = function (e) {
+ var view = Dom.findUpwardForNodeWithData(e.target, "_data");
+ if (!view) return;
+
+ if (e.keyCode == DOM_VK_UP) {
+ for (var i = view._index - 1; i >= 0; i --) {
+ var other = view.parentNode.childNodes[i];
+ if (other && other.offsetLeft == view.offsetLeft) {
+ other.focus();
+ return;
+ }
+ }
+ } else if (e.keyCode == DOM_VK_DOWN) {
+ for (var i = view._index + 1; i < view.parentNode.childNodes.length; i ++) {
+ var other = view.parentNode.childNodes[i];
+ if (other && other.offsetLeft == view.offsetLeft) {
+ other.focus();
+ return;
+ }
+ }
+ } else if (e.keyCode == DOM_VK_LEFT) {
+ if (view._prev) {
+ view._prev.focus();
+ return;
+ }
+ } else if (e.keyCode == DOM_VK_RIGHT) {
+ if (view._next) {
+ view._next.focus();
+ return;
+ }
+ } else if (e.keyCode == DOM_VK_TAB) {
+ this.filterInput.focus();
+ this.filterInput.select();
+ e.preventDefault();
+ } else if (e.keyCode == DOM_VK_ENTER || e.keyCode == DOM_VK_RETURN) {
+ this.returnData(view._data);
+ }
+};
CollectionResourceBrowserDialog.prototype.getMatchingResources = function (dirPath, relativePath, keyword) {
return new Promise(function (resolve, reject) {
var items = [];
@@ -184,7 +342,7 @@ CollectionResourceBrowserDialog.handleSVGObjectLoaded = function (e) {
var w = svg.getAttribute("width");
var h = svg.getAttribute("height");
svg.setAttribute("viewBox", "0 0 " + w + " " + h);
- object.parentNode._data.size = new Dimension(w, h);
+ object.parentNode._data.size = new Dimension(Math.round(parseFloat(w)), Math.round(parseFloat(h)));
};
CollectionResourceBrowserDialog.handleImageLoaded = function (e) {
var image = event.target;
@@ -242,25 +400,12 @@ CollectionResourceBrowserDialog.prototype.ensureVisibleItemsContent = function (
this.showItem(node, shouldShow);
}
};
-
+CollectionResourceBrowserDialog.SVG_IMAGE_DATA_PREFIX = "data:image/svg+xml;utf8,";
CollectionResourceBrowserDialog.prototype.handleItemDblClick = function (e) {
var data = Dom.findUpwardForData (e.target, "_data");
if (!data) return;
- if (this.returnType == CollectionResourceBrowserDialog.RETURN_IMAGEDATA) {
- var id = Pencil.controller.collectionResourceAsRefSync(this.collection, data.relativePath);
- this.close(new ImageData(data.size.w, data.size.h, ImageData.idToRefString(id)))
- } else if (this.returnType == CollectionResourceBrowserDialog.RETURN_CONTENT) {
- this.close({
- size: data.size,
- content: new PlainText(fs.readFileSync(data.path))
- });
- } else if (this.returnType == CollectionResourceBrowserDialog.RETURN_DOMCONTENT) {
- this.close({
- size: data.size,
- document: Dom.parseFile(data.path)
- });
- }
+ this.returnData(data);
};
CollectionResourceBrowserDialog.open = function (collection, options, callback) {
diff --git a/app/views/collections/CollectionResourceBrowserDialog.xhtml b/app/views/collections/CollectionResourceBrowserDialog.xhtml
index 9f28c94d..d45ed4ce 100644
--- a/app/views/collections/CollectionResourceBrowserDialog.xhtml
+++ b/app/views/collections/CollectionResourceBrowserDialog.xhtml
@@ -25,6 +25,10 @@
cursor: pointer;
position: relative;
}
+ @resultContainer .Item:focus {
+ outline: none;
+ background: lighten(@selected_bg, 40%);
+ }
@resultContainer .Item > object,
@resultContainer .Item > img {
width: 5ex;
@@ -43,16 +47,30 @@
box-shadow: 0px 0px 4px @selected_bg;
}
+ @collectionSelectionPane {
+ margin-bottom: 1ex;
+ padding-bottom: 1ex;
+ border-bottom: solid 1px rgba(0, 0, 0, 0.3);
+ }
+
+ @collectionCombo {
+ font-weight: bold;
+ }
+
@prefixCombo {
margin-left: 0.2em;
}
+
+
+
+
-
+
diff --git a/app/views/common/NotificationPopup.js b/app/views/common/NotificationPopup.js
index 6bea9233..54cf4abb 100644
--- a/app/views/common/NotificationPopup.js
+++ b/app/views/common/NotificationPopup.js
@@ -13,7 +13,7 @@ function NotificationPopup() {
__extend(Popup, NotificationPopup);
NotificationPopup.prototype.setup = function (message, actionTitle, actionHandler) {
- this.messagePane.innerHTML = Dom.htmlEncode(message);
+ this.messagePane.innerHTML = Dom.htmlEncode(message).replace(/\n/g, "
");
Dom.toggleClass(this.node(), "WithAction", actionTitle)
if (actionTitle) {
diff --git a/app/views/common/Popup.js b/app/views/common/Popup.js
index d1b040a3..02f15e01 100644
--- a/app/views/common/Popup.js
+++ b/app/views/common/Popup.js
@@ -133,6 +133,8 @@ Popup.prototype.isVisible = function () {
Popup.prototype.showAt = function (x, y, skipEvent, autoFlip) {
this.reparent();
+ console.log("Showing at: ", [x, y]);
+
if (this.mode) {
this.popupContainer.setAttribute("mode", this.mode);
}
diff --git a/app/views/common/UICommandManager.js b/app/views/common/UICommandManager.js
index 1ebc0aa3..d24f0347 100644
--- a/app/views/common/UICommandManager.js
+++ b/app/views/common/UICommandManager.js
@@ -137,7 +137,7 @@ window.document.addEventListener("focus", function (event) {
}, true);
-UICommandManager.register = function (command) {
+UICommandManager.register = function (command, control) {
command._run = command.run;
command.run = UICommandManager.checkAndRunFunction;
@@ -158,6 +158,12 @@ UICommandManager.register = function (command) {
document.body.addEventListener(eventNames[i], f, false);
}
}
+
+ if (control) {
+ UICommandManager.installControl(command.key, control);
+ }
+
+ return command;
};
UICommandManager.getCommand = function (commandKey) {
if (!commandKey) return;
@@ -179,7 +185,7 @@ UICommandManager.installControl = function (commandKey, control) {
};
UICommandManager.invalidateCommand = function (command) {
if (!command.controls) return;
- var valid = command.isValid ? command.isValid() : !command.disabled;
+ var valid = command.isValid ? command.isValid() : (command.isAvailable ? command.isAvailable() : !command.disabled);
for (var i = 0; i < command.controls.length; i ++) {
command.controls[i].disabled = !valid;
if (command.controls[i].setEnabled) command.controls[i].setEnabled(valid);
@@ -243,6 +249,8 @@ UICommandManager.handleKeyEvent = function (event) {
continue;
}
+ if (command.isAvailable && !command.isAvailable()) continue;
+
var eventCmdKey = command.parsedShortcut.command ? event.metaKey : false;
var eventCtrlKey = !command.parsedShortcut.command && IS_MAC ? event.metaKey : event.ctrlKey;
if (eventCmdKey == command.parsedShortcut.command
diff --git a/app/views/editors/NPatchSpecEditorDialog.js b/app/views/editors/NPatchSpecEditorDialog.js
index c5592ddc..515ed674 100644
--- a/app/views/editors/NPatchSpecEditorDialog.js
+++ b/app/views/editors/NPatchSpecEditorDialog.js
@@ -7,6 +7,11 @@ function NPatchSpecEditorDialog () {
this.bind("mousedown", this.handleGlobalMouseDown, this.yCellContainer);
this.bind("mouseup", this.handleGlobalMouseUp, document);
this.bind("mousemove", this.handleGlobalMouseMove, document);
+
+ this.bind("click", function () {
+ console.log(this.useDarkBackgroundCheckbox.checked);
+ this.container.setAttribute("dark", this.useDarkBackgroundCheckbox.checked);
+ }, this.useDarkBackgroundCheckbox);
}
__extend(Dialog, NPatchSpecEditorDialog);
@@ -146,8 +151,66 @@ NPatchSpecEditorDialog.prototype.invalidateCellPosition = function (cell) {
}
var info = cell._cellInfo;
if (info) {
- info.textContent = b - a;
+ info.textContent = (cell._data.to - cell._data.from);
+ if (!cell._isX) {
+ var w = Math.round(info.offsetWidth);
+ var r = (s/2) - (w/2);
+ info.style.transform = "rotate(-90deg) translate(-" + r + "px, 0px)";
+ }
+ }
+};
+NPatchSpecEditorDialog.commandsToData = function (pathCommands) {
+ var newData = "";
+
+ for (var command of pathCommands) {
+ if (newData) newData += " ";
+ newData += command.command;
+ if (command.points) {
+ for (var i = 0; i < command.points.length; i ++) {
+ newData += (i > 0 ? " " : "") + command.points[i].x + "," + command.points[i].y;
+ }
+ }
+ }
+
+ return newData;
+};
+
+NPatchSpecEditorDialog.generatePathSVGData = function (svgPathData, size) {
+ var specs = [];
+ var json = svgPathData.data;
+ if (!json.startsWith("json:")) return specs;
+ var parsedPathData = JSON.parse(json.substring(5));
+
+
+ for (var info of parsedPathData) {
+ var d = NPatchSpecEditorDialog.commandsToData(info.commands);
+ specs.push({
+ _name: "path",
+ _uri: PencilNamespaces.svg,
+ d: d,
+ style: "stroke: #000000; stroke-width: 1px; fill: rgba(0, 0, 0, 0.1);"
+ });
}
+
+ var svg = {
+ _name: "svg",
+ _uri: PencilNamespaces.svg,
+ width: svgPathData.w,
+ height: svgPathData.h,
+ viewBox: "0 0 " + size.w + " " + size.h,
+ _children: [
+ {
+ _name: "g",
+ _uri: PencilNamespaces.svg,
+ //transform: scale(size.w / svgPathData.w, size.h / svgPathData.h),
+ _children: specs
+ }
+ ]
+ }
+
+ var svgDom = Dom.newDOMElement(svg);
+ var svgData = encodeURIComponent(Dom.serializeNode(svgDom));
+ return "data:image/svg+xml," + svgData;
};
NPatchSpecEditorDialog.prototype.setup = function (options) {
this.options = options || {};
@@ -158,7 +221,13 @@ NPatchSpecEditorDialog.prototype.setup = function (options) {
var r0 = Math.max(this.options.imageData.w / maxW, this.options.imageData.h / maxH);
var r1 = Math.min(this.options.imageData.w / minSize, this.options.imageData.h / minSize);
- this.image.src = ImageData.refStringToUrl(this.options.imageData.data);
+ if (this.options.imageData.data && this.options.imageData.data.startsWith("json:")) {
+ this.image.src = NPatchSpecEditorDialog.generatePathSVGData(this.options.imageData, this.options.imageData);
+ } else if (this.options.imageData.data && this.options.imageData.data.startsWith("data:")) {
+ this.image.src = this.options.imageData.data;
+ } else {
+ this.image.src = ImageData.refStringToUrl(this.options.imageData.data);
+ }
var r = r1 < 1 ? r1 : r0;
this.setZoom(r);
diff --git a/app/views/editors/NPatchSpecEditorDialog.xhtml b/app/views/editors/NPatchSpecEditorDialog.xhtml
index 9ebfcdf0..49e9f244 100644
--- a/app/views/editors/NPatchSpecEditorDialog.xhtml
+++ b/app/views/editors/NPatchSpecEditorDialog.xhtml
@@ -52,7 +52,7 @@
height: 0.5em;
cursor: ns-resize;
}
- @xCellContainer > .Cell > .StartResizer {
+ @yCellContainer > .Cell > .StartResizer {
top: -0.25em;
}
@yCellContainer > .Cell > .EndResizer {
@@ -91,8 +91,6 @@
}
.CellContainer > .Cell > .YCellInfo > .Info {
margin-left: 0.25em;
- margin-top: 1em;
- transform: rotate(-90deg);
}
@xCellContainer > .Cell > .Indicator {
top: 1.5em;
@@ -113,6 +111,10 @@
@container {
align-items: center;
justify-content: center;
+ margin-bottom: 1ex;
+ }
+ @container[dark='true'] {
+ background: #555;
}
@container > * {
/* background: rgba(0, 0, 0, 0.2); */
@@ -134,4 +136,5 @@
+
diff --git a/app/views/editors/OnMenuEditor.js b/app/views/editors/OnMenuEditor.js
index e0791ba6..20f34e09 100644
--- a/app/views/editors/OnMenuEditor.js
+++ b/app/views/editors/OnMenuEditor.js
@@ -1,5 +1,25 @@
function OnMenuEditor() {
+ OnMenuEditor.globalSetup();
}
+
+OnMenuEditor.globalSetupDone = false;
+OnMenuEditor.globalSetup = function () {
+ if (OnMenuEditor.globalSetupDone) return;
+ OnMenuEditor.globalSetupDone = true;
+
+ OnMenuEditor.invokeSecondaryContentEditCommand = UICommandManager.register({
+ key: "invokeSecondaryContentEditCommand",
+ label: "invokeSecondaryContentEditCommand",
+ shortcut: "Shift+F2",
+ isAvailable: function () { return Pencil.activeCanvas && Pencil.activeCanvas.currentController && Pencil.activeCanvas.currentController.handleOtherContentEditAction },
+ run: function () {
+ var editActions = Pencil.activeCanvas.currentController.getContentEditActions();
+ if (editActions.length < 2) return;
+ Pencil.activeCanvas.currentController.handleOtherContentEditAction(editActions[1]);
+ }
+ });
+};
+
OnMenuEditor.prototype.install = function (canvas) {
this.canvas = canvas;
this.canvas.contextMenuEditor = this;
@@ -17,6 +37,7 @@ OnMenuEditor.prototype.generateMenuItems = function () {
if (!this.targetObject) return [];
var definedGroups = this.targetObject.getPropertyGroups();
var items = [];
+ var importantItems = [];
var thiz = this;
var allowDisabled = Config.get("dev.enable_disabled_in_menu", null);
@@ -63,6 +84,7 @@ OnMenuEditor.prototype.generateMenuItems = function () {
}
};
items.push(item);
+ importantItems.push(item);
} else if (property.type == Enum) {
var enumItem = {
type: "SubMenu",
@@ -90,7 +112,16 @@ OnMenuEditor.prototype.generateMenuItems = function () {
});
}
items.push(enumItem);
- } else if (property.type == ImageData) { //TODO: property constraint to enable n-patch edit?
+ } else if (property.type == ImageData) {
+ if (this.targetObject.def) {
+ var meta = this.targetObject.def.propertyMap[property.name].meta["npatch-edit"];
+ if (meta) {
+ var value = this.targetObject.evalExpression(meta, false);
+ if (!value) continue;
+ } else {
+ continue;
+ }
+ }
var value = thiz.targetObject.getProperty(property.name);
var imageNPathSpecEditItem = {
@@ -119,12 +150,36 @@ OnMenuEditor.prototype.generateMenuItems = function () {
previousImageDataMenu = imageNPathSpecEditItem;
items.push(imageNPathSpecEditItem);
+ importantItems.push(imageNPathSpecEditItem);
}
}
}
+ if (items.length > 10) {
+ var otherPropItem = {
+ label: "Other Properties",
+ type: "SubMenu",
+ subItems: []
+ };
+
+ for (var item of items) {
+ if (importantItems.indexOf(item) < 0) {
+ otherPropItem.subItems.push(item);
+ }
+ }
+
+ items = importantItems;
+ items.push(otherPropItem);
+ }
+
//actions
var actionItem = null;
+ var editActions = this.targetObject.getContentEditActions ? this.targetObject.getContentEditActions() : [];
+ var primaryActionId = null;
+ var secondaryActionId = null;
+ if (editActions.length > 0 && editActions[0].type == "action") primaryActionId = editActions[0].actionId;
+ if (editActions.length > 1 && editActions[1].type == "action") secondaryActionId = editActions[1].actionId;
+
if (this.targetObject.def && this.targetObject.performAction) {
for (var i in this.targetObject.def.actions) {
var action = this.targetObject.def.actions[i];
@@ -144,10 +199,16 @@ OnMenuEditor.prototype.generateMenuItems = function () {
subItems: []
}
}
+
+ var shortcut = null;
+ if (action.id == primaryActionId) shortcut = "F2";
+ if (action.id == secondaryActionId) shortcut = OnMenuEditor.invokeSecondaryContentEditCommand.shortcut;
+
actionItem.subItems.push({
label: action.displayName,
type: "Normal",
actionId: action.id,
+ shortcut: shortcut,
handleAction: function (){
thiz.targetObject.performAction(this.actionId);
thiz.canvas.invalidateEditors();
diff --git a/app/views/editors/OnScreenRichTextEditor.js b/app/views/editors/OnScreenRichTextEditor.js
index a052e927..25bf146a 100644
--- a/app/views/editors/OnScreenRichTextEditor.js
+++ b/app/views/editors/OnScreenRichTextEditor.js
@@ -2,6 +2,7 @@ function OnScreenRichTextEditor() {
BaseTemplatedWidget.call(this);
this.popup.allowMouseDragging = true;
+ this.acMenu = new Menu();
}
__extend(BaseTemplatedWidget, OnScreenRichTextEditor);
@@ -50,8 +51,10 @@ OnScreenRichTextEditor.prototype.install = function (canvas) {
}, false);
var thiz = this;
+ this.bind("keydown", this.handleACKeyDown, this.container);
this.bind("keypress", this.handleKeyPress, this.container);
this.bind("keyup", this.handleKeyPress, this.container);
+ this.bind("input", this.handleInput, this.container);
};
OnScreenRichTextEditor.prototype.addEditorEvent = function (name, handler) {
this.textEditor.addEventListener(name, handler, false);
@@ -68,9 +71,25 @@ OnScreenRichTextEditor.prototype.dettach = function () {
OnScreenRichTextEditor.prototype.handleShapeDoubleClicked = function (event) {
this.currentTarget = event.controller;
- if (!this.currentTarget || !this.currentTarget.getTextEditingInfo) return;
+ if (!this.currentTarget) return;
- this.textEditingInfo = this.currentTarget.getTextEditingInfo(event);
+ this.textEditingInfo = null;
+
+ if (this.currentTarget.getContentEditActions) {
+ var actions = this.currentTarget.getContentEditActions(event);
+ if (actions.length == 0) return;
+
+ var action = actions[0];
+ if (action.type == "text") {
+ this.textEditingInfo = action.editInfo;
+ } else {
+ this.currentTarget.handleOtherContentEditAction(action);
+ return;
+ }
+ } else {
+ if (!this.currentTarget.getTextEditingInfo) return;
+ this.textEditingInfo = this.currentTarget.getTextEditingInfo(event);
+ }
if (!this.textEditingInfo || this.textEditingInfo.readonly) return;
@@ -170,13 +189,131 @@ OnScreenRichTextEditor.prototype._setupEditor = function () {
thiz.textToolOverlay.node().style.visibility = "visible";
}, 10);
};
+OnScreenRichTextEditor.MIN_AC_LENGTH = 5;
+OnScreenRichTextEditor.prototype.handleInput = function (event) {
+ var selection = window.getSelection();
+ if (!selection || !selection.anchorNode) {
+ this.hideAutoComplete();
+ return;
+ }
+ if (!this.container.contains(selection.anchorNode)) {
+ this.hideAutoComplete();
+ return;
+ }
+ if (selection.anchorOffset < OnScreenRichTextEditor.MIN_AC_LENGTH) {
+ this.hideAutoComplete();
+ return;
+ }
+
+ var textContent = selection.anchorNode.textContent;
+ var text = textContent.substring(selection.anchorOffset - OnScreenRichTextEditor.MIN_AC_LENGTH, selection.anchorOffset);
+ var acSet = this.getAutoCompleteSet(text, textContent == text);
+ if (acSet && acSet.items && acSet.items.length > 0) {
+ this.showAutoComplete(acSet, selection.anchorNode, selection.anchorOffset);
+ } else {
+ this.hideAutoComplete();
+ }
+};
+
+OnScreenRichTextEditor.prototype.showAutoComplete = function (set, node, offset) {
+ this.acMenu.items = [];
+ function createItem(acItem) {
+ var label = (typeof(acItem) == "string" ? acItem : (acItem.label || acItem.toString()));
+ if (label.length > 40) label = label.substring(0, 40) + "... (" + label.length + " characters)";
+ return {
+ label: label,
+ run: function () {
+ var text = typeof(acItem) == "string" ? acItem : (acItem.content || acItem.toString());
+
+ var textContent = node.textContent;
+ var start = Math.max(0, offset - set.replacementSize);
+ var newText = textContent.substring(0, start) + text + textContent.substring(offset);
+ node.textContent = newText;
+ var selection = window.getSelection();
+ selection.collapseToStart();
+ selection.extend(node, start + text.length);
+ selection.collapseToEnd();
+ }
+ };
+ }
+ for (var acItem of set.items) {
+ this.acMenu.register(createItem(acItem));
+ }
+
+ var anchorNode = node;
+ if (!anchorNode.localName) anchorNode = anchorNode.parentNode;
+ this.acSelectedItemIndex = -1;
+ this.acMenu.showMenu(anchorNode, "left-inside", "bottom", 0, 5, true);
+};
+OnScreenRichTextEditor.prototype.hideAutoComplete = function () {
+ this.acMenu.hideMenu();
+};
+
+//TODO: Refactor this into AC set registration from stencil
+OnScreenRichTextEditor.prototype.getAutoCompleteSet = function (term, isFull) {
+ if (term && term.toLowerCase() == "lorem") {
+ var up = term.substring(0, 1) == "L";
+ return {
+ items: [
+ up ? capitalize(getLoremWord()) : getLoremWord(),
+ up ? loremIpsumSentence(3).trim() : loremIpsum(3),
+ up ? loremIpsumSentence(5).trim() : loremIpsum(5),
+ up ? loremIpsumSentence(10).trim() : loremIpsum(10),
+ up ? loremIpsumSentence(20).trim() : loremIpsum(20),
+ loremIpsumParagraph(40)
+ ],
+ replacementSize: term.length
+ }
+ }
+
+ return null;
+};
+OnScreenRichTextEditor.prototype.handleACKeyDown = function (event) {
+ if (!this.acMenu.isVisible()) return;
+ if (event.keyCode == DOM_VK_UP || event.keyCode == DOM_VK_DOWN) {
+ var items = this.acMenu.getMenuItemNodes();
+ if (event.keyCode == DOM_VK_DOWN) {
+ this.acSelectedItemIndex ++;
+ if (this.acSelectedItemIndex >= items.length) this.acSelectedItemIndex = 0;
+ } else {
+ this.acSelectedItemIndex --;
+ if (this.acSelectedItemIndex < 0) this.acSelectedItemIndex = items.length - 1;
+ }
+
+ for (var i = 0; i < items.length; i ++) {
+ if (i == this.acSelectedItemIndex) {
+ items[i].setAttribute("selected", "true");
+ } else {
+ items[i].removeAttribute("selected");
+ }
+ }
+
+ event.stopPropagation();
+ event.preventDefault();
+ } else if (event.keyCode == DOM_VK_RETURN || event.keyCode == DOM_VK_ENTER) {
+ var items = this.acMenu.getMenuItemNodes();
+ if (this.acSelectedItemIndex >= items.length || this.acSelectedItemIndex < 0) return;
+ var item = items[this.acSelectedItemIndex]._item;
+ if (item && item.run) item.run();
+ this.hideAutoComplete();
+
+ event.stopPropagation();
+ event.preventDefault();
+
+ this.cancelNextCommit = true;
+ }
+};
OnScreenRichTextEditor.prototype.handleKeyPress = function (event) {
if (this.textToolOverlay.settingFont) {
return;
}
if (event.keyCode == DOM_VK_RETURN && !event.shiftKey && !event.accelKey) {
+ if (this.cancelNextCommit) {
+ this.cancelNextCommit = false;
+ return;
+ }
var insideList = null;
try {
var node = window.getSelection().anchorNode;
@@ -185,7 +322,9 @@ OnScreenRichTextEditor.prototype.handleKeyPress = function (event) {
});
} catch (e) {}
- if (!insideList || event.ctrlKey) this.commitChange(event);
+ if (!insideList || event.ctrlKey) {
+ this.commitChange(event);
+ }
} else if (event.keyCode == DOM_VK_ESCAPE && this.popup == BaseWidget.getTopClosable()) {
this.cancelChange();
diff --git a/app/views/editors/PlainTextEditor.js b/app/views/editors/PlainTextEditor.js
index d97cadd0..09fcfdb6 100644
--- a/app/views/editors/PlainTextEditor.js
+++ b/app/views/editors/PlainTextEditor.js
@@ -9,7 +9,6 @@ PlainTextEditor.prototype.setup = function () {
thiz.fireChangeEvent();
}, false);
this.textarea.addEventListener("keyup", function (event) {
- console.log("keyup")
thiz.fireChangeEvent();
}, false);
};
diff --git a/app/views/editors/SharedGeomtryEditor.js b/app/views/editors/SharedGeomtryEditor.js
index 520fd3cc..e40d2a7a 100644
--- a/app/views/editors/SharedGeomtryEditor.js
+++ b/app/views/editors/SharedGeomtryEditor.js
@@ -41,11 +41,40 @@ SharedGeomtryEditor.prototype.setup = function () {
}
}, false);
+ var thiz = this;
+ UICommandManager.register({
+ key: "lockMovementCommand",
+ getLabel: function () {
+ return "Lock shape's movement";
+ },
+ shortcut: "F12",
+ run: function () {
+ var locked = thiz.lockMovementButton.getAttribute("checked") == "true";
+ if (locked) {
+ thiz.lockMovementButton.removeAttribute("checked");
+ } else {
+ thiz.lockMovementButton.setAttribute("checked", "true");
+ }
+
+ Pencil.controller.movementDisabled = !locked;
+ }
+ }, this.lockMovementButton);
+
this.container.ownerDocument.documentElement.addEventListener("p:ShapeGeometryModified", function (event) {
if (event.setter && event.setter == thiz) return;
thiz.invalidate();
}, false);
};
+SharedGeomtryEditor.prototype.toggleMovementLocking = function () {
+ var locked = this.lockMovementButton.getAttribute("checked") == "true";
+ if (locked) {
+ this.lockMovementButton.removeAttribute("checked");
+ } else {
+ this.lockMovementButton.setAttribute("checked", "true");
+ }
+
+ Pencil.controller.movementDisabled = !locked;
+};
SharedGeomtryEditor.prototype.handleCommandEvent = function () {
var currentGeo = this.targetObject.getGeometry();
var dx = this.shapeX.value - currentGeo.ctm.e;
@@ -125,8 +154,8 @@ SharedGeomtryEditor.prototype.attach = function (targetObject) {
this.handleBox = null;
- this.shapeX.value = Math.max(0, Math.round(geo.ctm.e));
- this.shapeY.value = Math.max(0, Math.round(geo.ctm.f));
+ this.shapeX.value = Math.round(geo.ctm.e);
+ this.shapeY.value = Math.round(geo.ctm.f);
this.shapeWidth.value = Math.round(geo.dim.w);
this.shapeHeight.value = Math.round(geo.dim.h);
diff --git a/app/views/editors/SharedGeomtryEditor.xhtml b/app/views/editors/SharedGeomtryEditor.xhtml
index 7f88790d..ac3023ff 100644
--- a/app/views/editors/SharedGeomtryEditor.xhtml
+++ b/app/views/editors/SharedGeomtryEditor.xhtml
@@ -6,10 +6,14 @@
@container > * + * {
margin-left: @toolbar_gap;
}
+ @lockMovementButton[checked='true'] {
+ color: #F00;
+ }
+
diff --git a/app/views/editors/SharedPropertyEditor.js b/app/views/editors/SharedPropertyEditor.js
index 598f5195..b73b531c 100644
--- a/app/views/editors/SharedPropertyEditor.js
+++ b/app/views/editors/SharedPropertyEditor.js
@@ -11,7 +11,6 @@ SharedPropertyEditor.prototype.setup = function () {
var thiz = this;
this.propertyContainer.addEventListener("p:ValueChanged", function(event) {
- console.log("p:ValueChanged", event);
if (!thiz.target) return;
var editor = Dom.findUpward(event.target, function (n) {
return n._property;
@@ -30,6 +29,10 @@ SharedPropertyEditor.prototype.setup = function () {
thiz.setDefaultProperties();
}
}, false);
+ this.propertyContainer.addEventListener("input", function(event) {
+ if (event.target != thiz.symbolNameInput || !thiz.target || !thiz.target.setSymbolName) return;
+ thiz.target.setSymbolName(event.target.value.trim());
+ }, false);
};
SharedPropertyEditor.prototype.getTitle = function() {
return "Properties";
@@ -45,17 +48,17 @@ SharedPropertyEditor.prototype.sizeChanged = function (expanded) {
}
}
SharedPropertyEditor.prototype.validationEditorUI = function() {
- if (!this.validationEditor) return ;
+ if (!this.validationEditor) return;
+ var allowDisabled = Config.get(Config.DEV_ENABLE_DISABLED_IN_PROP_PAGE);
for (var i = 0; i < this.validationEditor.length; i++) {
- this.validationEditor[i].style.display = "none";
var name = this.validationEditor[i]._property.name;
var meta = this.target.def.propertyMap[name].meta["disabled"];
- var value = this.target.evalExpression(meta, true);
+ var disabled = !allowDisabled && this.target.evalExpression(meta, true);
- if (!value) this.validationEditor[i].style.display = "inherit";
+ this.validationEditor[i].style.display = disabled ? "none" : "flex";
}
-}
+};
SharedPropertyEditor.prototype.attach = function (target) {
@@ -116,25 +119,13 @@ SharedPropertyEditor.prototype.attach = function (target) {
var properties = [];
- var allowDisabled = Config.get("dev.enable_disabled_in_property_page", null);
- if (allowDisabled == null) {
- Config.set("dev.enable_disabled_in_property_page", false);
- allowDisabled = false;
- }
+ var allowDisabled = Config.get(Config.DEV_ENABLE_DISABLED_IN_PROP_PAGE);
for (var i in definedGroups) {
var group = definedGroups[i];
for (var j in group.properties) {
var property = group.properties[j];
- if (this.target.def) {
- var meta = this.target.def.propertyMap[property.name].meta["disabled"];
- if (meta && !allowDisabled) {
- var value = this.target.evalExpression(meta, true);
- if (value) continue;
- }
- }
-
var editor = TypeEditorRegistry.getTypeEditor(property.type);
if (!editor) continue;
property._group = group;
@@ -169,6 +160,27 @@ SharedPropertyEditor.prototype.attach = function (target) {
});
thiz.propertyContainer.appendChild(hbox);
}
+
+ if (StencilCollectionBuilder.isDocumentConfiguredAsStencilCollection() && thiz.target.getSymbolName) {
+ thiz.propertyContainer.appendChild(Dom.newDOMElement({
+ _name: "vbox",
+ "class": "SymbolNameContainer",
+ _children: [
+ {
+ _name: "label",
+ _text: "Symbol Name:"
+ },
+ {
+ _name: "input",
+ type: "text",
+ _id: "symbolNameInput",
+ value: thiz.target.getSymbolName() || ""
+
+ }
+ ]
+ }, document, thiz));
+ }
+
thiz.propertyContainer.style.display = "flex";
thiz.propertyContainer.style.opacity = "1";
thiz.validationEditorUI();
@@ -229,11 +241,17 @@ SharedPropertyEditor.prototype.attach = function (target) {
thiz.propertyEditor[property.name] = editorWidget;
editorWrapper._property = property;
- if (property.reload) {
+
+ var meta = property.meta["disabled"];
+
+ if (meta) {
if (!thiz.validationEditor) thiz.validationEditor = [];
thiz.validationEditor.push(editorWrapper);
- editorWrapper.style.display = "none";
+
+ var disabled = !allowDisabled && thiz.target.evalExpression(meta, true);
+ editorWrapper.style.display = disabled ? "none" : "flex";
}
+
currentGroupNode.appendChild(editorWrapper);
window.setTimeout(executor(), 40);
};
diff --git a/app/views/editors/SharedPropertyEditor.xhtml b/app/views/editors/SharedPropertyEditor.xhtml
index ab525ec2..688206c1 100644
--- a/app/views/editors/SharedPropertyEditor.xhtml
+++ b/app/views/editors/SharedPropertyEditor.xhtml
@@ -82,6 +82,14 @@
font-weight: bold;
line-height: 2.4
}
+ @propertyContainer .SymbolNameContainer {
+ margin-top: 1em;
+ border-top: solid 1px rgba(0, 0, 0, 0.2);
+ padding-top: 1em;
+ }
+ @propertyContainer .SymbolNameContainer label {
+ margin-bottom: 0.3ex;
+ }
Select objects in the canvas to edit properties.
diff --git a/app/views/menus/MainMenu.js b/app/views/menus/MainMenu.js
index 285911f0..472ecb89 100644
--- a/app/views/menus/MainMenu.js
+++ b/app/views/menus/MainMenu.js
@@ -73,6 +73,8 @@ MainMenu.prototype.setup = function () {
if (Config.get("dev.enabled", null) == null) Config.set("dev.enabled", false);
+ var devEnable = Config.get("dev.enabled", false);
+
this.register(UICommandManager.getCommand("newDocumentCommand"));
this.register(UICommandManager.getCommand("openDocumentCommand"));
this.register(UICommandManager.getCommand("saveDocumentCommand"));
@@ -93,15 +95,15 @@ MainMenu.prototype.setup = function () {
}
});
- var developerToolSubItems = [];
- developerToolSubItems.push({
+ var toolSubItems = [];
+ toolSubItems.push({
key: "manageCollections",
label: "Manage Collections...",
run: function () {
new CollectionManagementDialog(Pencil.collectionPane).open();
}
});
- developerToolSubItems.push({
+ toolSubItems.push({
key: "manageExportTemplate",
label: "Manage Export Template...",
run: function () {
@@ -109,7 +111,7 @@ MainMenu.prototype.setup = function () {
templateDialog.open();
}
});
- developerToolSubItems.push({
+ toolSubItems.push({
key: "manageFontCommand",
label: "Manage Fonts...",
run: function () {
@@ -117,7 +119,9 @@ MainMenu.prototype.setup = function () {
}
});
- developerToolSubItems.push(Menu.SEPARATOR);
+ toolSubItems.push(Menu.SEPARATOR);
+
+ var developerToolSubItems = [];
developerToolSubItems.push({
key: "stencilGenerator",
label: "Stencil Generator...",
@@ -158,12 +162,45 @@ MainMenu.prototype.setup = function () {
Pencil.controller.exportAsLayout();
}
});
+ developerToolSubItems.push(Menu.SEPARATOR);
+
+ developerToolSubItems.push(UICommandManager.register({
+ key: "configureStencilCollection",
+ getLabel: function () {
+ return StencilCollectionBuilder.isDocumentConfiguredAsStencilCollection() ?
+ "Configure Stencil Collection..." : "Configure as Stencil Collection...";
+ },
+ isAvailable: function () { return Pencil.controller && Pencil.controller.doc; },
+ run: function () {
+ new StencilCollectionBuilder(Pencil.controller).configure();
+ }
+ }));
+ developerToolSubItems.push(UICommandManager.register({
+ key: "unconfigureStencilCollection",
+ label: "Unconfigure as Stencil Collection...",
+ isAvailable: function () { return StencilCollectionBuilder.isDocumentConfiguredAsStencilCollection(); },
+ run: function () {
+ new StencilCollectionBuilder(Pencil.controller).removeCurrentDocumentOptions();
+ }
+ }));
+
+ developerToolSubItems.push(UICommandManager.register({
+ key: "buildStencilCollection",
+ label: "Build Stencil Collection...",
+ shortcut: "Ctrl+B",
+ isAvailable: function () { return Pencil.controller && Pencil.controller.doc; },
+ run: function () {
+ new StencilCollectionBuilder(Pencil.controller).build();
+ }
+ }));
+ developerToolSubItems.push(Menu.SEPARATOR);
+
developerToolSubItems.push({
key: "copyAsShortcut",
label: "Generate Shortcut XML...",
isAvailable: function () {
return Pencil.activeCanvas && Pencil.activeCanvas.currentController
- && Pencil.activeCanvas.currentController.generateShortcutXML;
+ && Pencil.activeCanvas.currentController.generateShortcutXML && devEnable;
},
run: function () {
Pencil.activeCanvas.currentController.generateShortcutXML();
@@ -172,20 +209,27 @@ MainMenu.prototype.setup = function () {
});
developerToolSubItems.push(Menu.SEPARATOR);
- developerToolSubItems.push({
+ developerToolSubItems.push(UICommandManager.register({
key: "openDeveloperTools",
label: "Open Developer Tools",
shortcut: "Ctrl+Alt+Shift+P",
run: function () {
Pencil.app.mainWindow.openDevTools();
}
+ }));
+
+ toolSubItems.push({
+ key: "devToolCommand",
+ label: "Developer Tools",
+ type: "SubMenu",
+ subItems: developerToolSubItems
});
this.register({
key: "toolCommand",
label: "Tools",
type: "SubMenu",
- subItems: developerToolSubItems
+ subItems: toolSubItems
});
this.separator();
this.register({
@@ -198,14 +242,15 @@ MainMenu.prototype.setup = function () {
}
});
this.separator();
- this.register({
+ this.register(UICommandManager.register({
key: "exitApplicationCommand",
label: "Exit",
isValid: function () { return true; },
+ shortcut: "Ctrl+Q",
run: function () {
let remote = require("electron").remote;
let currentWindow = remote.getCurrentWindow();
currentWindow.close();
}
- });
+ }));
}
diff --git a/app/views/menus/Menu.js b/app/views/menus/Menu.js
index 446ef6cf..28c073b0 100644
--- a/app/views/menus/Menu.js
+++ b/app/views/menus/Menu.js
@@ -215,6 +215,9 @@ Menu.prototype.renderItem = function (item) {
return hbox;
};
+Menu.prototype.getMenuItemNodes = function () {
+ return this.popupContainer.childNodes;
+};
Menu.prototype.render = function () {
Dom.empty(this.popupContainer);
var actualItems = [];
diff --git a/app/views/menus/Menu.xhtml b/app/views/menus/Menu.xhtml
index 6475dd91..66c84127 100644
--- a/app/views/menus/Menu.xhtml
+++ b/app/views/menus/Menu.xhtml
@@ -20,7 +20,8 @@
margin: 0.8em 0em;
}
body .MenuPopupContainer .MenuItem.Active:not([disabled='true']),
- body .MenuPopupContainer .MenuItem:not([disabled='true']):hover {
+ body .MenuPopupContainer .MenuItem:not([disabled='true']):hover,
+ body .MenuPopupContainer .MenuItem[selected='true']:not([disabled='true']) {
background: @selected_bg;
color: @selected_fg;
}
diff --git a/app/views/tools/StencilCollectionBuilder.js b/app/views/tools/StencilCollectionBuilder.js
new file mode 100644
index 00000000..b51ab008
--- /dev/null
+++ b/app/views/tools/StencilCollectionBuilder.js
@@ -0,0 +1,1396 @@
+function StencilCollectionBuilder(controller) {
+ this.controller = controller;
+}
+
+StencilCollectionBuilder.COLLECTION_UTIL = `
+collection.BOUND_CALCULATOR = {
+ L: function (box, v) { return (box.x || 0) + v;},
+ T: function (box, v) { return (box.y || 0) + v;},
+ R: function (box, v) { return (box.x || 0) + box.w - v;},
+ B: function (box, v) { return (box.y || 0) + box.h - v;},
+ C: function (box, v) { return (box.x || 0) + box.w / 2 + v;},
+ M: function (box, v) { return (box.y || 0) + box.h / 2 + v;},
+ H0X: function (box, v, h0, h1) {
+ return h0.x + v;
+ },
+ H0Y: function (box, v, h0, h1) {
+ return h0.y + v;
+ },
+ H1X: function (box, v, h0, h1) {
+ return h1.x + v;
+ },
+ H1Y: function (box, v, h0, h1) {
+ return h1.y + v;
+ },
+
+ calculate: function (box, spec, h0, h1) {
+ spec.match(/^([A-Z0-9]*[A-Z])([0-9\-]+)$/)
+ return collection.BOUND_CALCULATOR[RegExp.$1](box, parseInt(RegExp.$2, 10), h0, h1);
+ }
+};
+collection.toBounds = function (box, textBounds, h0, h1) {
+ var literal = textBounds.value || textBounds.toString();
+ var parts = literal.split(",");
+ var x = collection.BOUND_CALCULATOR.calculate(box, parts[0], h0, h1);
+ var y = collection.BOUND_CALCULATOR.calculate(box, parts[1], h0, h1);
+ var w = collection.BOUND_CALCULATOR.calculate(box, parts[2], h0, h1) - x;
+ var h = collection.BOUND_CALCULATOR.calculate(box, parts[3], h0, h1) - y;
+
+ return new Bound(x, y, w, h);
+};
+collection.calculateBoundsFromPolicy = function (box, originalInfo, policy) {
+ var hLayout = Group.calculateLayout(originalInfo.x0, originalInfo.w0, originalInfo.gw0, policy.xPolicy, policy.wPolicy, box.w, originalInfo.w0);
+ var vLayout = Group.calculateLayout(originalInfo.y0, originalInfo.h0, originalInfo.gh0, policy.yPolicy, policy.hPolicy, box.h, originalInfo.h0);
+
+ return new Bound(Math.round(hLayout.pos), Math.round(vLayout.pos), Math.round(hLayout.size), Math.round(vLayout.size));
+};
+collection.copyClipboardImage = function (target, imageDataPropName, boxPropName) {
+ try {
+ var image = clipboard.readImage();
+ if (image) {
+ var id = Pencil.controller.nativeImageToRefSync(image);
+
+ var size = image.getSize();
+ var newImageData = new ImageData(size.width, size.height, ImageData.idToRefString(id));
+ target.setProperty(imageDataPropName, newImageData);
+ if (boxPropName) target.setProperty(boxPropName, new Dimension(size.width, size.height));
+ }
+ } catch (e) {
+ console.error(e);
+ }
+};
+
+collection.buildNPatchModel = function (cells, originalSize, newSize) {
+ var totalScaleSize = 0;
+ for (var cell of cells) totalScaleSize += (cell.to - cell.from);
+
+ var r = (newSize - (originalSize - totalScaleSize)) / totalScaleSize;
+
+ var models = [];
+ var total = 0;
+ var scaledTotal = 0;
+ var last = false;
+
+ //add a sentinel
+ cells = cells.concat([{from: originalSize, to: originalSize + 1}]);
+
+ for (var i = 0; i < cells.length; i ++) {
+ var cell = cells[i];
+ if (cell.from == cell.to) continue;
+
+ var last = (i == cell.length - 2);
+
+ var model = null;
+ if (cell.from > total) {
+ model = {
+ start: total,
+ size: cell.from - total,
+ scaledStart: scaledTotal,
+ scale: false
+ };
+
+ models.push(model);
+ total = cell.from;
+ scaledTotal += model.size;
+ }
+
+ if (cell.from >= originalSize) break;
+
+ var scaledSize = (last ? (newSize - (originalSize - cell.to) - scaledTotal) : (r * (cell.to - cell.from)));
+
+ model = {
+ start: total,
+ size: cell.to - cell.from,
+ scaledStart: scaledTotal,
+ scaledSize: scaledSize,
+ scale: true
+ };
+
+ model.r = model.scaledSize / model.size;
+
+ models.push(model);
+ total = cell.to;
+ scaledTotal += model.scaledSize;
+ }
+
+ return models;
+};
+
+collection.parsePathData = function (pathDataLiteral) {
+ function normalize(pin) {
+ pin.x = Math.round(pin.x);
+ if (typeof(pin.y) == "number") pin.y = Math.round(pin.y);
+ }
+ function normalizeAll(pins) {
+ for (var pin of pins) normalize(pin);
+ }
+
+ function processMultiPoints(points, current, chunk, relative) {
+ var count = Math.ceil(points.length / chunk);
+ for (var i = 0; i < count; i ++) {
+ var pin = points[i * chunk + (chunk - 1)];
+
+ for (var j = 0; j < (chunk - 1); j ++) {
+ var p = points[i * chunk + j];
+ if (relative) {
+ p.x += current.x;
+ p.y += current.y;
+ }
+
+ p.fixed = true;
+ }
+
+ normalize(pin);
+
+ if (relative) {
+ pin.x += current.x;
+ pin.y += current.y;
+ }
+ current = pin;
+ }
+
+ return current;
+ }
+
+ //parse the original data
+ var RE = /([A-Z])([^A-Z]*)/gi;
+ var commands = [];
+ var result = null;
+ var current = {x: 0, y: 0};
+ while ((result = RE.exec(pathDataLiteral))) {
+ var c = result[1];
+ var command = {
+ command: c.toUpperCase()
+ };
+ var data = result[2].trim();
+ if (data) {
+ var DATA_RE = /(\-?[0-9\.]+)(\,(\-?[0-9\.]+))?/g;
+ var points = [];
+ var result2 = null;
+ while ((result2 = DATA_RE.exec(data))) {
+ var x = parseFloat(result2[1]);
+ var y = result2[3];
+ if (y) y = parseFloat(y);
+ points.push({
+ x: x,
+ y: y
+ });
+ }
+
+ if (c == "M" || c == "L" || c == "T") {
+ normalizeAll(points);
+ command.points = points;
+ current = points[points.length - 1];
+ } else if (c == "m" || c == "l" || c == "t") {
+ for (var p of points) {
+ p.x += current.x;
+ p.y += current.y;
+
+ current = p;
+ }
+ normalizeAll(points);
+ command.points = points;
+ } else if (c == "H") {
+ for (var p of points) {
+ console.log("HX:", p.x);
+ p.y = current.y;
+ current = p;
+ }
+ normalizeAll(points);
+ command.points = points;
+ command.command = "L";
+ } else if (c == "h") {
+ for (var p of points) {
+ p.x += current.x;
+ p.y = current.y;
+ current = p;
+ }
+ normalizeAll(points);
+ command.points = points;
+ command.command = "L";
+ } else if (c == "V") {
+ for (var p of points) {
+ p.y = p.x;
+ p.x = current.x;
+ current = p;
+ }
+ normalizeAll(points);
+ command.points = points;
+ command.command = "L";
+ } else if (c == "v") {
+ for (var p of points) {
+ p.y = p.x + current.y;
+ p.x = current.x;
+ current = p;
+ }
+ normalizeAll(points);
+ command.points = points;
+ command.command = "L";
+ } else if (c == "c" || c == "C") {
+ current = processMultiPoints(points, current, 3, c == "c");
+ command.points = points;
+ } else if (c == "s" || c == "S") {
+ current = processMultiPoints(points, current, 2, c == "s");
+
+ command.points = points;
+ } else if (c == "q" || c == "Q") {
+ current = processMultiPoints(points, current, 2, c == "q");
+ command.points = points;
+ } else if ((c == "a" || c == "A") && points.length == 5) {
+ for (var p of points) {
+ p.fixed = true;
+ p.noRelativeRecalcuate = true;
+ console.log("p.y", p.y);
+ }
+ var pin = points[4];
+ pin.fixed = false;
+ pin.noRelativeRecalcuate = false;
+ if (c == "a") {
+ pin.x += current.y;
+ pin.y += current.y;
+ }
+ current = pin;
+
+ normalizeAll(points);
+ command.points = points;
+ command.command = "A";
+ }
+ }
+
+ commands.push(command);
+ }
+
+ return commands;
+
+};
+
+collection.calculateScaledPosition = function (value, models) {
+ if (!models || models.length == 0) return value;
+ var m = null;
+
+ if (value < models[0].start) {
+ m = models[0];
+ } else {
+ for (var model of models) {
+ if (model.start <= value && value < (model.start + model.size)) {
+ m = model;
+ break;
+ }
+ }
+
+ if (!m) m = models[models.length - 1];
+ }
+
+ if (m) {
+ var d = value - m.start;
+
+ if (m.scale) d *= m.r;
+
+ return d + m.scaledStart;
+ }
+
+ return value;
+};
+
+
+collection.scalePathData = function (pathCommands, xCells, yCells, originalSize, newSize) {
+ xCells = xCells || [];
+ yCells = yCells || [];
+
+ var xModel = collection.buildNPatchModel(xCells, originalSize.w, newSize.w);
+ var yModel = collection.buildNPatchModel(yCells, originalSize.h, newSize.h);
+
+ var newData = "";
+
+ for (var command of pathCommands) {
+ if (command.points) {
+ var last = -1;
+ for (var i = 0; i < command.points.length; i ++) {
+ var pin = command.points[i];
+ if (pin.fixed) {
+ continue;
+ }
+
+ var x = collection.calculateScaledPosition(pin.x, xModel);
+ var y = collection.calculateScaledPosition(pin.y, yModel);
+
+ for (var j = last + 1; j < i; j ++) {
+ if (command.points[j].noRelativeRecalcuate) continue;
+ command.points[j].x = x + command.points[j].x - pin.x;
+ if (typeof(command.points[j].y) == "number") command.points[j].y = y + command.points[j].y - pin.y;
+ }
+
+ pin.x = x;
+ pin.y = y;
+ last = i;
+ }
+ }
+
+ if (newData) newData += " ";
+ newData += command.command;
+ if (command.points) {
+ for (var i = 0; i < command.points.length; i ++) {
+ var y = command.points[i].y;
+ newData += (i > 0 ? " " : "") + command.points[i].x + (typeof(y) == "number" ? ("," + y) : "");
+ }
+ }
+ }
+
+ return newData;
+};
+collection.generatePathDOM = function (svgPathData, size, keepPathStyle) {
+ var specs = [];
+ var json = svgPathData.data;
+ if (!json.startsWith("json:")) return specs;
+ var parsedPathData = JSON.parse(json.substring(5));
+
+ for (var info of parsedPathData) {
+ var d = collection.scalePathData(info.commands, svgPathData.xCells, svgPathData.yCells, svgPathData, size);
+ specs.push({
+ _name: "path",
+ _uri: PencilNamespaces.svg,
+ d: d,
+ style: keepPathStyle ? info.style : ""
+ });
+ }
+
+ return Dom.newDOMFragment(specs);
+};
+collection.generateAdvancedRectPathData = function (box, strokeStyle, r, withTop, withRight, withBottom, withLeft, withTopLeftCorner, withTopRightCorner, withBottomRightCorner, withBottomLeftCorner) {
+ var x = r * 4 * (Math.sqrt(2) - 1) / 3;
+ var w = box.w - strokeStyle.w * ((withLeft ? 0.5 : 0) + (withRight ? 0.5 : 0));
+ var h = box.h - strokeStyle.w * ((withTop ? 0.5 : 0) + (withBottom ? 0.5 : 0));
+ var parts = [
+ ];
+ var close = true;
+ if (withTop) {
+ parts.push(L(w - (withRight && withTopRightCorner ? r : 0),0));
+ if (withRight && withTopRightCorner && r > 0) parts.push(c(x,0,r,r-x,r,r));
+ } else {
+ parts.push(M(w,0));
+ close = false;
+ }
+
+ if (withRight) {
+ parts.push(L(w,h - (withBottom && withBottomRightCorner ? r : 0)));
+ if (withBottom && withBottomRightCorner && r > 0) parts.push(c(0,x,x-r,r,0-r,r));
+ } else {
+ parts.push(M(w,h));
+ close = false;
+ }
+
+ if (withBottom) {
+ parts.push(L(withLeft && withBottomLeftCorner ? r : 0,h));
+ if (withLeft && withBottomLeftCorner && r > 0) parts.push(c(x-r,0,0-r,x-r,0-r,0-r));
+ } else {
+ parts.push(M(0,h));
+ close = false;
+ }
+
+ if (withLeft) {
+ parts.push(L(0,withTop && withTopLeftCorner ? r : 0));
+ if (withTop && withTopLeftCorner && r > 0) parts.push(c(0,0-x,r-x,0-r,r,0-r));
+ } else {
+ parts.push(M(0,0));
+ close = false;
+ }
+
+ if (close) parts.push(z);
+
+ var firstMove = -1;
+ for (var i = 0; i < parts.length; i ++) {
+ if (parts[i].indexOf("M") == 0) {
+ firstMove = i;
+ break;
+ }
+ }
+
+ if (firstMove > 0) {
+ while (firstMove > 0) {
+ parts.push(parts.shift());
+ firstMove --;
+ }
+ } else {
+ parts.unshift(M(withLeft ? r : 0,0));
+ }
+
+ return parts;
+};
+collection.toColorizedDOMNode = function (svgXML, color) {
+ if (!svgXML) return document.createDocumentFragment();
+
+ var svg = Dom.parseDocument(svgXML);
+
+ if (color) {
+ var c = color.toRGBAString();
+ Dom.workOn("//svg:*", svg, function (node) {
+ if (node.style.fill != "none") {
+ node.style.fill = c;
+ }
+ if (node.style.stroke && node.style.stroke != "none") {
+ node.style.stroke = c;
+ }
+
+ var a = node.getAttribute("fill");
+ if (a != "none") node.setAttribute("fill", c);
+
+ a = node.getAttribute("stroke");
+ if (a && a != "none") node.setAttribute("stroke", c);
+ });
+ }
+
+ var g = svg.createElementNS(PencilNamespaces.svg, "g");
+ while (svg.documentElement.firstChild) {
+ var child = svg.documentElement.firstChild;
+ svg.documentElement.removeChild(child);
+ g.appendChild(child);
+ }
+
+ return g;
+};
+`;
+
+StencilCollectionBuilder.COLLECTION_RESOURCE_SCRIPT = `
+collection.browseResource = function (setNames, type, returnType, callback) {
+ var options = {
+ prefixes: [],
+ type: type || CollectionResourceBrowserDialog.TYPE_BITMAP,
+ returnType: returnType || CollectionResourceBrowserDialog.RETURN_IMAGEDATA
+ };
+
+ setNames = (setNames || "").trim();
+
+ for (var resource of collection.RESOURCE_LIST) {
+ if ((!resource.type || resource.type == options.type) && (!setNames || setNames.indexOf(resource.name) >= 0)) {
+ options.prefixes.push(resource);
+ }
+ }
+
+ CollectionResourceBrowserDialog.open(collection, options, callback);
+};
+`;
+StencilCollectionBuilder.prototype.getPageMargin = function () {
+ var pageMargin = Pencil.controller.getDocumentPageMargin();
+ return pageMargin || 0;
+};
+StencilCollectionBuilder.prototype.toCollectionReadyImageData = function (imageData, name) {
+ var value = ImageData.fromString(imageData.toString());
+ if (value.data && value.data.match(/^ref:\/\//)) {
+ var id = ImageData.refStringToId(value.data);
+ if (id) {
+ var filePath = Pencil.controller.refIdToFilePath(id);
+
+ var bitmapImageFileName = (StencilCollectionBuilder._currentPage.name + "-" + name).replace(/[^a-z0-9\\-]+/gi, "").toLowerCase() + ".png";
+
+ if (!fs.existsSync(this.currentBitmapDir)) fs.mkdirSync(this.currentBitmapDir);
+
+ var targetPath = path.join(this.currentBitmapDir, bitmapImageFileName);
+ fs.writeFileSync(targetPath, fs.readFileSync(filePath));
+
+ value = new ImageData(value.w, value.h, "collection://bitmaps/" + bitmapImageFileName, value.xCells, value.yCells);
+ }
+ }
+ return value;
+};
+StencilCollectionBuilder.getCurrentDocumentOptions = function () {
+ var json = Pencil.controller.doc.properties.stencilBuilderOptions;
+ if (json) {
+ try {
+ return JSON.parse(json);
+ } catch (e) {
+ console.error(e);
+ }
+ }
+
+ return null;
+};
+StencilCollectionBuilder.prototype.setCurrentDocumentOptions = function (options) {
+ options.pageMargin = Config.get(Config.DEV_PAGE_MARGIN_SIZE);
+ Pencil.controller.doc.properties.stencilBuilderOptions = JSON.stringify(options);
+
+ window.globalEventBus && window.globalEventBus.broadcast("doc-options-change", {});
+};
+StencilCollectionBuilder.prototype.removeCurrentDocumentOptions = function (options) {
+ if (StencilCollectionBuilder.isDocumentConfiguredAsStencilCollection()) {
+ Dialog.confirm(
+ "Are you sure you want to remove the configuration and stop using this document as a stencil collection?", null,
+ "Yes, remove configuration", function () {
+ delete Pencil.controller.doc.properties.stencilBuilderOptions;
+ window.globalEventBus && window.globalEventBus.broadcast("doc-options-change", {});
+ },
+ "Cancel", function () {
+ }
+ );
+
+ }
+};
+StencilCollectionBuilder.prototype.makeDefaultOptions = function () {
+ options = options || {};
+ var defaultDocName = Pencil.controller.getDocumentName().replace(/\*/g, "").trim();
+ var systemUsername = os.userInfo().username;
+
+ options.displayName = defaultDocName;
+ options.id = (systemUsername + "." + defaultDocName.replace(/[^a-z0-9]+/gi, ""));
+ options.description = "";
+ options.author = systemUsername;
+ options.url = "";
+
+ options.extraScript = "";
+ options.embedReferencedFonts = true;
+ options.resourceSets = [];
+
+ return options;
+};
+StencilCollectionBuilder.prototype.configure = function () {
+ var thiz = this;
+ var currentOptions = StencilCollectionBuilder.getCurrentDocumentOptions();
+ new StencilCollectionDetailDialog().callback(function (options) {
+ thiz.setCurrentDocumentOptions(options);
+ if (Pencil.controller.documentPath) {
+ Pencil.documentHandler.saveDocument();
+ }
+ }).open(currentOptions);
+};
+StencilCollectionBuilder.isDocumentConfiguredAsStencilCollection = function () {
+ return Pencil.controller.doc && Pencil.controller.doc.properties && Pencil.controller.doc.properties.stencilBuilderOptions
+};
+StencilCollectionBuilder.prototype.build = function () {
+ var thiz = this;
+ function next(options, outputPath) {
+ if (options) {
+ options.outputPath = outputPath;
+ thiz.setCurrentDocumentOptions(options);
+ thiz.buildImpl(options);
+ } else {
+ new StencilCollectionDetailDialog("Build").callback(function (options) {
+ options.outputPath = outputPath;
+ thiz.setCurrentDocumentOptions(options);
+ thiz.buildImpl(options);
+ }).open(null);
+ }
+ }
+
+ var currentOptions = StencilCollectionBuilder.getCurrentDocumentOptions();
+
+ if (!currentOptions
+ || !currentOptions.outputPath
+ || !fs.existsSync(currentOptions.outputPath)
+ || Pencil.controller.doc._lastUsedStencilOutputPath != currentOptions.outputPath) {
+ dialog.showOpenDialog(remote.getCurrentWindow(), {
+ title: "Select Output Directory",
+ defaultPath: (currentOptions && currentOptions.outputPath && fs.existsSync(currentOptions.outputPath)) ? currentOptions.outputPath : os.homedir(),
+ properties: ["openDirectory"]
+ }, function (filenames) {
+ if (!filenames || filenames.length <= 0) return;
+ var selectedPath = filenames[0];
+
+ next(currentOptions, selectedPath);
+ });
+
+ } else {
+ next(currentOptions, currentOptions.outputPath);
+ }
+};
+StencilCollectionBuilder.prototype.buildImpl = function (options) {
+ if (!options) {
+ options = this.makeDefaultOptions();
+ }
+ StencilCollectionBuilder.INSTANCE = this;
+ var dir = options.outputPath;
+ this.iconDir = path.join(dir, "icons");
+
+ var thiz = this;
+
+ this.currentDir = dir;
+ this.currentBitmapDir = path.join(dir, "bitmaps");
+
+ if (!fs.existsSync(this.iconDir)) {
+ fs.mkdirSync(this.iconDir);
+ }
+
+ var dom = Controller.parser.parseFromString("", "text/xml");
+ var shapes = dom.documentElement;
+
+ shapes.setAttribute("id", options.id);
+ shapes.setAttribute("displayName", options.displayName);
+ shapes.setAttribute("author", options.author);
+ shapes.setAttribute("description", options.description);
+ shapes.setAttribute("url", options.url);
+
+ shapes.appendChild(Dom.newDOMElement({
+ _name: "Script",
+ _uri: PencilNamespaces.p,
+ comments: "Built-in util script",
+ _cdata: StencilCollectionBuilder.COLLECTION_UTIL
+ }));
+
+ if (options.extraScript) {
+ shapes.appendChild(Dom.newDOMElement({
+ _name: "Script",
+ _uri: PencilNamespaces.p,
+ comments: "Extra script",
+ _cdata: "\n" + options.extraScript + "\n"
+ }));
+ }
+
+ var resourceList = [];
+
+ //processing resources
+ if (options.resourceSets) {
+ var base = path.dirname(Pencil.controller.documentPath);
+
+ var resourceDirName = "resources";
+ var resourceDir = path.join(dir, resourceDirName);
+
+ if (!fsExistSync(resourceDir)) {
+ fs.mkdirSync(resourceDir);
+ }
+
+ for (var set of options.resourceSets) {
+ var sourcePath = base ? path.resolve(base, set.path) : set.path;
+ if (!fs.existsSync(sourcePath)) {
+ console.error("Resource dir not found: " + sourcePath);
+ continue;
+ }
+
+ var name = set.name.replace(/[^a-z0-9]+/gi, "_");
+ var destPath = path.join(resourceDir, name);
+ resourceList.push({
+ name: name,
+ prefix: resourceDirName + "/" + name
+ });
+
+ if (!fs.existsSync(destPath)) {
+ fs.mkdirSync(destPath);
+ var files = fs.readdirSync(sourcePath);
+ files.forEach(function (file) {
+ var curSource = path.join(sourcePath, file);
+ if (fs.lstatSync(curSource).isDirectory()) {
+ copyFolderRecursiveSync(curSource, destPath);
+ } else {
+ copyFileSync(curSource, destPath);
+ }
+ });
+ }
+ }
+
+ var script = "collection.RESOURCE_LIST = " + JSON.stringify(resourceList) + ";\n" + StencilCollectionBuilder.COLLECTION_RESOURCE_SCRIPT;
+ shapes.appendChild(Dom.newDOMElement({
+ _name: "Script",
+ _uri: PencilNamespaces.p,
+ comments: "Resource script",
+ _cdata: "\n" + script+ "\n"
+ }));
+ }
+
+ var globalPropertySpecs = [];
+
+ shapes.appendChild(Dom.newDOMElement({
+ _name: "Properties",
+ _uri: PencilNamespaces.p,
+ _children: [
+ {
+ _name: "PropertyGroup",
+ _uri: PencilNamespaces.p,
+ name: "Collection Properties",
+ _children: []
+ }
+ ]
+ }));
+
+ var layoutPage = null;
+
+ var pageMargin = this.getPageMargin();
+ var ts = new Date().getTime();
+
+ ApplicationPane._instance.setContentVisible(false);
+
+ var finalize = function () {
+ this.saveResultDom(dom, dir, options, function () {
+ var showDone = function () {
+ Pencil.controller.doc._lastUsedStencilOutputPath = options.outputPath;
+
+ thiz.progressListener.onTaskDone();
+ ApplicationPane._instance.setContentVisible(true);
+
+ var stencilPath = Config.get("dev.stencil.path", null);
+ var dirPath = Config.get("dev.stencil.dir", null);
+ if (stencilPath == options.outputPath || dirPath == path.dirname(options.outputPath)) {
+ CollectionManager.reloadDeveloperStencil(false);
+ NotificationPopup.show("Stencil collection '" + options.displayName + "' was successfully built.\n\nDeveloper stencil was also reloaded.", "View", function () {
+ shell.openItem(options.outputPath);
+ });
+ } else {
+ NotificationPopup.show("Stencil collection '" + options.displayName + "' was successfully built.", "View", function () {
+ shell.openItem(options.outputPath);
+ });
+ }
+
+ };
+
+ if (layoutPage) {
+ thiz.generateCollectionLayout(options.id, dir, layoutPage, showDone);
+ } else {
+ showDone();
+ }
+
+ });
+ }.bind(this); //END OF FINAL PROCESSING
+
+ var nonStencilPages = [];
+
+ var done = function () {
+ var globalPropertyMap = {};
+
+ //append global propert fragment
+ if (globalPropertySpecs && globalPropertySpecs.length > 0) {
+ var globalGroupNode = Dom.getSingle("/p:Shapes/p:Properties/p:PropertyGroup", dom);
+ globalGroupNode.appendChild(Dom.newDOMFragment(globalPropertySpecs, dom));
+
+ for (var spec of globalPropertySpecs) {
+ var prop = spec._prop;
+ globalPropertyMap[prop.name] = prop.type.fromString(prop.value);
+ }
+ }
+
+ //re-fill shape's property fragment
+ Dom.workOn("/p:Shapes/p:Shape", dom, function (shapeDefNode) {
+ if (shapeDefNode._propertyFragmentSpec && shapeDefNode._propertyFragmentSpec.length > 0) {
+ //generalizing global properties
+ for (var spec of shapeDefNode._propertyFragmentSpec) {
+ var prop = spec._prop;
+ var value = prop.type.fromString(prop.value);
+ if (!value) continue;
+
+ var previousDiff = null;
+
+ for (var globalSpec of globalPropertySpecs) {
+ if (spec.type != globalSpec.type) continue;
+
+ var globalName = globalSpec._prop.name;
+ var globalPropValue = globalPropertyMap[globalName];
+ if (!globalPropValue || !globalPropValue.generateTransformTo) continue;
+
+ var transformSpec = globalPropValue.generateTransformTo(value);
+ var diff = globalPropValue.getDiff ? globalPropValue.getDiff(value) : ((transformSpec === "" || transformSpec) ? transformSpec.length : 10000000);
+ if ((transformSpec === "" || transformSpec) && (previousDiff === null || previousDiff > diff)) {
+ delete spec._text;
+ spec._children = [{
+ _name: "E",
+ _uri: PencilNamespaces.p,
+ _text: "$$" + globalName + transformSpec
+ }];
+
+ previousDiff = diff;
+ }
+ }
+ }
+
+ var groupNode = Dom.getSingle("./p:Properties/p:PropertyGroup[@holder='true']", shapeDefNode);
+ groupNode.appendChild(Dom.newDOMFragment(shapeDefNode._propertyFragmentSpec, dom));
+ }
+ });
+
+ this.processShortcuts(nonStencilPages, dom, dir, options, globalPropertySpecs, globalPropertyMap, function () {
+ finalize();
+ });
+ }.bind(this);
+
+ this.progressListener = null;
+
+ var index = -1;
+ var pages = this.controller.doc.pages;
+ var next = function() {
+ index ++;
+ if (index >= pages.length) {
+ done();
+ return;
+ }
+
+ var page = pages[index];
+
+ this.progressListener.onProgressUpdated(`Exporting '${page.name}...'`, index, pages.length);
+
+ try {
+ if (page.name.toLowerCase() == "(layout)") {
+ layoutPage = page;
+ return;
+ }
+
+ ApplicationPane._instance.activatePage(page);
+ var svg = page.canvas.svg;
+ StencilCollectionBuilder._currentPage = page;
+
+ var properties = [];
+ var behaviors = [];
+ var actions = [];
+ var shapeId = page.name.replace(/[^a-z0-9\-]+/gi, "").toLowerCase();
+ var shapeSpec = {
+ _name: "Shape",
+ _uri: PencilNamespaces.p,
+ id: shapeId,
+ displayName: page.name,
+ icon: "icons/" + shapeId + ".png?token=" + ts,
+ _children: [
+ {
+ _name: "Properties",
+ _uri: PencilNamespaces.p,
+ _children: [
+ {
+ _name: "PropertyGroup",
+ _uri: PencilNamespaces.p,
+ name: "Common",
+ holder: "true",
+ _children: [] //leave this blank, actual property definitions will be filled later
+ }
+ ]
+ },
+ {
+ _name: "Behaviors",
+ _uri: PencilNamespaces.p,
+ _children: behaviors
+ },
+ {
+ _name: "Actions",
+ _uri: PencilNamespaces.p,
+ _children: actions
+ }
+ ]
+ };
+
+ if (page.note) shapeSpec.description = Dom.htmlStrip(page.note);
+
+ var propertyMap = {};
+
+ var contentNode = Dom.newDOMElement({
+ _name: "Content",
+ _uri: PencilNamespaces.p
+ }, dom);
+
+ var contentFragment = dom.createDocumentFragment();
+ var clipPathFragment = dom.createDocumentFragment();
+
+ var snaps = [];
+
+ var defs = null;
+ var clipPathNodeMap = {};
+
+ var hasContribution = false;
+
+ Dom.workOn(".//svg:g[@p:type='Shape']", svg, function (shapeNode) {
+ var c = page.canvas.createControllerFor(shapeNode);
+
+ if (!c.performAction) return;
+ var contribution = null;
+ try {
+ contribution = c.performAction("buildShapeContribution");
+ } catch (e) {
+ console.error("Error in building shape contribution:", e);
+ return;
+ }
+
+ if (!contribution) return;
+
+ for (var prop of contribution.properties) {
+ if (!prop.global) {
+ if (propertyMap[prop.name]) continue;
+ if (prop.name == "box") {
+ prop.value = new Dimension(page.width - 2 * pageMargin, page.height - 2 * pageMargin).toString();
+ }
+ }
+ var node = {
+ _name: "Property",
+ _uri: PencilNamespaces.p,
+ name: prop.name,
+ displayName: prop.displayName,
+ type: prop.type.name,
+ _text: prop.value,
+ _prop: prop
+ };
+ if (prop.meta) {
+ for (var metaName in prop.meta) {
+ node["p:" + metaName] = prop.meta[metaName];
+ }
+ }
+
+ if (prop.global) {
+ globalPropertySpecs.push(node);
+ } else {
+ propertyMap[prop.name] = node;
+ properties.push(node);
+ }
+ }
+
+ for (var targetName in contribution.behaviorMap) {
+ var set = contribution.behaviorMap[targetName];
+ var behaviorSpec = {
+ _name: "For",
+ _uri: PencilNamespaces.p,
+ ref: set.ref,
+ _children: []
+ }
+ for (var item of set.items) {
+ var itemSpec = {
+ _name: item.behavior,
+ _uri: PencilNamespaces.p,
+ _children: []
+ }
+ for (var arg of item.args) {
+ itemSpec._children.push({
+ _name: "Arg",
+ _uri: PencilNamespaces.p,
+ _text: arg
+ })
+ }
+ behaviorSpec._children.push(itemSpec);
+ }
+
+ behaviors.push(behaviorSpec);
+ }
+
+ for (var action of contribution.actions) {
+ var node = {
+ _name: "Action",
+ _uri: PencilNamespaces.p,
+ id: action.id,
+ displayName: action.displayName,
+ _children: [
+ {
+ _name: "Impl",
+ _uri: PencilNamespaces.p,
+ _cdata: action.impl
+ }
+ ]
+ };
+ if (action.meta) {
+ for (var metaName in action.meta) {
+ node["p:" + metaName] = action.meta[metaName];
+ }
+ }
+
+ actions.push(node);
+ }
+
+ if (contribution.snaps) {
+ for (var snap of contribution.snaps) {
+ snap._contribution = contribution;
+ snaps.push(snap);
+ }
+ }
+
+ if (contribution.contentFragment && contribution.contentFragment.childNodes.length > 0) {
+ if (contribution.clipPathName) {
+ var clipPathNode = clipPathNodeMap[contribution.clipPathName];
+ if (!clipPathNode) {
+ if (!defs) {
+ defs = contentNode.ownerDocument.createElementNS(PencilNamespaces.svg, "defs");
+ contentNode.appendChild(defs);
+ }
+
+ clipPathNode = contentNode.ownerDocument.createElementNS(PencilNamespaces.svg, "clipPath");
+ clipPathNode.setAttribute("id", contribution.clipPathName);
+ defs.appendChild(clipPathNode);
+ clipPathNodeMap[contribution.clipPathName] = clipPathNode;
+ }
+
+ clipPathNode.appendChild(contribution.contentFragment);
+ } else {
+ var e = contribution.contentFragment;
+ if (contribution.clippedByName) {
+ var g = contentNode.ownerDocument.createElementNS(PencilNamespaces.svg, "g");
+ g.appendChild(e);
+ g.setAttribute("style", "clip-path: url(#" + contribution.clippedByName + ");");
+
+ e = g;
+ }
+ contentFragment.appendChild(e);
+ }
+ }
+
+ hasContribution = true;
+ });
+
+ if (!hasContribution) {
+ nonStencilPages.push(page);
+ return;
+ }
+
+ contentNode.appendChild(contentFragment);
+
+ // if the shape has 'box', add standard snaps
+ if (propertyMap["box"]) {
+ snaps = [
+ {name: "Left", accept: "Left", horizontal: true, expression: "0"},
+ {name: "Right", accept: "Right", horizontal: true, expression: "$box.w"},
+ {name: "Top", accept: "Top", horizontal: false, expression: "0"},
+ {name: "Bottom", accept: "Bottom", horizontal: false, expression: "$box.h"},
+ {name: "VCenter", accept: "VCenter", horizontal: true, expression: "Math.round($box.w / 2)"},
+ {name: "HCenter", accept: "HCenter", horizontal: false, expression: "Math.round($box.h / 2)"},
+ ].concat(snaps);
+ }
+
+ if (snaps.length > 0) {
+ var impl = "";
+ var header = "";
+ var definedProp = {};
+ var definedBound = {};
+ function replaceReference(expression, snap) {
+ var expression = expression.replace(/\$([a-z][a-z0-9]*)/gi, function (zero, one) {
+ var name = "__prop_" + one;
+ if (!definedProp[one]) {
+ header += "var " + name + " = this.getProperty(\"" + one + "\");\n";
+ definedProp[one] = true;
+ }
+
+ return name;
+ });
+ expression = expression.replace(/this\.def\.collection/g, "@@@");
+ expression = expression.replace(/collection\./g, "this.def.collection.");
+ expression = expression.replace(/@@@/g, "this.def.collection");
+
+ if (expression.indexOf("@boundExpressionLiteral") >= 0) {
+ var boundVarName = "__bound_" + snap._contribution.targetElementName;
+ if (!definedBound[boundVarName]) {
+ header += "var " + boundVarName + " = " + replaceReference(snap._contribution._boundExpressionLiteral, snap) + ";\n";
+ definedBound[boundVarName] = true;
+ }
+ expression = expression.replace(/@boundExpressionLiteral/g, boundVarName);
+ }
+
+
+ return expression;
+ }
+ for (var snap of snaps) {
+ var expression = replaceReference(snap.expression, snap);
+
+ if (impl) impl += ",\n";
+ impl += "new SnappingData(" + JSON.stringify(snap.name) + ", " + expression + ", " + JSON.stringify(snap.accept) + ", " + snap.horizontal + ", this.id).makeLocal(true)";
+ }
+
+ impl = header + "\nvar snaps = [" + impl + "];\nreturn snaps;";
+
+ var snapActionNode = {
+ _name: "Action",
+ _uri: PencilNamespaces.p,
+ id: "getSnappingGuide",
+ _children: [
+ {
+ _name: "Impl",
+ _uri: PencilNamespaces.p,
+ _cdata: "\n" + impl + "\n"
+ }
+ ]
+ };
+
+ actions.push(snapActionNode);
+ }
+
+ var shape = Dom.newDOMElement(shapeSpec, dom);
+ if (!contentNode.hasChildNodes()) return;
+ shape.appendChild(contentNode);
+ shapes.appendChild(shape);
+
+ if (fs.existsSync(page.thumbPath)) {
+ var thumPath = path.join(thiz.iconDir, shapeId + ".png");
+ fs.createReadStream(page.thumbPath).pipe(fs.createWriteStream(thumPath));
+ }
+
+ shape._propertyFragmentSpec = properties;
+ } finally {
+ window.setTimeout(next, 10);
+ }
+ }.bind(this); //END OF PAGE PROCESSING
+
+ Util.beginProgressJob("Building collection...", function (listener) {
+ thiz.progressListener = listener;
+ next();
+ });
+};
+
+StencilCollectionBuilder.prototype.saveResultDom = function (dom, dir, options, callback) {
+ var xsltDOM = Dom.parseDocument(
+`
+
+
+
+
+
+
+
+`
+ );
+ var xsltProcessor = new XSLTProcessor();
+ xsltProcessor.importStylesheet(xsltDOM);
+
+ var result = xsltProcessor.transformToDocument(dom);
+
+ Dom.serializeNodeToFile(result, path.join(dir, "Definition.xml"));
+
+ if (callback) callback();
+};
+
+StencilCollectionBuilder.prototype.isShortcutPage = function (page, options) {
+ if (options.shortcutPageIds) {
+ return options.shortcutPageIds.indexOf(page.id) >= 0;
+ } else {
+ return page.name.toLowerCase().startsWith("(shortcut");
+ }
+};
+
+StencilCollectionBuilder.prototype.processShortcuts = function (pages, dom, dir, options, globalPropertySpecs, globalPropertyMap, callback) {
+ var thiz = this;
+ this.saveResultDom(dom, dir, options, function () {
+
+ //parse the resulted collection
+ var collection = new ShapeDefCollectionParser().parseURL(path.join(dir, "Definition.xml"));
+ var shortcutSpecs = [];
+
+ Util.workOnListAsync(pages, function(page, index, __callback) {
+ thiz.progressListener.onProgressUpdated(`Processing shortcuts in '${page.name}...'`, index, pages.length);
+ ApplicationPane._instance.activatePage(page);
+ var svg = page.canvas.svg;
+
+ var defIdPrefix = collection.id + ":";
+ var shapeNodes = Dom.getList(".//svg:g[@p:type='Shape']", svg);
+
+ Util.workOnListAsync(shapeNodes, function (shapeNode, index, __callback) {
+ thiz.progressListener.onProgressUpdated(`Processing shortcuts in '${page.name}...'`, index, shapeNodes.length);
+ var defId = page.canvas.getType(shapeNode);
+ var def = null;
+ var id = null;
+ var effectiveCollection = null;
+
+ if (!defId) {
+ __callback();
+ return;
+ }
+
+ if (defId.startsWith(defIdPrefix)) {
+ effectiveCollection = collection
+ id = defId.substring(defIdPrefix.length);
+ def = collection.shapeDefMap[defId];
+ } else {
+ if (options.allowExternalShortcuts || (typeof(options.allowExternalShortcuts) === "undefined")) {
+ var def = CollectionManager.shapeDefinition.locateDefinition(defId);
+ id = defId;
+ effectiveCollection = def.collection;
+ } else {
+ __callback();
+ return;
+ }
+ }
+
+ if (!def) {
+ __callback();
+ return;
+ }
+
+ var shape = new Shape(page.canvas, shapeNode, def);
+ var symbolName = shape.getSymbolName();
+ if (!symbolName) {
+ __callback();
+ return;
+ }
+
+ var spec = {
+ _name: "Shortcut",
+ _uri: PencilNamespaces.p,
+ to: id,
+ displayName: symbolName,
+ _children: [
+
+ ]
+ };
+
+ for (var name in def.propertyMap) {
+ var prop = def.getProperty(name);
+ var value = shape.getProperty(name);
+
+ if (prop.type == ImageData) {
+ if (value.data && value.data.match(/^ref:\/\//)) {
+ if (prop.initialValue && prop.initialValue.data && prop.initialValue.data.match(/^collection:\/\/(.+)$/)) {
+ var declaredPath = RegExp.$1;
+ var ref = ImageData.idToRefString(thiz.controller.generateCollectionResourceRefId(effectiveCollection, declaredPath));
+ if (ref == value.data) continue;
+ }
+ value = thiz.toCollectionReadyImageData(value, spec.displayName + "-" + prop.name);
+ }
+ }
+
+ if (!value) continue;
+ if (prop.initialValueExpression) {
+ shape._evalContext = {collection: def.collection};
+ var v = shape.evalExpression(prop.initialValueExpression);
+ if (v && value.toString() == v.toString()) continue;
+ }
+ if (prop.initialValue) {
+ if (value.toString() == prop.initialValue.toString()) continue;
+ }
+
+ var sp = {
+ _name: "PropertyValue",
+ _uri: PencilNamespaces.p,
+ name: name,
+ _text: value.toString()
+ };
+
+ var previousDiff = null;
+
+ for (var globalSpec of globalPropertySpecs) {
+ if (prop.type.name != globalSpec.type) continue;
+
+ var globalName = globalSpec._prop.name;
+
+ var globalPropValue = globalPropertyMap[globalName];
+ if (!globalPropValue || !globalPropValue.generateTransformTo) continue;
+
+ var transformSpec = globalPropValue.generateTransformTo(value);
+
+ var diff = globalPropValue.getDiff ? globalPropValue.getDiff(value) : ((transformSpec === "" || transformSpec) ? transformSpec.length : 10000000);
+ if ((transformSpec === "" || transformSpec) && (previousDiff === null || previousDiff > diff)) {
+ delete sp._text;
+ sp._children = [{
+ _name: "E",
+ _uri: PencilNamespaces.p,
+ _text: "$$" + globalName + transformSpec
+ }];
+
+ previousDiff = diff;
+ }
+ }
+
+ spec._children.push(sp);
+ }
+
+ var fileName = spec.displayName.replace(/[^a-z0-9\\-]+/gi, "").toLowerCase() + ".png";
+ var targetPath = path.join(thiz.iconDir, fileName);
+ Pencil.rasterizer.rasterizeSelectionToFile(shape, targetPath, function (p, error) {
+ if (!error) {
+ spec.icon = "icons/" + fileName;
+ }
+
+ shortcutSpecs.push(spec);
+ __callback();
+ });
+ }, __callback);
+ }, function () {
+ var fragment = Dom.newDOMFragment(shortcutSpecs, dom);
+ dom.documentElement.appendChild(fragment);
+ if (callback) callback();
+ });
+ });
+};
+StencilCollectionBuilder.prototype.generateCollectionLayout = function (collectionId, dir, page, callback) {
+ ApplicationPane._instance.activatePage(page);
+ var container = page.canvas.drawingLayer;
+ var pageMargin = StencilCollectionBuilder.INSTANCE.getPageMargin();
+
+ var pw = parseFloat(page.width) - 2 * pageMargin;
+ var ph = parseFloat(page.height) - 2 * pageMargin;
+
+ var items = [];
+
+ const IMAGE_FILE = "layout_image.png";
+
+ Dom.workOn(".//svg:g[@p:type='Shape']", container, function (g) {
+ var dx = 0; //rect.left;
+ var dy = 0; //rect.top;
+
+ var owner = g.ownerSVGElement;
+
+ if (owner.parentNode && owner.parentNode.getBoundingClientRect) {
+ var rect = owner.parentNode.getBoundingClientRect();
+ dx = rect.left;
+ dy = rect.top;
+ }
+
+ dx += pageMargin;
+ dy += pageMargin;
+
+ rect = g.getBoundingClientRect();
+
+ var linkingInfo = {
+ node: g,
+ sc: g.getAttributeNS(PencilNamespaces.p, "sc"),
+ refId: g.getAttributeNS(PencilNamespaces.p, "def"),
+ geo: {
+ x: rect.left - dx,
+ y: rect.top - dy,
+ w: rect.width - 2,
+ h: rect.height - 2
+ }
+ };
+
+ items.push(linkingInfo);
+ });
+
+ var current = 0;
+ var thiz = this;
+ var done = function () {
+ var html = document.createElementNS(PencilNamespaces.html, "html");
+
+ var body = document.createElementNS(PencilNamespaces.html, "body");
+ html.appendChild(body);
+
+ var div = document.createElementNS(PencilNamespaces.html, "div");
+ div.setAttribute("style", "position: relative; padding: 0px; margin: 0px; width: " + pw + "px; height: " + ph + "px;");
+ body.appendChild(div);
+
+ var bg = document.createElementNS(PencilNamespaces.html, "img");
+ bg.setAttribute("style", "width: " + pw + "px; height: " + ph + "px;");
+ bg.setAttribute("src", IMAGE_FILE + "?ts=" + (new Date().getTime()));
+ div.appendChild(bg);
+
+ for (var i = 0; i < items.length; i ++) {
+ var link = items[i];
+ var img = document.createElementNS(PencilNamespaces.html, "img");
+ img.setAttribute("src", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVQYV2NgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII=");
+ if (link.sc) {
+ img.setAttribute("sc-ref", link.sc);
+ } else {
+ img.setAttribute("ref", link.refId);
+ }
+ img.setAttribute("id", link.refId);
+ var css = new CSS();
+ css.set("position", "absolute");
+ css.set("left", "" + link.geo.x + "px");
+ css.set("top", "" + link.geo.y + "px");
+ css.set("width", "" + link.geo.w + "px");
+ css.set("height", "" + link.geo.h + "px");
+ img.setAttribute("style", css.toString());
+
+ div.appendChild(img);
+ }
+
+ Dom.serializeNodeToFile(html, path.join(dir, "Layout.xhtml"), "");
+ if (callback) callback();
+ };
+
+
+ var outputImage = path.join(dir, IMAGE_FILE);
+ Pencil.rasterizer.rasterizePageToFile(page, outputImage, function (p, error) {
+ done();
+ });
+};
+StencilCollectionBuilder.prototype.generatePrivateShapeDef = function (target, callback) {
+ var svg = target.svg.cloneNode(true);
+
+ var fakeDom = Controller.parser.parseFromString("", "text/xml");
+ fakeDom.documentElement.appendChild(svg);
+
+ Pencil.controller.prepareForEmbedding(fakeDom, function () {
+ fakeDom.documentElement.removeChild(svg);
+
+ var shapeDef = new PrivateShapeDef();
+ shapeDef.displayName = target.getSymbolName();
+ shapeDef.content = svg;
+ shapeDef.id = ("PrivateShapeDef_" + shapeDef.displayName).replace(/\s+/g, "_").toLowerCase();
+
+ Util.generateIcon(target, 64, 64, 2, null, function (icondata) {
+ shapeDef.iconData = icondata;
+ callback(shapeDef);
+ });
+ });
+};
diff --git a/app/views/tools/StencilCollectionDetailDialog.js b/app/views/tools/StencilCollectionDetailDialog.js
new file mode 100644
index 00000000..063f97e0
--- /dev/null
+++ b/app/views/tools/StencilCollectionDetailDialog.js
@@ -0,0 +1,112 @@
+function StencilCollectionDetailDialog(acceptActionLabel) {
+ Dialog.call(this);
+ this.title = "Stencil Collection Details";
+ this.acceptActionLabel = acceptActionLabel;
+
+ this.bind("click", this.handleSetBrowseEvent, this.setContainer);
+}
+__extend(Dialog, StencilCollectionDetailDialog);
+
+StencilCollectionDetailDialog.prototype.handleSetBrowseEvent = function (event) {
+ var targetInputName = event.target ? event.target.getAttribute("target") : null;
+ if (!targetInputName) return;
+
+ var input = this[targetInputName];
+ var nameInput = this[targetInputName.replace(/PathInput/, "NameInput")];
+
+ var defaultPath = Pencil.controller.documentPath ? path.dirname(Pencil.controller.documentPath) : os.homedir();
+ dialog.showOpenDialog(remote.getCurrentWindow(), {
+ title: "Select Resource Directory",
+ defaultPath: defaultPath,
+ properties: ["openDirectory"]
+ }, function (filenames) {
+ if (!filenames || filenames.length <= 0) return;
+ var selectedPath = filenames[0];
+ if (Pencil.controller.documentPath) {
+ var base = path.dirname(Pencil.controller.documentPath);
+ var relative = path.relative(base, selectedPath);
+ const LIMIT = ".." + path.sep + ".." + path.sep + ".."; // 3 levels up means bad
+ if (!relative.startsWith(LIMIT)) selectedPath = relative;
+ }
+
+ input.value = selectedPath;
+ if (!nameInput.value) {
+ nameInput.value = path.basename(filenames[0]);
+ }
+ });
+
+};
+StencilCollectionDetailDialog.prototype.setup = function (options) {
+ options = options || {};
+ this.originalOptions = options;
+ this.doc = options.doc || Pencil.controller.doc;
+
+ var defaultDocName = Pencil.controller.getDocumentName().replace(/\*/g, "").trim();
+ var systemUsername = os.userInfo().username;
+
+ this.displayNameInput.value = options.displayName || defaultDocName;
+ this.idInput.value = options.id || (systemUsername + "." + defaultDocName.replace(/[^a-z0-9]+/gi, ""));
+ this.descriptionInput.value = options.description || "";
+ this.authorNameInput.value = options.author || systemUsername;
+ this.urlInput.value = options.url || "";
+
+ this.scriptInput.value = options.extraScript || "";
+
+ this.embedReferencedFontsCheckbox.checked = typeof(options.embedReferencedFonts) != "boolean" ? true : options.embedReferencedFonts;
+
+ var resourceSets = options.resourceSets || [];
+
+ var index = 0;
+ for (var set of resourceSets) {
+ if (index > 4) break;
+ if (set && set.name && set.path) {
+ this["resourceSet" + index + "NameInput"].value = set.name.trim();
+ this["resourceSet" + index + "PathInput"].value = set.path;
+
+ index ++;
+ }
+ }
+};
+StencilCollectionDetailDialog.prototype.save = function () {
+ var options = {};
+
+ if (this.originalOptions) {
+ for (var name in this.originalOptions) options[name] = this.originalOptions[name];
+ }
+
+ try {
+
+ options.displayName = getRequiredValue(this.displayNameInput, "Please enter collection's name.");
+ options.id = getRequiredValue(this.idInput, "Please enter collection's id.");
+ options.description = this.descriptionInput.value;
+ options.author = getRequiredValue(this.authorNameInput, "Please enter author's name.");
+ options.url = getRequiredValue(this.urlInput, "Please enter a valid URL.", /^(http(s?):\/\/.+)?$/);
+
+ options.extraScript = this.scriptInput.value;
+ options.embedReferencedFonts = this.embedReferencedFontsCheckbox.checked;
+
+ options.resourceSets = [];
+ for (var i = 0; i < 5; i ++) {
+ var setName = this["resourceSet" + i + "NameInput"].value;
+ var setPath = this["resourceSet" + i + "PathInput"].value;
+ if (setName && setPath) {
+ options.resourceSets.push({
+ name: setName.trim(),
+ path: setPath
+ });
+ }
+ };
+
+ this.close(options);
+ } catch (e) {
+ handleCommonValidationError(e);
+ return false;
+ }
+};
+
+StencilCollectionDetailDialog.prototype.getDialogActions = function () {
+ return [
+ { type: "accept", title: this.acceptActionLabel || "Save", run: function () { return this.save(); } },
+ Dialog.ACTION_CANCEL
+ ];
+};
diff --git a/app/views/tools/StencilCollectionDetailDialog.xhtml b/app/views/tools/StencilCollectionDetailDialog.xhtml
new file mode 100644
index 00000000..02e489d7
--- /dev/null
+++ b/app/views/tools/StencilCollectionDetailDialog.xhtml
@@ -0,0 +1,152 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Fonts:
+
+
+
+
+ Resources:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/appveyor.yml b/appveyor.yml
index 1d8163a3..0be68296 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,4 +1,4 @@
-version: 3.0.1+{build}
+version: 3.0.2+{build}
platform:
- x64