// namespaces
var dwv = dwv || {};
dwv.tool = dwv.tool || {};
dwv.tool.draw = dwv.tool.draw || {};
/**
* The Konva namespace.
*
* @external Konva
* @see https://konvajs.org/
*/
var Konva = Konva || {};
/**
* Get the default anchor shape.
*
* @param {number} x The X position.
* @param {number} y The Y position.
* @param {string} id The shape id.
* @param {object} style The application style.
* @returns {object} The default anchor shape.
*/
dwv.tool.draw.getDefaultAnchor = function (x, y, id, style) {
return new Konva.Ellipse({
x: x,
y: y,
stroke: '#999',
fill: 'rgba(100,100,100,0.7',
strokeWidth: style.getStrokeWidth(),
strokeScaleEnabled: false,
radius: style.applyZoomScale(3),
name: 'anchor',
id: id,
dragOnTop: false,
draggable: true,
visible: false
});
};
/**
* Shape editor.
*
* @param {object} app The associated application.
* @class
*/
dwv.tool.ShapeEditor = function (app) {
/**
* Shape factory list
*
* @type {object}
* @private
*/
var shapeFactoryList = null;
/**
* Current shape factory.
*
* @type {object}
* @private
*/
var currentFactory = null;
/**
* Edited shape.
*
* @private
* @type {object}
*/
var shape = null;
/**
* Edited view controller. Used for quantification update.
*
* @private
* @type {object}
*/
var viewController = null;
/**
* Active flag.
*
* @private
* @type {boolean}
*/
var isActive = false;
/**
* Draw event callback.
*
* @private
* @type {Function}
*/
var drawEventCallback = null;
/**
* Set the tool options.
*
* @param {Array} list The list of shape classes.
*/
this.setFactoryList = function (list) {
shapeFactoryList = list;
};
/**
* Set the shape to edit.
*
* @param {object} inshape The shape to edit.
*/
this.setShape = function (inshape) {
shape = inshape;
if (shape) {
// remove old anchors
removeAnchors();
// find a factory for the input shape
var group = shape.getParent();
var keys = Object.keys(shapeFactoryList);
currentFactory = null;
for (var i = 0; i < keys.length; ++i) {
var factory = new shapeFactoryList[keys[i]];
if (factory.isFactoryGroup(group)) {
currentFactory = factory;
// stop at first find
break;
}
}
if (currentFactory === null) {
throw new Error('Could not find a factory to update shape.');
}
// add new anchors
addAnchors();
}
};
/**
* Set the associated image.
*
* @param {object} vc The associated view controller.
*/
this.setViewController = function (vc) {
viewController = vc;
};
/**
* Get the edited shape.
*
* @returns {object} The edited shape.
*/
this.getShape = function () {
return shape;
};
/**
* Get the active flag.
*
* @returns {boolean} The active flag.
*/
this.isActive = function () {
return isActive;
};
/**
* Set the draw event callback.
*
* @param {object} callback The callback.
*/
this.setDrawEventCallback = function (callback) {
drawEventCallback = callback;
};
/**
* Enable the editor. Redraws the layer.
*/
this.enable = function () {
isActive = true;
if (shape) {
setAnchorsVisible(true);
if (shape.getLayer()) {
shape.getLayer().draw();
}
}
};
/**
* Disable the editor. Redraws the layer.
*/
this.disable = function () {
isActive = false;
if (shape) {
setAnchorsVisible(false);
if (shape.getLayer()) {
shape.getLayer().draw();
}
}
};
/**
* Reset the anchors.
*/
this.resetAnchors = function () {
// remove previous controls
removeAnchors();
// add anchors
addAnchors();
// set them visible
setAnchorsVisible(true);
};
/**
* Apply a function on all anchors.
*
* @param {object} func A f(shape) function.
* @private
*/
function applyFuncToAnchors(func) {
if (shape && shape.getParent()) {
var anchors = shape.getParent().find('.anchor');
anchors.forEach(func);
}
}
/**
* Set anchors visibility.
*
* @param {boolean} flag The visible flag.
* @private
*/
function setAnchorsVisible(flag) {
applyFuncToAnchors(function (anchor) {
anchor.visible(flag);
});
}
/**
* Set anchors active.
*
* @param {boolean} flag The active (on/off) flag.
*/
this.setAnchorsActive = function (flag) {
var func = null;
if (flag) {
func = function (anchor) {
setAnchorOn(anchor);
};
} else {
func = function (anchor) {
setAnchorOff(anchor);
};
}
applyFuncToAnchors(func);
};
/**
* Remove anchors.
*
* @private
*/
function removeAnchors() {
applyFuncToAnchors(function (anchor) {
anchor.remove();
});
}
/**
* Add the shape anchors.
*
* @private
*/
function addAnchors() {
// exit if no shape or no layer
if (!shape || !shape.getLayer()) {
return;
}
// get shape group
var group = shape.getParent();
// activate and add anchors to group
var anchors = currentFactory.getAnchors(shape, app.getStyle());
for (var i = 0; i < anchors.length; ++i) {
// set anchor on
setAnchorOn(anchors[i]);
// add the anchor to the group
group.add(anchors[i]);
}
}
/**
* Get a simple clone of the input anchor.
*
* @param {object} anchor The anchor to clone.
* @returns {object} A clone of the input anchor.
* @private
*/
function getClone(anchor) {
// create closure to properties
var parent = anchor.getParent();
var id = anchor.id();
var x = anchor.x();
var y = anchor.y();
// create clone object
var clone = {};
clone.getParent = function () {
return parent;
};
clone.id = function () {
return id;
};
clone.x = function () {
return x;
};
clone.y = function () {
return y;
};
return clone;
}
/**
* Set the anchor on listeners.
*
* @param {object} anchor The anchor to set on.
* @private
*/
function setAnchorOn(anchor) {
var startAnchor = null;
// command name based on shape type
var shapeDisplayName = dwv.tool.GetShapeDisplayName(shape);
// drag start listener
anchor.on('dragstart.edit', function (evt) {
startAnchor = getClone(this);
// prevent bubbling upwards
evt.cancelBubble = true;
});
// drag move listener
anchor.on('dragmove.edit', function (evt) {
var layerDetails = dwv.gui.getLayerDetailsFromEvent(evt.evt);
var layerGroup = app.getLayerGroupById(layerDetails.groupId);
var drawLayer = layerGroup.getActiveDrawLayer();
// validate the anchor position
dwv.tool.validateAnchorPosition(drawLayer.getBaseSize(), this);
// update shape
currentFactory.update(this, app.getStyle(), viewController);
// redraw
if (this.getLayer()) {
this.getLayer().draw();
} else {
dwv.logger.warn('No layer to draw the anchor!');
}
// prevent bubbling upwards
evt.cancelBubble = true;
});
// drag end listener
anchor.on('dragend.edit', function (evt) {
var endAnchor = getClone(this);
// store the change command
var chgcmd = new dwv.tool.ChangeGroupCommand(
shapeDisplayName,
currentFactory.update,
startAnchor,
endAnchor,
this.getLayer(),
viewController,
app.getStyle()
);
chgcmd.onExecute = drawEventCallback;
chgcmd.onUndo = drawEventCallback;
chgcmd.execute();
app.addToUndoStack(chgcmd);
// reset start anchor
startAnchor = endAnchor;
// prevent bubbling upwards
evt.cancelBubble = true;
});
// mouse down listener
anchor.on('mousedown touchstart', function () {
this.moveToTop();
});
// mouse over styling
anchor.on('mouseover.edit', function () {
// style is handled by the group
this.stroke('#ddd');
if (this.getLayer()) {
this.getLayer().draw();
} else {
dwv.logger.warn('No layer to draw the anchor!');
}
});
// mouse out styling
anchor.on('mouseout.edit', function () {
// style is handled by the group
this.stroke('#999');
if (this.getLayer()) {
this.getLayer().draw();
} else {
dwv.logger.warn('No layer to draw the anchor!');
}
});
}
/**
* Set the anchor off listeners.
*
* @param {object} anchor The anchor to set off.
* @private
*/
function setAnchorOff(anchor) {
anchor.off('dragstart.edit');
anchor.off('dragmove.edit');
anchor.off('dragend.edit');
anchor.off('mousedown touchstart');
anchor.off('mouseover.edit');
anchor.off('mouseout.edit');
}
};