From 899853c963f1d64b51e547827ed3b69c1c317165 Mon Sep 17 00:00:00 2001 From: Flint O'Brien Date: Sun, 1 May 2016 22:58:41 -0400 Subject: [PATCH] Fixed Layers in svgcanvas. Moved Layer class. New HistoryRecordingservice. Canvas was referencing drawing.all_layers and drawing.current_layer. Both variables now represent Layer instead of group element and should be considered private. Moved Layer class to layer.js New HistoryRecordingService added to help with moving Layer code out of Canvas. Started using it in Canvas.mergLayer --- editor/draw.js | 275 +++++++++++++++++-------------------- editor/historyrecording.js | 173 +++++++++++++++++++++++ editor/layer.js | 200 +++++++++++++++++++++++++++ editor/svg-editor.html | 2 + editor/svg-editor.js | 2 +- editor/svgcanvas.js | 135 ++++-------------- editor/svgedit.js | 4 +- test/draw_test.html | 4 +- 8 files changed, 528 insertions(+), 267 deletions(-) create mode 100644 editor/historyrecording.js create mode 100644 editor/layer.js diff --git a/editor/draw.js b/editor/draw.js index 84113eac..c658a43b 100644 --- a/editor/draw.js +++ b/editor/draw.js @@ -20,8 +20,6 @@ if (!svgedit.draw) { } // alias var NS = svgedit.NS; -var LAYER_CLASS = svgedit.LAYER_CLASS; -var LAYER_CLASS_REGEX = svgedit.LAYER_CLASS_REGEX; var visElems = 'a,circle,ellipse,foreignObject,g,image,line,path,polygon,polyline,rect,svg,text,tspan,use'.split(','); @@ -32,147 +30,6 @@ var RandomizeModes = { }; var randomize_ids = RandomizeModes.LET_DOCUMENT_DECIDE; -/** - * Add class 'layer' to the element - * - * Parameters: - * @param {SVGGElement} elem - The SVG element to update - */ -function addLayerClass(elem) { - var classes = elem.getAttribute('class'); - if (classes === null || classes === undefined || classes.length === 0) { - elem.setAttribute('class', LAYER_CLASS); - } else if (! LAYER_CLASS_REGEX.test(classes)) { - elem.setAttribute('class', classes + ' ' + LAYER_CLASS); - } -} - -function createLayer(name, svgElem) { - if (!svgElem) { - return undefined; - } - var svgdoc = svgElem.ownerDocument; - var new_layer = svgdoc.createElementNS(NS.SVG, "g"); - var layer_title = svgdoc.createElementNS(NS.SVG, "title"); - layer_title.textContent = name; - new_layer.appendChild(layer_title); - svgElem.appendChild(new_layer); - return new_layer; -} - - - /** - * This class encapsulates the concept of a layer in the drawing. It can be constructed with - * an existing group element or, with three parameters, will create a new layer group element. - * @param {string} name - Layer name - * @param {SVGGElement} group - SVG group element that constitutes the layer or null if a group should be created and added to the DOM.. - * @param {SVGGElement} svgElem - The SVG DOM element. If defined, use this to add - * a new layer to the document. - */ -var Layer = svgedit.draw.Layer = function(name, group, svgElem) { - this.name_ = name; - this.group_ = group || createLayer(name, svgElem); - - addLayerClass(this.group_); - svgedit.utilities.walkTree(this.group_, function(e){e.setAttribute("style", "pointer-events:inherit");}); - - this.group_.setAttribute("style", svgElem ? "pointer-events:all" : "pointer-events:none"); -}; - -/** - * Get the layer's name. - * @returns {string} The layer name - */ -Layer.prototype.getName = function() { - return this.name_; -}; - -/** - * Get the group element for this layer. - * @returns {SVGGElement} The layer SVG group - */ -Layer.prototype.getGroup = function() { - return this.group_; -}; - -/** - * Active this layer so it takes pointer events. - */ -Layer.prototype.activate = function() { - this.group_.setAttribute("style", "pointer-events:all"); -}; - -/** - * Deactive this layer so it does NOT take pointer events. - */ -Layer.prototype.deactivate = function() { - this.group_.setAttribute("style", "pointer-events:none"); -}; - -/** - * Set this layer visible or hidden based on 'visible' parameter. - * @param {boolean} visible - If true, make visible; otherwise, hide it. - */ -Layer.prototype.setVisible = function(visible) { - var expected = visible === undefined || visible ? "inline" : "none"; - var oldDisplay = this.group_.getAttribute("display"); - if (oldDisplay !== expected) { - this.group_.setAttribute("display", expected); - } -}; - -/** - * Is this layer visible? - * @returns {boolean} True if visible. - */ -Layer.prototype.isVisible = function() { - return this.group_.getAttribute('display') !== 'none'; -}; - -/** - * Get layer opacity. - * @returns {number} Opacity value. - */ -Layer.prototype.getOpacity = function() { - var opacity = this.group_.getAttribute('opacity'); - if (opacity === null || opacity === undefined) { - return 1; - } - return parseFloat(opacity); -}; - -/** - * Sets the opacity of this layer. If opacity is not a value between 0.0 and 1.0, - * nothing happens. - * @param {number} opacity - A float value in the range 0.0-1.0 - */ -Layer.prototype.setOpacity = function(opacity) { - if (typeof opacity === 'number' && opacity >= 0.0 && opacity <= 1.0) { - this.group_.setAttribute('opacity', opacity); - } -}; - -/** - * Append children to this layer. - * @param {SVGGElement} children - The children to append to this layer. - */ -Layer.prototype.appendChildren = function(children) { - for (var i = 0; i < children.length; ++i) { - this.group_.appendChild(children[i]); - } -}; - -/** - * Remove this layer's group from the DOM. No more functions on group can be called after this. - * @param {SVGGElement} children - The children to append to this layer. - * @returns {SVGGElement} The layer SVG group that was just removed. - */ -Layer.prototype.removeGroup = function() { - var parent = this.group_.parentNode; - var group = parent.removeChild(this.group_); - this.group_ = undefined; - return group; -}; @@ -235,13 +92,17 @@ svgedit.draw.Drawing = function(svgElem, opt_idPrefix) { * The z-ordered array of Layer objects. Each layer has a name * and group element. * The first layer is the one at the bottom of the rendering. - * @type {Layer[]} + * @type {Array.} */ this.all_layers = []; /** * Map of all_layers by name. - * @type {Object.} + * + * Note: Layers are ordered, but referenced externally by name; so, we need both container + * types depending on which function is called (i.e. all_layers and layer_map). + * + * @type {Object.} */ this.layer_map = {}; @@ -418,6 +279,15 @@ svgedit.draw.Drawing.prototype.getCurrentLayer = function() { return this.current_layer ? this.current_layer.getGroup() : null; }; +/** + * Get a layer by name. + * @returns {SVGGElement} The SVGGElement representing the named layer or null. + */ +svgedit.draw.Drawing.prototype.getLayerByName = function(name) { + var layer = this.layer_map[name]; + return layer ? layer.getGroup() : null; +}; + /** * Returns the name of the currently selected layer. If an error occurs, an empty string * is returned. @@ -427,6 +297,107 @@ svgedit.draw.Drawing.prototype.getCurrentLayerName = function () { return this.current_layer ? this.current_layer.getName() : ''; }; +/** + * Set the current layer's name. + * @param {string} name - The new name. + * @returns {Object} If the name was changed, returns {title:SVGGElement, previousName:string}; otherwise null. + */ +svgedit.draw.Drawing.prototype.setCurrentLayerName = function (name) { + return this.current_layer ? this.current_layer.setName(name) : null; +}; + +/** + * Set the current layer's position. + * @param {number} newpos - The zero-based index of the new position of the layer. Range should be 0 to layers-1 + * @returns {Object} If the name was changed, returns {title:SVGGElement, previousName:string}; otherwise null. + */ +svgedit.draw.Drawing.prototype.setCurrentLayerPosition = function (newpos) { + var layer_count = this.getNumLayers(); + if (!this.current_layer || newpos < 0 || newpos >= layer_count) { + return null; + } + + var oldpos; + for (oldpos = 0; oldpos < layer_count; ++oldpos) { + if (this.all_layers[oldpos] == this.current_layer) {break;} + } + // some unknown error condition (current_layer not in all_layers) + if (oldpos == layer_count) { return null; } + + if (oldpos != newpos) { + // if our new position is below us, we need to insert before the node after newpos + var refGroup = null; + var current_group = this.current_layer.getGroup(); + var oldNextSibling = current_group.nextSibling; + if (newpos > oldpos ) { + if (newpos < layer_count-1) { + refGroup = this.all_layers[newpos+1].getGroup(); + } + } + // if our new position is above us, we need to insert before the node at newpos + else { + refGroup = this.all_layers[newpos].getGroup(); + } + this.svgElem_.insertBefore(current_group, refGroup); + + this.identifyLayers(); + this.setCurrentLayer(this.getLayerName(newpos)); + + return { + currentGroup: current_group, + oldNextSibling: oldNextSibling + }; + } + return null; +}; + +svgedit.draw.Drawing.prototype.mergeLayer = function (hrService) { + var current_group = this.current_layer.getGroup(); + var prevGroup = $(current_group).prev()[0]; + if (!prevGroup) {return null;} + + hrService.startBatchCommand('Merge Layer'); + + var layerNextSibling = current_group.nextSibling; + hrService.removeElement(current_group, layerNextSibling, this.svgElem_); + + while (current_group.firstChild) { + var child = current_group.firstChild; + if (child.localName == 'title') { + hrService.removeElement(child, child.nextSibling, current_group); + current_group.removeChild(child); + continue; + } + var oldNextSibling = child.nextSibling; + prevGroup.appendChild(child); + hrService.moveElement(child, oldNextSibling, current_group); + } + + // Remove current layer's group + this.current_layer.removeGroup(); + // Remove the current layer and set the previous layer as the new current layer + var index = this.all_layers.indexOf(this.current_layer); + if (index > 0) { + var name = this.current_layer.getName(); + this.current_layer = this.all_layers[index-1] + this.all_layers.splice(index, 1); + delete this.layer_map[name]; + } + + hrService.endBatchCommand(); +}; + +svgedit.draw.Drawing.prototype.mergeAllLayers = function (hrService) { + // Set the current layer to the last layer. + this.current_layer = this.all_layers[this.all_layers.length-1]; + + hrService.startBatchCommand('Merge all Layers'); + while (this.all_layers.length > 1) { + this.mergeLayer(hrService); + } + hrService.endBatchCommand(); +}; + /** * Sets the current layer. If the name is not a valid layer name, then this * function returns false. Otherwise it returns true. This is not an @@ -479,7 +450,7 @@ function findLayerNameInGroup(group) { /** * Given a set of names, return a new unique name. - * @param {string[]} existingLayerNames - Existing layer names. + * @param {Array.} existingLayerNames - Existing layer names. * @returns {string} - The new name. */ function getNewLayerName(existingLayerNames) { @@ -510,9 +481,9 @@ svgedit.draw.Drawing.prototype.identifyLayers = function() { var name = findLayerNameInGroup(child); if (name) { layernames.push(name); - layer = new Layer(name, child); + layer = new svgedit.draw.Layer(name, child); this.all_layers.push(layer); - this.layer_map[ name] = layer; + this.layer_map[name] = layer; } else { // if group did not have a name, it is an orphan orphans.push(child); @@ -526,10 +497,10 @@ svgedit.draw.Drawing.prototype.identifyLayers = function() { // If orphans or no layers found, create a new layer and add all the orphans to it if (orphans.length > 0 || !childgroups) { - layer = new Layer(getNewLayerName(layernames), null, this.svgElem_); + layer = new svgedit.draw.Layer(getNewLayerName(layernames), null, this.svgElem_); layer.appendChildren(orphans); this.all_layers.push(layer); - this.layer_map[ name] = layer; + this.layer_map[name] = layer; } else { layer.activate(); } @@ -551,9 +522,9 @@ svgedit.draw.Drawing.prototype.createLayer = function(name) { if (name === undefined || name === null || name === '' || this.layer_map[name]) { name = getNewLayerName(Object.keys(this.layer_map)); } - var layer = new Layer(name, null, this.svgElem_); + var layer = new svgedit.draw.Layer(name, null, this.svgElem_); this.all_layers.push(layer); - this.layer_map[ name] = layer; + this.layer_map[name] = layer; this.current_layer = layer; return layer.getGroup(); }; diff --git a/editor/historyrecording.js b/editor/historyrecording.js new file mode 100644 index 00000000..3176e897 --- /dev/null +++ b/editor/historyrecording.js @@ -0,0 +1,173 @@ +/*globals svgedit*/ +/*jslint vars: true, eqeq: true */ +/** + * Package: svgedit.history + * + * Licensed under the MIT License + * + * Copyright(c) 2010 Alexis Deveria + * Copyright(c) 2010 Jeff Schiller + * Copyright(c) 2016 Flint O'Brien + */ + +// Dependencies: +// 1) history.js + +(function() { + 'use strict'; + +if (!svgedit.history) { + svgedit.history = {}; +} +var history = svgedit.history; + +/** + * History recording service. + * + * A single service object that can be passed around to provide history + * recording. There is a simple start/end interface for batch commands. + * Easy to mock for unit tests. Built on top of history classes in history.js. + * + * HistoryRecordingService.NO_HISTORY is a singleton that can be passed in to functions + * that record history. This helps when the caller requires that no history be recorded. + * + * Usage: + * The following will record history: insert, batch, insert. + * ``` + * hrService = new svgedit.history.HistoryRecordingService(this.undoMgr); + * hrService.insertElement(elem, text); // add simple command to history. + * hrService.startBatchCommand('create two elements'); + * hrService.changeElement(elem, attrs, text); // add to batchCommand + * hrService.changeElement(elem, attrs2, text); // add to batchCommand + * hrService.endBatchCommand(); // add batch command with two change commands to history. + * hrService.insertElement(elem, text); // add simple command to history. + * ``` + * + * Note that all functions return this, so commands can be chained, like so: + * + * ``` + * hrService + * .startBatchCommand('create two elements') + * .insertElement(elem, text) + * .changeElement(elem, attrs, text) + * .endBatchCommand(); + * ``` + * + * @param {svgedit.history.UndoManager} undoManager - The undo manager. + * A value of null is valid for cases where no history recording is required. + * See singleton: HistoryRecordingService.NO_HISTORY + */ +var HistoryRecordingService = history.HistoryRecordingService = function(undoManager) { + this.undoManager = undoManager; + this.currentBatchCommand = null; + this.batchCommandStack = []; +}; + +/** + * @type {svgedit.history.HistoryRecordingService} NO_HISTORY - Singleton that can be passed + * in to functions that record history, but the caller requires that no history be recorded. + */ +HistoryRecordingService.NO_HISTORY = new HistoryRecordingService(); + +/** + * Start a batch command so multiple commands can recorded as a single history command. + * Requires a corresponding call to endBatchCommand. Start and end commands can be nested. + * + * @param {string} text - Optional string describing the batch command. + * @returns {svgedit.history.HistoryRecordingService} + */ +HistoryRecordingService.prototype.startBatchCommand = function(text) { + if (!this.undoManager) {return this;} + this.currentBatchCommand = new history.BatchCommand(text); + this.batchCommandStack.push(this.currentBatchCommand); + return this; +}; + +/** + * End a batch command and add it to the history or a parent batch command. + * @returns {svgedit.history.HistoryRecordingService} + */ +HistoryRecordingService.prototype.endBatchCommand = function() { + if (!this.undoManager) {return this;} + if (this.currentBatchCommand) { + var batchCommand = this.currentBatchCommand; + this.batchCommandStack.pop(); + var length = this.batchCommandStack.length; + this.currentBatchCommand = length ? this.batchCommandStack[length-1] : null; + this._addCommand(batchCommand); + } + return this; +}; + +/** + * Add a MoveElementCommand to the history or current batch command + * @param {Element} elem - The DOM element that was moved + * @param {Element} oldNextSibling - The element's next sibling before it was moved + * @param {Element} oldParent - The element's parent before it was moved + * @param {string} [text] - An optional string visible to user related to this change + * @returns {svgedit.history.HistoryRecordingService} + */ +HistoryRecordingService.prototype.moveElement = function(elem, oldNextSibling, oldParent, text) { + if (!this.undoManager) {return this;} + this._addCommand(new history.MoveElementCommand(elem, oldNextSibling, oldParent, text)); + return this; +}; + +/** + * Add an InsertElementCommand to the history or current batch command + * @param {Element} elem - The DOM element that was added + * @param {string} [text] - An optional string visible to user related to this change + * @returns {svgedit.history.HistoryRecordingService} + */ +HistoryRecordingService.prototype.insertElement = function(elem, text) { + if (!this.undoManager) {return this;} + this._addCommand(new history.InsertElementCommand(elem, text)); + return this; +}; + + +/** + * Add a RemoveElementCommand to the history or current batch command + * @param {Element} elem - The DOM element that was removed + * @param {Element} oldNextSibling - The element's next sibling before it was removed + * @param {Element} oldParent - The element's parent before it was removed + * @param {string} [text] - An optional string visible to user related to this change + * @returns {svgedit.history.HistoryRecordingService} + */ +HistoryRecordingService.prototype.removeElement = function(elem, oldNextSibling, oldParent, text) { + if (!this.undoManager) {return this;} + this._addCommand(new history.RemoveElementCommand(elem, oldNextSibling, oldParent, text)); + return this; +}; + + +/** + * Add a ChangeElementCommand to the history or current batch command + * @param {Element} elem - The DOM element that was changed + * @param {object} attrs - An object with the attributes to be changed and the values they had *before* the change + * @param {string} [text] - An optional string visible to user related to this change + * @returns {svgedit.history.HistoryRecordingService} + */ +HistoryRecordingService.prototype.changeElement = function(elem, attrs, text) { + if (!this.undoManager) {return this;} + this._addCommand(new history.ChangeElementCommand(elem, attrs, text)); + return this; +}; + +/** + * Private function to add a command to the history or current batch command. + * @param cmd + * @returns {svgedit.history.HistoryRecordingService} + * @private + */ +HistoryRecordingService.prototype._addCommand = function(cmd) { + if (!this.undoManager) {return this;} + if (this.currentBatchCommand) { + this.currentBatchCommand.addSubCommand(cmd); + } else { + this.undoManager.addCommandToHistory(cmd); + } +}; + + +}()); diff --git a/editor/layer.js b/editor/layer.js new file mode 100644 index 00000000..bab48197 --- /dev/null +++ b/editor/layer.js @@ -0,0 +1,200 @@ +/*globals svgedit*/ +/*jslint vars: true, eqeq: true */ +/** + * Package: svgedit.history + * + * Licensed under the MIT License + * + * Copyright(c) 2011 Jeff Schiller + * Copyright(c) 2016 Flint O'Brien + */ + +// Dependencies: +// 1) svgedit.js +// 2) draw.js + +(function() { + 'use strict'; + +if (!svgedit.draw) { + svgedit.draw = {}; +} +var NS = svgedit.NS; + + +/** + * This class encapsulates the concept of a layer in the drawing. It can be constructed with + * an existing group element or, with three parameters, will create a new layer group element. + * @param {string} name - Layer name + * @param {SVGGElement} group - SVG group element that constitutes the layer or null if a group should be created and added to the DOM.. + * @param {SVGGElement} svgElem - The SVG DOM element. If defined, use this to add + * a new layer to the document. + */ +var Layer = svgedit.draw.Layer = function(name, group, svgElem) { + this.name_ = name; + this.group_ = group; + + if (!group) { + // Create a group element with title and add it to the DOM. + var svgdoc = svgElem.ownerDocument; + this.group_ = svgdoc.createElementNS(NS.SVG, "g"); + var layer_title = svgdoc.createElementNS(NS.SVG, "title"); + layer_title.textContent = name; + this.group_.appendChild(layer_title); + svgElem.appendChild(this.group_); + } + + addLayerClass(this.group_); + svgedit.utilities.walkTree(this.group_, function(e){e.setAttribute("style", "pointer-events:inherit");}); + + this.group_.setAttribute("style", svgElem ? "pointer-events:all" : "pointer-events:none"); +}; + +/** + * @type {string} CLASS_NAME - class attribute assigned to all layer groups. + */ +Layer.CLASS_NAME = 'layer'; + +/** + * @type {RegExp} CLASS_REGEX - Used to test presence of class Layer.CLASS_NAME + */ +Layer.CLASS_REGEX = new RegExp('(\\s|^)' + Layer.CLASS_NAME + '(\\s|$)'); + + +/** + * Get the layer's name. + * @returns {string} The layer name + */ +Layer.prototype.getName = function() { + return this.name_; +}; + +/** + * Get the group element for this layer. + * @returns {SVGGElement} The layer SVG group + */ +Layer.prototype.getGroup = function() { + return this.group_; +}; + +/** + * Active this layer so it takes pointer events. + */ +Layer.prototype.activate = function() { + this.group_.setAttribute("style", "pointer-events:all"); +}; + +/** + * Deactive this layer so it does NOT take pointer events. + */ +Layer.prototype.deactivate = function() { + this.group_.setAttribute("style", "pointer-events:none"); +}; + +/** + * Set this layer visible or hidden based on 'visible' parameter. + * @param {boolean} visible - If true, make visible; otherwise, hide it. + */ +Layer.prototype.setVisible = function(visible) { + var expected = visible === undefined || visible ? "inline" : "none"; + var oldDisplay = this.group_.getAttribute("display"); + if (oldDisplay !== expected) { + this.group_.setAttribute("display", expected); + } +}; + +/** + * Is this layer visible? + * @returns {boolean} True if visible. + */ +Layer.prototype.isVisible = function() { + return this.group_.getAttribute('display') !== 'none'; +}; + +/** + * Get layer opacity. + * @returns {number} Opacity value. + */ +Layer.prototype.getOpacity = function() { + var opacity = this.group_.getAttribute('opacity'); + if (opacity === null || opacity === undefined) { + return 1; + } + return parseFloat(opacity); +}; + +/** + * Sets the opacity of this layer. If opacity is not a value between 0.0 and 1.0, + * nothing happens. + * @param {number} opacity - A float value in the range 0.0-1.0 + */ +Layer.prototype.setOpacity = function(opacity) { + if (typeof opacity === 'number' && opacity >= 0.0 && opacity <= 1.0) { + this.group_.setAttribute('opacity', opacity); + } +}; + +/** + * Append children to this layer. + * @param {SVGGElement} children - The children to append to this layer. + */ +Layer.prototype.appendChildren = function(children) { + for (var i = 0; i < children.length; ++i) { + this.group_.appendChild(children[i]); + } +}; + +Layer.prototype.getTitleElement = function() { + var len = this.group_.childNodes.length; + for (var i = 0; i < len; ++i) { + var child = this.group_.childNodes.item(i); + if (child && child.tagName === 'title') { + return child; + } + } + return null; +}; + +Layer.prototype.setName = function(name) { + var previousName = this.name_; + name = svgedit.utilities.toXml(name); + // now change the underlying title element contents + var title = this.getTitleElement(); + if (title) { + while (title.firstChild) { title.removeChild(title.firstChild); } + title.textContent = name; + this.name_ = name; + return {title: title, previousName: previousName}; + } + return null; +}; + +/** + * Remove this layer's group from the DOM. No more functions on group can be called after this. + * @param {SVGGElement} children - The children to append to this layer. + * @returns {SVGGElement} The layer SVG group that was just removed. + */ +Layer.prototype.removeGroup = function() { + var parent = this.group_.parentNode; + var group = parent.removeChild(this.group_); + this.group_ = undefined; + return group; +}; + + +/** + * Add class Layer.CLASS_NAME to the element (usually class='layer'). + * + * Parameters: + * @param {SVGGElement} elem - The SVG element to update + */ +function addLayerClass(elem) { + var classes = elem.getAttribute('class'); + if (classes === null || classes === undefined || classes.length === 0) { + elem.setAttribute('class', Layer.CLASS_NAME); + } else if (! Layer.CLASS_REGEX.test(classes)) { + elem.setAttribute('class', classes + ' ' + Layer.CLASS_NAME); + } +} + +}()); diff --git a/editor/svg-editor.html b/editor/svg-editor.html index efc8315a..dab8e6f8 100644 --- a/editor/svg-editor.html +++ b/editor/svg-editor.html @@ -39,10 +39,12 @@ + + diff --git a/editor/svg-editor.js b/editor/svg-editor.js index 868f6fc8..459821ba 100644 --- a/editor/svg-editor.js +++ b/editor/svg-editor.js @@ -1841,7 +1841,7 @@ TODOS * @returns {boolean} True if the element is a layer */ function isLayer(elem) { - return elem && elem.tagName === 'g' && svgedit.LAYER_CLASS_REGEX.test(elem.getAttribute('class')) + return elem && elem.tagName === 'g' && svgedit.draw.Layer.CLASS_REGEX.test(elem.getAttribute('class')) } // called when any element has changed diff --git a/editor/svgcanvas.js b/editor/svgcanvas.js index 9137f6ce..af4f153f 100644 --- a/editor/svgcanvas.js +++ b/editor/svgcanvas.js @@ -5055,39 +5055,17 @@ this.setCurrentLayer = function(name) { // Returns: // true if the rename succeeded, false otherwise. this.renameCurrentLayer = function(newname) { - var i; var drawing = getCurrentDrawing(); - if (drawing.current_layer) { - var oldLayer = drawing.current_layer; - // setCurrentLayer will return false if the name doesn't already exist - // this means we are free to rename our oldLayer - if (!canvas.setCurrentLayer(newname)) { + var layer = drawing.getCurrentLayer(); + if (layer) { + var result = drawing.setCurrentLayerName( newname); + if (result) { var batchCmd = new svgedit.history.BatchCommand('Rename Layer'); - // find the index of the layer - for (i = 0; i < drawing.getNumLayers(); ++i) { - if (drawing.all_layers[i][1] == oldLayer) {break;} - } - var oldname = drawing.getLayerName(i); - drawing.all_layers[i][0] = svgedit.utilities.toXml(newname); - - // now change the underlying title element contents - var len = oldLayer.childNodes.length; - for (i = 0; i < len; ++i) { - var child = oldLayer.childNodes.item(i); - // found the element, now append all the - if (child && child.tagName == 'title') { - // wipe out old name - while (child.firstChild) { child.removeChild(child.firstChild); } - child.textContent = newname; - - batchCmd.addSubCommand(new svgedit.history.ChangeElementCommand(child, {'#text':oldname})); - addCommandToHistory(batchCmd); - call('changed', [oldLayer]); - return true; - } - } + batchCmd.addSubCommand(new svgedit.history.ChangeElementCommand(result.title, {'#text':result.previousName})); + addCommandToHistory(batchCmd); + call('changed', [layer]); + return true; } - drawing.current_layer = oldLayer; } return false; }; @@ -5105,36 +5083,11 @@ this.renameCurrentLayer = function(newname) { // true if the current layer position was changed, false otherwise. this.setCurrentLayerPosition = function(newpos) { var oldpos, drawing = getCurrentDrawing(); - if (drawing.current_layer && newpos >= 0 && newpos < drawing.getNumLayers()) { - for (oldpos = 0; oldpos < drawing.getNumLayers(); ++oldpos) { - if (drawing.all_layers[oldpos][1] == drawing.current_layer) {break;} - } - // some unknown error condition (current_layer not in all_layers) - if (oldpos == drawing.getNumLayers()) { return false; } - - if (oldpos != newpos) { - // if our new position is below us, we need to insert before the node after newpos - var refLayer = null; - var oldNextSibling = drawing.current_layer.nextSibling; - if (newpos > oldpos ) { - if (newpos < drawing.getNumLayers()-1) { - refLayer = drawing.all_layers[newpos+1][1]; - } - } - // if our new position is above us, we need to insert before the node at newpos - else { - refLayer = drawing.all_layers[newpos][1]; - } - svgcontent.insertBefore(drawing.current_layer, refLayer); - addCommandToHistory(new svgedit.history.MoveElementCommand(drawing.current_layer, oldNextSibling, svgcontent)); - - identifyLayers(); - canvas.setCurrentLayer(drawing.getLayerName(newpos)); - - return true; - } + var result = drawing.setCurrentLayerPosition(newpos); + if (result) { + addCommandToHistory(new svgedit.history.MoveElementCommand(result.currentGroup, result.oldNextSibling, svgcontent)); + return true; } - return false; }; @@ -5179,14 +5132,8 @@ this.setLayerVisibility = function(layername, bVisible) { this.moveSelectedToLayer = function(layername) { // find the layer var i; - var layer = null; var drawing = getCurrentDrawing(); - for (i = 0; i < drawing.getNumLayers(); ++i) { - if (drawing.getLayerName(i) == layername) { - layer = drawing.all_layers[i][1]; - break; - } - } + var layer = drawing.getLayerByName(layername); if (!layer) {return false;} var batchCmd = new svgedit.history.BatchCommand('Move Elements to Layer'); @@ -5209,57 +5156,25 @@ this.moveSelectedToLayer = function(layername) { return true; }; -this.mergeLayer = function(skipHistory) { - var batchCmd = new svgedit.history.BatchCommand('Merge Layer'); - var drawing = getCurrentDrawing(); - var prev = $(drawing.current_layer).prev()[0]; - if (!prev) {return;} - var childs = drawing.current_layer.childNodes; - var len = childs.length; - var layerNextSibling = drawing.current_layer.nextSibling; - batchCmd.addSubCommand(new svgedit.history.RemoveElementCommand(drawing.current_layer, layerNextSibling, svgcontent)); - while (drawing.current_layer.firstChild) { - var ch = drawing.current_layer.firstChild; - if (ch.localName == 'title') { - var chNextSibling = ch.nextSibling; - batchCmd.addSubCommand(new svgedit.history.RemoveElementCommand(ch, chNextSibling, drawing.current_layer)); - drawing.current_layer.removeChild(ch); - continue; - } - var oldNextSibling = ch.nextSibling; - prev.appendChild(ch); - batchCmd.addSubCommand(new svgedit.history.MoveElementCommand(ch, oldNextSibling, drawing.current_layer)); +this.mergeLayer = function(hrService) { + if (!hrService) { + hrService = new svgedit.history.HistoryRecordingService(this.undoMgr); } - - // Remove current layer - svgcontent.removeChild(drawing.current_layer); - - if (!skipHistory) { - clearSelection(); - identifyLayers(); - - call('changed', [svgcontent]); - - addCommandToHistory(batchCmd); - } - - drawing.current_layer = prev; - return batchCmd; + getCurrentDrawing().mergeLayer(hrService); + clearSelection(); + leaveContext(); + call('changed', [svgcontent]); }; -this.mergeAllLayers = function() { - var batchCmd = new svgedit.history.BatchCommand('Merge all Layers'); - var drawing = getCurrentDrawing(); - drawing.current_layer = drawing.all_layers[drawing.getNumLayers()-1][1]; - while ($(svgcontent).children('g').length > 1) { - batchCmd.addSubCommand(canvas.mergeLayer(true)); +this.mergeAllLayers = function(hrService) { + if (!hrService) { + hrService = new svgedit.history.HistoryRecordingService(this.undoMgr); } - + getCurrentDrawing().mergeAllLayers(hrService); clearSelection(); - identifyLayers(); + leaveContext(); call('changed', [svgcontent]); - addCommandToHistory(batchCmd); }; // Function: leaveContext diff --git a/editor/svgedit.js b/editor/svgedit.js index cf2fcb87..6149b2de 100644 --- a/editor/svgedit.js +++ b/editor/svgedit.js @@ -15,10 +15,8 @@ svgedit = { XLINK: 'http://www.w3.org/1999/xlink', XML: 'http://www.w3.org/XML/1998/namespace', XMLNS: 'http://www.w3.org/2000/xmlns/' // see http://www.w3.org/TR/REC-xml-names/#xmlReserved - }, - LAYER_CLASS: 'layer' + } }; -svgedit.LAYER_CLASS_REGEX = new RegExp('(\\s|^)' + svgedit.LAYER_CLASS + '(\\s|$)'); // return the svgedit.NS with key values switched and lowercase svgedit.getReverseNS = function() {'use strict'; diff --git a/test/draw_test.html b/test/draw_test.html index fc0eca47..531d949a 100644 --- a/test/draw_test.html +++ b/test/draw_test.html @@ -6,9 +6,11 @@ <link rel='stylesheet' href='qunit/qunit.css' type='text/css'/> <script src='../editor/jquery.js'></script> <script src='../editor/svgedit.js'></script> + <script src='../editor/pathseg.js'></script> <script src='../editor/browser.js'></script> <script src='../editor/svgutils.js'></script> <script src='../editor/draw.js'></script> + <script src='../editor/layer.js'></script> <script src='qunit/qunit.js'></script> <script> $(function() { @@ -19,7 +21,7 @@ } }; var NS = svgedit.NS; - var LAYER_CLASS = svgedit.LAYER_CLASS; + var LAYER_CLASS = svgedit.draw.Layer.CLASS_NAME; var NONCE = 'foo'; var LAYER1 = 'Layer 1'; var LAYER2 = 'Layer 2';