forked from enviPath/enviPy
Current Dev State
This commit is contained in:
1560
static/js/ketcher2/script/editor/action.js
Normal file
1560
static/js/ketcher2/script/editor/action.js
Normal file
File diff suppressed because it is too large
Load Diff
253
static/js/ketcher2/script/editor/closest.js
Normal file
253
static/js/ketcher2/script/editor/closest.js
Normal file
@ -0,0 +1,253 @@
|
||||
/****************************************************************************
|
||||
* 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');
|
||||
|
||||
const SELECTION_DISTANCE_COEFFICIENT = 0.4;
|
||||
|
||||
function findClosestAtom(restruct, pos, skip, minDist) {
|
||||
var closestAtom = null;
|
||||
var maxMinDist = SELECTION_DISTANCE_COEFFICIENT;
|
||||
var skipId = skip && skip.map === 'atoms' ? skip.id : null;
|
||||
minDist = minDist || maxMinDist;
|
||||
minDist = Math.min(minDist, maxMinDist);
|
||||
restruct.atoms.each(function (aid, atom) {
|
||||
if (aid !== skipId) {
|
||||
var dist = Vec2.dist(pos, atom.a.pp);
|
||||
if (dist < minDist) {
|
||||
closestAtom = aid;
|
||||
minDist = dist;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (closestAtom !== null) {
|
||||
return {
|
||||
id: closestAtom,
|
||||
dist: minDist
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function findClosestBond(restruct, pos, skip, minDist, scale) { // eslint-disable-line max-params
|
||||
var closestBond = null;
|
||||
var closestBondCenter = null;
|
||||
var maxMinDist = SELECTION_DISTANCE_COEFFICIENT;
|
||||
minDist = minDist || maxMinDist;
|
||||
minDist = Math.min(minDist, maxMinDist);
|
||||
var minCDist = minDist;
|
||||
restruct.bonds.each(function (bid, bond) {
|
||||
var p1 = restruct.atoms.get(bond.b.begin).a.pp,
|
||||
p2 = restruct.atoms.get(bond.b.end).a.pp;
|
||||
var mid = Vec2.lc2(p1, 0.5, p2, 0.5);
|
||||
var cdist = Vec2.dist(pos, mid);
|
||||
if (cdist < minCDist) {
|
||||
minCDist = cdist;
|
||||
closestBondCenter = bid;
|
||||
}
|
||||
});
|
||||
restruct.bonds.each(function (bid, bond) {
|
||||
var hb = restruct.molecule.halfBonds.get(bond.b.hb1);
|
||||
var d = hb.dir;
|
||||
var n = hb.norm;
|
||||
var p1 = restruct.atoms.get(bond.b.begin).a.pp,
|
||||
p2 = restruct.atoms.get(bond.b.end).a.pp;
|
||||
|
||||
var inStripe = Vec2.dot(pos.sub(p1), d) * Vec2.dot(pos.sub(p2), d) < 0;
|
||||
if (inStripe) {
|
||||
var dist = Math.abs(Vec2.dot(pos.sub(p1), n));
|
||||
if (dist < minDist) {
|
||||
closestBond = bid;
|
||||
minDist = dist;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (closestBondCenter !== null) {
|
||||
return {
|
||||
id: closestBondCenter,
|
||||
dist: minCDist
|
||||
};
|
||||
}
|
||||
if (closestBond !== null &&
|
||||
minDist > SELECTION_DISTANCE_COEFFICIENT * scale) { // hack (ported from old code)
|
||||
return {
|
||||
id: closestBond,
|
||||
dist: minDist
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function findClosestChiralFlag(restruct, pos) {
|
||||
var minDist;
|
||||
var ret = null;
|
||||
// there is only one chiral flag, but we treat it as a "map" for convenience
|
||||
restruct.chiralFlags.each(function (id, item) {
|
||||
var p = item.pp;
|
||||
if (Math.abs(pos.x - p.x) < 1.0) {
|
||||
var dist = Math.abs(pos.y - p.y);
|
||||
if (dist < 0.3 && (!ret || dist < minDist)) {
|
||||
minDist = dist;
|
||||
ret = { id: id, dist: minDist };
|
||||
}
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
function findClosestDataSGroupData(restruct, pos) {
|
||||
var minDist = null;
|
||||
var ret = null;
|
||||
restruct.sgroupData.each(function (id, item) {
|
||||
if (item.sgroup.type !== 'DAT')
|
||||
throw new Error('Data group expected');
|
||||
if (item.sgroup.data.fieldName !== "MRV_IMPLICIT_H") {
|
||||
var box = item.sgroup.dataArea;
|
||||
var inBox = box.p0.y < pos.y && box.p1.y > pos.y && box.p0.x < pos.x && box.p1.x > pos.x;
|
||||
var xDist = Math.min(Math.abs(box.p0.x - pos.x), Math.abs(box.p1.x - pos.x));
|
||||
if (inBox && (ret == null || xDist < minDist)) {
|
||||
ret = { id: id, dist: xDist };
|
||||
minDist = xDist;
|
||||
}
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
function findClosestFrag(restruct, pos, skip, minDist) {
|
||||
minDist = Math.min(minDist || SELECTION_DISTANCE_COEFFICIENT,
|
||||
SELECTION_DISTANCE_COEFFICIENT);
|
||||
var ret = null;
|
||||
var skipId = skip && skip.map === 'frags' ? skip.id : null;
|
||||
restruct.frags.each(function (fid, frag) {
|
||||
if (fid != skipId) {
|
||||
var bb = frag.calcBBox(restruct, fid); // TODO any faster way to obtain bb?
|
||||
if (bb.p0.y < pos.y && bb.p1.y > pos.y && bb.p0.x < pos.x && bb.p1.x > pos.x) {
|
||||
var xDist = Math.min(Math.abs(bb.p0.x - pos.x), Math.abs(bb.p1.x - pos.x));
|
||||
if (!ret || xDist < minDist) {
|
||||
minDist = xDist;
|
||||
ret = { id: fid, dist: minDist };
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
function findClosestRGroup(restruct, pos, skip, minDist) {
|
||||
minDist = Math.min(minDist || SELECTION_DISTANCE_COEFFICIENT,
|
||||
SELECTION_DISTANCE_COEFFICIENT);
|
||||
var ret = null;
|
||||
restruct.rgroups.each(function (rgid, rgroup) {
|
||||
if (rgid != skip && rgroup.labelBox && rgroup.labelBox.contains(pos, 0.5)) {
|
||||
var dist = Vec2.dist(rgroup.labelBox.centre(), pos);
|
||||
if (!ret || dist < minDist) {
|
||||
minDist = dist;
|
||||
ret = { id: rgid, dist: minDist };
|
||||
}
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
function findClosestRxnArrow(restruct, pos) {
|
||||
var minDist;
|
||||
var ret = null;
|
||||
restruct.rxnArrows.each(function (id, arrow) {
|
||||
var p = arrow.item.pp;
|
||||
if (Math.abs(pos.x - p.x) < 1.0) {
|
||||
var dist = Math.abs(pos.y - p.y);
|
||||
if (dist < 0.3 && (!ret || dist < minDist)) {
|
||||
minDist = dist;
|
||||
ret = { id: id, dist: minDist };
|
||||
}
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
function findClosestRxnPlus(restruct, pos) {
|
||||
var minDist;
|
||||
var ret = null;
|
||||
restruct.rxnPluses.each(function (id, plus) {
|
||||
var p = plus.item.pp;
|
||||
var dist = Math.max(Math.abs(pos.x - p.x), Math.abs(pos.y - p.y));
|
||||
if (dist < 0.3 && (!ret || dist < minDist)) {
|
||||
minDist = dist;
|
||||
ret = { id: id, dist: minDist };
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
function findClosestSGroup(restruct, pos) {
|
||||
var ret = null;
|
||||
var minDist = SELECTION_DISTANCE_COEFFICIENT;
|
||||
restruct.molecule.sgroups.each(function (sgid, sg) {
|
||||
var d = sg.bracketDir,
|
||||
n = d.rotateSC(1, 0);
|
||||
var pg = new Vec2(Vec2.dot(pos, d), Vec2.dot(pos, n));
|
||||
for (var i = 0; i < sg.areas.length; ++i) {
|
||||
var box = sg.areas[i];
|
||||
var inBox = box.p0.y < pg.y && box.p1.y > pg.y && box.p0.x < pg.x && box.p1.x > pg.x;
|
||||
var xDist = Math.min(Math.abs(box.p0.x - pg.x), Math.abs(box.p1.x - pg.x));
|
||||
if (inBox && (ret == null || xDist < minDist)) {
|
||||
ret = sgid;
|
||||
minDist = xDist;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (ret != null) {
|
||||
return {
|
||||
id: ret,
|
||||
dist: minDist
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
var findMaps = {
|
||||
atoms: findClosestAtom,
|
||||
bonds: findClosestBond,
|
||||
chiralFlags: findClosestChiralFlag,
|
||||
sgroupData: findClosestDataSGroupData,
|
||||
sgroups: findClosestSGroup,
|
||||
rxnArrows: findClosestRxnArrow,
|
||||
rxnPluses: findClosestRxnPlus,
|
||||
frags: findClosestFrag,
|
||||
rgroups: findClosestRGroup
|
||||
};
|
||||
|
||||
function findClosestItem(restruct, pos, maps, skip, scale) { // eslint-disable-line max-params
|
||||
maps = maps || Object.keys(findMaps);
|
||||
return maps.reduce(function (res, mp) {
|
||||
var minDist = res ? res.dist : null;
|
||||
var item = findMaps[mp](restruct, pos, skip, minDist, scale);
|
||||
if (item !== null && (res === null || item.dist < res.dist)) {
|
||||
return {
|
||||
map: mp,
|
||||
id: item.id,
|
||||
dist: item.dist
|
||||
};
|
||||
}
|
||||
return res;
|
||||
}, null);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
atom: findClosestAtom, // used in Actions
|
||||
item: findClosestItem
|
||||
};
|
||||
364
static/js/ketcher2/script/editor/index.js
Normal file
364
static/js/ketcher2/script/editor/index.js
Normal file
@ -0,0 +1,364 @@
|
||||
/****************************************************************************
|
||||
* 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 s = require('subscription');
|
||||
|
||||
var Set = require('../util/set');
|
||||
var Vec2 = require('../util/vec2');
|
||||
|
||||
var Struct = require('../chem/struct');
|
||||
|
||||
var Render = require('../render');
|
||||
var Action = require('./action');
|
||||
|
||||
var closest = require('./closest');
|
||||
|
||||
var toolMap = {
|
||||
rgroupatom: require('./tool/rgroupatom'),
|
||||
select: require('./tool/select'),
|
||||
sgroup: require('./tool/sgroup'),
|
||||
eraser: require('./tool/eraser'),
|
||||
atom: require('./tool/atom'),
|
||||
bond: require('./tool/bond'),
|
||||
chain: require('./tool/chain'),
|
||||
chiralFlag: require('./tool/chiral-flag'),
|
||||
template: require('./tool/template'),
|
||||
charge: require('./tool/charge'),
|
||||
rgroupfragment: require('./tool/rgroupfragment'),
|
||||
apoint: require('./tool/apoint'),
|
||||
attach: require('./tool/attach'),
|
||||
reactionarrow: require('./tool/reactionarrow'),
|
||||
reactionplus: require('./tool/reactionplus'),
|
||||
reactionmap: require('./tool/reactionmap'),
|
||||
reactionunmap: require('./tool/reactionunmap'),
|
||||
paste: require('./tool/paste'),
|
||||
rotate: require('./tool/rotate')
|
||||
};
|
||||
|
||||
const SCALE = 40; // const
|
||||
const HISTORY_SIZE = 32; // put me to options
|
||||
|
||||
var structObjects = ['atoms', 'bonds', 'frags', 'sgroups', 'sgroupData', 'rgroups', 'rxnArrows', 'rxnPluses', 'chiralFlags'];
|
||||
|
||||
function Editor(clientArea, options) {
|
||||
this.render = new Render(clientArea, Object.assign({
|
||||
scale: SCALE
|
||||
}, options));
|
||||
|
||||
this._selection = null; // eslint-disable-line
|
||||
this._tool = null; // eslint-disable-line
|
||||
this.historyStack = [];
|
||||
this.historyPtr = 0;
|
||||
|
||||
this.event = {
|
||||
message: new s.Subscription(),
|
||||
elementEdit: new s.PipelineSubscription(),
|
||||
bondEdit: new s.PipelineSubscription(),
|
||||
rgroupEdit: new s.PipelineSubscription(),
|
||||
sgroupEdit: new s.PipelineSubscription(),
|
||||
sdataEdit: new s.PipelineSubscription(),
|
||||
quickEdit: new s.PipelineSubscription(),
|
||||
attachEdit: new s.PipelineSubscription(),
|
||||
change: new s.PipelineSubscription(),
|
||||
selectionChange: new s.PipelineSubscription()
|
||||
};
|
||||
|
||||
domEventSetup(this, clientArea);
|
||||
}
|
||||
|
||||
Editor.prototype.tool = function (name, opts) {
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
if (arguments.length > 0) {
|
||||
if (this._tool && this._tool.cancel)
|
||||
this._tool.cancel();
|
||||
var tool = toolMap[name](this, opts);
|
||||
if (!tool)
|
||||
return null;
|
||||
this._tool = tool;
|
||||
}
|
||||
return this._tool;
|
||||
/* eslint-enable no-underscore-dangle */
|
||||
};
|
||||
|
||||
Editor.prototype.struct = function (value) {
|
||||
if (arguments.length > 0) {
|
||||
this.selection(null);
|
||||
this.update(Action.fromNewCanvas(this.render.ctab,
|
||||
value || new Struct()));
|
||||
recoordinate(this, getStructCenter(this.render.ctab));
|
||||
}
|
||||
return this.render.ctab.molecule;
|
||||
};
|
||||
|
||||
Editor.prototype.options = function (value) {
|
||||
if (arguments.length > 0) {
|
||||
var struct = this.render.ctab.molecule;
|
||||
var zoom = this.render.options.zoom;
|
||||
this.render.clientArea.innerHTML = '';
|
||||
this.render = new Render(this.render.clientArea, Object.assign({ scale: SCALE }, value));
|
||||
this.render.setMolecule(struct); // TODO: reuse this.struct here?
|
||||
this.render.setZoom(zoom);
|
||||
this.render.update();
|
||||
}
|
||||
return this.render.options;
|
||||
};
|
||||
|
||||
Editor.prototype.zoom = function (value) {
|
||||
if (arguments.length > 0) {
|
||||
this.render.setZoom(value);
|
||||
recoordinate(this, getStructCenter(this.render.ctab,
|
||||
this.selection()));
|
||||
this.render.update();
|
||||
}
|
||||
return this.render.options.zoom;
|
||||
};
|
||||
|
||||
Editor.prototype.selection = function (ci) {
|
||||
var restruct = this.render.ctab;
|
||||
if (arguments.length > 0) {
|
||||
this._selection = null; // eslint-disable-line
|
||||
if (ci === 'all') { // TODO: better way will be this.struct()
|
||||
ci = structObjects.reduce(function (res, key) {
|
||||
res[key] = restruct[key].ikeys();
|
||||
return res;
|
||||
}, {});
|
||||
}
|
||||
|
||||
if (ci === 'descriptors') {
|
||||
restruct = this.render.ctab;
|
||||
ci = { sgroupData: restruct['sgroupData'].ikeys() };
|
||||
}
|
||||
|
||||
if (ci) {
|
||||
var res = {};
|
||||
for (var key in ci) {
|
||||
if (ci.hasOwnProperty(key) && ci[key].length > 0) // TODO: deep merge
|
||||
res[key] = ci[key].slice();
|
||||
}
|
||||
if (Object.keys(res) !== 0)
|
||||
this._selection = res; // eslint-disable-line
|
||||
}
|
||||
|
||||
this.render.ctab.setSelection(this._selection); // eslint-disable-line
|
||||
this.event.selectionChange.dispatch(this._selection); // eslint-disable-line
|
||||
|
||||
this.render.update();
|
||||
}
|
||||
return this._selection; // eslint-disable-line
|
||||
};
|
||||
|
||||
Editor.prototype.hover = function (ci) {
|
||||
var tool = this._tool; // eslint-disable-line
|
||||
if ('ci' in tool && (!ci || tool.ci.map !== ci.map || tool.ci.id !== ci.id)) {
|
||||
this.highlight(tool.ci, false);
|
||||
delete tool.ci;
|
||||
}
|
||||
if (ci && this.highlight(ci, true))
|
||||
tool.ci = ci;
|
||||
};
|
||||
|
||||
Editor.prototype.highlight = function (ci, visible) {
|
||||
if (['atoms', 'bonds', 'rxnArrows', 'rxnPluses', 'chiralFlags', 'frags',
|
||||
'rgroups', 'sgroups', 'sgroupData'].indexOf(ci.map) === -1)
|
||||
return false;
|
||||
|
||||
var rnd = this.render;
|
||||
var item = rnd.ctab[ci.map].get(ci.id);
|
||||
if (!item)
|
||||
return true; // TODO: fix, attempt to highlight a deleted item
|
||||
if ((ci.map === 'sgroups' && item.item.type === 'DAT') || ci.map === 'sgroupData') {
|
||||
// set highlight for both the group and the data item
|
||||
var item1 = rnd.ctab.sgroups.get(ci.id);
|
||||
var item2 = rnd.ctab.sgroupData.get(ci.id);
|
||||
if (item1)
|
||||
item1.setHighlight(visible, rnd);
|
||||
if (item2)
|
||||
item2.setHighlight(visible, rnd);
|
||||
} else {
|
||||
item.setHighlight(visible, rnd);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
Editor.prototype.update = function (action, ignoreHistory) {
|
||||
if (action === true) {
|
||||
this.render.update(true); // force
|
||||
} else {
|
||||
if (!ignoreHistory && !action.isDummy()) {
|
||||
this.historyStack.splice(this.historyPtr, HISTORY_SIZE + 1, action);
|
||||
if (this.historyStack.length > HISTORY_SIZE)
|
||||
this.historyStack.shift();
|
||||
this.historyPtr = this.historyStack.length;
|
||||
this.event.change.dispatch(action); // TODO: stoppable here
|
||||
}
|
||||
this.render.update();
|
||||
}
|
||||
};
|
||||
|
||||
Editor.prototype.historySize = function () {
|
||||
return {
|
||||
undo: this.historyPtr,
|
||||
redo: this.historyStack.length - this.historyPtr
|
||||
};
|
||||
};
|
||||
|
||||
Editor.prototype.undo = function () {
|
||||
if (this.historyPtr === 0)
|
||||
throw new Error('Undo stack is empty');
|
||||
|
||||
if (this.tool() && this.tool().cancel)
|
||||
this.tool().cancel();
|
||||
this.selection(null);
|
||||
this.historyPtr--;
|
||||
var action = this.historyStack[this.historyPtr].perform(this.render.ctab);
|
||||
this.historyStack[this.historyPtr] = action;
|
||||
this.event.change.dispatch(action);
|
||||
this.render.update();
|
||||
};
|
||||
|
||||
Editor.prototype.redo = function () {
|
||||
if (this.historyPtr === this.historyStack.length)
|
||||
throw new Error('Redo stack is empty');
|
||||
|
||||
if (this.tool() && this.tool().cancel)
|
||||
this.tool().cancel();
|
||||
this.selection(null);
|
||||
var action = this.historyStack[this.historyPtr].perform(this.render.ctab);
|
||||
this.historyStack[this.historyPtr] = action;
|
||||
this.historyPtr++;
|
||||
this.event.change.dispatch(action);
|
||||
this.render.update();
|
||||
};
|
||||
|
||||
Editor.prototype.on = function (eventName, handler) {
|
||||
this.event[eventName].add(handler);
|
||||
};
|
||||
|
||||
function domEventSetup(editor, clientArea) {
|
||||
// TODO: addEventListener('resize', ...);
|
||||
['click', 'dblclick', 'mousedown', 'mousemove',
|
||||
'mouseup', 'mouseleave'].forEach(eventName => {
|
||||
const subs = editor.event[eventName] = new s.DOMSubscription();
|
||||
clientArea.addEventListener(eventName, subs.dispatch.bind(subs));
|
||||
|
||||
subs.add(event => {
|
||||
editor.lastEvent = event;
|
||||
if (editor.tool() && eventName in editor.tool())
|
||||
editor.tool()[eventName](event);
|
||||
return true;
|
||||
}, -1);
|
||||
});
|
||||
}
|
||||
|
||||
Editor.prototype.findItem = function (event, maps, skip) {
|
||||
var pos = global._ui_editor ? new Vec2(this.render.page2obj(event)) : // eslint-disable-line
|
||||
new Vec2(event.pageX, event.pageY).sub(elementOffset(this.render.clientArea));
|
||||
var options = this.render.options;
|
||||
|
||||
return closest.item(this.render.ctab, pos, maps, skip, options.scale);
|
||||
};
|
||||
|
||||
Editor.prototype.explicitSelected = function () {
|
||||
var selection = this.selection() || {};
|
||||
var res = structObjects.reduce(function (res, key) {
|
||||
res[key] = selection[key] ? selection[key].slice() : [];
|
||||
return res;
|
||||
}, {});
|
||||
|
||||
var struct = this.render.ctab.molecule;
|
||||
// "auto-select" the atoms for the bonds in selection
|
||||
if ('bonds' in res) {
|
||||
res.bonds.forEach(function (bid) {
|
||||
var bond = struct.bonds.get(bid);
|
||||
res.atoms = res.atoms || [];
|
||||
if (res.atoms.indexOf(bond.begin) < 0) res.atoms.push(bond.begin);
|
||||
if (res.atoms.indexOf(bond.end) < 0) res.atoms.push(bond.end);
|
||||
});
|
||||
}
|
||||
// "auto-select" the bonds with both atoms selected
|
||||
if ('atoms' in res && 'bonds' in res) {
|
||||
struct.bonds.each(function (bid) {
|
||||
if (!('bonds' in res) || res.bonds.indexOf(bid) < 0) {
|
||||
var bond = struct.bonds.get(bid);
|
||||
if (res.atoms.indexOf(bond.begin) >= 0 && res.atoms.indexOf(bond.end) >= 0) {
|
||||
res.bonds = res.bonds || [];
|
||||
res.bonds.push(bid);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
Editor.prototype.structSelected = function () {
|
||||
var struct = this.render.ctab.molecule;
|
||||
var selection = this.explicitSelected();
|
||||
var dst = struct.clone(Set.fromList(selection.atoms),
|
||||
Set.fromList(selection.bonds), true);
|
||||
|
||||
// Copy by its own as Struct.clone doesn't support
|
||||
// arrows/pluses id sets
|
||||
struct.rxnArrows.each(function (id, item) {
|
||||
if (selection.rxnArrows.indexOf(id) != -1)
|
||||
dst.rxnArrows.add(item.clone());
|
||||
});
|
||||
struct.rxnPluses.each(function (id, item) {
|
||||
if (selection.rxnPluses.indexOf(id) != -1)
|
||||
dst.rxnPluses.add(item.clone());
|
||||
});
|
||||
dst.isChiral = struct.isChiral;
|
||||
|
||||
// TODO: should be reaction only if arrwos? check this logic
|
||||
dst.isReaction = struct.isReaction &&
|
||||
(dst.rxnArrows.count() || dst.rxnPluses.count());
|
||||
|
||||
return dst;
|
||||
};
|
||||
|
||||
Editor.prototype.alignDescriptors = function () {
|
||||
this.selection(null);
|
||||
const action = Action.fromDescriptorsAlign(this.render.ctab);
|
||||
this.update(action);
|
||||
this.render.update(true);
|
||||
};
|
||||
|
||||
function recoordinate(editor, rp/* , vp*/) {
|
||||
// rp is a point in scaled coordinates, which will be positioned
|
||||
// vp is the point where the reference point should now be (in view coordinates)
|
||||
// or the center if not set
|
||||
console.assert(rp, 'Reference point not specified');
|
||||
editor.render.setScrollOffset(0, 0);
|
||||
}
|
||||
|
||||
function getStructCenter(restruct, selection) {
|
||||
var bb = restruct.getVBoxObj(selection || {});
|
||||
return Vec2.lc2(bb.p0, 0.5, bb.p1, 0.5);
|
||||
}
|
||||
|
||||
// TODO: find DOM shorthand
|
||||
function elementOffset(element) {
|
||||
var top = 0,
|
||||
left = 0;
|
||||
do {
|
||||
top += element.offsetTop || 0;
|
||||
left += element.offsetLeft || 0;
|
||||
element = element.offsetParent;
|
||||
} while (element);
|
||||
return new Vec2(left, top);
|
||||
}
|
||||
|
||||
module.exports = Editor;
|
||||
980
static/js/ketcher2/script/editor/op.js
Normal file
980
static/js/ketcher2/script/editor/op.js
Normal file
@ -0,0 +1,980 @@
|
||||
/****************************************************************************
|
||||
* 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 Set = require('../util/set');
|
||||
var scale = require('../util/scale');
|
||||
|
||||
var Struct = require('../chem/struct');
|
||||
var ReStruct = require('../render/restruct');
|
||||
|
||||
var DEBUG = { debug: false, logcnt: 0, logmouse: false, hl: false };
|
||||
DEBUG.logMethod = function () { };
|
||||
|
||||
function Base() {
|
||||
this.type = 'OpBase';
|
||||
|
||||
// assert here?
|
||||
this.execute = function () {
|
||||
throw new Error('Operation.execute() is not implemented');
|
||||
};
|
||||
this.invert = function () {
|
||||
throw new Error('Operation.invert() is not implemented');
|
||||
};
|
||||
|
||||
this.perform = function (restruct) {
|
||||
this.execute(restruct);
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
if (!this._inverted) {
|
||||
this._inverted = this.invert();
|
||||
this._inverted._inverted = this;
|
||||
}
|
||||
return this._inverted;
|
||||
};
|
||||
this.isDummy = function (restruct) {
|
||||
return this._isDummy ? this._isDummy(restruct) : false;
|
||||
/* eslint-enable no-underscore-dangle */
|
||||
};
|
||||
}
|
||||
|
||||
function AtomAdd(atom, pos) {
|
||||
this.data = { aid: null, atom: atom, pos: pos };
|
||||
this.execute = function (restruct) {
|
||||
var struct = restruct.molecule;
|
||||
var pp = {};
|
||||
if (this.data.atom) {
|
||||
for (var p in this.data.atom)
|
||||
if (this.data.atom.hasOwnProperty(p)) pp[p] = this.data.atom[p];
|
||||
}
|
||||
pp.label = pp.label || 'C';
|
||||
if (!(typeof this.data.aid === "number"))
|
||||
this.data.aid = struct.atoms.add(new Struct.Atom(pp));
|
||||
else
|
||||
struct.atoms.set(this.data.aid, new Struct.Atom(pp));
|
||||
|
||||
// notifyAtomAdded
|
||||
var atomData = new ReStruct.Atom(restruct.molecule.atoms.get(this.data.aid));
|
||||
atomData.component = restruct.connectedComponents.add(Set.single(this.data.aid));
|
||||
restruct.atoms.set(this.data.aid, atomData);
|
||||
restruct.markAtom(this.data.aid, 1);
|
||||
|
||||
struct.atomSetPos(this.data.aid, new Vec2(this.data.pos));
|
||||
};
|
||||
this.invert = function () {
|
||||
var ret = new AtomDelete();
|
||||
ret.data = this.data;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
AtomAdd.prototype = new Base();
|
||||
|
||||
function AtomDelete(aid) {
|
||||
this.data = { aid: aid, atom: null, pos: null };
|
||||
this.execute = function (restruct) {
|
||||
var struct = restruct.molecule;
|
||||
if (!this.data.atom) {
|
||||
this.data.atom = struct.atoms.get(this.data.aid);
|
||||
this.data.pos = this.data.atom.pp;
|
||||
}
|
||||
|
||||
// notifyAtomRemoved(this.data.aid);
|
||||
var atom = restruct.atoms.get(this.data.aid);
|
||||
var set = restruct.connectedComponents.get(atom.component);
|
||||
Set.remove(set, this.data.aid);
|
||||
if (Set.size(set) == 0)
|
||||
restruct.connectedComponents.remove(atom.component);
|
||||
restruct.clearVisel(atom.visel);
|
||||
restruct.atoms.unset(this.data.aid);
|
||||
restruct.markItemRemoved();
|
||||
|
||||
struct.atoms.remove(this.data.aid);
|
||||
};
|
||||
this.invert = function () {
|
||||
var ret = new AtomAdd();
|
||||
ret.data = this.data;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
AtomDelete.prototype = new Base();
|
||||
|
||||
function AtomAttr(aid, attribute, value) {
|
||||
this.data = { aid: aid, attribute: attribute, value: value };
|
||||
this.data2 = null;
|
||||
this.execute = function (restruct) {
|
||||
var atom = restruct.molecule.atoms.get(this.data.aid);
|
||||
if (!this.data2)
|
||||
this.data2 = { aid: this.data.aid, attribute: this.data.attribute, value: atom[this.data.attribute] };
|
||||
atom[this.data.attribute] = this.data.value;
|
||||
invalidateAtom(restruct, this.data.aid);
|
||||
};
|
||||
this._isDummy = function (restruct) { // eslint-disable-line no-underscore-dangle
|
||||
return restruct.molecule.atoms.get(this.data.aid)[this.data.attribute] == this.data.value;
|
||||
};
|
||||
this.invert = function () {
|
||||
var ret = new AtomAttr();
|
||||
ret.data = this.data2;
|
||||
ret.data2 = this.data;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
AtomAttr.prototype = new Base();
|
||||
|
||||
function AtomMove(aid, d, noinvalidate) {
|
||||
this.data = { aid: aid, d: d, noinvalidate: noinvalidate };
|
||||
this.execute = function (restruct) {
|
||||
var struct = restruct.molecule;
|
||||
var aid = this.data.aid;
|
||||
var d = this.data.d;
|
||||
struct.atoms.get(aid).pp.add_(d); // eslint-disable-line no-underscore-dangle
|
||||
restruct.atoms.get(aid).visel.translate(scale.obj2scaled(d, restruct.render.options));
|
||||
this.data.d = d.negated();
|
||||
if (!this.data.noinvalidate)
|
||||
invalidateAtom(restruct, aid, 1);
|
||||
};
|
||||
this._isDummy = function () { // eslint-disable-line no-underscore-dangle
|
||||
return this.data.d.x == 0 && this.data.d.y == 0;
|
||||
};
|
||||
this.invert = function () {
|
||||
var ret = new AtomMove();
|
||||
ret.data = this.data;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
AtomMove.prototype = new Base();
|
||||
|
||||
function BondMove(bid, d) {
|
||||
this.data = { bid: bid, d: d };
|
||||
this.execute = function (restruct) {
|
||||
restruct.bonds.get(this.data.bid).visel.translate(scale.obj2scaled(this.data.d, restruct.render.options));
|
||||
this.data.d = this.data.d.negated();
|
||||
};
|
||||
this.invert = function () {
|
||||
var ret = new BondMove();
|
||||
ret.data = this.data;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
BondMove.prototype = new Base();
|
||||
|
||||
function LoopMove(id, d) {
|
||||
this.data = { id: id, d: d };
|
||||
this.execute = function (restruct) {
|
||||
// not sure if there should be an action to move a loop in the first place
|
||||
// but we have to somehow move the aromatic ring, which is associated with the loop, rather than with any of the bonds
|
||||
if (restruct.reloops.get(this.data.id) && restruct.reloops.get(this.data.id).visel)
|
||||
restruct.reloops.get(this.data.id).visel.translate(scale.obj2scaled(this.data.d, restruct.render.options));
|
||||
this.data.d = this.data.d.negated();
|
||||
};
|
||||
this.invert = function () {
|
||||
var ret = new LoopMove();
|
||||
ret.data = this.data;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
LoopMove.prototype = new Base();
|
||||
|
||||
function SGroupAtomAdd(sgid, aid) {
|
||||
this.type = 'OpSGroupAtomAdd';
|
||||
this.data = { sgid, aid };
|
||||
|
||||
this.execute = function (restruct) {
|
||||
const struct = restruct.molecule;
|
||||
const aid = this.data.aid;
|
||||
const sgid = this.data.sgid;
|
||||
const atom = struct.atoms.get(aid);
|
||||
const sg = struct.sgroups.get(sgid);
|
||||
|
||||
if (sg.atoms.indexOf(aid) >= 0)
|
||||
throw new Error('The same atom cannot be added to an S-group more than once');
|
||||
|
||||
if (!atom)
|
||||
throw new Error('OpSGroupAtomAdd: Atom ' + aid + ' not found');
|
||||
|
||||
struct.atomAddToSGroup(sgid, aid);
|
||||
invalidateAtom(restruct, aid);
|
||||
};
|
||||
|
||||
this.invert = function () {
|
||||
const ret = new SGroupAtomRemove();
|
||||
ret.data = this.data;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
SGroupAtomAdd.prototype = new Base();
|
||||
|
||||
function SGroupAtomRemove(sgid, aid) {
|
||||
this.type = 'OpSGroupAtomRemove';
|
||||
this.data = { sgid, aid };
|
||||
|
||||
this.execute = function (restruct) {
|
||||
const aid = this.data.aid;
|
||||
const sgid = this.data.sgid;
|
||||
const struct = restruct.molecule;
|
||||
const atom = struct.atoms.get(aid);
|
||||
const sg = struct.sgroups.get(sgid);
|
||||
|
||||
Struct.SGroup.removeAtom(sg, aid);
|
||||
Set.remove(atom.sgs, sgid);
|
||||
invalidateAtom(restruct, aid);
|
||||
};
|
||||
|
||||
this.invert = function () {
|
||||
const ret = new SGroupAtomAdd();
|
||||
ret.data = this.data;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
SGroupAtomRemove.prototype = new Base();
|
||||
|
||||
function SGroupAttr(sgid, attr, value) {
|
||||
this.type = 'OpSGroupAttr';
|
||||
this.data = { sgid, attr, value };
|
||||
|
||||
this.execute = function (restruct) {
|
||||
const struct = restruct.molecule;
|
||||
const sgid = this.data.sgid;
|
||||
const sg = struct.sgroups.get(sgid);
|
||||
|
||||
if (sg.type === 'DAT' && restruct.sgroupData.has(sgid)) {
|
||||
// clean the stuff here, else it might be left behind if the sgroups is set to "attached"
|
||||
restruct.clearVisel(restruct.sgroupData.get(sgid).visel);
|
||||
restruct.sgroupData.unset(sgid);
|
||||
}
|
||||
|
||||
this.data.value = sg.setAttr(this.data.attr, this.data.value);
|
||||
};
|
||||
|
||||
this.invert = function () {
|
||||
const ret = new SGroupAttr();
|
||||
ret.data = this.data;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
SGroupAttr.prototype = new Base();
|
||||
|
||||
function SGroupCreate(sgid, type, pp) {
|
||||
this.type = 'OpSGroupCreate';
|
||||
this.data = { sgid, type, pp };
|
||||
|
||||
this.execute = function (restruct) {
|
||||
const struct = restruct.molecule;
|
||||
const sg = new Struct.SGroup(this.data.type);
|
||||
const sgid = this.data.sgid;
|
||||
|
||||
sg.id = sgid;
|
||||
struct.sgroups.set(sgid, sg);
|
||||
|
||||
if (this.data.pp)
|
||||
struct.sgroups.get(sgid).pp = new Vec2(this.data.pp);
|
||||
|
||||
restruct.sgroups.set(sgid, new ReStruct.SGroup(struct.sgroups.get(sgid)));
|
||||
this.data.sgid = sgid;
|
||||
};
|
||||
|
||||
this.invert = function () {
|
||||
const ret = new SGroupDelete();
|
||||
ret.data = this.data;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
SGroupCreate.prototype = new Base();
|
||||
|
||||
function SGroupDelete(sgid) {
|
||||
this.type = 'OpSGroupDelete';
|
||||
this.data = { sgid };
|
||||
|
||||
this.execute = function (restruct) {
|
||||
const struct = restruct.molecule;
|
||||
const sgid = this.data.sgid;
|
||||
const sg = restruct.sgroups.get(sgid);
|
||||
|
||||
this.data.type = sg.item.type;
|
||||
this.data.pp = sg.item.pp;
|
||||
|
||||
if (sg.item.type === 'DAT' && restruct.sgroupData.has(sgid)) {
|
||||
restruct.clearVisel(restruct.sgroupData.get(sgid).visel);
|
||||
restruct.sgroupData.unset(sgid);
|
||||
}
|
||||
|
||||
restruct.clearVisel(sg.visel);
|
||||
if (sg.item.atoms.length !== 0)
|
||||
throw new Error('S-Group not empty!');
|
||||
|
||||
restruct.sgroups.unset(sgid);
|
||||
struct.sgroups.remove(sgid);
|
||||
};
|
||||
|
||||
this.invert = function () {
|
||||
const ret = new SGroupCreate();
|
||||
ret.data = this.data;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
SGroupDelete.prototype = new Base();
|
||||
|
||||
function SGroupAddToHierarchy(sgid, parent, children) {
|
||||
this.type = 'OpSGroupAddToHierarchy';
|
||||
this.data = { sgid, parent, children };
|
||||
|
||||
this.execute = function (restruct) {
|
||||
const struct = restruct.molecule;
|
||||
const sgid = this.data.sgid;
|
||||
const relations = struct.sGroupForest.insert(sgid, parent, children);
|
||||
|
||||
this.data.parent = relations.parent;
|
||||
this.data.children = relations.children;
|
||||
};
|
||||
|
||||
this.invert = function () {
|
||||
const ret = new SGroupRemoveFromHierarchy();
|
||||
ret.data = this.data;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
SGroupAddToHierarchy.prototype = new Base();
|
||||
|
||||
function SGroupRemoveFromHierarchy(sgid) {
|
||||
this.type = 'OpSGroupRemoveFromHierarchy';
|
||||
this.data = { sgid };
|
||||
|
||||
this.execute = function (restruct) {
|
||||
const struct = restruct.molecule;
|
||||
const sgid = this.data.sgid;
|
||||
|
||||
this.data.parent = struct.sGroupForest.parent.get(sgid);
|
||||
this.data.children = struct.sGroupForest.children.get(sgid);
|
||||
struct.sGroupForest.remove(sgid);
|
||||
};
|
||||
|
||||
this.invert = function () {
|
||||
const ret = new SGroupAddToHierarchy();
|
||||
ret.data = this.data;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
SGroupRemoveFromHierarchy.prototype = new Base();
|
||||
|
||||
function BondAdd(begin, end, bond) {
|
||||
this.data = { bid: null, bond: bond, begin: begin, end: end };
|
||||
this.execute = function (restruct) { // eslint-disable-line max-statements
|
||||
var struct = restruct.molecule;
|
||||
if (this.data.begin == this.data.end)
|
||||
throw new Error('Distinct atoms expected');
|
||||
if (DEBUG.debug && this.molecule.checkBondExists(this.data.begin, this.data.end))
|
||||
throw new Error('Bond already exists');
|
||||
|
||||
invalidateAtom(restruct, this.data.begin, 1);
|
||||
invalidateAtom(restruct, this.data.end, 1);
|
||||
|
||||
var pp = {};
|
||||
if (this.data.bond) {
|
||||
for (var p in this.data.bond)
|
||||
if (this.data.bond.hasOwnProperty(p)) pp[p] = this.data.bond[p];
|
||||
}
|
||||
pp.type = pp.type || Struct.Bond.PATTERN.TYPE.SINGLE;
|
||||
pp.begin = this.data.begin;
|
||||
pp.end = this.data.end;
|
||||
|
||||
if (!(typeof this.data.bid === "number"))
|
||||
this.data.bid = struct.bonds.add(new Struct.Bond(pp));
|
||||
else
|
||||
struct.bonds.set(this.data.bid, new Struct.Bond(pp));
|
||||
struct.bondInitHalfBonds(this.data.bid);
|
||||
struct.atomAddNeighbor(struct.bonds.get(this.data.bid).hb1);
|
||||
struct.atomAddNeighbor(struct.bonds.get(this.data.bid).hb2);
|
||||
|
||||
// notifyBondAdded
|
||||
restruct.bonds.set(this.data.bid, new ReStruct.Bond(restruct.molecule.bonds.get(this.data.bid)));
|
||||
restruct.markBond(this.data.bid, 1);
|
||||
};
|
||||
this.invert = function () {
|
||||
var ret = new BondDelete();
|
||||
ret.data = this.data;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
BondAdd.prototype = new Base();
|
||||
|
||||
function BondDelete(bid) {
|
||||
this.data = { bid: bid, bond: null, begin: null, end: null };
|
||||
this.execute = function (restruct) { // eslint-disable-line max-statements
|
||||
var struct = restruct.molecule;
|
||||
if (!this.data.bond) {
|
||||
this.data.bond = struct.bonds.get(this.data.bid);
|
||||
this.data.begin = this.data.bond.begin;
|
||||
this.data.end = this.data.bond.end;
|
||||
}
|
||||
|
||||
invalidateBond(restruct, this.data.bid);
|
||||
|
||||
// notifyBondRemoved
|
||||
var rebond = restruct.bonds.get(this.data.bid);
|
||||
[rebond.b.hb1, rebond.b.hb2].forEach(function (hbid) {
|
||||
var hb = restruct.molecule.halfBonds.get(hbid);
|
||||
if (hb.loop >= 0)
|
||||
restruct.loopRemove(hb.loop);
|
||||
}, restruct);
|
||||
restruct.clearVisel(rebond.visel);
|
||||
restruct.bonds.unset(this.data.bid);
|
||||
restruct.markItemRemoved();
|
||||
|
||||
var bond = struct.bonds.get(this.data.bid);
|
||||
[bond.hb1, bond.hb2].forEach(function (hbid) {
|
||||
var hb = struct.halfBonds.get(hbid);
|
||||
var atom = struct.atoms.get(hb.begin);
|
||||
var pos = atom.neighbors.indexOf(hbid);
|
||||
var prev = (pos + atom.neighbors.length - 1) % atom.neighbors.length;
|
||||
var next = (pos + 1) % atom.neighbors.length;
|
||||
struct.setHbNext(atom.neighbors[prev], atom.neighbors[next]);
|
||||
atom.neighbors.splice(pos, 1);
|
||||
}, this);
|
||||
struct.halfBonds.unset(bond.hb1);
|
||||
struct.halfBonds.unset(bond.hb2);
|
||||
|
||||
struct.bonds.remove(this.data.bid);
|
||||
};
|
||||
this.invert = function () {
|
||||
var ret = new BondAdd();
|
||||
ret.data = this.data;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
BondDelete.prototype = new Base();
|
||||
|
||||
function BondAttr(bid, attribute, value) {
|
||||
this.data = { bid: bid, attribute: attribute, value: value };
|
||||
this.data2 = null;
|
||||
this.execute = function (restruct) {
|
||||
var bond = restruct.molecule.bonds.get(this.data.bid);
|
||||
if (!this.data2)
|
||||
this.data2 = { bid: this.data.bid, attribute: this.data.attribute, value: bond[this.data.attribute] };
|
||||
|
||||
bond[this.data.attribute] = this.data.value;
|
||||
|
||||
invalidateBond(restruct, this.data.bid);
|
||||
if (this.data.attribute === 'type')
|
||||
invalidateLoop(restruct, this.data.bid);
|
||||
};
|
||||
this._isDummy = function (restruct) { // eslint-disable-line no-underscore-dangle
|
||||
return restruct.molecule.bonds.get(this.data.bid)[this.data.attribute] == this.data.value;
|
||||
};
|
||||
this.invert = function () {
|
||||
var ret = new BondAttr();
|
||||
ret.data = this.data2;
|
||||
ret.data2 = this.data;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
BondAttr.prototype = new Base();
|
||||
|
||||
function FragmentAdd(frid) {
|
||||
this.frid = (typeof frid === 'undefined') ? null : frid;
|
||||
this.execute = function (restruct) {
|
||||
var struct = restruct.molecule;
|
||||
var frag = {};
|
||||
if (this.frid == null)
|
||||
this.frid = struct.frags.add(frag);
|
||||
else
|
||||
struct.frags.set(this.frid, frag);
|
||||
restruct.frags.set(this.frid, new ReStruct.Frag(frag)); // TODO add ReStruct.notifyFragmentAdded
|
||||
};
|
||||
this.invert = function () {
|
||||
return new FragmentDelete(this.frid);
|
||||
};
|
||||
}
|
||||
FragmentAdd.prototype = new Base();
|
||||
|
||||
function FragmentDelete(frid) {
|
||||
this.frid = frid;
|
||||
this.execute = function (restruct) {
|
||||
var struct = restruct.molecule;
|
||||
invalidateItem(restruct, 'frags', this.frid, 1);
|
||||
restruct.frags.unset(this.frid);
|
||||
struct.frags.remove(this.frid); // TODO add ReStruct.notifyFragmentRemoved
|
||||
};
|
||||
this.invert = function () {
|
||||
return new FragmentAdd(this.frid);
|
||||
};
|
||||
}
|
||||
FragmentDelete.prototype = new Base();
|
||||
|
||||
function RGroupAttr(rgid, attribute, value) {
|
||||
this.data = { rgid: rgid, attribute: attribute, value: value };
|
||||
this.data2 = null;
|
||||
this.execute = function (restruct) {
|
||||
var rgp = restruct.molecule.rgroups.get(this.data.rgid);
|
||||
if (!this.data2)
|
||||
this.data2 = { rgid: this.data.rgid, attribute: this.data.attribute, value: rgp[this.data.attribute] };
|
||||
|
||||
rgp[this.data.attribute] = this.data.value;
|
||||
|
||||
invalidateItem(restruct, 'rgroups', this.data.rgid);
|
||||
};
|
||||
this._isDummy = function (restruct) { // eslint-disable-line no-underscore-dangle
|
||||
return restruct.molecule.rgroups.get(this.data.rgid)[this.data.attribute] == this.data.value;
|
||||
};
|
||||
this.invert = function () {
|
||||
var ret = new RGroupAttr();
|
||||
ret.data = this.data2;
|
||||
ret.data2 = this.data;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
RGroupAttr.prototype = new Base();
|
||||
|
||||
function RGroupFragment(rgid, frid, rg) {
|
||||
this.type = 'OpAddOrDeleteRGFragment';
|
||||
this.rgid_new = rgid;
|
||||
this.rg_new = rg;
|
||||
this.rgid_old = null;
|
||||
this.rg_old = null;
|
||||
this.frid = frid;
|
||||
|
||||
this.execute = function (restruct) { // eslint-disable-line max-statements
|
||||
const struct = restruct.molecule;
|
||||
this.rgid_old = this.rgid_old || Struct.RGroup.findRGroupByFragment(struct.rgroups, this.frid);
|
||||
this.rg_old = (this.rgid_old ? struct.rgroups.get(this.rgid_old) : null);
|
||||
|
||||
if (this.rg_old) {
|
||||
this.rg_old.frags.remove(this.rg_old.frags.keyOf(this.frid));
|
||||
restruct.clearVisel(restruct.rgroups.get(this.rgid_old).visel);
|
||||
|
||||
if (this.rg_old.frags.count() === 0) {
|
||||
restruct.rgroups.unset(this.rgid_old);
|
||||
struct.rgroups.unset(this.rgid_old);
|
||||
restruct.markItemRemoved();
|
||||
} else {
|
||||
restruct.markItem('rgroups', this.rgid_old, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.rgid_new) {
|
||||
let rgNew = struct.rgroups.get(this.rgid_new);
|
||||
if (!rgNew) {
|
||||
rgNew = this.rg_new || new Struct.RGroup();
|
||||
struct.rgroups.set(this.rgid_new, rgNew);
|
||||
restruct.rgroups.set(this.rgid_new, new ReStruct.RGroup(rgNew));
|
||||
} else {
|
||||
restruct.markItem('rgroups', this.rgid_new, 1);
|
||||
}
|
||||
rgNew.frags.add(this.frid);
|
||||
}
|
||||
};
|
||||
|
||||
this.invert = function () {
|
||||
return new RGroupFragment(this.rgid_old, this.frid, this.rg_old);
|
||||
};
|
||||
}
|
||||
RGroupFragment.prototype = new Base();
|
||||
|
||||
function UpdateIfThen(rgNew, rgOld) {
|
||||
this.type = 'OpUpdateIfThenValues';
|
||||
this.rgid_new = rgNew;
|
||||
this.rgid_old = rgOld;
|
||||
this.ifThenHistory = {};
|
||||
|
||||
this.execute = function (restruct) {
|
||||
const struct = restruct.molecule;
|
||||
|
||||
struct.rgroups.keys().forEach(rgKey => {
|
||||
const rgValue = struct.rgroups.get(rgKey);
|
||||
|
||||
if (rgValue.ifthen === this.rgid_old) {
|
||||
rgValue.ifthen = this.rgid_new;
|
||||
this.ifThenHistory[rgKey] = this.rgid_old;
|
||||
struct.rgroups.set(rgKey, rgValue);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
this.invert = function () {
|
||||
return new RestoreIfThen(this.rgid_new, this.rgid_old, this.ifThenHistory);
|
||||
};
|
||||
}
|
||||
UpdateIfThen.prototype = new Base();
|
||||
|
||||
function RestoreIfThen(rgNew, rgOld, history) {
|
||||
this.type = 'OpRestoreIfThenValues';
|
||||
this.rgid_new = rgNew;
|
||||
this.rgid_old = rgOld;
|
||||
this.ifThenHistory = history || {};
|
||||
|
||||
this.execute = function (restruct) {
|
||||
const struct = restruct.molecule;
|
||||
|
||||
Object.keys(this.ifThenHistory).forEach(rgid => {
|
||||
const rgValue = struct.rgroups.get(rgid);
|
||||
rgValue.ifthen = this.ifThenHistory[rgid];
|
||||
struct.rgroups.set(rgid, rgValue);
|
||||
});
|
||||
};
|
||||
|
||||
this.invert = function () {
|
||||
return new UpdateIfThen(this.rgid_old, this.rgid_new);
|
||||
};
|
||||
}
|
||||
RestoreIfThen.prototype = new Base();
|
||||
|
||||
function RxnArrowAdd(pos) {
|
||||
this.data = { arid: null, pos: pos };
|
||||
this.execute = function (restruct) {
|
||||
var struct = restruct.molecule;
|
||||
if (!(typeof this.data.arid === 'number'))
|
||||
this.data.arid = struct.rxnArrows.add(new Struct.RxnArrow());
|
||||
else
|
||||
struct.rxnArrows.set(this.data.arid, new Struct.RxnArrow());
|
||||
|
||||
// notifyRxnArrowAdded
|
||||
restruct.rxnArrows.set(this.data.arid, new ReStruct.RxnArrow(restruct.molecule.rxnArrows.get(this.data.arid)));
|
||||
|
||||
struct.rxnArrowSetPos(this.data.arid, new Vec2(this.data.pos));
|
||||
|
||||
invalidateItem(restruct, 'rxnArrows', this.data.arid, 1);
|
||||
};
|
||||
this.invert = function () {
|
||||
var ret = new RxnArrowDelete();
|
||||
ret.data = this.data;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
RxnArrowAdd.prototype = new Base();
|
||||
|
||||
function RxnArrowDelete(arid) {
|
||||
this.data = { arid: arid, pos: null };
|
||||
this.execute = function (restruct) {
|
||||
var struct = restruct.molecule;
|
||||
if (!this.data.pos)
|
||||
this.data.pos = struct.rxnArrows.get(this.data.arid).pp;
|
||||
|
||||
// notifyRxnArrowRemoved
|
||||
restruct.markItemRemoved();
|
||||
restruct.clearVisel(restruct.rxnArrows.get(this.data.arid).visel);
|
||||
restruct.rxnArrows.unset(this.data.arid);
|
||||
|
||||
struct.rxnArrows.remove(this.data.arid);
|
||||
};
|
||||
this.invert = function () {
|
||||
var ret = new RxnArrowAdd();
|
||||
ret.data = this.data;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
RxnArrowDelete.prototype = new Base();
|
||||
|
||||
function RxnArrowMove(id, d, noinvalidate) {
|
||||
this.data = { id: id, d: d, noinvalidate: noinvalidate };
|
||||
this.execute = function (restruct) {
|
||||
var struct = restruct.molecule;
|
||||
var id = this.data.id;
|
||||
var d = this.data.d;
|
||||
struct.rxnArrows.get(id).pp.add_(d); // eslint-disable-line no-underscore-dangle
|
||||
restruct.rxnArrows.get(id).visel.translate(scale.obj2scaled(d, restruct.render.options));
|
||||
this.data.d = d.negated();
|
||||
if (!this.data.noinvalidate)
|
||||
invalidateItem(restruct, 'rxnArrows', id, 1);
|
||||
};
|
||||
this.invert = function () {
|
||||
var ret = new RxnArrowMove();
|
||||
ret.data = this.data;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
RxnArrowMove.prototype = new Base();
|
||||
|
||||
function RxnPlusAdd(pos) {
|
||||
this.data = { plid: null, pos: pos };
|
||||
this.execute = function (restruct) {
|
||||
var struct = restruct.molecule;
|
||||
if (!(typeof this.data.plid === 'number'))
|
||||
this.data.plid = struct.rxnPluses.add(new Struct.RxnPlus());
|
||||
else
|
||||
struct.rxnPluses.set(this.data.plid, new Struct.RxnPlus());
|
||||
|
||||
// notifyRxnPlusAdded
|
||||
restruct.rxnPluses.set(this.data.plid, new ReStruct.RxnPlus(restruct.molecule.rxnPluses.get(this.data.plid)));
|
||||
|
||||
struct.rxnPlusSetPos(this.data.plid, new Vec2(this.data.pos));
|
||||
|
||||
invalidateItem(restruct, 'rxnPluses', this.data.plid, 1);
|
||||
};
|
||||
this.invert = function () {
|
||||
var ret = new RxnPlusDelete();
|
||||
ret.data = this.data;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
RxnPlusAdd.prototype = new Base();
|
||||
|
||||
function RxnPlusDelete(plid) {
|
||||
this.data = { plid: plid, pos: null };
|
||||
this.execute = function (restruct) {
|
||||
var struct = restruct.molecule;
|
||||
if (!this.data.pos)
|
||||
this.data.pos = struct.rxnPluses.get(this.data.plid).pp;
|
||||
|
||||
// notifyRxnPlusRemoved
|
||||
restruct.markItemRemoved();
|
||||
restruct.clearVisel(restruct.rxnPluses.get(this.data.plid).visel);
|
||||
restruct.rxnPluses.unset(this.data.plid);
|
||||
|
||||
struct.rxnPluses.remove(this.data.plid);
|
||||
};
|
||||
this.invert = function () {
|
||||
var ret = new RxnPlusAdd();
|
||||
ret.data = this.data;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
RxnPlusDelete.prototype = new Base();
|
||||
|
||||
function RxnPlusMove(id, d, noinvalidate) {
|
||||
this.data = { id: id, d: d, noinvalidate: noinvalidate };
|
||||
this.execute = function (restruct) {
|
||||
var struct = restruct.molecule;
|
||||
var id = this.data.id;
|
||||
var d = this.data.d;
|
||||
struct.rxnPluses.get(id).pp.add_(d); // eslint-disable-line no-underscore-dangle
|
||||
restruct.rxnPluses.get(id).visel.translate(scale.obj2scaled(d, restruct.render.options));
|
||||
this.data.d = d.negated();
|
||||
if (!this.data.noinvalidate)
|
||||
invalidateItem(restruct, 'rxnPluses', id, 1);
|
||||
};
|
||||
this.invert = function () {
|
||||
var ret = new RxnPlusMove();
|
||||
ret.data = this.data;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
RxnPlusMove.prototype = new Base();
|
||||
|
||||
function SGroupDataMove(id, d) {
|
||||
this.data = { id: id, d: d };
|
||||
this.execute = function (restruct) {
|
||||
var struct = restruct.molecule;
|
||||
struct.sgroups.get(this.data.id).pp.add_(this.data.d); // eslint-disable-line no-underscore-dangle
|
||||
this.data.d = this.data.d.negated();
|
||||
invalidateItem(restruct, 'sgroupData', this.data.id, 1); // [MK] this currently does nothing since the DataSGroupData Visel only contains the highlighting/selection and SGroups are redrawn every time anyway
|
||||
};
|
||||
this.invert = function () {
|
||||
var ret = new SGroupDataMove();
|
||||
ret.data = this.data;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
SGroupDataMove.prototype = new Base();
|
||||
|
||||
function CanvasLoad(struct) {
|
||||
this.data = { struct: struct };
|
||||
this.execute = function (restruct) {
|
||||
var oldStruct = restruct.molecule;
|
||||
restruct.clearVisels(); // TODO: What is it?
|
||||
restruct.render.setMolecule(this.data.struct);
|
||||
this.data.struct = oldStruct;
|
||||
};
|
||||
|
||||
this.invert = function () {
|
||||
var ret = new CanvasLoad();
|
||||
ret.data = this.data;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
CanvasLoad.prototype = new Base();
|
||||
|
||||
function ChiralFlagAdd(pos) {
|
||||
this.data = { pos: pos };
|
||||
this.execute = function (restruct) {
|
||||
var struct = restruct.molecule;
|
||||
if (restruct.chiralFlags.count() > 0) {
|
||||
// throw new Error('Cannot add more than one Chiral flag');
|
||||
restruct.clearVisel(restruct.chiralFlags.get(0).visel);
|
||||
restruct.chiralFlags.unset(0);
|
||||
}
|
||||
|
||||
restruct.chiralFlags.set(0, new ReStruct.ChiralFlag(pos));
|
||||
struct.isChiral = true;
|
||||
invalidateItem(restruct, 'chiralFlags', 0, 1);
|
||||
};
|
||||
this.invert = function () {
|
||||
var ret = new ChiralFlagDelete();
|
||||
ret.data = this.data;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
ChiralFlagAdd.prototype = new Base();
|
||||
|
||||
function ChiralFlagDelete() {
|
||||
this.data = { pos: null };
|
||||
this.execute = function (restruct) {
|
||||
var struct = restruct.molecule;
|
||||
if (restruct.chiralFlags.count() < 1)
|
||||
throw new Error('Cannot remove chiral flag');
|
||||
restruct.clearVisel(restruct.chiralFlags.get(0).visel);
|
||||
this.data.pos = restruct.chiralFlags.get(0).pp;
|
||||
restruct.chiralFlags.unset(0);
|
||||
struct.isChiral = false;
|
||||
};
|
||||
this.invert = function () {
|
||||
var ret = new ChiralFlagAdd(this.data.pos);
|
||||
ret.data = this.data;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
ChiralFlagDelete.prototype = new Base();
|
||||
|
||||
function ChiralFlagMove(d) {
|
||||
this.data = { d: d };
|
||||
this.execute = function (restruct) {
|
||||
restruct.chiralFlags.get(0).pp.add_(this.data.d); // eslint-disable-line no-underscore-dangle
|
||||
this.data.d = this.data.d.negated();
|
||||
invalidateItem(restruct, 'chiralFlags', 0, 1);
|
||||
};
|
||||
this.invert = function () {
|
||||
var ret = new ChiralFlagMove();
|
||||
ret.data = this.data;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
ChiralFlagMove.prototype = new Base();
|
||||
|
||||
function AlignDescriptors() {
|
||||
this.type = 'OpAlignDescriptors';
|
||||
this.history = {};
|
||||
|
||||
this.execute = function (restruct) {
|
||||
const sgroups = restruct.molecule.sgroups.values().reverse();
|
||||
|
||||
let alignPoint = sgroups.reduce(
|
||||
(acc, sg) => new Vec2(
|
||||
Math.max(sg.bracketBox.p1.x, acc.x),
|
||||
Math.min(sg.bracketBox.p0.y, acc.y)
|
||||
), new Vec2(0.0, Infinity)
|
||||
)
|
||||
.add(new Vec2(0.5, -0.5));
|
||||
|
||||
sgroups.forEach(sg => {
|
||||
this.history[sg.id] = sg.pp;
|
||||
alignPoint = alignPoint.add(new Vec2(0.0, 0.5));
|
||||
sg.pp = alignPoint;
|
||||
restruct.molecule.sgroups.set(sg.id, sg);
|
||||
});
|
||||
};
|
||||
|
||||
this.invert = function () {
|
||||
return new RestoreDescriptorsPosition(this.history);
|
||||
};
|
||||
}
|
||||
AlignDescriptors.prototype = new Base();
|
||||
|
||||
function RestoreDescriptorsPosition(history) {
|
||||
this.type = 'OpRestoreDescriptorsPosition';
|
||||
this.history = history;
|
||||
|
||||
this.execute = function (restruct) {
|
||||
const sgroups = restruct.molecule.sgroups.values();
|
||||
|
||||
sgroups.forEach(sg => {
|
||||
sg.pp = this.history[sg.id];
|
||||
restruct.molecule.sgroups.set(sg.id, sg);
|
||||
});
|
||||
};
|
||||
|
||||
this.invert = function () {
|
||||
return new AlignDescriptors();
|
||||
};
|
||||
}
|
||||
RestoreDescriptorsPosition.prototype = new Base();
|
||||
|
||||
function invalidateAtom(restruct, aid, level) {
|
||||
var atom = restruct.atoms.get(aid);
|
||||
restruct.markAtom(aid, level ? 1 : 0);
|
||||
var hbs = restruct.molecule.halfBonds;
|
||||
for (var i = 0; i < atom.a.neighbors.length; ++i) {
|
||||
var hbid = atom.a.neighbors[i];
|
||||
if (hbs.has(hbid)) {
|
||||
var hb = hbs.get(hbid);
|
||||
restruct.markBond(hb.bid, 1);
|
||||
restruct.markAtom(hb.end, 0);
|
||||
if (level)
|
||||
invalidateLoop(restruct, hb.bid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function invalidateLoop(restruct, bid) {
|
||||
var bond = restruct.bonds.get(bid);
|
||||
var lid1 = restruct.molecule.halfBonds.get(bond.b.hb1).loop;
|
||||
var lid2 = restruct.molecule.halfBonds.get(bond.b.hb2).loop;
|
||||
if (lid1 >= 0)
|
||||
restruct.loopRemove(lid1);
|
||||
if (lid2 >= 0)
|
||||
restruct.loopRemove(lid2);
|
||||
}
|
||||
|
||||
function invalidateBond(restruct, bid) {
|
||||
var bond = restruct.bonds.get(bid);
|
||||
invalidateLoop(restruct, bid);
|
||||
invalidateAtom(restruct, bond.b.begin, 0);
|
||||
invalidateAtom(restruct, bond.b.end, 0);
|
||||
}
|
||||
|
||||
function invalidateItem(restruct, map, id, level) {
|
||||
if (map === 'atoms') {
|
||||
invalidateAtom(restruct, id, level);
|
||||
} else if (map === 'bonds') {
|
||||
invalidateBond(restruct, id);
|
||||
if (level > 0)
|
||||
invalidateLoop(restruct, id);
|
||||
} else {
|
||||
restruct.markItem(map, id, level);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
AtomAdd: AtomAdd,
|
||||
AtomDelete: AtomDelete,
|
||||
AtomAttr: AtomAttr,
|
||||
AtomMove: AtomMove,
|
||||
BondMove: BondMove,
|
||||
LoopMove: LoopMove,
|
||||
SGroupAtomAdd: SGroupAtomAdd,
|
||||
SGroupAtomRemove: SGroupAtomRemove,
|
||||
SGroupAttr: SGroupAttr,
|
||||
SGroupCreate: SGroupCreate,
|
||||
SGroupDelete: SGroupDelete,
|
||||
SGroupAddToHierarchy: SGroupAddToHierarchy,
|
||||
SGroupRemoveFromHierarchy: SGroupRemoveFromHierarchy,
|
||||
BondAdd: BondAdd,
|
||||
BondDelete: BondDelete,
|
||||
BondAttr: BondAttr,
|
||||
FragmentAdd: FragmentAdd,
|
||||
FragmentDelete: FragmentDelete,
|
||||
RGroupAttr: RGroupAttr,
|
||||
RGroupFragment: RGroupFragment,
|
||||
RxnArrowAdd: RxnArrowAdd,
|
||||
RxnArrowDelete: RxnArrowDelete,
|
||||
RxnArrowMove: RxnArrowMove,
|
||||
RxnPlusAdd: RxnPlusAdd,
|
||||
RxnPlusDelete: RxnPlusDelete,
|
||||
RxnPlusMove: RxnPlusMove,
|
||||
SGroupDataMove: SGroupDataMove,
|
||||
CanvasLoad: CanvasLoad,
|
||||
ChiralFlagAdd: ChiralFlagAdd,
|
||||
ChiralFlagDelete: ChiralFlagDelete,
|
||||
ChiralFlagMove: ChiralFlagMove,
|
||||
UpdateIfThen: UpdateIfThen,
|
||||
AlignDescriptors: AlignDescriptors,
|
||||
RestoreDescriptorsPosition: RestoreDescriptorsPosition
|
||||
};
|
||||
52
static/js/ketcher2/script/editor/tool/apoint.js
Normal file
52
static/js/ketcher2/script/editor/tool/apoint.js
Normal 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;
|
||||
119
static/js/ketcher2/script/editor/tool/atom.js
Normal file
119
static/js/ketcher2/script/editor/tool/atom.js
Normal 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
|
||||
});
|
||||
65
static/js/ketcher2/script/editor/tool/attach.js
Normal file
65
static/js/ketcher2/script/editor/tool/attach.js
Normal 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;
|
||||
171
static/js/ketcher2/script/editor/tool/bond.js
Normal file
171
static/js/ketcher2/script/editor/tool/bond.js
Normal 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
|
||||
});
|
||||
111
static/js/ketcher2/script/editor/tool/chain.js
Normal file
111
static/js/ketcher2/script/editor/tool/chain.js
Normal 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;
|
||||
53
static/js/ketcher2/script/editor/tool/charge.js
Normal file
53
static/js/ketcher2/script/editor/tool/charge.js
Normal 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;
|
||||
34
static/js/ketcher2/script/editor/tool/chiral-flag.js
Normal file
34
static/js/ketcher2/script/editor/tool/chiral-flag.js
Normal 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;
|
||||
86
static/js/ketcher2/script/editor/tool/eraser.js
Normal file
86
static/js/ketcher2/script/editor/tool/eraser.js
Normal 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;
|
||||
83
static/js/ketcher2/script/editor/tool/helper/lasso.js
Normal file
83
static/js/ketcher2/script/editor/tool/helper/lasso.js
Normal 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;
|
||||
160
static/js/ketcher2/script/editor/tool/helper/locate.js
Normal file
160
static/js/ketcher2/script/editor/tool/helper/locate.js
Normal 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
|
||||
};
|
||||
59
static/js/ketcher2/script/editor/tool/paste.js
Normal file
59
static/js/ketcher2/script/editor/tool/paste.js
Normal 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;
|
||||
65
static/js/ketcher2/script/editor/tool/reactionarrow.js
Normal file
65
static/js/ketcher2/script/editor/tool/reactionarrow.js
Normal 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;
|
||||
129
static/js/ketcher2/script/editor/tool/reactionmap.js
Normal file
129
static/js/ketcher2/script/editor/tool/reactionmap.js
Normal 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;
|
||||
60
static/js/ketcher2/script/editor/tool/reactionplus.js
Normal file
60
static/js/ketcher2/script/editor/tool/reactionplus.js
Normal 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;
|
||||
51
static/js/ketcher2/script/editor/tool/reactionunmap.js
Normal file
51
static/js/ketcher2/script/editor/tool/reactionunmap.js
Normal 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;
|
||||
69
static/js/ketcher2/script/editor/tool/rgroupatom.js
Normal file
69
static/js/ketcher2/script/editor/tool/rgroupatom.js
Normal 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;
|
||||
71
static/js/ketcher2/script/editor/tool/rgroupfragment.js
Normal file
71
static/js/ketcher2/script/editor/tool/rgroupfragment.js
Normal 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;
|
||||
154
static/js/ketcher2/script/editor/tool/rotate.js
Normal file
154
static/js/ketcher2/script/editor/tool/rotate.js
Normal 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;
|
||||
254
static/js/ketcher2/script/editor/tool/select.js
Normal file
254
static/js/ketcher2/script/editor/tool/select.js
Normal 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;
|
||||
346
static/js/ketcher2/script/editor/tool/sgroup.js
Normal file
346
static/js/ketcher2/script/editor/tool/sgroup.js
Normal 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
|
||||
});
|
||||
258
static/js/ketcher2/script/editor/tool/template.js
Normal file
258
static/js/ketcher2/script/editor/tool/template.js
Normal 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;
|
||||
52
static/js/ketcher2/script/editor/tool/utils.js
Normal file
52
static/js/ketcher2/script/editor/tool/utils.js
Normal 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
|
||||
};
|
||||
Reference in New Issue
Block a user