com.actelion.research.gui.editor.GenericEditorArea Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of openchemlib Show documentation
Show all versions of openchemlib Show documentation
Open Source Chemistry Library
/*
* Copyright (c) 1997 - 2016
* Actelion Pharmaceuticals Ltd.
* Gewerbestrasse 16
* CH-4123 Allschwil, Switzerland
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of the the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
package com.actelion.research.gui.editor;
import com.actelion.research.chem.*;
import com.actelion.research.chem.coords.CoordinateInventor;
import com.actelion.research.chem.io.RDFileParser;
import com.actelion.research.chem.io.RXNFileParser;
import com.actelion.research.chem.name.StructureNameResolver;
import com.actelion.research.chem.reaction.*;
import com.actelion.research.gui.FileHelper;
import com.actelion.research.gui.LookAndFeelHelper;
import com.actelion.research.gui.clipboard.IClipboardHandler;
import com.actelion.research.gui.generic.*;
import com.actelion.research.gui.hidpi.HiDPIHelper;
import com.actelion.research.gui.swing.SwingCursorHelper;
import com.actelion.research.util.ColorHelper;
import java.awt.*;
import java.awt.geom.Point2D;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.TreeMap;
public class GenericEditorArea implements GenericEventListener {
public static final int MODE_MULTIPLE_FRAGMENTS = 1;
public static final int MODE_MARKUSH_STRUCTURE = 2;
public static final int MODE_REACTION = 4;
public static final int MODE_DRAWING_OBJECTS = 8;
public static final String TEMPLATE_TYPE_KEY = "TEMPLATE";
public static final String TEMPLATE_TYPE_REACTION_QUERIES = "REACTION_QUERIES";
public static final String TEMPLATE_SECTION_KEY = "SECTION";
public static final int DEFAULT_ALLOWED_PSEUDO_ATOMS
= Molecule.cPseudoAtomsHydrogenIsotops
| Molecule.cPseudoAtomsAminoAcids
| Molecule.cPseudoAtomR
| Molecule.cPseudoAtomsRGroups
| Molecule.cPseudoAtomAttachmentPoint;
private static final int MAX_CONNATOMS = 8;
private static final int MIN_BOND_LENGTH_SQUARE = 100;
private static final int KEY_IS_ATOM_LABEL = 1;
private static final int KEY_IS_SUBSTITUENT = 2;
private static final int KEY_IS_VALID_START = 3;
private static final int KEY_IS_INVALID = 4;
private static final int RGB_BLUE = 0xFF0000FF;
private static final int RGB_RED = 0xFFFF0000;
private static final int RGB_DARK_RED = 0xFF800000;
private static final int RGB_BLACK = 0xFF000000;
private static final int RGB_GRAY = 0xFF808080;
private static final String ITEM_COPY_STRUCTURE = "Copy Structure";
private static final String ITEM_COPY_REACTION = "Copy Reaction";
private static final String ITEM_PASTE_STRUCTURE = "Paste Structure";
private static final String ITEM_PASTE_REACTION = "Paste Reaction";
private static final String ITEM_USE_TEMPLATE = "Use Reaction Template...";
private static final String ITEM_PASTE_WITH_NAME = ITEM_PASTE_STRUCTURE + " or Name";
private static final String ITEM_LOAD_REACTION = "Open Reaction File...";
private static final String ITEM_ADD_AUTO_MAPPING = "Auto-Map Reaction";
private static final String ITEM_REMOVE_MAPPING = "Remove Manual Atom Mapping";
private static final String ITEM_FLIP_HORIZONTALLY = "Flip Horizontally";
private static final String ITEM_FLIP_VERTICALLY = "Flip Vertically";
private static final String ITEM_FLIP_ROTATE180 = "Rotate 180°";
private static final String ITEM_SHOW_HELP = "Help Me";
// development items
private static final String ITEM_SHOW_ATOM_BOND_NUMBERS = "Show Atom & Bond Numbers";
private static final String ITEM_SHOW_SYMMETRY = "Show Symmetry";
private static final String ITEM_SHOW_NORMAL = "Show Normal";
private static final long WARNING_MILLIS = 1200;
private static final float FRAGMENT_MAX_CLICK_DISTANCE = 24.0f;
private static final float FRAGMENT_GROUPING_DISTANCE = 1.4f; // in average bond lengths
private static final float FRAGMENT_CLEANUP_DISTANCE = 1.5f; // in average bond lengths
private static final float DEFAULT_ARROW_LENGTH = 0.08f; // relative to panel width
protected static final int UPDATE_NONE = 0;
protected static final int UPDATE_REDRAW = 1;
// redraw molecules and drawing objects with their current coordinates
protected static final int UPDATE_CHECK_VIEW = 2;
// redraw with on-the-fly coordinate transformation only if current coords do not fit within view area
// (new coords are generated for one draw() only; the original coords are not changed)
protected static final int UPDATE_CHECK_COORDS = 3;
// redraw with in-place coordinate transformation only if current coords do not fit within view area
// (the original atom and object coords are replaced by the new ones)
protected static final int UPDATE_SCALE_COORDS = 4;
// redraw with in-place coordinate transformation; molecules and objects are scaled to fill
// the view unless the maximum average bond length reaches the optimum.
protected static final int UPDATE_SCALE_COORDS_USE_FRAGMENTS = 5;
// as UPDATE_SCALE_COORDS but uses fragments from mFragment rather than creating a
// fresh mFragment list from mMol. Used for setting a reaction or fragment list from outside.
protected static final int UPDATE_INVENT_COORDS = 6;
// redraw with in-place coordinate transformation; first all molecules' coordinates
// are generated from scratch, then molecules and objects are scaled to fill
// the view unless the maximum average bond length reaches the optimum.
private static final int DEFAULT_SELECTION_BACKGROUND = 0xFF80A4C0;
private static final int cRequestNone = 0;
private static final int cRequestNewBond = 1;
private static final int cRequestNewChain = 2;
private static final int cRequestMoveSingle = 3;
private static final int cRequestMoveSelected = 4;
private static final int cRequestLassoSelect = 5;
private static final int cRequestSelectRect = 6;
private static final int cRequestZoomAndRotate = 7;
private static final int cRequestMapAtoms = 8;
private static final int cRequestCopySelected = 9;
private static final int cRequestMoveObject = 10;
private static final int cRequestCopyObject = 11;
private static IReactionMapper sMapper;
private static String[][] sReactionQueryTemplates;
private int mMode, mChainAtoms, mCurrentTool, mCustomAtomicNo, mCustomAtomMass, mCustomAtomValence, mCustomAtomRadical,
mCurrentHiliteAtom, mCurrentHiliteBond, mPendingRequest, mEventsScheduled, mFirstAtomKey,
mCurrentCursor, mReactantCount, mUpdateMode, mDisplayMode, mAtom1, mAtom2, mMaxAVBL, mAllowedPseudoAtoms;
private int[] mChainAtom, mFragmentNo, mHiliteBondSet;
private double mX1, mY1, mX2, mY2, mWidth, mHeight, mUIScaling, mTextSizeFactor;
private double[] mX, mY, mChainAtomX, mChainAtomY;
private boolean mAltIsDown, mShiftIsDown, mMouseIsDown, mIsAddingToSelection, mAtomColorSupported, mAllowQueryFeatures,
mAllowFragmentChangeOnPasteOrDrop;
private boolean[] mIsSelectedAtom, mIsSelectedObject;
private String mCustomAtomLabel,mWarningMessage,mAtomKeyStrokeSuggestion;
private String[] mAtomText;
private ExtendedDepictor mDepictor;
private StereoMolecule mMol; // molecule being modified directly by the drawing editor
private Molecule mUndoMol; // molecule in undo buffer
private StereoMolecule[] mFragment; // in case of MODE_MULTIPLE_FRAGMENTS contains valid stereo fragments
// for internal and external read-only-access (reconstructed at any change)
private DrawingObjectList mDrawingObjectList, mUndoDrawingObjectList;
private AbstractDrawingObject mCurrentHiliteObject;
private GenericPolygon mLassoRegion;
private ArrayList mListeners;
private IClipboardHandler mClipboardHandler;
private StringBuilder mAtomKeyStrokeBuffer;
private GenericUIHelper mUIHelper;
private GenericCanvas mCanvas;
/**
* @param mol an empty or valid stereo molecule
* @param mode 0 or a meaningful combination of the mode flags, e.g. MODE_REACTION | MODE_DRAWING_OBJECTS
*/
public GenericEditorArea(StereoMolecule mol, int mode, GenericUIHelper helper, GenericCanvas canvas) {
mMol = mol;
mMode = mode;
mUIHelper = helper;
mCanvas = canvas;
mListeners = new ArrayList<>();
mCurrentTool = GenericEditorToolbar.cToolStdBond;
mCurrentHiliteAtom = -1;
mCurrentHiliteBond = -1;
mCurrentHiliteObject = null;
mAtom1 = -1;
mCustomAtomicNo = 6;
mCustomAtomMass = 0;
mCustomAtomValence = -1;
mCustomAtomRadical = 0;
mCustomAtomLabel = null;
mAllowQueryFeatures = true;
mAllowFragmentChangeOnPasteOrDrop = false;
mPendingRequest = cRequestNone;
mCurrentCursor = SwingCursorHelper.cPointerCursor;
mAtomKeyStrokeBuffer = new StringBuilder();
mAllowedPseudoAtoms = DEFAULT_ALLOWED_PSEUDO_ATOMS;
mTextSizeFactor = 1.0;
mUIScaling = HiDPIHelper.getUIScaleFactor();
mMaxAVBL = HiDPIHelper.scale(AbstractDepictor.cOptAvBondLen);
if ((mMode & (MODE_REACTION | MODE_MARKUSH_STRUCTURE)) != 0) {
mMode |= (MODE_MULTIPLE_FRAGMENTS);
}
if ((mMode & (MODE_DRAWING_OBJECTS | MODE_REACTION)) != 0) {
mDrawingObjectList = new DrawingObjectList();
}
mUpdateMode = UPDATE_SCALE_COORDS;
/* dumpBytesOfGif("/chainCursor.gif");
dumpBytesOfGif("/deleteCursor.gif");
dumpBytesOfGif("/handCursor.gif");
dumpBytesOfGif("/handPlusCursor.gif");
dumpBytesOfGif("/fistCursor.gif");
dumpBytesOfGif("/lassoCursor.gif");
dumpBytesOfGif("/lassoPlusCursor.gif");
dumpBytesOfGif("/rectCursor.gif");
dumpBytesOfGif("/rectPlusCursor.gif"); */
}
/**
* @return null or String[n][2] with pairs of reaction name and rxn-idcode
*/
public static String[][] getReactionQueryTemplates() {
return sReactionQueryTemplates;
}
/**
* @param templates null or String[n][2] with pairs of reaction name and rxn-idcode
*/
public static void setReactionQueryTemplates(String[][] templates) {
sReactionQueryTemplates = templates;
}
public GenericUIHelper getUIHelper() {
return mUIHelper;
}
/**
* Call this after initialization to get clipboard support
*
* @param h
*/
public void setClipboardHandler(IClipboardHandler h) {
mClipboardHandler = h;
}
private void update(int mode) {
mUpdateMode = Math.max(mUpdateMode, mode);
mCanvas.repaint();
}
public static void setReactionMapper(IReactionMapper mapper) {
sMapper = mapper;
}
public void paintContent(GenericDrawContext context) {
if (mWidth != mCanvas.getCanvasWidth() || mHeight != mCanvas.getCanvasHeight()) {
mWidth = mCanvas.getCanvasWidth();
mHeight = mCanvas.getCanvasHeight();
if (mUpdateMode");
}
if (validity == KEY_IS_SUBSTITUENT) {
context.setRGB(RGB_GRAY);
context.drawString(x+context.getBounds(s).getWidth(), y, mAtomKeyStrokeSuggestion.substring(s.length()));
}
}
context.setRGB(foreground);
switch (mPendingRequest) {
case cRequestNewBond:
int x1, y1, x2, y2, xdiff, ydiff;
x1 = (int)mX1;
y1 = (int)mY1;
if (mCurrentHiliteAtom == -1 || mCurrentHiliteAtom == mAtom1) {
x2 = (int)mX2;
y2 = (int)mY2;
} else {
x2 = (int)mMol.getAtomX(mCurrentHiliteAtom);
y2 = (int)mMol.getAtomY(mCurrentHiliteAtom);
}
switch (mCurrentTool) {
case GenericEditorToolbar.cToolStdBond:
context.drawLine(x1, y1, x2, y2);
break;
case GenericEditorToolbar.cToolUpBond:
xdiff = (y1 - y2) / 9;
ydiff = (x2 - x1) / 9;
GenericPolygon p = new GenericPolygon(3);
p.addPoint( x1, y1);
p.addPoint(x2 + xdiff, y2 + ydiff);
p.addPoint(x2 - xdiff, y2 - ydiff);
context.fillPolygon(p);
break;
case GenericEditorToolbar.cToolDownBond:
int xx1, xx2, yy1, yy2;
xdiff = x2 - x1;
ydiff = y2 - y1;
for (int i = 2; i<17; i += 2) {
xx1 = x1 + i * xdiff / 17 - i * ydiff / 128;
yy1 = y1 + i * ydiff / 17 + i * xdiff / 128;
xx2 = x1 + i * xdiff / 17 + i * ydiff / 128;
yy2 = y1 + i * ydiff / 17 - i * xdiff / 128;
context.drawLine(xx1, yy1, xx2, yy2);
}
break;
}
break;
case cRequestNewChain:
if (mChainAtoms>0) {
context.drawLine((int)mX1, (int)mY1, (int)mChainAtomX[0], (int)mChainAtomY[0]);
}
if (mChainAtoms>1) {
for (int i = 1; i l) {
mListeners.add(l);
}
protected void buttonPressed(int button) {
switch (button) {
case GenericEditorToolbar.cButtonClear:
clearAll();
return;
case GenericEditorToolbar.cButtonCleanStructure:
storeState();
updateAndFireEvent(UPDATE_INVENT_COORDS);
return;
case GenericEditorToolbar.cButtonUndo:
restoreState();
updateAndFireEvent(UPDATE_CHECK_VIEW);
return;
}
}
public void clearAll() {
if (mDrawingObjectList != null) {
mDrawingObjectList.clear();
update(UPDATE_REDRAW);
}
storeState();
boolean isFragment = mMol.isFragment();
mMol.clear();
mMol.setFragment(isFragment);
if (mUndoMol.getAllAtoms() != 0)
updateAndFireEvent(UPDATE_REDRAW);
else
update(UPDATE_REDRAW);
}
public void toolChanged(int newTool) {
if (mCurrentTool != newTool) {
if (mCurrentTool == GenericEditorToolbar.cToolMapper
|| newTool == GenericEditorToolbar.cToolMapper) {
update(UPDATE_REDRAW);
}
mCurrentTool = newTool;
}
}
public void setCustomAtom(int atomicNo, int mass, int valence, int radical, String customLabel) {
mCustomAtomicNo = atomicNo;
mCustomAtomMass = mass;
mCustomAtomValence = valence;
mCustomAtomRadical = radical;
mCustomAtomLabel = customLabel;
}
@Override
public void eventHappened(GenericEvent e) {
if (e instanceof GenericActionEvent)
eventHappened((GenericActionEvent)e);
else if (e instanceof GenericKeyEvent)
eventHappened((GenericKeyEvent)e);
else if (e instanceof GenericMouseEvent)
eventHappened((GenericMouseEvent)e);
}
private void eventHappened(GenericActionEvent e) {
String command = e.getMessage();
if (command.equals(ITEM_COPY_STRUCTURE) || command.equals(ITEM_COPY_REACTION)) {
copy();
} else if (command.equals(ITEM_PASTE_REACTION)) {
pasteReaction();
} else if (command.startsWith(ITEM_USE_TEMPLATE)) {
useTemplate(command.substring(ITEM_USE_TEMPLATE.length()));
} else if (command.startsWith(ITEM_PASTE_STRUCTURE)) {
pasteMolecule();
} else if (command.equals(ITEM_LOAD_REACTION)) {
openReaction();
} else if (command.equals(ITEM_ADD_AUTO_MAPPING)) {
autoMapReaction();
updateAndFireEvent(Math.max(mUpdateMode, UPDATE_REDRAW));
} else if (command.equals(ITEM_REMOVE_MAPPING)) {
removeManualMapping();
} else if (command.equals(ITEM_FLIP_HORIZONTALLY)) {
flip(true);
} else if (command.equals(ITEM_FLIP_VERTICALLY)) {
flip(false);
} else if (command.equals(ITEM_FLIP_ROTATE180)) {
rotate180();
} else if (command.equals(ITEM_SHOW_HELP)) {
showHelpDialog();
} else if (command.startsWith("atomColor")) {
int index = command.indexOf(':');
int atom = Integer.parseInt(command.substring(9, index));
int color = Integer.parseInt(command.substring(index + 1));
if (mMol.isSelectedAtom(atom)) {
for (int i = 0; i=originalAtoms);
updateAndFireEvent(UPDATE_CHECK_COORDS);
}
return true;
}
private void useTemplate(String rxncode) {
storeState();
setReaction(ReactionEncoder.decode(rxncode, true));
}
private boolean atomCoordinatesCollide(StereoMolecule mol, double tolerance) {
int count = 0;
tolerance *= tolerance;
for (int i=0; i {
try {
Thread.sleep(WARNING_MILLIS);
} catch (InterruptedException ie) {
}
mWarningMessage = null;
mCanvas.repaint();
}).start();
}
private void eventHappened(GenericMouseEvent e) {
if (e.getWhat() == GenericMouseEvent.MOUSE_PRESSED) {
if (mCurrentHiliteAtom != -1 && mAtomKeyStrokeBuffer.length() != 0)
expandAtomKeyStrokes(mAtomKeyStrokeBuffer.toString());
mAtomKeyStrokeBuffer.setLength(0);
if (e.isPopupTrigger()) {
handlePopupTrigger(e.getX(), e.getY());
return;
}
if (e.getButton() == 1) {
if (e.getClickCount() == 2) {
return;
}
mMouseIsDown = true;
updateCursor();
mousePressedButton1(e);
}
}
if (e.getWhat() == GenericMouseEvent.MOUSE_RELEASED) {
if (e.isPopupTrigger()) {
handlePopupTrigger(e.getX(), e.getY());
return;
}
if (e.getButton() == 1) {
if (e.getClickCount() == 2) {
handleDoubleClick(e.getX(), e.getY());
return;
}
mMouseIsDown = false;
updateCursor();
mouseReleasedButton1();
}
}
if (e.getWhat() == GenericMouseEvent.MOUSE_ENTERED) {
mUIHelper.grabFocus();
updateCursor();
}
if (e.getWhat() == GenericMouseEvent.MOUSE_MOVED) {
mMouseIsDown = false;
int x = e.getX();
int y = e.getY();
if (trackHiliting(x, y, false)) {
mCanvas.repaint();
}
updateCursor();
}
if (e.getWhat() == GenericMouseEvent.MOUSE_DRAGGED) {
mMouseIsDown = true;
mX2 = e.getX();
mY2 = e.getY();
boolean repaintNeeded = trackHiliting(mX2, mY2, true);
switch (mPendingRequest) {
case cRequestNewChain:
double lastX, lastY;
if (mChainAtoms>0) {
lastX = mChainAtomX[mChainAtoms - 1];
lastY = mChainAtomY[mChainAtoms - 1];
} else {
lastX = 0;
lastY = 0;
}
double avbl = getScaledAVBL();
double s0 = (int)avbl;
double s1 = (int)(0.866 * avbl);
double s2 = (int)(0.5 * avbl);
double dx = mX2 - mX1;
double dy = mY2 - mY1;
if (Math.abs(dy)>Math.abs(dx)) {
mChainAtoms = (int)(2 * Math.abs(dy) / (s0 + s2));
if (Math.abs(dy) % (s0 + s2)>s0) {
mChainAtoms++;
}
mChainAtomX = new double[mChainAtoms];
mChainAtomY = new double[mChainAtoms];
if (mX20) {
mChainAtom = new int[mChainAtoms];
for (int i = 0; i='4' && ch<='7') {
if (mMol.addRingToBond(mCurrentHiliteBond, ch - '0', false, getScaledAVBL()))
updateAndFireEvent(UPDATE_CHECK_COORDS);
} else if (ch == 'a' || ch == 'b') { // ChemDraw uses 'a', we use 'b' since a long time
if (mMol.addRingToBond(mCurrentHiliteBond, 6, true, getScaledAVBL()))
updateAndFireEvent(UPDATE_CHECK_COORDS);
} else {
boolean bondChanged =
(ch == '0') ? changeHighlightedBond(Molecule.cBondTypeMetalLigand)
: (ch == '1') ? changeHighlightedBond(Molecule.cBondTypeSingle)
: (ch == '2') ? changeHighlightedBond(Molecule.cBondTypeDouble)
: (ch == '3') ? changeHighlightedBond(Molecule.cBondTypeTriple)
: (ch == 'u') ? changeHighlightedBond(Molecule.cBondTypeUp)
: (ch == 'd') ? changeHighlightedBond(Molecule.cBondTypeDown)
: (ch == 'c') ? changeHighlightedBond(Molecule.cBondTypeCross)
: (ch == 'm') ? changeHighlightedBond(Molecule.cBondTypeMetalLigand)
: false;
if (bondChanged)
updateAndFireEvent(UPDATE_REDRAW);
}
} else if (mCurrentHiliteAtom != -1) {
int ch = e.getKey();
boolean isFirst = (mAtomKeyStrokeBuffer.length() == 0);
if (isFirst)
mFirstAtomKey = ch;
else {
if (mFirstAtomKey == 'l') { // if we don't want first 'l' to be a chlorine
mAtomKeyStrokeBuffer.setLength(0);
mAtomKeyStrokeBuffer.append('L');
}
mFirstAtomKey = -1;
}
if (isFirst && ch == 'l') { // if no chars are following, we interpret 'l' as chlorine analog to ChemDraw
mAtomKeyStrokeBuffer.append("Cl");
update(UPDATE_REDRAW);
} else if (isFirst && (ch == '+' || ch == '-')) {
storeState();
if (mMol.changeAtomCharge(mCurrentHiliteAtom, ch == '+'))
updateAndFireEvent(UPDATE_CHECK_COORDS);
} else if (isFirst && ch == '.') {
storeState();
int newRadical = (mMol.getAtomRadical(mCurrentHiliteAtom) == Molecule.cAtomRadicalStateD) ?
0 : Molecule.cAtomRadicalStateD;
mMol.setAtomRadical(mCurrentHiliteAtom, newRadical);
updateAndFireEvent(UPDATE_CHECK_COORDS);
} else if (isFirst && ch == ':') {
storeState();
int newRadical = (mMol.getAtomRadical(mCurrentHiliteAtom) == Molecule.cAtomRadicalStateT) ? Molecule.cAtomRadicalStateS
: (mMol.getAtomRadical(mCurrentHiliteAtom) == Molecule.cAtomRadicalStateS) ? 0 : Molecule.cAtomRadicalStateT;
mMol.setAtomRadical(mCurrentHiliteAtom, newRadical);
updateAndFireEvent(UPDATE_CHECK_COORDS);
} else if (isFirst && ch == 'q' && mMol.isFragment()) {
showAtomQFDialog(mCurrentHiliteAtom);
} else if (isFirst && mMol.isFragment() && (ch == 'x' || ch == 'X')) {
int[] list = { 9, 17, 35, 53 };
mMol.setAtomList(mCurrentHiliteAtom, list);
updateAndFireEvent(UPDATE_CHECK_COORDS);
} else if (isFirst && ch == '?') {
storeState();
if (mMol.changeAtom(mCurrentHiliteAtom, 0, 0, -1, 0)) {
updateAndFireEvent(UPDATE_CHECK_COORDS);
}
} else if (isFirst && ch>48 && ch<=57) {
if (mMol.getFreeValence(mCurrentHiliteAtom)>0) {
storeState();
int chainAtoms = ch - 47;
int atom1 = mCurrentHiliteAtom;
int hydrogenCount = mMol.getAllAtoms() - mMol.getAtoms();
for (int i = 1; i=65 && ch<=90)
|| (ch>=97 && ch<=122)
|| (ch>=48 && ch<=57)
|| (ch == '-')) {
mAtomKeyStrokeBuffer.append((char)ch);
update(UPDATE_REDRAW);
} else if (ch == '\n' || ch == '\r') {
expandAtomKeyStrokes(mAtomKeyStrokeBuffer.toString());
}
} else if (mCurrentHiliteAtom == -1 && mCurrentHiliteBond == -1) {
if ((mMode & (MODE_REACTION | MODE_MARKUSH_STRUCTURE | MODE_MULTIPLE_FRAGMENTS)) == 0) {
int ch = e.getKey();
if (ch == 'h')
flip(true);
if (ch == 'v')
flip(false);
}
}
}
if (e.getWhat() == GenericKeyEvent.KEY_RELEASED) {
if (e.getKey() == GenericKeyEvent.KEY_SHIFT) {
mShiftIsDown = false;
updateCursor();
}
if (e.getKey() == GenericKeyEvent.KEY_ALT) {
mAltIsDown = false;
updateCursor();
}
if (e.getKey() == GenericKeyEvent.KEY_CTRL) {
updateCursor();
}
}
}
private boolean changeHighlightedBond(int type) {
storeState();
return mMol.changeBond(mCurrentHiliteBond, type);
}
private void handlePopupTrigger(int x, int y) {
GenericPopupMenu popup = null;
if (mClipboardHandler != null) {
popup = mUIHelper.createPopupMenu(this);
String item1 = analyseCopy(false) ? ITEM_COPY_REACTION : ITEM_COPY_STRUCTURE;
popup.addItem(item1, null, mMol.getAllAtoms() != 0);
String item3 = (StructureNameResolver.getInstance() == null) ? ITEM_PASTE_STRUCTURE : ITEM_PASTE_WITH_NAME;
popup.addItem(item3, null, true);
if ((mMode & MODE_REACTION) != 0) {
popup.addItem(ITEM_PASTE_REACTION, null, true);
popup.addSeparator();
if (sReactionQueryTemplates != null && mMol.isFragment()) {
boolean isSubMenu = false;
for (String[] template: sReactionQueryTemplates) {
if (TEMPLATE_SECTION_KEY.equals(template[0])) {
if (isSubMenu)
popup.endSubMenu();
popup.startSubMenu("Use " + template[1] + " Template");
isSubMenu = true;
continue;
}
if (!isSubMenu) {
popup.startSubMenu("Use Template");
isSubMenu = true;
}
popup.addItem(template[0], ITEM_USE_TEMPLATE + template[1], true);
}
popup.endSubMenu();
}
}
if ((mMode & MODE_REACTION) != 0)
popup.addItem(ITEM_LOAD_REACTION, null, true);
}
if ((mMode & MODE_REACTION) != 0 && mCurrentTool == GenericEditorToolbar.cToolMapper) {
if (popup == null)
popup = mUIHelper.createPopupMenu(this);
else
popup.addSeparator();
popup.addItem(ITEM_ADD_AUTO_MAPPING, null, true);
popup.addItem(ITEM_REMOVE_MAPPING, null, true);
}
if (mCurrentTool == GenericEditorToolbar.cToolZoom) {
if (popup == null)
popup = mUIHelper.createPopupMenu(this);
else
popup.addSeparator();
popup.addItem(ITEM_FLIP_HORIZONTALLY, null, true);
popup.addItem(ITEM_FLIP_VERTICALLY, null, true);
popup.addItem(ITEM_FLIP_ROTATE180, null, true);
}
if (mAtomColorSupported && mCurrentHiliteAtom != -1) {
int atomColor = mMol.getAtomColor(mCurrentHiliteAtom);
if (popup == null)
popup = mUIHelper.createPopupMenu(this);
else
popup.addSeparator();
popup.startSubMenu("Set Atom Color");
popup.addRadioButtonItem(" ", "atomColor" + mCurrentHiliteAtom + ":" + Molecule.cAtomColorNone, RGB_BLACK, atomColor == Molecule.cAtomColorNone);
popup.addRadioButtonItem(" ", "atomColor" + mCurrentHiliteAtom + ":" + Molecule.cAtomColorBlue, AbstractDepictor.COLOR_BLUE, atomColor == Molecule.cAtomColorBlue);
popup.addRadioButtonItem(" ", "atomColor" + mCurrentHiliteAtom + ":" + Molecule.cAtomColorDarkRed, AbstractDepictor.COLOR_DARK_RED, atomColor == Molecule.cAtomColorDarkRed);
popup.addRadioButtonItem(" ", "atomColor" + mCurrentHiliteAtom + ":" + Molecule.cAtomColorRed, AbstractDepictor.COLOR_RED, atomColor == Molecule.cAtomColorRed);
popup.addRadioButtonItem(" ", "atomColor" + mCurrentHiliteAtom + ":" + Molecule.cAtomColorDarkGreen, AbstractDepictor.COLOR_DARK_GREEN, atomColor == Molecule.cAtomColorDarkGreen);
popup.addRadioButtonItem(" ", "atomColor" + mCurrentHiliteAtom + ":" + Molecule.cAtomColorGreen, AbstractDepictor.COLOR_GREEN, atomColor == Molecule.cAtomColorGreen);
popup.addRadioButtonItem(" ", "atomColor" + mCurrentHiliteAtom + ":" + Molecule.cAtomColorMagenta, AbstractDepictor.COLOR_MAGENTA, atomColor == Molecule.cAtomColorMagenta);
popup.addRadioButtonItem(" ", "atomColor" + mCurrentHiliteAtom + ":" + Molecule.cAtomColorOrange, AbstractDepictor.COLOR_ORANGE, atomColor == Molecule.cAtomColorOrange);
popup.endSubMenu();
}
if (popup == null)
popup = mUIHelper.createPopupMenu(this);
else
popup.addSeparator();
popup.addItem(ITEM_SHOW_HELP, null, true);
if (System.getProperty("development") != null) {
if (popup == null)
popup = mUIHelper.createPopupMenu(this);
else
popup.addSeparator();
popup.addItem(ITEM_SHOW_ATOM_BOND_NUMBERS, null, true);
popup.addItem(ITEM_SHOW_SYMMETRY, null, true);
popup.addItem(ITEM_SHOW_NORMAL, null, true);
}
if (popup != null)
popup.show(x, y);
}
private void handleDoubleClick(int x, int y) {
int atom = mMol.findAtom(x, y);
int bond = mMol.findBond(x, y);
if (mCurrentTool == GenericEditorToolbar.cToolLassoPointer) {
if (mMol.isFragment()) {
if (atom != -1) {
showAtomQFDialog(atom);
} else if (bond != -1) {
showBondQFDialog(bond);
} else if (mCurrentHiliteObject != null) {
if (!mShiftIsDown) {
for (int i = 0; imMol.getAtomX(i)) {
minX = mMol.getAtomX(i);
}
if (maxXminX) {
double centerX = (maxX + minX) / 2;
for (int i = 0; i move atom (and if atom is selected then all selected stuff)
mAtom1 = mMol.findAtom(mX1, mY1);
if (mAtom1 != -1) {
mAtom2 = -1;
mX1 = mMol.getAtomX(mAtom1);
mY1 = mMol.getAtomY(mAtom1);
if (mMol.isSelectedAtom(mAtom1)) {
mPendingRequest = mShiftIsDown ? cRequestCopySelected : cRequestMoveSelected;
} else {
mPendingRequest = cRequestMoveSingle;
}
}
// if bond was hit -> move bond (and if atom is selected then all selected stuff)
if (mPendingRequest == cRequestNone) {
int bondClicked = mMol.findBond(mX1, mY1);
if (bondClicked != -1) {
mAtom1 = mMol.getBondAtom(0, bondClicked);
mAtom2 = mMol.getBondAtom(1, bondClicked);
if (mMol.isSelectedBond(bondClicked)) {
mPendingRequest = mShiftIsDown ? cRequestCopySelected : cRequestMoveSelected;
} else {
mPendingRequest = cRequestMoveSingle;
}
}
}
// if object was hit -> move object (and if atom is selected then all selected stuff)
if (mPendingRequest == cRequestNone) {
if (mCurrentHiliteObject != null) {
if (mCurrentHiliteObject.isSelected()) {
mPendingRequest = mShiftIsDown ? cRequestCopySelected : cRequestMoveSelected;
} else {
mPendingRequest = (mShiftIsDown && !(mCurrentHiliteObject instanceof ReactionArrow)) ?
cRequestCopyObject : cRequestMoveObject;
}
}
}
if (mPendingRequest != cRequestNone) {
mX = new double[mMol.getAllAtoms()];
mY = new double[mMol.getAllAtoms()];
for (int i = 0; i0) {
if (mAtom1 == -1) {
mAtom1 = mMol.addAtom(mX1, mY1);
}
if (mChainAtom[0] == -1) {
mChainAtom[0] = mMol.addAtom(mChainAtomX[0],
mChainAtomY[0]);
}
if (mChainAtom[0] != -1) {
mMol.addBond(mAtom1, mChainAtom[0]);
}
}
if (mChainAtoms>1) {
for (int i = 1; i %d (%d)\n", mAtom1, mAtom2, atom2);
int mapNoAtom1 = mMol.getAtomMapNo(mAtom1);
if (atom2 == -1) {
storeState();
if (mapNoAtom1 != 0) {
mapNoChanged = true;
for (int atom = 0; atom oldToNewMapNo = new TreeMap<>();
int nextMapNo = 1;
final int fakeAtomMassBase = 512;
// Mark the manually mapped atoms such that the mapper uses them first priority and
// to be able to re-assign them later as manually mapped.
int[] fragmentAtom = new int[mFragment.length];
for (int atom = 0; atomfakeAtomMassBase);
if (hasFakeAtomMass) {
// rescue new mapNo
int newMapNo = mFragment[fragment].getAtomMass(fragmentAtom[fragment]) - fakeAtomMassBase;
// repair fake atom mass
mFragment[fragment].setAtomMass(fragmentAtom[fragment], mMol.getAtomMass(atom));
mMol.setAtomMapNo(atom, newMapNo, false);
mFragment[fragment].setAtomMapNo(fragmentAtom[fragment], newMapNo, false);
} else {
// take generated mapNo from reaction
int generatedMapNo = mFragment[fragment].getAtomMapNo(fragmentAtom[fragment]);
Integer newMapNo = 0;
if (generatedMapNo != 0) {
newMapNo = oldToNewMapNo.get(generatedMapNo);
if (newMapNo == null)
oldToNewMapNo.put(generatedMapNo, newMapNo = new Integer(nextMapNo++));
}
mMol.setAtomMapNo(atom, newMapNo, true);
mFragment[fragment].setAtomMapNo(fragmentAtom[fragment], newMapNo, true);
}
fragmentAtom[fragment]++;
}
} else {
// restore original atom masses in fragments and copy molecule's mapping number into fragments
fragmentAtom = new int[mFragment.length];
for (int atom = 0; atom l = m.map(rxn);
// if (l != null && l.size() > 0) {
// AStarReactionMapper.SlimMapping sm = l.get(0);
//// int[] mps = sm.getMapping();
//// for (int i = 0; i < mps.length; i++) {
//// System.out.printf("Maps %d -> %d\n", i, mps[i]);
//// }
// m.activateMapping(sm);
//// for (int i = 0; i < mFragment.length; i++) {
//// StereoMolecule mol = mFragment[i];
//// for (int a = 0; a < mol.getAllAtoms(); a++) {
//// System.out.printf("T Map %d = %d\n", a, mol.getAtomMapNo(a));
//// }
//// }
//
// int[] fragmentAtom = new int[mFragment.length];
// for (int atom = 0; atom < mMol.getAllAtoms(); atom++) {
// int fragment = mFragmentNo[atom];
// if (mMol.getAtomMapNo(atom) == 0)
// mMol.setAtomMapNo(atom, mFragment[fragment].getAtomMapNo(fragmentAtom[fragment]), true);
// fragmentAtom[fragment]++;
// }
// }
}
/**
* Checks whether this bond is a stereo bond and whether it refers to a
* stereo center or BINAP bond, making it eligible for ESR information.
*
* @param stereoBond the up/down stereo bond
* @return
*/
private boolean qualifiesForESR(int stereoBond){
return mMol.isStereoBond(stereoBond) && (getESRAtom(stereoBond) != -1 || getESRBond(stereoBond) != -1);
}
/**
* Locates the stereo center with parity 1 or 2 that is defined by the stereo bond.
*
* @param stereoBond
* @return stereo center atom or -1 if no stereo center found
*/
private int getESRAtom(int stereoBond){
int atom = mMol.getBondAtom(0, stereoBond);
if (mMol.getAtomParity(atom) != Molecule.cAtomParityNone) {
return (mMol.isAtomParityPseudo(atom)
|| (mMol.getAtomParity(atom) != Molecule.cAtomParity1
&& mMol.getAtomParity(atom) != Molecule.cAtomParity2)) ? -1 : atom;
}
if (mMol.getAtomPi(atom) == 1) {
for (int i = 0; idistance) {
minDistance = distance;
fragment = mFragmentNo[atom];
}
}
return fragment;
}
/**
* requires cHelperNeighbours
*
* @param atom
*/
private void suggestNewX2AndY2(int atom)
{
double newAngle = Math.PI * 2 / 3;
if (atom != -1) {
double angle[] = new double[MAX_CONNATOMS + 1];
for (int i = 0; i0; i--) { // bubble sort
for (int j = 0; jangle[j + 1]) {
double temp = angle[j];
angle[j] = angle[j + 1];
angle[j + 1] = temp;
}
}
}
angle[mMol.getAllConnAtomsPlusMetalBonds(atom)] = angle[0] + Math.PI * 2;
int largestNo = 0;
double largestDiff = 0.0;
for (int i = 0; i=mMol.getAtoms()) {
theAtom = -1;
}
}
if (theBond == -1
&& theAtom == -1
&& mCurrentTool != GenericEditorToolbar.cToolChain
&& mCurrentTool != GenericEditorToolbar.cToolMapper
&& mCurrentTool != GenericEditorToolbar.cToolUnknownParity
&& mCurrentTool != GenericEditorToolbar.cToolPosCharge
&& mCurrentTool != GenericEditorToolbar.cToolNegCharge
&& mCurrentTool != GenericEditorToolbar.cToolAtomH
&& mCurrentTool != GenericEditorToolbar.cToolAtomC
&& mCurrentTool != GenericEditorToolbar.cToolAtomN
&& mCurrentTool != GenericEditorToolbar.cToolAtomO
&& mCurrentTool != GenericEditorToolbar.cToolAtomSi
&& mCurrentTool != GenericEditorToolbar.cToolAtomP
&& mCurrentTool != GenericEditorToolbar.cToolAtomS
&& mCurrentTool != GenericEditorToolbar.cToolAtomF
&& mCurrentTool != GenericEditorToolbar.cToolAtomCl
&& mCurrentTool != GenericEditorToolbar.cToolAtomBr
&& mCurrentTool != GenericEditorToolbar.cToolAtomI
&& mCurrentTool != GenericEditorToolbar.cToolCustomAtom) {
theBond = mMol.findBond(x, y);
}
if (theBond != -1
&& (mCurrentTool == GenericEditorToolbar.cToolESRAbs
|| mCurrentTool == GenericEditorToolbar.cToolESRAnd
|| mCurrentTool == GenericEditorToolbar.cToolESROr)
&& !qualifiesForESR(theBond)) {
theBond = -1;
}
// don't change object hiliting while dragging
AbstractDrawingObject hiliteObject = mCurrentHiliteObject;
if (!isDragging && mDrawingObjectList != null) {
hiliteObject = null;
if (theAtom == -1 && theBond == -1
&& (mCurrentTool == GenericEditorToolbar.cToolLassoPointer
|| mCurrentTool == GenericEditorToolbar.cToolDelete
|| mCurrentTool == GenericEditorToolbar.cToolText)) {
for (AbstractDrawingObject theObject : mDrawingObjectList) {
if (mCurrentTool == GenericEditorToolbar.cToolLassoPointer
|| (mCurrentTool == GenericEditorToolbar.cToolDelete && !(theObject instanceof ReactionArrow))
|| (mCurrentTool == GenericEditorToolbar.cToolText && theObject instanceof TextDrawingObject)) {
if (theObject.checkHiliting(x, y)) {
hiliteObject = theObject;
if (mCurrentHiliteObject != null && mCurrentHiliteObject != theObject) {
mCurrentHiliteObject.clearHiliting();
}
break;
}
}
}
}
}
boolean repaintNeeded = (mCurrentHiliteAtom != theAtom
|| mCurrentHiliteBond != theBond
|| mCurrentHiliteObject != hiliteObject
|| hiliteObject != null);
if (mCurrentHiliteAtom != theAtom) {
if (mCurrentHiliteAtom != -1 && mAtomKeyStrokeBuffer.length() != 0)
expandAtomKeyStrokes(mAtomKeyStrokeBuffer.toString());
mCurrentHiliteAtom = theAtom;
mAtomKeyStrokeBuffer.setLength(0);
fireEventLater(new EditorEvent(this, EditorEvent.WHAT_HILITE_ATOM_CHANGED, true));
}
if (mCurrentHiliteBond != theBond) {
mCurrentHiliteBond = theBond;
fireEventLater(new EditorEvent(this, EditorEvent.WHAT_HILITE_BOND_CHANGED, true));
}
mCurrentHiliteObject = hiliteObject;
return repaintNeeded;
}
private int getAtomKeyStrokeValidity(String s){
if (Molecule.getAtomicNoFromLabel(s, mAllowedPseudoAtoms) != 0)
return KEY_IS_ATOM_LABEL;
mAtomKeyStrokeSuggestion = NamedSubstituents.identify(s);
if (mAtomKeyStrokeSuggestion == null)
return isValidAtomKeyStrokeStart(s) ? KEY_IS_VALID_START : KEY_IS_INVALID;
if (mAtomKeyStrokeSuggestion.length() == 0)
return KEY_IS_VALID_START;
else
return KEY_IS_SUBSTITUENT;
}
/**
* @param s
* @return true if adding one or more chars may still create a valid key stroke sequence
*/
private boolean isValidAtomKeyStrokeStart(String s){
if (s.length()<3)
for (int i=1; i=0; i--) {
AbstractDrawingObject object = mDrawingObjectList.get(i);
if (object.isSelected() && !(object instanceof ReactionArrow)) {
mDrawingObjectList.add(object.clone());
}
}
}
}
private void fireEventLater(EditorEvent e) {
final int what = e.getWhat();
if ((what & mEventsScheduled) == 0) {
mUIHelper.runLater(() -> {
mEventsScheduled &= ~what;
for (GenericEventListener l : mListeners)
l.eventHappened(e);
} );
mEventsScheduled |= what;
}
}
/**
* Redraws the molecule(s) or the reaction after scaling coordinates.
* Then analyses fragment membership and recreate individual molecules, reaction, or markush structure
* Then, fires molecule change events with userChange=false, i.e. external change.
*/
public void moleculeChanged() {
update(UPDATE_SCALE_COORDS);
fireEventLater(new EditorEvent(this, EditorEvent.WHAT_MOLECULE_CHANGED, false));
}
/**
* Redraws the molecule(s) or the reaction after scaling coordinates.
* Then analyses fragment membership and recreate individual molecules, reaction, or markush structure
* Then, fires molecule change events with userChange=false, i.e. external change.
* @param updateMode
*/
private void updateAndFireEvent(int updateMode) {
update(updateMode);
fireEventLater(new EditorEvent(this, EditorEvent.WHAT_MOLECULE_CHANGED, true));
}
public StereoMolecule getMolecule()
{
return mMol;
}
public void setMolecule (StereoMolecule theMolecule){
if (mMol == theMolecule) {
return;
}
storeState();
mMol = theMolecule;
mMode = 0;
mDrawingObjectList = null;
moleculeChanged();
}
public StereoMolecule[] getFragments() {
return mFragment;
}
public void setFragments(StereoMolecule[]fragment) {
mMol.clear();
mFragment = fragment;
for (int i = 0; ifragment2) {
mergeFragments[fragment1][fragment2] = true;
} else {
mergeFragments[fragment2][fragment1] = true;
}
}
}
}
}
int[] newFragmentIndex = new int[fragments];
for (int fragment = 0; fragmentmaxIndex) {
newFragmentIndex[k]--;
}
}
}
}
}
}
for (int atom = 0; atom() {
public int compare(int[] fragmentDescriptor1, int[] fragmentDescriptor2) {
if ((mMode & (MODE_REACTION | MODE_MARKUSH_STRUCTURE)) != 0) {
if (fragmentDescriptor1[1] != fragmentDescriptor2[1]) {
return (fragmentDescriptor1[1] == 0) ? -1 : 1;
}
}
return (cog[fragmentDescriptor1[0]].x
+ cog[fragmentDescriptor1[0]].y
1 ? new Point2D.Double(sumx / atoms, sumy / atoms) : null;
}
private void rotate180() {
boolean selectedOnly = false;
for (int atom = 0; atom