From 2ef8c0556ddc24bddf6a0245e07ce0c091f7c79b Mon Sep 17 00:00:00 2001 From: Le Quang Thanh Date: Wed, 5 Apr 2017 18:06:40 +0700 Subject: [PATCH 01/34] Fixed show n-patch info --- app/views/editors/NPatchSpecEditorDialog.js | 7 ++++++- app/views/editors/NPatchSpecEditorDialog.xhtml | 4 +--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/views/editors/NPatchSpecEditorDialog.js b/app/views/editors/NPatchSpecEditorDialog.js index c5592ddc..4a072859 100644 --- a/app/views/editors/NPatchSpecEditorDialog.js +++ b/app/views/editors/NPatchSpecEditorDialog.js @@ -146,7 +146,12 @@ NPatchSpecEditorDialog.prototype.invalidateCellPosition = function (cell) { } var info = cell._cellInfo; if (info) { - info.textContent = b - a; + info.textContent = s + 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.prototype.setup = function (options) { diff --git a/app/views/editors/NPatchSpecEditorDialog.xhtml b/app/views/editors/NPatchSpecEditorDialog.xhtml index 9ebfcdf0..73ca7a7b 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; From 76f7ad02053c37926926fb68fc8cb5f02ffe1b6a Mon Sep 17 00:00:00 2001 From: Duong Thanh An Date: Wed, 12 Apr 2017 23:58:05 +0700 Subject: [PATCH 02/34] Bug fixing and initial support for Stencil Builder --- app/app.xhtml | 1 + app/pencil-core/behavior/SVGTextLayout.js | 3 +- app/pencil-core/behavior/commonBehaviors.js | 19 +- app/pencil-core/canvasHelper/canvasImpl.js | 35 + .../canvasHelper/snappingHelper.js | 18 + app/pencil-core/common/Canvas.js | 27 + app/pencil-core/common/config.js | 15 + app/pencil-core/common/controller.js | 2 +- .../definition/shapeDefCollectionParser.js | 62 +- app/pencil-core/propertyType/strokeStyle.js | 2 +- app/pencil-core/target/shape.js | 1 + app/views/common/UICommandManager.js | 2 + app/views/editors/NPatchSpecEditorDialog.js | 64 +- .../editors/NPatchSpecEditorDialog.xhtml | 5 + app/views/editors/SharedGeomtryEditor.js | 16 +- app/views/editors/SharedGeomtryEditor.xhtml | 4 + app/views/editors/SharedPropertyEditor.js | 1 - app/views/menus/MainMenu.js | 13 +- app/views/tools/StencilCollectionBuilder.js | 666 ++++++++++++++++++ 19 files changed, 946 insertions(+), 10 deletions(-) create mode 100644 app/views/tools/StencilCollectionBuilder.js diff --git a/app/app.xhtml b/app/app.xhtml index 0f8d5221..3109f566 100644 --- a/app/app.xhtml +++ b/app/app.xhtml @@ -211,6 +211,7 @@ + diff --git a/app/pencil-core/behavior/SVGTextLayout.js b/app/pencil-core/behavior/SVGTextLayout.js index 92445670..a246c2a8 100644 --- a/app/pencil-core/behavior/SVGTextLayout.js +++ b/app/pencil-core/behavior/SVGTextLayout.js @@ -272,6 +272,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 +297,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..fac769a3 100644 --- a/app/pencil-core/behavior/commonBehaviors.js +++ b/app/pencil-core/behavior/commonBehaviors.js @@ -646,8 +646,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..60573158 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,36 @@ CanvasImpl.setupGrid = function () { } } }; +CanvasImpl.drawMargin = function () { + var enable = Config.get(Config.DEV_USE_PAGE_MARGIN); + if (!enable) { + if (this.marginPath) this.marginPath.parentNode.removeChild(this.marginPath); + return; + } + + var margin = Config.get(Config.DEV_PAGE_MARGIN_SIZE) * 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..ab8ff597 100644 --- a/app/pencil-core/canvasHelper/snappingHelper.js +++ b/app/pencil-core/canvasHelper/snappingHelper.js @@ -111,6 +111,24 @@ SnappingHelper.prototype.rebuildSnappingGuide = function () { } } } + + var pageMargin = Config.get(Config.DEV_USE_PAGE_MARGIN); + if (pageMargin) { + var margin = Config.get(Config.DEV_PAGE_MARGIN_SIZE); + 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..ff98ae70 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); @@ -1148,6 +1149,8 @@ Canvas.prototype.handleMouseMove = function (event, fake) { if (!fake) { event.preventDefault(); event.stopPropagation(); + + if (this.movementDisabled) return; } if (this.currentController.markAsMoving) @@ -2520,21 +2523,41 @@ Canvas.prototype.sizeToContent = function (hPadding, vPadding) { return newSize; }; Canvas.prototype.sizeToContent__ = function (hPadding, vPadding) { + var pageMargin = Config.get(Config.DEV_USE_PAGE_MARGIN) ? Config.get(Config.DEV_PAGE_MARGIN_SIZE) : 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); 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 +2579,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/config.js b/app/pencil-core/common/config.js index d01d2f72..1cbcc604 100644 --- a/app/pencil-core/common/config.js +++ b/app/pencil-core/common/config.js @@ -51,3 +51,18 @@ 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_USE_PAGE_MARGIN = Config.define("dev.pageMargin.enabled", false); +Config.DEV_PAGE_MARGIN_SIZE = Config.define("dev.pageMargin.size", 40); +Config.DEV_PAGE_MARGIN_COLOR = Config.define("dev.pageMargin.color", "rgba(0, 0, 0, 0.2)"); diff --git a/app/pencil-core/common/controller.js b/app/pencil-core/common/controller.js index 83eecae1..0213e943 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; diff --git a/app/pencil-core/definition/shapeDefCollectionParser.js b/app/pencil-core/definition/shapeDefCollectionParser.js index f576f4f2..ae538d64 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,12 +332,33 @@ 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 @@ -529,8 +550,47 @@ 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(shapeDef.parentShapeDef.propertyGroups); + for (var name in shapeDef.parentShapeDef.propertyMap) { + shapeDef.propertyMap[name] = shapeDef.parentShapeDef.propertyMap[name]; + } + + 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/propertyType/strokeStyle.js b/app/pencil-core/propertyType/strokeStyle.js index 91c049e1..40197413 100644 --- a/app/pencil-core/propertyType/strokeStyle.js +++ b/app/pencil-core/propertyType/strokeStyle.js @@ -1,5 +1,5 @@ function StrokeStyle(w, array) { - this.w = w ? w : 1; + this.w = (typeof(w) == "number") ? w : 1; this.array = array ? array : null; } StrokeStyle.REG_EX = /^([0-9]+)\|([0-9 \,]*)$/; diff --git a/app/pencil-core/target/shape.js b/app/pencil-core/target/shape.js index 34c69eb6..22edf29e 100644 --- a/app/pencil-core/target/shape.js +++ b/app/pencil-core/target/shape.js @@ -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; diff --git a/app/views/common/UICommandManager.js b/app/views/common/UICommandManager.js index 1ebc0aa3..ef72973e 100644 --- a/app/views/common/UICommandManager.js +++ b/app/views/common/UICommandManager.js @@ -158,6 +158,8 @@ UICommandManager.register = function (command) { document.body.addEventListener(eventNames[i], f, false); } } + + return command; }; UICommandManager.getCommand = function (commandKey) { if (!commandKey) return; diff --git a/app/views/editors/NPatchSpecEditorDialog.js b/app/views/editors/NPatchSpecEditorDialog.js index c5592ddc..d2a36418 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); @@ -149,6 +154,59 @@ NPatchSpecEditorDialog.prototype.invalidateCellPosition = function (cell) { info.textContent = b - a; } }; +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: info.style || "" + }); + } + + 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 || {}; var minSize = 10 * Util.em(); @@ -158,7 +216,11 @@ 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 { + 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..fd8c49dd 100644 --- a/app/views/editors/NPatchSpecEditorDialog.xhtml +++ b/app/views/editors/NPatchSpecEditorDialog.xhtml @@ -113,6 +113,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 +138,5 @@ + diff --git a/app/views/editors/SharedGeomtryEditor.js b/app/views/editors/SharedGeomtryEditor.js index 520fd3cc..25e02f00 100644 --- a/app/views/editors/SharedGeomtryEditor.js +++ b/app/views/editors/SharedGeomtryEditor.js @@ -41,11 +41,23 @@ SharedGeomtryEditor.prototype.setup = function () { } }, false); + this.bind("click", this.toggleMovementLocking, 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 +137,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..b2ae304e 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..3fa7109e 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; diff --git a/app/views/menus/MainMenu.js b/app/views/menus/MainMenu.js index 285911f0..59116464 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")); @@ -158,12 +160,21 @@ MainMenu.prototype.setup = function () { Pencil.controller.exportAsLayout(); } }); + developerToolSubItems.push(UICommandManager.register({ + key: "buildStencilCollection", + label: "Build Stencil Collection...", + shortcut: "Ctrl+B", + isAvailable: function () { return Pencil.controller && Pencil.controller.doc && devEnable; }, + run: function () { + new StencilCollectionBuilder(Pencil.controller).build(); + } + })); 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(); diff --git a/app/views/tools/StencilCollectionBuilder.js b/app/views/tools/StencilCollectionBuilder.js new file mode 100644 index 00000000..eaa477e9 --- /dev/null +++ b/app/views/tools/StencilCollectionBuilder.js @@ -0,0 +1,666 @@ +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); +}; +`; + +StencilCollectionBuilder.prototype.getPageMargin = function () { + var pageMargin = Config.get(Config.DEV_USE_PAGE_MARGIN); + if (!pageMargin) return 0; + return Config.get(Config.DEV_PAGE_MARGIN_SIZE); +}; +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.prototype.build = function () { + StencilCollectionBuilder.INSTANCE = this; + var dir = "/home/dgthanhan/Projects/Pencil/V3/Stencils/Generated/Sample1"; + var iconDir = path.join(dir, "icons"); + + this.currentDir = dir; + this.currentBitmapDir = path.join(dir, "bitmaps"); + + if (!fs.existsSync(iconDir)) { + fs.mkdirSync(iconDir); + } + + var dom = Controller.parser.parseFromString("", "text/xml"); + var shapes = dom.documentElement; + + var username = os.userInfo().username; + var docName = this.controller.getDocumentName().replace(/\*/g, "").trim(); + + shapes.setAttribute("id", username + "." + docName.replace(/[^a-z0-9]+/gi, "")); + shapes.setAttribute("displayName", docName); + shapes.setAttribute("author", username); + shapes.setAttribute("description", ""); + + + shapes.appendChild(Dom.newDOMElement({ + _name: "Script", + _uri: PencilNamespaces.p, + comments: "Built-in util script", + _cdata: StencilCollectionBuilder.COLLECTION_UTIL + })); + + var pageMargin = this.getPageMargin(); + var ts = new Date().getTime(); + + for (var page of this.controller.doc.pages) { + this.controller.activatePage(page); + var svg = page.canvas.svg; + StencilCollectionBuilder._currentPage = page; + + if (page.note) shapes.setAttribute("description", Dom.htmlStrip(page.note)); + + 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", + _children: properties + } + ] + }, + { + _name: "Behaviors", + _uri: PencilNamespaces.p, + _children: behaviors + }, + { + _name: "Actions", + _uri: PencilNamespaces.p, + _children: actions + } + ] + }; + + var propertyMap = {}; + + var contentNode = Dom.newDOMElement({ + _name: "Content", + _uri: PencilNamespaces.p + }, dom); + + var snaps = []; + + Dom.workOn(".//svg:g[@p:type='Shape']", svg, function (shapeNode) { + var c = page.canvas.createControllerFor(shapeNode); + 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 (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 + }; + if (prop.meta) { + for (var metaName in prop.meta) { + node["p:" + metaName] = prop.meta[metaName]; + } + } + + 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) { + contentNode.appendChild(contribution.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); + shape.appendChild(contentNode); + shapes.appendChild(shape); + + if (fs.existsSync(page.thumbPath)) { + var thumPath = path.join(iconDir, shapeId + ".png"); + fs.createReadStream(page.thumbPath).pipe(fs.createWriteStream(thumPath)); + } + } + + var xsltDOM = Dom.parseDocument( +` + + + + + + + +` + ); + var xsltProcessor = new XSLTProcessor(); + xsltProcessor.importStylesheet(xsltDOM); + + var result = xsltProcessor.transformToDocument(dom); + + Dom.serializeNodeToFile(result, path.join(dir, "Definition.xml")); + console.log("Stencil definition saved."); +}; From e61fb1583d8f9780817a1cc9c6dc9cc0c446cd9c Mon Sep 17 00:00:00 2001 From: Duong Thanh An Date: Sat, 15 Apr 2017 19:58:17 +0700 Subject: [PATCH 03/34] adding transform method for color and font data type to support transformation auto-calculation --- app/pencil-core/propertyType/color.js | 25 ++++++++++++++++++ app/pencil-core/propertyType/font.js | 38 +++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/app/pencil-core/propertyType/color.js b/app/pencil-core/propertyType/color.js index 174248d4..1613affa 100644 --- a/app/pencil-core/propertyType/color.js +++ b/app/pencil-core/propertyType/color.js @@ -174,6 +174,31 @@ 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) { + 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; +} + 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 () { From c3c37dcafd95a1d1a0c46a7dda9168a0753277cf Mon Sep 17 00:00:00 2001 From: Duong Thanh An Date: Sat, 15 Apr 2017 20:02:56 +0700 Subject: [PATCH 04/34] Enhancing inherits parsing to allow sub-shape to redefine properties --- app/pencil-core/definition/shapeDef.js | 39 ++++++++++++++----- .../definition/shapeDefCollectionParser.js | 10 ++++- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/app/pencil-core/definition/shapeDef.js b/app/pencil-core/definition/shapeDef.js index 362c5551..914c4d47 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; @@ -127,13 +156,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 ae538d64..cf5e7056 100644 --- a/app/pencil-core/definition/shapeDefCollectionParser.js +++ b/app/pencil-core/definition/shapeDefCollectionParser.js @@ -363,7 +363,7 @@ ShapeDefCollectionParser.prototype.loadCustomLayout = function (installDirPath) //parse properties Dom.workOn("./p:Properties/p:PropertyGroup", shapeDefNode, function (propGroupNode) { - var group = new PropertyGroup; + var group = new PropertyGroup(); group.name = propGroupNode.getAttribute("name"); Dom.workOn("./p:Property", propGroupNode, function (propNode) { @@ -410,6 +410,10 @@ 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; }); @@ -535,6 +539,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); From 9e6b4a5a82323b4b32bcd4a07a4b4d3566b51e21 Mon Sep 17 00:00:00 2001 From: Duong Thanh An Date: Sat, 15 Apr 2017 20:04:01 +0700 Subject: [PATCH 05/34] Fix 'disabled' handling for share property editor and remove logs --- app/pencil-core/editor/handleEditor.js | 1 - app/views/editors/PlainTextEditor.js | 1 - app/views/editors/SharedPropertyEditor.js | 34 ++++++++++------------- 3 files changed, 14 insertions(+), 22 deletions(-) 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/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/SharedPropertyEditor.js b/app/views/editors/SharedPropertyEditor.js index 3fa7109e..726e894b 100644 --- a/app/views/editors/SharedPropertyEditor.js +++ b/app/views/editors/SharedPropertyEditor.js @@ -44,17 +44,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) { @@ -115,25 +115,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; @@ -228,11 +216,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); }; From d4144e9a54aa3407e3d157714be9f6a16b030bcf Mon Sep 17 00:00:00 2001 From: Duong Thanh An Date: Sat, 15 Apr 2017 20:04:41 +0700 Subject: [PATCH 06/34] Refactoring config --- app/pencil-core/common/config.js | 2 ++ app/pencil-core/common/util.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/pencil-core/common/config.js b/app/pencil-core/common/config.js index 1cbcc604..a86cd055 100644 --- a/app/pencil-core/common/config.js +++ b/app/pencil-core/common/config.js @@ -66,3 +66,5 @@ Config.define = function (name, defaultValue) { Config.DEV_USE_PAGE_MARGIN = Config.define("dev.pageMargin.enabled", false); Config.DEV_PAGE_MARGIN_SIZE = Config.define("dev.pageMargin.size", 40); 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/util.js b/app/pencil-core/common/util.js index bf33bf21..53e7a458 100644 --- a/app/pencil-core/common/util.js +++ b/app/pencil-core/common/util.js @@ -2147,7 +2147,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); } From 1d589c0e021727a8e5fbf478167aac0fcab7a051 Mon Sep 17 00:00:00 2001 From: Duong Thanh An Date: Sat, 15 Apr 2017 20:05:42 +0700 Subject: [PATCH 07/34] Implement collection properties support and perform auto-calculation for shape property transform --- app/views/tools/StencilCollectionBuilder.js | 86 +++++++++++++++++++-- 1 file changed, 79 insertions(+), 7 deletions(-) diff --git a/app/views/tools/StencilCollectionBuilder.js b/app/views/tools/StencilCollectionBuilder.js index eaa477e9..a3a9765e 100644 --- a/app/views/tools/StencilCollectionBuilder.js +++ b/app/views/tools/StencilCollectionBuilder.js @@ -413,6 +413,21 @@ StencilCollectionBuilder.prototype.build = function () { _cdata: StencilCollectionBuilder.COLLECTION_UTIL })); + var globalPropertySpecs = []; + + shapes.appendChild(Dom.newDOMElement({ + _name: "Properties", + _uri: PencilNamespaces.p, + _children: [ + { + _name: "PropertyGroup", + _uri: PencilNamespaces.p, + name: "Collection Properties", + _children: [] + } + ] + })); + var pageMargin = this.getPageMargin(); var ts = new Date().getTime(); @@ -442,7 +457,8 @@ StencilCollectionBuilder.prototype.build = function () { _name: "PropertyGroup", _uri: PencilNamespaces.p, name: "Common", - _children: properties + holder: "true", + _children: [] //leave this blank, actual property definitions will be filled later } ] }, @@ -481,9 +497,11 @@ StencilCollectionBuilder.prototype.build = function () { if (!contribution) return; for (var prop of contribution.properties) { - if (propertyMap[prop.name]) continue; - if (prop.name == "box") { - prop.value = new Dimension(page.width - 2 * pageMargin, page.height - 2 * pageMargin).toString(); + 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", @@ -491,7 +509,8 @@ StencilCollectionBuilder.prototype.build = function () { name: prop.name, displayName: prop.displayName, type: prop.type.name, - _text: prop.value + _text: prop.value, + _prop: prop }; if (prop.meta) { for (var metaName in prop.meta) { @@ -499,8 +518,12 @@ StencilCollectionBuilder.prototype.build = function () { } } - propertyMap[prop.name] = node; - properties.push(node); + if (prop.global) { + globalPropertySpecs.push(node); + } else { + propertyMap[prop.name] = node; + properties.push(node); + } } for (var targetName in contribution.behaviorMap) { @@ -634,6 +657,7 @@ StencilCollectionBuilder.prototype.build = function () { } var shape = Dom.newDOMElement(shapeSpec, dom); + if (!contentNode.hasChildNodes()) continue; shape.appendChild(contentNode); shapes.appendChild(shape); @@ -641,8 +665,56 @@ StencilCollectionBuilder.prototype.build = function () { var thumPath = path.join(iconDir, shapeId + ".png"); fs.createReadStream(page.thumbPath).pipe(fs.createWriteStream(thumPath)); } + + shape._propertyFragmentSpec = properties; + } + + 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; + + 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); + if (transformSpec === "" || transformSpec) { + delete spec._text; + spec._children = [{ + _name: "E", + _uri: PencilNamespaces.p, + _text: "$$" + globalName + transformSpec + }]; + } + } + } + + var groupNode = Dom.getSingle("./p:Properties/p:PropertyGroup[@holder='true']", shapeDefNode); + groupNode.appendChild(Dom.newDOMFragment(shapeDefNode._propertyFragmentSpec, dom)); + } + }); + var xsltDOM = Dom.parseDocument( ` Date: Sun, 16 Apr 2017 23:14:41 +0700 Subject: [PATCH 08/34] Adding UI for configuring document as stencil doc --- app/app.xhtml | 1 + app/pencil-core/canvasHelper/canvasImpl.js | 7 +- .../canvasHelper/snappingHelper.js | 5 +- app/pencil-core/common/Canvas.js | 4 + app/pencil-core/common/config.js | 3 +- app/pencil-core/common/controller.js | 12 ++ app/pencil-core/common/util.js | 27 ++++ app/views/common/UICommandManager.js | 4 +- app/views/menus/MainMenu.js | 46 ++++-- app/views/tools/StencilCollectionBuilder.js | 126 +++++++++++++-- .../tools/StencilCollectionDetailDialog.js | 107 ++++++++++++ .../tools/StencilCollectionDetailDialog.xhtml | 152 ++++++++++++++++++ 12 files changed, 460 insertions(+), 34 deletions(-) create mode 100644 app/views/tools/StencilCollectionDetailDialog.js create mode 100644 app/views/tools/StencilCollectionDetailDialog.xhtml diff --git a/app/app.xhtml b/app/app.xhtml index 3109f566..5cf29cff 100644 --- a/app/app.xhtml +++ b/app/app.xhtml @@ -212,6 +212,7 @@ + diff --git a/app/pencil-core/canvasHelper/canvasImpl.js b/app/pencil-core/canvasHelper/canvasImpl.js index 60573158..4086d3ee 100644 --- a/app/pencil-core/canvasHelper/canvasImpl.js +++ b/app/pencil-core/canvasHelper/canvasImpl.js @@ -45,13 +45,14 @@ CanvasImpl.setupGrid = function () { } }; CanvasImpl.drawMargin = function () { - var enable = Config.get(Config.DEV_USE_PAGE_MARGIN); - if (!enable) { + var unzommedMargin = Pencil.controller.getDocumentPageMargin(); + if (!unzommedMargin) { if (this.marginPath) this.marginPath.parentNode.removeChild(this.marginPath); + this.marginPath = null; return; } - var margin = Config.get(Config.DEV_PAGE_MARGIN_SIZE) * this.zoom; + var margin = unzommedMargin * this.zoom; var color = Config.get(Config.DEV_PAGE_MARGIN_COLOR); if (!this.marginPatternDef) { diff --git a/app/pencil-core/canvasHelper/snappingHelper.js b/app/pencil-core/canvasHelper/snappingHelper.js index ab8ff597..a943bd27 100644 --- a/app/pencil-core/canvasHelper/snappingHelper.js +++ b/app/pencil-core/canvasHelper/snappingHelper.js @@ -112,9 +112,8 @@ SnappingHelper.prototype.rebuildSnappingGuide = function () { } } - var pageMargin = Config.get(Config.DEV_USE_PAGE_MARGIN); - if (pageMargin) { - var margin = Config.get(Config.DEV_PAGE_MARGIN_SIZE); + var margin = Pencil.controller.getDocumentPageMargin(); + if (margin) { var uid = Util.newUUID(); this.snappingGuide[uid] = { vertical: [ diff --git a/app/pencil-core/common/Canvas.js b/app/pencil-core/common/Canvas.js index ff98ae70..3b2f26d2 100644 --- a/app/pencil-core/common/Canvas.js +++ b/app/pencil-core/common/Canvas.js @@ -339,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)); } diff --git a/app/pencil-core/common/config.js b/app/pencil-core/common/config.js index a86cd055..b764dd40 100644 --- a/app/pencil-core/common/config.js +++ b/app/pencil-core/common/config.js @@ -63,8 +63,7 @@ Config.define = function (name, defaultValue) { return name; }; -Config.DEV_USE_PAGE_MARGIN = Config.define("dev.pageMargin.enabled", false); -Config.DEV_PAGE_MARGIN_SIZE = Config.define("dev.pageMargin.size", 40); +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 0213e943..e1f2318e 100644 --- a/app/pencil-core/common/controller.js +++ b/app/pencil-core/common/controller.js @@ -1414,6 +1414,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/util.js b/app/pencil-core/common/util.js index 53e7a458..cde8c464 100644 --- a/app/pencil-core/common/util.js +++ b/app/pencil-core/common/util.js @@ -2391,4 +2391,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/views/common/UICommandManager.js b/app/views/common/UICommandManager.js index ef72973e..a7ea0584 100644 --- a/app/views/common/UICommandManager.js +++ b/app/views/common/UICommandManager.js @@ -181,7 +181,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); @@ -245,6 +245,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/menus/MainMenu.js b/app/views/menus/MainMenu.js index 59116464..bff28a26 100644 --- a/app/views/menus/MainMenu.js +++ b/app/views/menus/MainMenu.js @@ -95,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 () { @@ -111,7 +111,7 @@ MainMenu.prototype.setup = function () { templateDialog.open(); } }); - developerToolSubItems.push({ + toolSubItems.push({ key: "manageFontCommand", label: "Manage Fonts...", run: function () { @@ -119,7 +119,9 @@ MainMenu.prototype.setup = function () { } }); - developerToolSubItems.push(Menu.SEPARATOR); + toolSubItems.push(Menu.SEPARATOR); + + var developerToolSubItems = []; developerToolSubItems.push({ key: "stencilGenerator", label: "Stencil Generator...", @@ -160,15 +162,31 @@ 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: "buildStencilCollection", label: "Build Stencil Collection...", shortcut: "Ctrl+B", - isAvailable: function () { return Pencil.controller && Pencil.controller.doc && devEnable; }, + 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...", @@ -183,20 +201,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({ @@ -209,14 +234,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/tools/StencilCollectionBuilder.js b/app/views/tools/StencilCollectionBuilder.js index a3a9765e..e948fb49 100644 --- a/app/views/tools/StencilCollectionBuilder.js +++ b/app/views/tools/StencilCollectionBuilder.js @@ -359,9 +359,8 @@ collection.generatePathDOM = function (svgPathData, size, keepPathStyle) { `; StencilCollectionBuilder.prototype.getPageMargin = function () { - var pageMargin = Config.get(Config.DEV_USE_PAGE_MARGIN); - if (!pageMargin) return 0; - return Config.get(Config.DEV_PAGE_MARGIN_SIZE); + var pageMargin = Pencil.controller.getDocumentPageMargin(); + return pageMargin || 0; }; StencilCollectionBuilder.prototype.toCollectionReadyImageData = function (imageData, name) { var value = ImageData.fromString(imageData.toString()); @@ -382,9 +381,96 @@ StencilCollectionBuilder.prototype.toCollectionReadyImageData = function (imageD } 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.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; + new StencilCollectionDetailDialog().callback(function (options) { + thiz.setCurrentDocumentOptions(options); + if (Pencil.controller.documentPath) { + Pencil.documentHandler.saveDocument(); + } + }).open(StencilCollectionBuilder.getCurrentDocumentOptions()); +}; +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 = "/home/dgthanhan/Projects/Pencil/V3/Stencils/Generated/Sample1"; + var dir = options.outputPath; var iconDir = path.join(dir, "icons"); this.currentDir = dir; @@ -397,14 +483,11 @@ StencilCollectionBuilder.prototype.build = function () { var dom = Controller.parser.parseFromString("", "text/xml"); var shapes = dom.documentElement; - var username = os.userInfo().username; - var docName = this.controller.getDocumentName().replace(/\*/g, "").trim(); - - shapes.setAttribute("id", username + "." + docName.replace(/[^a-z0-9]+/gi, "")); - shapes.setAttribute("displayName", docName); - shapes.setAttribute("author", username); - shapes.setAttribute("description", ""); - + 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", @@ -413,6 +496,15 @@ StencilCollectionBuilder.prototype.build = function () { _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 globalPropertySpecs = []; shapes.appendChild(Dom.newDOMElement({ @@ -436,8 +528,6 @@ StencilCollectionBuilder.prototype.build = function () { var svg = page.canvas.svg; StencilCollectionBuilder._currentPage = page; - if (page.note) shapes.setAttribute("description", Dom.htmlStrip(page.note)); - var properties = []; var behaviors = []; var actions = []; @@ -475,6 +565,8 @@ StencilCollectionBuilder.prototype.build = function () { ] }; + if (page.note) shapeSpec.description = Dom.htmlStrip(page.note); + var propertyMap = {}; var contentNode = Dom.newDOMElement({ @@ -734,5 +826,9 @@ StencilCollectionBuilder.prototype.build = function () { var result = xsltProcessor.transformToDocument(dom); Dom.serializeNodeToFile(result, path.join(dir, "Definition.xml")); - console.log("Stencil definition saved."); + + Pencil.controller.doc._lastUsedStencilOutputPath = options.outputPath; + NotificationPopup.show("Stencil collection '" + options.displayName + "' was successfully built.", "View", function () { + shell.openItem(options.outputPath); + }); }; diff --git a/app/views/tools/StencilCollectionDetailDialog.js b/app/views/tools/StencilCollectionDetailDialog.js new file mode 100644 index 00000000..e6d4c2a5 --- /dev/null +++ b/app/views/tools/StencilCollectionDetailDialog.js @@ -0,0 +1,107 @@ +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.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 = {}; + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +