Files
enviPy-bayer/static/js/ketcher2/script/editor/tool/select.js
2025-06-23 20:13:54 +02:00

255 lines
8.2 KiB
JavaScript

/****************************************************************************
* 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;