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 56d9d693..c70df219 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';