Merge pull request #107 from gec/createLayerFix
Fixed Layers in svgcanvas. Moved Layer class. HistoryRecordingservicemaster
commit
53cfe68afa
275
editor/draw.js
275
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.<Layer>}
|
||||
*/
|
||||
this.all_layers = [];
|
||||
|
||||
/**
|
||||
* Map of all_layers by name.
|
||||
* @type {Object.<string,Layer>}
|
||||
*
|
||||
* 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.<string, Layer>}
|
||||
*/
|
||||
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.<string>} 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();
|
||||
};
|
||||
|
|
|
@ -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 <strong>single</strong> 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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}());
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}());
|
|
@ -39,10 +39,12 @@
|
|||
<script src="svgutils.js"></script>
|
||||
<script src="sanitize.js"></script>
|
||||
<script src="history.js"></script>
|
||||
<script src="historyrecording.js"></script>
|
||||
<script src="coords.js"></script>
|
||||
<script src="recalculate.js"></script>
|
||||
<script src="select.js"></script>
|
||||
<script src="draw.js"></script>
|
||||
<script src="layer.js"></script>
|
||||
<script src="path.js"></script>
|
||||
<script src="svgcanvas.js"></script>
|
||||
<script src="svg-editor.js"></script>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 <title> 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
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
Loading…
Reference in New Issue