Current Dev State

This commit is contained in:
Tim Lorsbach
2025-06-23 20:13:54 +02:00
parent b4f9bb277d
commit ded50edaa2
22617 changed files with 4345095 additions and 174 deletions

View File

@ -0,0 +1,52 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***************************************************************************/
var Action = require('../action');
function APointTool(editor) {
if (!(this instanceof APointTool))
return new APointTool(editor);
this.editor = editor;
this.editor.selection(null);
}
APointTool.prototype.mousemove = function (event) {
this.editor.hover(this.editor.findItem(event, ['atoms']));
};
APointTool.prototype.mouseup = function (event) {
var editor = this.editor;
var struct = editor.render.ctab.molecule;
var ci = editor.findItem(event, ['atoms']);
if (ci && ci.map === 'atoms') {
this.editor.hover(null);
var atom = struct.atoms.get(ci.id);
var res = editor.event.elementEdit.dispatch({
attpnt: atom.attpnt
});
Promise.resolve(res).then(function (newatom) {
if (atom.attpnt != newatom.attpnt) {
var action = Action.fromAtomsAttrs(editor.render.ctab, ci.id, newatom);
editor.update(action);
}
});
return true;
}
};
module.exports = APointTool;

View File

@ -0,0 +1,119 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***************************************************************************/
var Struct = require('../../chem/struct');
var Action = require('../action');
var utils = require('./utils');
function AtomTool(editor, atomProps) {
if (!(this instanceof AtomTool)) {
if (!editor.selection() || !editor.selection().atoms)
return new AtomTool(editor, atomProps);
var action = Action.fromAtomsAttrs(editor.render.ctab, editor.selection().atoms,
atomProps, true);
editor.update(action);
editor.selection(null);
return null;
}
this.editor = editor;
this.atomProps = atomProps;
this.bondProps = { type: 1, stereo: Struct.Bond.PATTERN.STEREO.NONE };
}
AtomTool.prototype.mousedown = function (event) {
this.editor.hover(null);
var ci = this.editor.findItem(event, ['atoms']);
if (!ci) { // ci.type == 'Canvas'
this.dragCtx = {};
} else if (ci.map === 'atoms') {
this.dragCtx = { item: ci };
}
};
AtomTool.prototype.mousemove = function (event) {
var rnd = this.editor.render;
if (!this.dragCtx || !this.dragCtx.item) {
this.editor.hover(this.editor.findItem(event, ['atoms']));
return;
}
var dragCtx = this.dragCtx;
var ci = this.editor.findItem(event, ['atoms']);
if (ci && ci.map === 'atoms' && ci.id === dragCtx.item.id) {
// fromAtomsAttrs
this.editor.hover(this.editor.findItem(event, ['atoms']));
return;
}
// fromAtomAddition
var atom = rnd.ctab.molecule.atoms.get(dragCtx.item.id);
var newAtomPos = utils.calcNewAtomPos(atom.pp, rnd.page2obj(event));
if (dragCtx.action)
dragCtx.action.perform(rnd.ctab);
dragCtx.action = Action.fromBondAddition(rnd.ctab,
this.bondProps, dragCtx.item.id, Object.assign({}, this.atomProps), newAtomPos, newAtomPos
)[0];
this.editor.update(dragCtx.action, true);
};
AtomTool.prototype.mouseup = function (event) {
if (this.dragCtx) {
var dragCtx = this.dragCtx;
var rnd = this.editor.render;
this.editor.update(dragCtx.action || (
dragCtx.item ?
Action.fromAtomsAttrs(rnd.ctab, dragCtx.item.id, this.atomProps, true) :
Action.fromAtomAddition(rnd.ctab, rnd.page2obj(event), this.atomProps)
));
delete this.dragCtx;
}
};
function atomLongtapEvent(tool, render) {
const dragCtx = tool.dragCtx;
const editor = tool.editor;
const atomid = dragCtx.item && dragCtx.item.id;
const atom = atomid ? // edit atom or add atom
render.ctab.molecule.atoms.get(atomid) :
new Struct.Atom({ label: '' });
// TODO: longtab event
dragCtx.timeout = setTimeout(function () {
delete tool.dragCtx;
editor.selection(null);
const res = editor.event.quickEdit.dispatch(atom);
Promise.resolve(res).then(function (newatom) {
const action = atomid ?
Action.fromAtomsAttrs(render.ctab, atomid, newatom) :
Action.fromAtomAddition(render.ctab, dragCtx.xy0, newatom);
editor.update(action);
});
}, 750);
dragCtx.stopTapping = function () {
if (dragCtx.timeout) {
clearTimeout(dragCtx.timeout);
delete tool.dragCtx.timeout;
}
};
}
module.exports = Object.assign(AtomTool, {
atomLongtapEvent: atomLongtapEvent
});

View File

@ -0,0 +1,65 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***************************************************************************/
var element = require('../../chem/element');
function AttachTool(editor, attachPoints) {
if (!(this instanceof AttachTool))
return new AttachTool(editor, attachPoints);
this.attach = attachPoints || { atomid: 0, bondid: 0 };
this.editor = editor;
this.editor.selection({
atoms: [this.attach.atomid],
bonds: [this.attach.bondid]
});
}
AttachTool.prototype.mousemove = function (event) {
var rnd = this.editor.render;
var ci = this.editor.findItem(event, ['atoms', 'bonds']);
var struct = rnd.ctab.molecule;
if (ci && ((ci.map === 'atoms' && element.map[struct.atoms.get(ci.id).label]) || ci.map === 'bonds'))
this.editor.hover(ci);
else
this.editor.hover(null);
return true;
};
AttachTool.prototype.mouseup = function (event) {
var editor = this.editor;
var rnd = editor.render;
var struct = rnd.ctab.molecule;
var ci = editor.findItem(event, ['atoms', 'bonds']);
if (ci && ((ci.map === 'atoms' && element.map[struct.atoms.get(ci.id).label]) || ci.map === 'bonds')) {
if (ci.map === 'atoms')
this.attach.atomid = ci.id;
else
this.attach.bondid = ci.id;
this.editor.selection({
atoms: [this.attach.atomid],
bonds: [this.attach.bondid]
});
this.editor.event.attachEdit.dispatch(this.attach);
}
return true;
};
module.exports = AttachTool;

View File

@ -0,0 +1,171 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***************************************************************************/
var Vec2 = require('../../util/vec2');
var Struct = require('../../chem/struct');
var Action = require('../action');
var utils = require('./utils');
function BondTool(editor, bondProps) {
if (!(this instanceof BondTool)) {
// Action.fromBondAttrs(editor.render.ctab,
// editor.selection().bonds, {
// type: bondType(mode).type,
// stereo: Bond.PATTERN.STEREO.NONE })
editor.selection(null);
return new BondTool(editor, bondProps);
}
this.editor = editor;
this.atomProps = { label: 'C' };
this.bondProps = bondProps;
}
BondTool.prototype.mousedown = function (event) {
var rnd = this.editor.render;
this.editor.hover(null);
this.dragCtx = {
xy0: rnd.page2obj(event),
item: this.editor.findItem(event, ['atoms', 'bonds'])
};
if (!this.dragCtx.item) // ci.type == 'Canvas'
delete this.dragCtx.item;
return true;
};
BondTool.prototype.mousemove = function (event) { // eslint-disable-line max-statements
var editor = this.editor;
var rnd = editor.render;
if ('dragCtx' in this) {
var dragCtx = this.dragCtx;
if (!('item' in dragCtx) || dragCtx.item.map === 'atoms') {
if ('action' in dragCtx)
dragCtx.action.perform(rnd.ctab);
var i1, i2, p1, p2;
if (('item' in dragCtx && dragCtx.item.map === 'atoms')) {
// first mousedown event intersect with any atom
i1 = dragCtx.item.id;
i2 = editor.findItem(event, ['atoms'], dragCtx.item);
} else {
// first mousedown event intersect with any canvas
i1 = this.atomProps;
p1 = dragCtx.xy0;
i2 = editor.findItem(event, ['atoms']);
}
var dist = Number.MAX_VALUE;
if (i2 && i2.map === 'atoms') {
// after mousedown events is appered, cursor is moved and then cursor intersects any atoms
i2 = i2.id;
} else {
i2 = this.atomProps;
var xy1 = rnd.page2obj(event);
dist = Vec2.dist(dragCtx.xy0, xy1);
if (p1) {
// rotation only, leght of bond = 1;
p2 = utils.calcNewAtomPos(p1, xy1);
} else {
// first mousedown event intersect with any atom and
// rotation only, leght of bond = 1;
var atom = rnd.ctab.molecule.atoms.get(i1);
p1 = utils.calcNewAtomPos(atom.pp.get_xy0(), xy1);
}
}
// don't rotate the bond if the distance between the start and end point is too small
if (dist > 0.3)
dragCtx.action = Action.fromBondAddition(rnd.ctab, this.bondProps, i1, i2, p1, p2)[0];
else
delete dragCtx.action;
this.editor.update(dragCtx.action, true);
return true;
}
}
this.editor.hover(this.editor.findItem(event, ['atoms', 'bonds']));
return true;
};
BondTool.prototype.mouseup = function (event) { // eslint-disable-line max-statements
if ('dragCtx' in this) {
var dragCtx = this.dragCtx;
var rnd = this.editor.render;
var struct = rnd.ctab.molecule;
if ('action' in dragCtx) {
this.editor.update(dragCtx.action);
} else if (!('item' in dragCtx)) {
var xy = rnd.page2obj(event);
var v = new Vec2(1.0 / 2, 0).rotate(
this.bondProps.type == Struct.Bond.PATTERN.TYPE.SINGLE ? -Math.PI / 6 : 0
);
var bondAddition = Action.fromBondAddition(rnd.ctab,
this.bondProps, { label: 'C' }, { label: 'C' },
Vec2.diff(xy, v), Vec2.sum(xy, v));
this.editor.update(bondAddition[0]);
} else if (dragCtx.item.map === 'atoms') {
// when does it hapend?
this.editor.update(Action.fromBondAddition(rnd.ctab, this.bondProps, dragCtx.item.id)[0]);
} else if (dragCtx.item.map === 'bonds') {
var bondProps = Object.assign({}, this.bondProps);
var bond = struct.bonds.get(dragCtx.item.id);
this.editor.update(bondChangingAction(rnd.ctab, dragCtx.item.id, bond, bondProps));
}
delete this.dragCtx;
}
return true;
};
/**
* @param itemID - bond id in structure
* @param bond - bond for change
* @param bondProps - bondTool properties
* @returns Action
*/
function bondChangingAction(restruct, itemID, bond, bondProps) {
if (bondProps.stereo !== Struct.Bond.PATTERN.STEREO.NONE && //
bondProps.type === Struct.Bond.PATTERN.TYPE.SINGLE &&
bond.type === bondProps.type && bond.stereo === bondProps.stereo)
// if bondTool is stereo and equal to bond for change
return Action.fromBondFlipping(restruct, itemID);
var loop = plainBondTypes.indexOf(bondProps.type) >= 0 ? plainBondTypes : null;
if (bondProps.stereo === Struct.Bond.PATTERN.STEREO.NONE &&
bondProps.type === Struct.Bond.PATTERN.TYPE.SINGLE &&
bond.stereo === Struct.Bond.PATTERN.STEREO.NONE &&
loop)
// if `Single bond` tool is chosen and bond for change in `plainBondTypes`
bondProps.type = loop[(loop.indexOf(bond.type) + 1) % loop.length];
return Action.fromBondAttrs(restruct, itemID, bondProps,
bondFlipRequired(restruct.molecule, bond, bondProps));
}
function bondFlipRequired(struct, bond, attrs) {
return attrs.type == Struct.Bond.PATTERN.TYPE.SINGLE &&
bond.stereo == Struct.Bond.PATTERN.STEREO.NONE &&
attrs.stereo != Struct.Bond.PATTERN.STEREO.NONE &&
struct.atoms.get(bond.begin).neighbors.length <
struct.atoms.get(bond.end).neighbors.length;
}
var plainBondTypes = [
Struct.Bond.PATTERN.TYPE.SINGLE,
Struct.Bond.PATTERN.TYPE.DOUBLE,
Struct.Bond.PATTERN.TYPE.TRIPLE
];
module.exports = Object.assign(BondTool, {
bondChangingAction: bondChangingAction
});

View File

@ -0,0 +1,111 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***************************************************************************/
var Vec2 = require('../../util/vec2');
var Struct = require('../../chem/struct');
var Action = require('../action');
var utils = require('./utils');
var Atom = require('./atom');
var Bond = require('./bond');
function ChainTool(editor) {
if (!(this instanceof ChainTool))
return new ChainTool(editor);
this.editor = editor;
this.editor.selection(null);
}
ChainTool.prototype.mousedown = function (event) {
var rnd = this.editor.render;
var ci = this.editor.findItem(event, ['atoms', 'bonds']);
this.editor.hover(null);
this.dragCtx = {
xy0: rnd.page2obj(event),
item: ci
};
if (ci && ci.map === 'atoms') {
this.editor.selection({ atoms: [ci.id] }); // for change atom
// this event has to be stopped in others events by `tool.dragCtx.stopTapping()`
Atom.atomLongtapEvent(this, rnd);
}
if (!this.dragCtx.item) // ci.type == 'Canvas'
delete this.dragCtx.item;
return true;
};
ChainTool.prototype.mousemove = function (event) { // eslint-disable-line max-statements
var editor = this.editor;
var rnd = editor.render;
if (this.dragCtx) {
if ('stopTapping' in this.dragCtx)
this.dragCtx.stopTapping();
this.editor.selection(null);
var dragCtx = this.dragCtx;
if (!('item' in dragCtx) || dragCtx.item.map === 'atoms') {
if ('action' in dragCtx)
dragCtx.action.perform(rnd.ctab);
var atoms = rnd.ctab.molecule.atoms;
var pos0 = dragCtx.item ? atoms.get(dragCtx.item.id).pp :
dragCtx.xy0;
var pos1 = rnd.page2obj(event);
var sectCount = Math.ceil(Vec2.diff(pos1, pos0).length());
var angle = event.ctrlKey ? utils.calcAngle(pos0, pos1) :
utils.fracAngle(pos0, pos1);
dragCtx.action = Action.fromChain(rnd.ctab, pos0, angle, sectCount,
dragCtx.item ? dragCtx.item.id : null);
editor.event.message.dispatch({
info: sectCount + " sectors"
});
this.editor.update(dragCtx.action, true);
return true;
}
}
this.editor.hover(this.editor.findItem(event, ['atoms', 'bonds']));
return true;
};
ChainTool.prototype.mouseup = function () {
var rnd = this.editor.render;
var struct = rnd.ctab.molecule;
if (this.dragCtx) {
if ('stopTapping' in this.dragCtx)
this.dragCtx.stopTapping();
var dragCtx = this.dragCtx;
var action = dragCtx.action;
if (!action && dragCtx.item && dragCtx.item.map === 'bonds') {
var bond = struct.bonds.get(dragCtx.item.id);
action = Bond.bondChangingAction(rnd.ctab, dragCtx.item.id, bond, {
type: Struct.Bond.PATTERN.TYPE.SINGLE,
stereo: Struct.Bond.PATTERN.STEREO.NONE
});
}
delete this.dragCtx;
if (action)
this.editor.update(action);
}
return true;
};
ChainTool.prototype.cancel = ChainTool.prototype.mouseleave =
ChainTool.prototype.mouseup;
module.exports = ChainTool;

View File

@ -0,0 +1,53 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***************************************************************************/
var Action = require('../action');
var element = require('../../chem/element');
function ChargeTool(editor, charge) {
if (!(this instanceof ChargeTool))
return new ChargeTool(editor, charge);
this.editor = editor;
this.editor.selection(null);
this.charge = charge;
}
ChargeTool.prototype.mousemove = function (event) {
var rnd = this.editor.render;
var ci = this.editor.findItem(event, ['atoms']);
var struct = rnd.ctab.molecule;
if (ci && ci.map === 'atoms' && element.map[struct.atoms.get(ci.id).label])
this.editor.hover(ci);
else
this.editor.hover(null);
return true;
};
ChargeTool.prototype.mouseup = function (event) {
var editor = this.editor;
var rnd = editor.render;
var struct = rnd.ctab.molecule;
var ci = editor.findItem(event, ['atoms']);
if (ci && ci.map === 'atoms' && element.map[struct.atoms.get(ci.id).label]) {
this.editor.hover(null);
this.editor.update(Action.fromAtomsAttrs(rnd.ctab, ci.id, {
charge: struct.atoms.get(ci.id).charge + this.charge
}));
}
return true;
};
module.exports = ChargeTool;

View File

@ -0,0 +1,34 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***************************************************************************/
var Action = require('../action');
function ChiralFlagTool(editor) {
if (!(this instanceof ChiralFlagTool)) {
this.editor = editor;
const rnd = this.editor.render;
let action = null;
if (rnd.ctab.molecule.isChiral === false)
action = Action.fromChiralFlagAddition(rnd.ctab);
else
action = Action.fromChiralFlagDeletion(rnd.ctab);
this.editor.update(action);
}
}
module.exports = ChiralFlagTool;

View File

@ -0,0 +1,86 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***************************************************************************/
var Action = require('../action');
var LassoHelper = require('./helper/lasso');
function EraserTool(editor, mode) {
if (!(this instanceof EraserTool)) {
if (!editor.selection())
return new EraserTool(editor, mode);
var action = Action.fromFragmentDeletion(editor.render.ctab, editor.selection());
editor.update(action);
editor.selection(null);
return null;
}
this.editor = editor;
this.maps = ['atoms', 'bonds', 'rxnArrows', 'rxnPluses', 'sgroups', 'sgroupData', 'chiralFlags'];
this.lassoHelper = new LassoHelper(mode || 0, editor);
}
EraserTool.prototype.mousedown = function (event) {
var ci = this.editor.findItem(event, this.maps);
if (!ci) // ci.type == 'Canvas'
this.lassoHelper.begin(event);
};
EraserTool.prototype.mousemove = function (event) {
if (this.lassoHelper.running())
this.editor.selection(this.lassoHelper.addPoint(event));
else
this.editor.hover(this.editor.findItem(event, this.maps));
};
EraserTool.prototype.mouseleave = function (event) {
if (this.lassoHelper.running(event))
this.lassoHelper.end(event);
};
EraserTool.prototype.mouseup = function (event) { // eslint-disable-line max-statements
var rnd = this.editor.render;
if (this.lassoHelper.running()) { // TODO it catches more events than needed, to be re-factored
this.editor.update(Action.fromFragmentDeletion(rnd.ctab, this.lassoHelper.end(event)));
this.editor.selection(null);
} else {
var ci = this.editor.findItem(event, this.maps);
if (ci) { // ci.type != 'Canvas'
this.editor.hover(null);
if (ci.map === 'atoms') {
this.editor.update(Action.fromAtomDeletion(rnd.ctab, ci.id));
} else if (ci.map === 'bonds') {
this.editor.update(Action.fromBondDeletion(rnd.ctab, ci.id));
} else if (ci.map === 'sgroups' || ci.map === 'sgroupData') {
this.editor.update(Action.fromSgroupDeletion(rnd.ctab, ci.id));
} else if (ci.map === 'rxnArrows') {
this.editor.update(Action.fromArrowDeletion(rnd.ctab, ci.id));
} else if (ci.map === 'rxnPluses') {
this.editor.update(Action.fromPlusDeletion(rnd.ctab, ci.id));
} else if (ci.map === 'chiralFlags') {
this.editor.update(Action.fromChiralFlagDeletion(rnd.ctab));
} else {
// TODO re-factoring needed - should be "map-independent"
console.error('EraserTool: unable to delete the object ' + ci.map + '[' + ci.id + ']');
return;
}
this.editor.selection(null);
}
}
};
module.exports = EraserTool;

View File

@ -0,0 +1,83 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***************************************************************************/
var locate = require('./locate');
var draw = require('../../../render/draw');
var scale = require('../../../util/scale');
function LassoHelper(mode, editor, fragment) {
this.mode = mode;
this.fragment = fragment;
this.editor = editor;
}
LassoHelper.prototype.getSelection = function () {
var rnd = this.editor.render;
if (this.mode == 0)
return locate.inPolygon(rnd.ctab, this.points);
else if (this.mode == 1)
return locate.inRectangle(rnd.ctab, this.points[0], this.points[1]);
else
throw new Error('Selector mode unknown'); // eslint-disable-line no-else-return
};
LassoHelper.prototype.begin = function (event) {
var rnd = this.editor.render;
this.points = [rnd.page2obj(event)];
if (this.mode == 1)
this.points.push(this.points[0]);
};
LassoHelper.prototype.running = function () {
return !!this.points;
};
LassoHelper.prototype.addPoint = function (event) {
if (this.points) {
var rnd = this.editor.render;
if (this.mode == 0)
this.points.push(rnd.page2obj(event));
else if (this.mode == 1)
this.points = [this.points[0], rnd.page2obj(event)];
this.update();
return this.getSelection();
}
return null;
};
LassoHelper.prototype.update = function () {
if (this.selection) {
this.selection.remove();
this.selection = null;
}
if (this.points && this.points.length > 1) {
var rnd = this.editor.render;
var dp = this.points.map(function (p) {
return scale.obj2scaled(p, rnd.options).add(rnd.options.offset);
});
this.selection = this.mode == 0 ?
draw.selectionPolygon(rnd.paper, dp, rnd.options) :
draw.selectionRectangle(rnd.paper, dp[0], dp[1], rnd.options);
}
};
LassoHelper.prototype.end = function () {
var ret = this.getSelection();
this.points = null;
this.update(null);
return ret;
};
module.exports = LassoHelper;

View File

@ -0,0 +1,160 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***************************************************************************/
var Vec2 = require('../../../util/vec2');
function getElementsInRectangle(restruct, p0, p1) {
var bondList = [];
var atomList = [];
var x0 = Math.min(p0.x, p1.x),
x1 = Math.max(p0.x, p1.x),
y0 = Math.min(p0.y, p1.y),
y1 = Math.max(p0.y, p1.y);
restruct.bonds.each(function (bid, bond) {
var centre = Vec2.lc2(restruct.atoms.get(bond.b.begin).a.pp, 0.5,
restruct.atoms.get(bond.b.end).a.pp, 0.5);
if (centre.x > x0 && centre.x < x1 && centre.y > y0 && centre.y < y1)
bondList.push(bid);
});
restruct.atoms.each(function (aid, atom) {
if (atom.a.pp.x > x0 && atom.a.pp.x < x1 && atom.a.pp.y > y0 && atom.a.pp.y < y1)
atomList.push(aid);
});
var rxnArrowsList = [];
var rxnPlusesList = [];
restruct.rxnArrows.each(function (id, item) {
if (item.item.pp.x > x0 && item.item.pp.x < x1 && item.item.pp.y > y0 && item.item.pp.y < y1)
rxnArrowsList.push(id);
});
restruct.rxnPluses.each(function (id, item) {
if (item.item.pp.x > x0 && item.item.pp.x < x1 && item.item.pp.y > y0 && item.item.pp.y < y1)
rxnPlusesList.push(id);
});
var chiralFlagList = [];
restruct.chiralFlags.each(function (id, item) {
if (item.pp.x > x0 && item.pp.x < x1 && item.pp.y > y0 && item.pp.y < y1)
chiralFlagList.push(id);
});
var sgroupDataList = [];
restruct.sgroupData.each(function (id, item) {
if (item.sgroup.pp.x > x0 && item.sgroup.pp.x < x1 && item.sgroup.pp.y > y0 && item.sgroup.pp.y < y1)
sgroupDataList.push(id);
});
return {
atoms: atomList,
bonds: bondList,
rxnArrows: rxnArrowsList,
rxnPluses: rxnPlusesList,
chiralFlags: chiralFlagList,
sgroupData: sgroupDataList
};
}
function getElementsInPolygon(restruct, rr) { // eslint-disable-line max-statements
var bondList = [];
var atomList = [];
var r = [];
for (var i = 0; i < rr.length; ++i)
r[i] = new Vec2(rr[i].x, rr[i].y);
restruct.bonds.each(function (bid, bond) {
var centre = Vec2.lc2(restruct.atoms.get(bond.b.begin).a.pp, 0.5,
restruct.atoms.get(bond.b.end).a.pp, 0.5);
if (isPointInPolygon(r, centre))
bondList.push(bid);
});
restruct.atoms.each(function (aid, atom) {
if (isPointInPolygon(r, atom.a.pp))
atomList.push(aid);
});
var rxnArrowsList = [];
var rxnPlusesList = [];
restruct.rxnArrows.each(function (id, item) {
if (isPointInPolygon(r, item.item.pp))
rxnArrowsList.push(id);
});
restruct.rxnPluses.each(function (id, item) {
if (isPointInPolygon(r, item.item.pp))
rxnPlusesList.push(id);
});
var chiralFlagList = [];
restruct.chiralFlags.each(function (id, item) {
if (isPointInPolygon(r, item.pp))
chiralFlagList.push(id);
});
var sgroupDataList = [];
restruct.sgroupData.each(function (id, item) {
if (isPointInPolygon(r, item.sgroup.pp))
sgroupDataList.push(id);
});
return {
atoms: atomList,
bonds: bondList,
rxnArrows: rxnArrowsList,
rxnPluses: rxnPlusesList,
chiralFlags: chiralFlagList,
sgroupData: sgroupDataList
};
}
// TODO: test me see testPolygon from
// 'Remove unused methods from render' commit
function isPointInPolygon(r, p) { // eslint-disable-line max-statements
var d = new Vec2(0, 1);
var n = d.rotate(Math.PI / 2);
var v0 = Vec2.diff(r[r.length - 1], p);
var n0 = Vec2.dot(n, v0);
var d0 = Vec2.dot(d, v0);
var w0 = null;
var counter = 0;
var eps = 1e-5;
var flag1 = false,
flag0 = false;
for (var i = 0; i < r.length; ++i) {
var v1 = Vec2.diff(r[i], p);
var w1 = Vec2.diff(v1, v0);
var n1 = Vec2.dot(n, v1);
var d1 = Vec2.dot(d, v1);
flag1 = false;
if (n1 * n0 < 0) {
if (d1 * d0 > -eps) {
if (d0 > -eps)
flag1 = true;
/* eslint-disable no-mixed-operators*/
} else if ((Math.abs(n0) * Math.abs(d1) - Math.abs(n1) * Math.abs(d0)) * d1 > 0) {
/* eslint-enable no-mixed-operators*/
flag1 = true;
}
}
if (flag1 && flag0 && Vec2.dot(w1, n) * Vec2.dot(w0, n) >= 0)
flag1 = false;
if (flag1)
counter++;
v0 = v1;
n0 = n1;
d0 = d1;
w0 = w1;
flag0 = flag1;
}
return (counter % 2) != 0;
}
module.exports = {
inRectangle: getElementsInRectangle,
inPolygon: getElementsInPolygon
};

View File

@ -0,0 +1,59 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***************************************************************************/
var Action = require('../action');
function PasteTool(editor, struct) {
if (!(this instanceof PasteTool))
return new PasteTool(editor, struct);
this.editor = editor;
this.editor.selection(null);
this.struct = struct;
var rnd = editor.render;
var point = editor.lastEvent ?
rnd.page2obj(editor.lastEvent) : null;
this.action = Action.fromPaste(rnd.ctab, this.struct, point);
this.editor.update(this.action, true);
}
PasteTool.prototype.mousemove = function (event) {
var rnd = this.editor.render;
if (this.action)
this.action.perform(rnd.ctab);
this.action = Action.fromPaste(rnd.ctab, this.struct, rnd.page2obj(event));
this.editor.update(this.action, true);
};
PasteTool.prototype.mouseup = function () {
if (this.action) {
var action = this.action;
delete this.action;
this.editor.update(action);
}
};
PasteTool.prototype.cancel = PasteTool.prototype.mouseleave = function () {
var rnd = this.editor.render;
if (this.action) {
this.action.perform(rnd.ctab); // revert the action
delete this.action;
rnd.update();
}
};
module.exports = PasteTool;

View File

@ -0,0 +1,65 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***************************************************************************/
var Action = require('../action');
function ReactionArrowTool(editor) {
if (!(this instanceof ReactionArrowTool))
return new ReactionArrowTool(editor);
this.editor = editor;
this.editor.selection(null);
}
ReactionArrowTool.prototype.mousedown = function (event) {
var rnd = this.editor.render;
var ci = this.editor.findItem(event, ['rxnArrows']);
if (ci && ci.map === 'rxnArrows') {
this.editor.hover(null);
this.editor.selection({ rxnArrows: [ci.id] });
this.dragCtx = {
xy0: rnd.page2obj(event),
action: new Action()
};
}
};
ReactionArrowTool.prototype.mousemove = function (event) {
var rnd = this.editor.render;
if ('dragCtx' in this) {
if (this.dragCtx.action)
this.dragCtx.action.perform(rnd.ctab);
this.dragCtx.action = Action.fromMultipleMove(
rnd.ctab,
this.editor.selection() || {},
rnd.page2obj(event).sub(this.dragCtx.xy0)
);
this.editor.update(this.dragCtx.action, true);
} else {
this.editor.hover(this.editor.findItem(event, ['rxnArrows']));
}
};
ReactionArrowTool.prototype.mouseup = function (event) {
var rnd = this.editor.render;
if (this.dragCtx) {
this.editor.update(this.dragCtx.action); // TODO investigate, subsequent undo/redo fails
delete this.dragCtx;
} else if (rnd.ctab.molecule.rxnArrows.count() < 1) {
this.editor.update(Action.fromArrowAddition(rnd.ctab, rnd.page2obj(event)));
}
};
module.exports = ReactionArrowTool;

View File

@ -0,0 +1,129 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***************************************************************************/
var Set = require('../../util/set');
var scale = require('../../util/scale');
var Action = require('../action');
var draw = require('../../render/draw');
function ReactionMapTool(editor) {
if (!(this instanceof ReactionMapTool))
return new ReactionMapTool(editor);
this.editor = editor;
this.editor.selection(null);
this.rcs = this.editor.render.ctab.molecule.getComponents();
}
ReactionMapTool.prototype.mousedown = function (event) {
var rnd = this.editor.render;
var ci = this.editor.findItem(event, ['atoms']);
if (ci && ci.map === 'atoms') {
this.editor.hover(null);
this.dragCtx = {
item: ci,
xy0: rnd.page2obj(event)
};
}
};
ReactionMapTool.prototype.mousemove = function (event) {
var rnd = this.editor.render;
if ('dragCtx' in this) {
var ci = this.editor.findItem(event, ['atoms'], this.dragCtx.item);
var atoms = rnd.ctab.molecule.atoms;
if (ci && ci.map === 'atoms' && isValidMap(this.rcs, this.dragCtx.item.id, ci.id)) {
this.editor.hover(ci);
this.updateLine(atoms.get(this.dragCtx.item.id).pp, atoms.get(ci.id).pp);
} else {
this.editor.hover(null);
this.updateLine(atoms.get(this.dragCtx.item.id).pp, rnd.page2obj(event));
}
} else {
this.editor.hover(this.editor.findItem(event, ['atoms']));
}
};
ReactionMapTool.prototype.updateLine = function (p1, p2) {
if (this.line) {
this.line.remove();
this.line = null;
}
if (p1 && p2) {
var rnd = this.editor.render;
this.line = draw.selectionLine(rnd.paper,
scale.obj2scaled(p1, rnd.options).add(rnd.options.offset),
scale.obj2scaled(p2, rnd.options).add(rnd.options.offset),
rnd.options);
}
};
ReactionMapTool.prototype.mouseup = function (event) { // eslint-disable-line max-statements
if ('dragCtx' in this) {
var rnd = this.editor.render;
var ci = this.editor.findItem(event, ['atoms'], this.dragCtx.item);
if (ci && ci.map === 'atoms' && isValidMap(this.rcs, this.dragCtx.item.id, ci.id)) {
var action = new Action();
var atoms = rnd.ctab.molecule.atoms;
var atom1 = atoms.get(this.dragCtx.item.id);
var atom2 = atoms.get(ci.id);
var aam1 = atom1.aam;
var aam2 = atom2.aam;
if (!aam1 || aam1 != aam2) {
if (aam1 && aam1 != aam2 || !aam1 && aam2) { // eslint-disable-line no-mixed-operators
atoms.each(
function (aid, atom) {
if (aid != this.dragCtx.item.id && (aam1 && atom.aam == aam1 || aam2 && atom.aam == aam2)) // eslint-disable-line no-mixed-operators
action.mergeWith(Action.fromAtomsAttrs(rnd.ctab, aid, { aam: 0 }));
},
this
);
}
if (aam1) {
action.mergeWith(Action.fromAtomsAttrs(rnd.ctab, ci.id, { aam: aam1 }));
} else {
var aam = 0;
atoms.each(function (aid, atom) {
aam = Math.max(aam, atom.aam || 0);
});
action.mergeWith(Action.fromAtomsAttrs(rnd.ctab, this.dragCtx.item.id, { aam: aam + 1 }));
action.mergeWith(Action.fromAtomsAttrs(rnd.ctab, ci.id, { aam: aam + 1 }));
}
this.editor.update(action);
}
}
this.updateLine(null);
delete this.dragCtx;
}
this.editor.hover(null);
};
function isValidMap(rcs, aid1, aid2) {
var t1, t2;
for (var ri = 0; (!t1 || !t2) && ri < rcs.reactants.length; ri++) {
var ro = Set.list(rcs.reactants[ri]);
if (!t1 && ro.indexOf(aid1) >= 0) t1 = 'r';
if (!t2 && ro.indexOf(aid2) >= 0) t2 = 'r';
}
for (var pi = 0; (!t1 || !t2) && pi < rcs.products.length; pi++) {
var po = Set.list(rcs.products[pi]);
if (!t1 && po.indexOf(aid1) >= 0) t1 = 'p';
if (!t2 && po.indexOf(aid2) >= 0) t2 = 'p';
}
return t1 && t2 && t1 != t2;
}
module.exports = ReactionMapTool;

View File

@ -0,0 +1,60 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***************************************************************************/
var Action = require('../action');
function ReactionPlusTool(editor) {
if (!(this instanceof ReactionPlusTool))
return new ReactionPlusTool(editor);
this.editor = editor;
this.editor.selection(null);
}
ReactionPlusTool.prototype.mousedown = function (event) {
var rnd = this.editor.render;
var ci = this.editor.findItem(event, ['rxnPluses']);
if (ci && ci.map === 'rxnPluses') {
this.editor.hover(null);
this.editor.selection({ rxnPluses: [ci.id] });
this.dragCtx = { xy0: rnd.page2obj(event) };
}
};
ReactionPlusTool.prototype.mousemove = function (event) {
var rnd = this.editor.render;
if ('dragCtx' in this) {
if (this.dragCtx.action)
this.dragCtx.action.perform(rnd.ctab);
this.dragCtx.action = Action.fromMultipleMove(
rnd.ctab,
this.editor.selection() || {},
rnd.page2obj(event).sub(this.dragCtx.xy0)
);
this.editor.update(this.dragCtx.action, true);
} else {
this.editor.hover(this.editor.findItem(event, ['rxnPluses']));
}
};
ReactionPlusTool.prototype.mouseup = function (event) {
var rnd = this.editor.render;
if (this.dragCtx) {
this.editor.update(this.dragCtx.action); // TODO investigate, subsequent undo/redo fails
delete this.dragCtx;
} else {
this.editor.update(Action.fromPlusAddition(rnd.ctab, rnd.page2obj(event)));
}
};
module.exports = ReactionPlusTool;

View File

@ -0,0 +1,51 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***************************************************************************/
var Action = require('../action');
function ReactionUnmapTool(editor) {
if (!(this instanceof ReactionUnmapTool))
return new ReactionUnmapTool(editor);
this.editor = editor;
this.editor.selection(null);
}
ReactionUnmapTool.prototype.mousemove = function (event) {
var ci = this.editor.findItem(event, ['atoms']);
if (ci && ci.map === 'atoms')
this.editor.hover(this.editor.render.ctab.molecule.atoms.get(ci.id).aam ? ci : null);
else
this.editor.hover(null);
};
ReactionUnmapTool.prototype.mouseup = function (event) {
var ci = this.editor.findItem(event, ['atoms']);
var atoms = this.editor.render.ctab.molecule.atoms;
if (ci && ci.map === 'atoms' && atoms.get(ci.id).aam) {
var action = new Action();
var aam = atoms.get(ci.id).aam;
atoms.each(
function (aid, atom) {
if (atom.aam == aam)
action.mergeWith(Action.fromAtomsAttrs(this.editor.render.ctab, aid, { aam: 0 }));
},
this
);
this.editor.update(action);
}
this.editor.hover(null);
};
module.exports = ReactionUnmapTool;

View File

@ -0,0 +1,69 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***************************************************************************/
var Struct = require('../../chem/struct');
var Action = require('../action');
function RGroupAtomTool(editor) {
if (!(this instanceof RGroupAtomTool)) {
// TODO: map atoms with labels
editor.selection(null);
return new RGroupAtomTool(editor);
}
this.editor = editor;
}
RGroupAtomTool.prototype.mousemove = function (event) {
this.editor.hover(this.editor.findItem(event, ['atoms']));
};
RGroupAtomTool.prototype.mouseup = function (event) {
var rnd = this.editor.render;
var ci = this.editor.findItem(event, ['atoms']);
if (!ci) { // ci.type == 'Canvas'
this.editor.hover(null);
propsDialog(this.editor, null, rnd.page2obj(event));
return true;
} else if (ci.map === 'atoms') {
this.editor.hover(null);
propsDialog(this.editor, ci.id);
return true;
}
};
function propsDialog(editor, id, pos) {
var struct = editor.render.ctab.molecule;
var atom = (id || id === 0) ? struct.atoms.get(id) : null;
var rglabel = atom ? atom.rglabel : 0;
var label = atom ? atom.label : 'R#';
var res = editor.event.elementEdit.dispatch({
label: 'R#', rglabel: rglabel
});
Promise.resolve(res).then(function (elem) {
elem = Object.assign({}, Struct.Atom.attrlist, elem); // TODO review: using Atom.attrlist as a source of default property values
if (!id && id !== 0 && elem.rglabel) {
editor.update(Action.fromAtomAddition(editor.render.ctab, pos, elem));
} else if (rglabel != elem.rglabel || label !== 'R#') {
elem.aam = atom.aam; // WTF??
editor.update(Action.fromAtomsAttrs(editor.render.ctab, id, elem));
}
});
}
module.exports = RGroupAtomTool;

View File

@ -0,0 +1,71 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***************************************************************************/
var Struct = require('../../chem/struct');
var Action = require('../action');
function RGroupFragmentTool(editor) {
if (!(this instanceof RGroupFragmentTool)) {
// TODO: check if it's a fragments already
editor.selection(null);
return new RGroupFragmentTool(editor);
}
this.editor = editor;
}
RGroupFragmentTool.prototype.mousemove = function (event) {
this.editor.hover(this.editor.findItem(event, ['frags', 'rgroups']));
};
RGroupFragmentTool.prototype.mouseup = function (event) {
const editor = this.editor;
const struct = editor.render.ctab.molecule;
const ci = editor.findItem(event, ['frags', 'rgroups']);
if (ci) {
this.editor.hover(null);
const label = (ci.map === 'rgroups') ? ci.id :
Struct.RGroup.findRGroupByFragment(struct.rgroups, ci.id) || null;
const rg = Object.assign({ label: label },
ci.map === 'frags' ? null :
struct.rgroups.get(ci.id));
const res = editor.event.rgroupEdit.dispatch(rg);
Promise.resolve(res).then(newRg => {
const restruct = editor.render.ctab;
let action = null;
if (ci.map !== 'rgroups') {
const rgidOld = Struct.RGroup.findRGroupByFragment(restruct.molecule.rgroups, ci.id);
action = Action.fromRGroupFragment(restruct, newRg.label, ci.id)
.mergeWith(Action.fromUpdateIfThen(restruct, newRg.label, rgidOld));
} else {
action = Action.fromRGroupAttrs(restruct, ci.id, newRg);
}
editor.update(action);
});
return true;
}
};
module.exports = RGroupFragmentTool;

View File

@ -0,0 +1,154 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***************************************************************************/
var Vec2 = require('../../util/vec2');
var Action = require('../action');
var utils = require('./utils');
function RotateTool(editor, dir) {
if (!(this instanceof RotateTool)) {
if (!dir)
return new RotateTool(editor);
var restruct = editor.render.ctab;
var selection = editor.selection();
var singleBond = selection && selection.bonds &&
Object.keys(selection).length === 1 &&
selection.bonds.length == 1;
var action = !singleBond ? Action.fromFlip(restruct, selection, dir) :
Action.fromBondAlign(restruct, selection.bonds[0], dir);
editor.update(action);
return null;
}
this.editor = editor;
if (!editor.selection() || !editor.selection().atoms)
// otherwise, clear selection
this.editor.selection(null);
}
RotateTool.prototype.mousedown = function (event) {
var xy0 = new Vec2();
var selection = this.editor.selection();
var rnd = this.editor.render;
var struct = rnd.ctab.molecule;
if (selection && selection.atoms) {
console.assert(selection.atoms.length > 0);
var rotId = null;
var rotAll = false;
selection.atoms.forEach(function (aid) {
var atom = struct.atoms.get(aid);
xy0.add_(atom.pp); // eslint-disable-line no-underscore-dangle
if (rotAll)
return;
atom.neighbors.find(function (nei) {
var hb = struct.halfBonds.get(nei);
if (selection.atoms.indexOf(hb.end) === -1) {
if (hb.loop >= 0) {
var neiAtom = struct.atoms.get(aid);
if (!neiAtom.neighbors.find(function (neiNei) {
var neiHb = struct.halfBonds.get(neiNei);
return neiHb.loop >= 0 && selection.atoms.indexOf(neiHb.end) !== -1;
})) {
rotAll = true;
return true;
}
}
if (rotId == null) {
rotId = aid;
} else if (rotId != aid) {
rotAll = true;
return true;
}
}
return false;
});
});
if (!rotAll && rotId != null)
xy0 = struct.atoms.get(rotId).pp;
else
xy0 = xy0.scaled(1 / selection.atoms.length);
} else {
struct.atoms.each(function (id, atom) {
xy0.add_(atom.pp); // eslint-disable-line no-underscore-dangle
});
// poor man struct center (without chiral, sdata, etc)
xy0 = xy0.scaled(1 / struct.atoms.count());
}
this.dragCtx = {
xy0: xy0,
angle1: utils.calcAngle(xy0, rnd.page2obj(event))
};
return true;
};
RotateTool.prototype.mousemove = function (event) { // eslint-disable-line max-statements
if ('dragCtx' in this) {
var rnd = this.editor.render;
var dragCtx = this.dragCtx;
var pos = rnd.page2obj(event);
var angle = utils.calcAngle(dragCtx.xy0, pos) - dragCtx.angle1;
if (!event.ctrlKey)
angle = utils.fracAngle(angle);
var degrees = utils.degrees(angle);
if ('angle' in dragCtx && dragCtx.angle == degrees) return true;
if ('action' in dragCtx)
dragCtx.action.perform(rnd.ctab);
dragCtx.angle = degrees;
dragCtx.action = Action.fromRotate(rnd.ctab, this.editor.selection(), dragCtx.xy0, angle);
if (degrees > 180)
degrees -= 360;
else if (degrees <= -180)
degrees += 360;
this.editor.event.message.dispatch({ info: degrees + 'º' });
this.editor.update(dragCtx.action, true);
}
return true;
};
RotateTool.prototype.mouseup = function () {
if (this.dragCtx) {
var action = this.dragCtx.action;
delete this.dragCtx;
if (action)
this.editor.update(action);
else
this.editor.selection(null);
}
return true;
};
RotateTool.prototype.cancel = RotateTool.prototype.mouseleave =
RotateTool.prototype.mouseup;
module.exports = RotateTool;

View File

@ -0,0 +1,254 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***************************************************************************/
var Set = require('../../util/set');
var Action = require('../action');
var Struct = require('../../chem/struct');
var LassoHelper = require('./helper/lasso');
var SGroup = require('./sgroup');
var Atom = require('./atom');
function SelectTool(editor, mode) {
if (!(this instanceof SelectTool))
return new SelectTool(editor, mode);
this.editor = editor;
this.lassoHelper = new LassoHelper(mode === 'lasso' ? 0 : 1, editor, mode === 'fragment');
}
SelectTool.prototype.mousedown = function (event) { // eslint-disable-line max-statements
var rnd = this.editor.render;
var ctab = rnd.ctab;
var struct = ctab.molecule;
this.editor.hover(null); // TODO review hovering for touch devicess
var selectFragment = (this.lassoHelper.fragment || event.ctrlKey);
var ci = this.editor.findItem(
event,
selectFragment ?
['frags', 'sgroups', 'sgroupData', 'rgroups', 'rxnArrows', 'rxnPluses', 'chiralFlags'] :
['atoms', 'bonds', 'sgroups', 'sgroupData', 'rgroups', 'rxnArrows', 'rxnPluses', 'chiralFlags']
);
this.dragCtx = {
item: ci,
xy0: rnd.page2obj(event)
};
if (!ci) { // ci.type == 'Canvas'
Atom.atomLongtapEvent(this, rnd);
delete this.dragCtx.item;
if (!this.lassoHelper.fragment)
this.lassoHelper.begin(event);
} else {
this.editor.hover(null);
if (!isSelected(rnd, this.editor.selection(), ci)) {
var sel = closestToSel(ci);
if (ci.map === 'frags') {
var frag = ctab.frags.get(ci.id);
sel = {
atoms: frag.fragGetAtoms(rnd, ci.id),
bonds: frag.fragGetBonds(rnd, ci.id)
};
} else if (ci.map === 'sgroups') {
var sgroup = ctab.sgroups.get(ci.id).item;
sel = {
atoms: Struct.SGroup.getAtoms(struct, sgroup),
bonds: Struct.SGroup.getBonds(struct, sgroup)
};
} else if (ci.map === 'rgroups') {
var rgroup = ctab.rgroups.get(ci.id);
sel = {
atoms: rgroup.getAtoms(rnd),
bonds: rgroup.getBonds(rnd)
};
}
this.editor.selection(!event.shiftKey ? sel :
selMerge(sel, this.editor.selection()));
}
if (ci.map === 'atoms')
// this event has to be stopped in others events by `tool.dragCtx.stopTapping()`
Atom.atomLongtapEvent(this, rnd);
}
return true;
};
SelectTool.prototype.mousemove = function (event) {
var rnd = this.editor.render;
if (this.dragCtx && this.dragCtx.stopTapping)
this.dragCtx.stopTapping();
if (this.dragCtx && this.dragCtx.item) {
// moving selected objects
if (this.dragCtx.action) {
this.dragCtx.action.perform(rnd.ctab);
this.editor.update(this.dragCtx.action, true); // redraw the elements in unshifted position, lest the have different offset
}
this.dragCtx.action = Action.fromMultipleMove(
rnd.ctab,
this.editor.explicitSelected(),
rnd.page2obj(event).sub(this.dragCtx.xy0));
// finding & highlighting object to stick to
if (['atoms'/* , 'bonds'*/].indexOf(this.dragCtx.item.map) >= 0) {
// TODO add bond-to-bond fusing
var ci = this.editor.findItem(event, [this.dragCtx.item.map], this.dragCtx.item);
this.editor.hover((ci && ci.map == this.dragCtx.item.map) ? ci : null);
}
this.editor.update(this.dragCtx.action, true);
} else if (this.lassoHelper.running()) {
var sel = this.lassoHelper.addPoint(event);
this.editor.selection(!event.shiftKey ? sel :
selMerge(sel, this.editor.selection()));
} else {
this.editor.hover(
this.editor.findItem(event,
(this.lassoHelper.fragment || event.ctrlKey) ?
['frags', 'sgroups', 'sgroupData', 'rgroups', 'rxnArrows', 'rxnPluses', 'chiralFlags'] :
['atoms', 'bonds', 'sgroups', 'sgroupData', 'rgroups', 'rxnArrows', 'rxnPluses', 'chiralFlags']
)
);
}
return true;
};
SelectTool.prototype.mouseup = function (event) { // eslint-disable-line max-statements
if (this.dragCtx && this.dragCtx.stopTapping)
this.dragCtx.stopTapping();
if (this.dragCtx && this.dragCtx.item) {
if (['atoms'/* , 'bonds'*/].indexOf(this.dragCtx.item.map) >= 0) {
// TODO add bond-to-bond fusing
var ci = this.editor.findItem(event, [this.dragCtx.item.map], this.dragCtx.item);
if (ci && ci.map == this.dragCtx.item.map) {
var restruct = this.editor.render.ctab;
this.editor.hover(null);
this.editor.selection(null);
this.dragCtx.action = this.dragCtx.action ?
Action.fromAtomMerge(restruct, this.dragCtx.item.id, ci.id).mergeWith(this.dragCtx.action) :
Action.fromAtomMerge(restruct, this.dragCtx.item.id, ci.id);
}
}
if (this.dragCtx.action)
this.editor.update(this.dragCtx.action);
delete this.dragCtx;
} else if (this.lassoHelper.running()) { // TODO it catches more events than needed, to be re-factored
var sel = this.lassoHelper.end();
this.editor.selection(!event.shiftKey ? sel :
selMerge(sel, this.editor.selection()));
} else if (this.lassoHelper.fragment) {
this.editor.selection(null);
}
return true;
};
SelectTool.prototype.dblclick = function (event) { // eslint-disable-line max-statements
var editor = this.editor;
var rnd = this.editor.render;
var ci = this.editor.findItem(event, ['atoms', 'bonds', 'sgroups', 'sgroupData']);
if (!ci) return;
var struct = rnd.ctab.molecule;
if (ci.map === 'atoms') {
this.editor.selection(closestToSel(ci));
var atom = struct.atoms.get(ci.id);
var ra = editor.event.elementEdit.dispatch(atom);
Promise.resolve(ra).then(function (newatom) {
// TODO: deep compare to not produce dummy, e.g.
// atom.label != attrs.label || !atom.atomList.equals(attrs.atomList)
editor.update(Action.fromAtomsAttrs(rnd.ctab, ci.id, newatom));
});
} else if (ci.map === 'bonds') {
this.editor.selection(closestToSel(ci));
var bond = rnd.ctab.bonds.get(ci.id).b;
var rb = editor.event.bondEdit.dispatch(bond);
Promise.resolve(rb).then(function (newbond) {
editor.update(Action.fromBondAttrs(rnd.ctab, ci.id, newbond));
});
} else if (ci.map === 'sgroups' || ci.map === 'sgroupData') {
this.editor.selection(closestToSel(ci));
SGroup.dialog(this.editor, ci.id);
// } else if (ci.map == 'sgroupData') {
// SGroup.dialog(this.editor, ci.sgid);
}
return true;
};
SelectTool.prototype.cancel = SelectTool.prototype.mouseleave = function () {
if (this.dragCtx && this.dragCtx.stopTapping)
this.dragCtx.stopTapping();
if (this.dragCtx && this.dragCtx.action) {
var action = this.dragCtx.action;
this.editor.update(action);
}
if (this.lassoHelper.running())
this.editor.selection(this.lassoHelper.end());
delete this.dragCtx;
this.editor.hover(null);
};
function closestToSel(ci) {
var res = {};
res[ci.map] = [ci.id];
return res;
}
// TODO: deep-merge?
function selMerge(selection, add) {
if (add) {
for (var item in add) {
if (add.hasOwnProperty(item)) {
if (!selection[item]) {
selection[item] = add[item].slice();
} else {
selection[item] = uniqArray(selection[item],
add[item]);
}
}
}
}
return selection;
}
function uniqArray(dest, add) {
for (var i = 0; i < add.length; i++) {
if (dest.indexOf(add[i]) < 0)
dest.push(add[i]);
}
return dest;
}
function isSelected(render, selection, item) {
if (!selection)
return false;
var ctab = render.ctab;
if (item.map === 'frags' || item.map === 'rgroups') {
var atoms = item.map === 'frags' ?
ctab.frags.get(item.id).fragGetAtoms(render, item.id) :
ctab.rgroups.get(item.id).getAtoms(render);
return !!selection['atoms'] &&
Set.subset(Set.fromList(atoms), Set.fromList(selection['atoms']));
}
return !!selection[item.map] &&
selection[item.map].indexOf(item.id) > -1;
}
module.exports = SelectTool;

View File

@ -0,0 +1,346 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***************************************************************************/
const isEqual = require('lodash/fp/isEqual');
const uniq = require('lodash/fp/uniq');
const LassoHelper = require('./helper/lasso');
const Action = require('../action');
const Struct = require('../../chem/struct');
const Set = require('../../util/set');
const Contexts = require('../../util/constants').SgContexts;
const searchMaps = ['atoms', 'bonds', 'sgroups', 'sgroupData'];
function SGroupTool(editor, type) {
if (!(this instanceof SGroupTool)) {
var selection = editor.selection() || {};
if (!selection.atoms && !selection.bonds)
return new SGroupTool(editor, type);
var sgroups = editor.render.ctab.molecule.sgroups;
var selectedAtoms = editor.selection().atoms;
var id = sgroups.find(function (_, sgroup) {
return isEqual(sgroup.atoms, selectedAtoms);
});
propsDialog(editor, id !== undefined ? id : null, type);
editor.selection(null);
return null;
}
this.editor = editor;
this.type = type;
this.lassoHelper = new LassoHelper(1, editor);
this.editor.selection(null);
}
SGroupTool.prototype.mousedown = function (event) {
var ci = this.editor.findItem(event, searchMaps);
if (!ci) // ci.type == 'Canvas'
this.lassoHelper.begin(event);
};
SGroupTool.prototype.mousemove = function (event) {
if (this.lassoHelper.running(event))
this.editor.selection(this.lassoHelper.addPoint(event));
else
this.editor.hover(this.editor.findItem(event, searchMaps));
};
SGroupTool.prototype.mouseleave = function (event) {
if (this.lassoHelper.running(event))
this.lassoHelper.end(event);
};
SGroupTool.prototype.mouseup = function (event) {
var id = null; // id of an existing group, if we're editing one
var selection = null; // atoms to include in a newly created group
if (this.lassoHelper.running(event)) { // TODO it catches more events than needed, to be re-factored
selection = this.lassoHelper.end(event);
} else {
var ci = this.editor.findItem(event, searchMaps);
if (!ci) // ci.type == 'Canvas'
return;
this.editor.hover(null);
if (ci.map === 'atoms') {
// if we click the SGroup tool on a single atom or bond, make a group out of those
selection = { atoms: [ci.id] };
} else if (ci.map === 'bonds') {
var bond = this.editor.render.ctab.bonds.get(ci.id);
selection = { atoms: [bond.b.begin, bond.b.end] };
} else if (ci.map === 'sgroups' || ci.map === 'sgroupData') {
id = ci.id;
} else {
return;
}
}
// TODO: handle click on an existing group?
if (id !== null || (selection && selection.atoms))
propsDialog(this.editor, id, this.type);
};
function propsDialog(editor, id, defaultType) {
const restruct = editor.render.ctab;
const struct = restruct.molecule;
const selection = editor.selection() || {};
const sg = id !== null ? struct.sgroups.get(id) : null;
const type = sg ? sg.type : defaultType;
const eventName = type === 'DAT' ? 'sdataEdit' : 'sgroupEdit';
if (!selection.atoms && !selection.bonds && !sg) {
console.info('There is no selection or sgroup');
return;
}
let attrs = null;
if (sg) {
attrs = sg.getAttrs();
attrs.context = getContextBySgroup(restruct, sg.atoms);
} else {
attrs = {
context: getContextBySelection(restruct, selection)
};
}
const res = editor.event[eventName].dispatch({
type: type,
attrs: attrs
});
Promise.resolve(res).then(newSg => {
// TODO: check before signal
if (newSg.type !== 'DAT' && // when data s-group separates
checkOverlapping(struct, selection.atoms || [])) {
editor.event.message.dispatch({
error: 'Partial S-group overlapping is not allowed.'
});
} else {
if (!sg && newSg.type !== 'DAT' && (!selection.atoms || selection.atoms.length === 0))
return;
const isDataSg = sg && sg.getAttrs().context === newSg.attrs.context;
if (isDataSg) {
const action = Action.fromSeveralSgroupAddition(restruct, newSg.type, sg.atoms, newSg.attrs)
.mergeWith(Action.fromSgroupDeletion(restruct, id));
editor.update(action);
editor.selection(selection);
return;
}
const result = fromContextType(id, editor, newSg, selection);
editor.update(result.action);
editor.selection(result.selection);
}
}).catch(result => {
console.info('rejected', result);
});
}
function getContextBySgroup(restruct, sgAtoms) {
const struct = restruct.molecule;
if (sgAtoms.length === 1)
return Contexts.Atom;
if (manyComponentsSelected(restruct, sgAtoms))
return Contexts.Multifragment;
if (singleComponentSelected(restruct, sgAtoms))
return Contexts.Fragment;
const atomMap = sgAtoms.reduce((acc, aid) => {
acc[aid] = true;
return acc;
}, {});
const sgBonds = struct.bonds
.values()
.filter(bond => atomMap[bond.begin] && atomMap[bond.end]);
return anyChainedBonds(sgBonds) ? Contexts.Group : Contexts.Bond;
}
function getContextBySelection(restruct, selection) {
const struct = restruct.molecule;
if (selection.atoms && !selection.bonds)
return Contexts.Atom;
const bonds = selection.bonds.map(bondid => struct.bonds.get(bondid));
if (!anyChainedBonds(bonds))
return Contexts.Bond;
selection.atoms = selection.atoms || [];
const atomSelectMap = atomMap(selection.atoms);
const allBondsSelected = bonds.every(bond =>
atomSelectMap[bond.begin] !== undefined && atomSelectMap[bond.end] !== undefined
);
if (singleComponentSelected(restruct, selection.atoms) && allBondsSelected)
return Contexts.Fragment;
return manyComponentsSelected(restruct, selection.atoms) ? Contexts.Multifragment : Contexts.Group;
}
function fromContextType(id, editor, newSg, currSelection) {
const restruct = editor.render.ctab;
const sg = restruct.molecule.sgroups.get(id);
const sourceAtoms = (sg && sg.atoms) || currSelection.atoms || [];
const context = newSg.attrs.context;
const result = getActionForContext(context, restruct, newSg, sourceAtoms, currSelection);
result.selection = result.selection || currSelection;
if (id !== null && id !== undefined)
result.action = result.action.mergeWith(Action.fromSgroupDeletion(restruct, id));
editor.selection(result.selection);
return result;
}
function getActionForContext(context, restruct, newSg, sourceAtoms, selection) {
if (context === Contexts.Bond)
return Action.fromBondAction(restruct, newSg, sourceAtoms, selection);
const atomsFromBonds = getAtomsFromBonds(restruct.molecule, selection.bonds);
const newSourceAtoms = uniq(sourceAtoms.concat(atomsFromBonds));
if (context === Contexts.Fragment)
return Action.fromGroupAction(restruct, newSg, newSourceAtoms, restruct.atoms.keys());
if (context === Contexts.Multifragment)
return Action.fromMultiFragmentAction(restruct, newSg, newSourceAtoms);
if (context === Contexts.Group)
return Action.fromGroupAction(restruct, newSg, newSourceAtoms, newSourceAtoms);
if (context === Contexts.Atom)
return Action.fromAtomAction(restruct, newSg, newSourceAtoms);
return {
action: Action.fromSeveralSgroupAddition(restruct, newSg.type, sourceAtoms, newSg.attrs)
};
}
// tools
function atomMap(atoms) {
atoms = atoms || [];
return atoms.reduce((acc, atomid) => {
acc[atomid] = atomid;
return acc;
}, {});
}
function anyChainedBonds(bonds) {
if (bonds.length === 0)
return true;
for (let i = 0; i < bonds.length; ++i) {
const fixedBond = bonds[i];
for (let j = 0; j < bonds.length; ++j) {
if (i === j)
continue;
const bond = bonds[j];
if (fixedBond.end === bond.begin || fixedBond.end === bond.end)
return true;
}
}
return false;
}
function singleComponentSelected(restruct, atoms) {
return countOfSelectedComponents(restruct, atoms) === 1;
}
function manyComponentsSelected(restruct, atoms) {
return countOfSelectedComponents(restruct, atoms) > 1;
}
function countOfSelectedComponents(restruct, atoms) {
const atomSelectMap = atomMap(atoms);
return restruct.connectedComponents.values()
.reduce((acc, component) => {
const componentAtoms = Object.keys(component);
const count = componentAtoms
.reduce((acc, atom) => acc + (atomSelectMap[atom] === undefined), 0);
return acc + (count === 0 ? 1 : 0);
}, 0);
}
function getAtomsFromBonds(struct, bonds) {
bonds = bonds || [];
return bonds.reduce((acc, bondid) => {
const bond = struct.bonds.get(bondid);
acc = acc.concat([bond.begin, bond.end]);
return acc;
}, []);
}
function checkOverlapping(struct, atoms) {
var verified = {};
var atomsHash = {};
atoms.forEach(function (id) {
atomsHash[id] = true;
});
return 0 <= atoms.findIndex(function (id) {
var atom = struct.atoms.get(id);
var sgroups = Set.list(atom.sgs);
return 0 <= sgroups.findIndex(function (sid) {
var sg = struct.sgroups.get(sid);
if (sg.type === 'DAT' || sid in verified)
return false;
var sgAtoms = Struct.SGroup.getAtoms(struct, sg);
if (sgAtoms.length < atoms.length) {
var ind = sgAtoms.findIndex(function (aid) {
return !(aid in atomsHash);
});
if (0 <= ind) return true;
}
return 0 <= atoms.findIndex(function (aid) {
return (sgAtoms.indexOf(aid) === -1);
});
});
});
}
module.exports = Object.assign(SGroupTool, {
dialog: propsDialog
});

View File

@ -0,0 +1,258 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***************************************************************************/
var Set = require('../../util/set');
var Vec2 = require('../../util/vec2');
var Action = require('../action');
var utils = require('./utils');
function TemplateTool(editor, tmpl) {
if (!(this instanceof TemplateTool))
return new TemplateTool(editor, tmpl);
this.editor = editor;
this.editor.selection(null);
this.template = {
aid: parseInt(tmpl.aid) || 0,
bid: parseInt(tmpl.bid) || 0
};
var frag = tmpl.struct;
frag.rescale();
var xy0 = new Vec2();
frag.atoms.each(function (aid, atom) {
xy0.add_(atom.pp); // eslint-disable-line no-underscore-dangle
});
this.template.molecule = frag; // preloaded struct
this.findItems = [];
this.template.xy0 = xy0.scaled(1 / (frag.atoms.count() || 1)); // template center
var atom = frag.atoms.get(this.template.aid);
if (atom) {
this.template.angle0 = utils.calcAngle(atom.pp, this.template.xy0); // center tilt
this.findItems.push('atoms');
}
var bond = frag.bonds.get(this.template.bid);
if (bond) {
this.template.sign = getSign(frag, bond, this.template.xy0); // template location sign against attachment bond
this.findItems.push('bonds');
}
}
TemplateTool.prototype.mousedown = function (event) { // eslint-disable-line max-statements
var editor = this.editor;
var rnd = editor.render;
this.editor.hover(null);
this.dragCtx = {
xy0: rnd.page2obj(event),
item: editor.findItem(event, this.findItems)
};
var dragCtx = this.dragCtx;
var ci = dragCtx.item;
if (!ci) { // ci.type == 'Canvas'
delete dragCtx.item;
} else if (ci.map === 'bonds') {
// calculate fragment center
var molecule = rnd.ctab.molecule;
var xy0 = new Vec2();
var bond = molecule.bonds.get(ci.id);
var frid = molecule.atoms.get(bond.begin).fragment;
var frIds = molecule.getFragmentIds(frid);
var count = 0;
var loop = molecule.halfBonds.get(bond.hb1).loop;
if (loop < 0)
loop = molecule.halfBonds.get(bond.hb2).loop;
if (loop >= 0) {
var loopHbs = molecule.loops.get(loop).hbs;
loopHbs.forEach(function (hb) {
xy0.add_(molecule.atoms.get(molecule.halfBonds.get(hb).begin).pp); // eslint-disable-line no-underscore-dangle
count++;
});
} else {
Set.each(frIds, function (id) {
xy0.add_(molecule.atoms.get(id).pp); // eslint-disable-line no-underscore-dangle
count++;
});
}
dragCtx.v0 = xy0.scaled(1 / count);
var sign = getSign(molecule, bond, dragCtx.v0);
// calculate default template flip
dragCtx.sign1 = sign || 1;
dragCtx.sign2 = this.template.sign;
}
return true;
};
TemplateTool.prototype.mousemove = function (event) { // eslint-disable-line max-statements
var editor = this.editor;
var rnd = editor.render;
if (this.dragCtx) {
var dragCtx = this.dragCtx;
var ci = dragCtx.item;
var pos0;
var pos1 = rnd.page2obj(event);
var angle;
var extraBond;
dragCtx.mouse_moved = true;
var struct = rnd.ctab.molecule;
// calc initial pos and is extra bond needed
if (!ci) { // ci.type == 'Canvas'
pos0 = dragCtx.xy0;
} else if (ci.map === 'atoms') {
pos0 = struct.atoms.get(ci.id).pp;
extraBond = Vec2.dist(pos0, pos1) > 1;
} else if (ci.map === 'bonds') {
var bond = struct.bonds.get(ci.id);
var sign = getSign(struct, bond, pos1);
if (dragCtx.sign1 * this.template.sign > 0)
sign = -sign;
if (sign != dragCtx.sign2 || !dragCtx.action) {
// undo previous action
if ('action' in dragCtx)
dragCtx.action.perform(rnd.ctab);
dragCtx.sign2 = sign;
dragCtx.action = Action.fromTemplateOnBond(rnd.ctab, ci.id, this.template, dragCtx.sign1 * dragCtx.sign2 > 0);
this.editor.update(dragCtx.action, true);
}
return true;
}
angle = utils.calcAngle(pos0, pos1);
if (!event.ctrlKey)
angle = utils.fracAngle(angle);
var degrees = utils.degrees(angle);
// check if anything changed since last time
if (dragCtx.hasOwnProperty('angle') && dragCtx.angle === degrees) {
if (!dragCtx.hasOwnProperty('extra_bond') || dragCtx.extra_bond === extraBond)
return true;
}
// undo previous action
if (dragCtx.action)
dragCtx.action.perform(rnd.ctab);
// create new action
dragCtx.angle = degrees;
if (!ci) { // ci.type == 'Canvas'
dragCtx.action = Action.fromTemplateOnCanvas(
rnd.ctab,
pos0,
angle,
this.template
);
} else if (ci.map === 'atoms') {
dragCtx.action = Action.fromTemplateOnAtom(
rnd.ctab,
ci.id,
angle,
extraBond,
this.template
);
dragCtx.extra_bond = extraBond;
}
this.editor.update(dragCtx.action, true);
return true;
}
this.editor.hover(this.editor.findItem(event, this.findItems));
return true;
};
TemplateTool.prototype.mouseup = function (event) { // eslint-disable-line max-statements
var editor = this.editor;
var rnd = editor.render;
if (this.dragCtx) {
var dragCtx = this.dragCtx;
var ci = dragCtx.item;
var restruct = rnd.ctab;
var struct = restruct.molecule;
if (!dragCtx.action) {
if (!ci) { // ci.type == 'Canvas'
dragCtx.action = Action.fromTemplateOnCanvas(rnd.ctab, dragCtx.xy0, 0, this.template);
} else if (ci.map === 'atoms') {
var degree = restruct.atoms.get(ci.id).a.neighbors.length;
if (degree > 1) { // common case
dragCtx.action = Action.fromTemplateOnAtom(
restruct,
ci.id,
null,
true,
this.template
);
} else if (degree == 1) { // on chain end
var neiId = struct.halfBonds.get(struct.atoms.get(ci.id).neighbors[0]).end;
var atom = struct.atoms.get(ci.id);
var nei = struct.atoms.get(neiId);
var angle = utils.calcAngle(nei.pp, atom.pp);
dragCtx.action = Action.fromTemplateOnAtom(
restruct,
ci.id,
event.ctrlKey ? angle : utils.fracAngle(angle),
false,
this.template
);
} else { // on single atom
dragCtx.action = Action.fromTemplateOnAtom(
restruct,
ci.id,
0,
false,
this.template
);
}
} else if (ci.map === 'bonds') {
dragCtx.action = Action.fromTemplateOnBond(restruct, ci.id, this.template, dragCtx.sign1 * dragCtx.sign2 > 0);
}
this.editor.update(dragCtx.action, true);
}
var action = this.dragCtx.action;
delete this.dragCtx;
if (action && !action.isDummy())
this.editor.update(action);
}
};
TemplateTool.prototype.cancel = TemplateTool.prototype.mouseleave =
TemplateTool.prototype.mouseup;
function getSign(molecule, bond, v) {
var begin = molecule.atoms.get(bond.begin).pp;
var end = molecule.atoms.get(bond.end).pp;
var sign = Vec2.cross(Vec2.diff(begin, end), Vec2.diff(v, end));
if (sign > 0) return 1;
if (sign < 0) return -1;
return 0;
}
module.exports = TemplateTool;

View File

@ -0,0 +1,52 @@
/****************************************************************************
* Copyright 2017 EPAM Systems
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***************************************************************************/
var Vec2 = require('../../util/vec2');
var FRAC = Math.PI / 12; // '15º'
function setFracAngle(angle) {
FRAC = Math.PI / 180 * angle;
}
function calcAngle(pos0, pos1) {
var v = Vec2.diff(pos1, pos0);
return Math.atan2(v.y, v.x);
}
function fracAngle(angle) {
if (arguments.length > 1)
angle = calcAngle(arguments[0], arguments[1]);
return Math.round(angle / FRAC) * FRAC;
}
function calcNewAtomPos(pos0, pos1) {
var v = new Vec2(1, 0).rotate(fracAngle(pos0, pos1));
v.add_(pos0); // eslint-disable-line no-underscore-dangle
return v;
}
function degrees(angle) {
return Math.round(angle / Math.PI * 180);
}
module.exports = {
calcAngle: calcAngle,
fracAngle: fracAngle,
calcNewAtomPos: calcNewAtomPos,
degrees: degrees,
setFracAngle: setFracAngle
};