com.actelion.research.gui.CompoundCollectionPane 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 2017 Idorsia Pharmaceuticals Ltd., Hegenheimermattweg 91, CH-4123 Allschwil, Switzerland
*
* This file is part of DataWarrior.
*
* DataWarrior is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* DataWarrior is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along with DataWarrior.
* If not, see http://www.gnu.org/licenses/.
*
* @author Thomas Sander
*/
package com.actelion.research.gui;
import com.actelion.research.chem.*;
import com.actelion.research.gui.clipboard.IClipboardHandler;
import com.actelion.research.gui.dnd.MoleculeDragAdapter;
import com.actelion.research.gui.dnd.MoleculeDropAdapter;
import com.actelion.research.gui.dnd.MoleculeTransferable;
import com.actelion.research.gui.hidpi.HiDPIHelper;
import com.actelion.research.util.ColorHelper;
import com.actelion.research.util.CursorHelper;
import javax.swing.*;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.event.*;
import java.awt.geom.Rectangle2D;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
public class CompoundCollectionPane extends JScrollPane
implements ActionListener,CompoundCollectionListener,MouseListener,MouseMotionListener,StructureListener {
private static final long serialVersionUID = 0x20060904;
private static final String[] MESSAGE = { "" };
private static final String ADD = "Add...";
private static final String EDIT = "Edit...";
private static final String REMOVE = "Remove";
private static final String REMOVE_ALL = "Remove All";
private static final String COPY = "Copy";
private static final String PASTE = "Paste";
private static final String OPEN = "Add From File...";
private static final String SAVE_DWAR = "Save DataWarrior-File...";
private static final String SAVE_SDF2 = "Save SD-File V2...";
private static final String SAVE_SDF3 = "Save SD-File V3...";
public static final int FILE_SUPPORT_NONE = 0;
public static final int FILE_SUPPORT_OPEN_FILES = 1;
public static final int FILE_SUPPORT_SAVE_FILES = 2;
public static final int FILE_SUPPORT_OPEN_AND_SAVE_FILES = 3;
private static final int ALLOWED_DRAG_ACTIONS = DnDConstants.ACTION_COPY_OR_MOVE;
private static final int ALLOWED_DROP_ACTIONS = DnDConstants.ACTION_COPY_OR_MOVE;
private final static int cWhiteSpace = 4;
private CompoundCollectionModel mModel;
private IClipboardHandler mClipboardHandler;
private MoleculeFilter mCompoundFilter;
private int mDragActions,mDisplayMode,mSelectedIndex,mHighlightedIndex,
mEditedIndex,mFileSupport,mStructureSize,mDragIndex,mDropIndex;
private Dimension mContentSize,mCellSize;
private JPanel mContentPanel;
private boolean mIsVertical,mIsEditable,mIsSelectable,mCreateFragments,
mIsEnabled,mShowValidationError,mInternalDragAndDropIsMove;
private ScrollPaneAutoScrollerWhenDragging mScroller;
/**
* This is a visual component to display and edit a compound collection maintained
* by a CompoundCollectionModel. Three variations of DefaultCompoundCollectionModel
* (.Native, .Molecule, and .IDCode) are available, which internally keep molecule
* instances as Object, StereoMolecule or String, respectively. If one of these
* default model is used, than the CompoundCollectionPane's T must match this class.
* @param model
* @param isVertical
*/
public CompoundCollectionPane(CompoundCollectionModel model, boolean isVertical) {
this(model, isVertical, 0, ALLOWED_DRAG_ACTIONS, ALLOWED_DROP_ACTIONS);
}
public CompoundCollectionPane(CompoundCollectionModel model, boolean isVertical, int displayMode,
int dragAction, int dropAction) {
mModel = model;
mModel.addCompoundCollectionListener(this);
mIsEnabled = true;
mIsVertical = isVertical;
mDisplayMode = displayMode;
mFileSupport = FILE_SUPPORT_OPEN_AND_SAVE_FILES;
mStructureSize = 0;
mSelectedIndex = -1;
mHighlightedIndex = -1;
mDragIndex = -1;
mDropIndex = -1;
init();
initializeDragAndDrop(dragAction, dropAction);
mScroller = new ScrollPaneAutoScrollerWhenDragging(this, isVertical);
}
public CompoundCollectionModel getModel() {
return mModel;
}
public void setEnabled(boolean b) {
super.setEnabled(b);
if (mIsVertical)
getVerticalScrollBar().setEnabled(b);
else
getHorizontalScrollBar().setEnabled(b);
mIsEnabled = b;
repaint();
}
/**
* Defines the behaviour for internal drag&drop. The default is false,
* which means that a dragged structure is copied to the new internal position.
* @param b
*/
public void setInternalDragAndDropIsMove(boolean b) {
mInternalDragAndDropIsMove = b;
}
/**
* Defines the width or height of individual structure cells,
* depending on whether the the CompoundCollectionPane is horizontal
* or vertical, respectively. Setting size to 0 (default) causes
* an automatic behavior with a square cell areas and width and height
* being implicitly defined by the CompoundCollectionPane component size.
*/
public void setStructureSize(int size) {
mStructureSize = size;
validateSize();
repaint();
}
/**
* Defines, whether the list and individual structures can be edited.
* @param editable
*/
public void setEditable(boolean editable) {
mIsEditable = editable;
updateMouseListening();
}
/**
* Defines, whether the popup menu contains 'Open' and/or 'Save' items.
* As default both, OPEN and SAVE options are active.
* @param fileSupport one of the FILE_SUPPORT_... options
*/
public void setFileSupport(int fileSupport) {
mFileSupport = fileSupport;
}
public void setCompoundFilter(MoleculeFilter filter) {
if (mCompoundFilter != filter) {
mCompoundFilter = filter;
if (mCompoundFilter != null)
for (int i=mModel.getSize()-1; i>=0; i--)
if (!mCompoundFilter.moleculeQualifies(mModel.getMolecule(i)))
mModel.remove(i);
}
}
public void setSelectable(boolean selectable) {
mIsSelectable = selectable;
updateMouseListening();
}
/**
* Defines whether new created structures are fragments of molecules.
* @param createFragments
*/
public void setCreateFragments(boolean createFragments) {
mCreateFragments = createFragments;
}
/**
* Defines whether a large red question mark is shown
* in case of a structure validation error.
* @param showError
*/
public void setShowValidationError(boolean showError) {
mShowValidationError = showError;
}
/**
* call this in order to get clipboard support
*/
public void setClipboardHandler(IClipboardHandler h) {
mClipboardHandler = h;
}
public IClipboardHandler getClipboardHandler() {
return mClipboardHandler;
}
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals(COPY) && mHighlightedIndex != -1) {
mClipboardHandler.copyMolecule(mModel.getMolecule(mHighlightedIndex));
}
else if (e.getActionCommand().equals(PASTE)) {
int index = (mHighlightedIndex == -1) ? mModel.getSize() : mHighlightedIndex;
ArrayList molList = mClipboardHandler.pasteMolecules();
if (molList != null) {
int errorCount = 0;
for (StereoMolecule mol:molList) {
mol.setFragment(mCreateFragments);
if (mCompoundFilter == null || mCompoundFilter.moleculeQualifies(mol))
mModel.addMolecule(index, mol);
else
errorCount++;
}
if (errorCount != 0)
JOptionPane.showMessageDialog(getParentFrame(), errorCount+" compound(s) could not be added, because they doesn't qualify.");
}
}
else if (e.getActionCommand().equals(ADD)) {
editStructure(-1);
}
else if (e.getActionCommand().equals(EDIT) && mHighlightedIndex != -1) {
editStructure(mHighlightedIndex);
}
else if (e.getActionCommand().equals(REMOVE) && mHighlightedIndex != -1) {
mModel.remove(mHighlightedIndex);
mHighlightedIndex = -1;
}
else if (e.getActionCommand().equals(REMOVE_ALL)) {
mModel.clear();
mHighlightedIndex = -1;
}
else if (e.getActionCommand().equals(OPEN)) {
ArrayList compounds = new FileHelper(getParentFrame()).readStructuresFromFile(true);
if (compounds != null) {
for (StereoMolecule compound:compounds)
compound.setFragment(mCreateFragments);
if (mCompoundFilter != null) {
int count = 0;
for (int i = compounds.size() - 1; i >= 0; i--) {
if (!mCompoundFilter.moleculeQualifies(compounds.get(i))) {
compounds.remove(i);
count++;
}
}
if (count != 0) {
JOptionPane.showMessageDialog(getParentFrame(),Integer.toString(count).concat(" compounds were removed, because they don't qualify."));
}
}
mModel.addMoleculeList(compounds);
}
}
else if (e.getActionCommand().equals(SAVE_DWAR)) {
String filename = new FileHelper(getParentFrame()).selectFileToSave(
"Save DataWarrior File", FileHelper.cFileTypeDataWarrior, "Untitled");
if (filename != null) {
try {
String title = mCreateFragments ? "Fragment" : "Structure";
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filename),"UTF-8"));
writer.write("");
writer.newLine();
writer.write("");
writer.newLine();
writer.write("");
writer.newLine();
writer.write(" ");
writer.newLine();
writer.write("");
writer.newLine();
writer.write("");
writer.newLine();
writer.write("");
writer.newLine();
writer.write("");
writer.newLine();
writer.write("");
writer.newLine();
writer.write("");
writer.newLine();
writer.write(" ");
writer.newLine();
writer.write(title+"\tcoords");
writer.newLine();
for (int i=0; i=i1 && mDropIndex<=i2) {
Rectangle bounds = getMoleculeBounds(mDropIndex);
g.setColor(ColorHelper.getContrastColor(Color.GRAY, background));
if (mIsVertical)
g.fillRect(bounds.x-2, bounds.y-4, bounds.width+4, 5);
else
g.fillRect(bounds.x-4, bounds.y-2, 5, bounds.height+4);
}
}
};
setHorizontalScrollBarPolicy(mIsVertical ? JScrollPane.HORIZONTAL_SCROLLBAR_NEVER : JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
setVerticalScrollBarPolicy(mIsVertical ? JScrollPane.VERTICAL_SCROLLBAR_ALWAYS : JScrollPane.VERTICAL_SCROLLBAR_NEVER);
setViewportView(mContentPanel);
}
public void collectionUpdated(int fromIndex, int toIndex) {
if (mSelectedIndex >= fromIndex && mSelectedIndex <= toIndex)
mSelectedIndex = -1;
if (mHighlightedIndex >= fromIndex && mHighlightedIndex <= toIndex)
mHighlightedIndex = -1;
repaint();
}
private Rectangle getMoleculeBounds(int molIndex) {
int x = cWhiteSpace/2;
int y = cWhiteSpace/2;
if (mIsVertical)
y += molIndex * mCellSize.height;
else
x += molIndex * mCellSize.width;
return new Rectangle(x, y, mCellSize.width-cWhiteSpace, mCellSize.height-cWhiteSpace);
}
private int getMoleculeIndex(int x, int y) {
if (mModel.getSize() == 0 || mCellSize.width == 0 || mCellSize.height == 0)
return -1;
Point p = getViewport().getViewPosition();
int index = (mIsVertical) ? (y+p.y) / mCellSize.height
: (x+p.x) / mCellSize.width;
return (index < mModel.getSize()) ? index : -1;
}
public void mouseClicked(MouseEvent e) {
if (mIsEnabled && mIsEditable && e.getClickCount() == 2 && mHighlightedIndex != -1) {
editStructure(mHighlightedIndex);
}
}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
public void mousePressed(MouseEvent e) {
if (mIsEnabled) {
if (e.isPopupTrigger()) {
handlePopupTrigger(e);
}
else if (mIsSelectable) {
int index = getMoleculeIndex(e.getX(), e.getY());
if (mSelectedIndex != index) {
mSelectedIndex = index;
setSelection(index);
repaint();
}
}
}
}
public void mouseReleased(MouseEvent e) {
if (mIsEnabled && e.isPopupTrigger())
handlePopupTrigger(e);
}
public void mouseDragged(MouseEvent e) {}
public void mouseMoved(MouseEvent e) {
if (mIsEnabled) {
int index = getMoleculeIndex(e.getX(), e.getY());
if (mHighlightedIndex != index) {
mHighlightedIndex = index;
setCursor(CursorHelper.getCursor(index == -1 ? CursorHelper.cPointerCursor : CursorHelper.cHandCursor));
repaint();
}
}
mDragIndex = -1;
}
private void handlePopupTrigger(MouseEvent e) {
JPopupMenu popup = new JPopupMenu();
JMenuItem item = new JMenuItem(ADD);
item.addActionListener(this);
popup.add(item);
if (mHighlightedIndex != -1) {
item = new JMenuItem(EDIT);
item.addActionListener(this);
popup.add(item);
item = new JMenuItem(REMOVE);
item.addActionListener(this);
popup.add(item);
}
if (mModel.getSize() != 0) {
item = new JMenuItem(REMOVE_ALL);
item.addActionListener(this);
popup.add(item);
}
if (mClipboardHandler != null) {
popup.addSeparator();
if (mHighlightedIndex != -1) {
item = new JMenuItem(COPY);
item.addActionListener(this);
popup.add(item);
}
item = new JMenuItem(PASTE);
item.addActionListener(this);
popup.add(item);
}
if (mFileSupport != 0) {
popup.addSeparator();
if ((mFileSupport & FILE_SUPPORT_OPEN_FILES) != 0) {
item = new JMenuItem(OPEN);
item.addActionListener(this);
popup.add(item);
}
if ((mFileSupport & FILE_SUPPORT_SAVE_FILES) != 0 && mModel.getSize() != 0) {
item = new JMenuItem(SAVE_DWAR);
item.addActionListener(this);
popup.add(item);
item = new JMenuItem(SAVE_SDF2);
item.addActionListener(this);
popup.add(item);
item = new JMenuItem(SAVE_SDF3);
item.addActionListener(this);
popup.add(item);
}
}
popup.show(this, e.getX(), e.getY());
}
public void structureChanged(StereoMolecule mol) {
if (mEditedIndex == -1) { // new structure
if (mol.getAllAtoms() != 0) {
if (mCompoundFilter == null || mCompoundFilter.moleculeQualifies(mol))
mModel.addMolecule(mModel.getSize(), mol);
else
JOptionPane.showMessageDialog(getParentFrame(),"The compound could not be added, because it doesn't fullfil all criteria.");
}
}
else {
if (mol.getAllAtoms() == 0)
mModel.remove(mEditedIndex);
else {
if (mCompoundFilter == null || mCompoundFilter.moleculeQualifies(mol))
mModel.setMolecule(mEditedIndex, mol);
else
JOptionPane.showMessageDialog(getParentFrame(),"The compound could not be changed, because the changed structure doesn't fullfil all criteria.");
}
}
}
/**
* May be overridden to act on selection changes
* @param molIndex
*/
public void setSelection(int molIndex) {}
private void validateSize() {
Rectangle viewportBounds = getViewportBorderBounds();
int width = mIsVertical ? viewportBounds.width : mStructureSize == 0 ? viewportBounds.height : mStructureSize;
int height = !mIsVertical ? viewportBounds.height : mStructureSize == 0 ? viewportBounds.width : mStructureSize;
mCellSize = new Dimension(width, height);
if (mIsVertical) {
height *= mModel.getSize();
if (height < viewportBounds.height)
height = viewportBounds.height;
}
else {
width *= mModel.getSize();
if (width < viewportBounds.width)
width = viewportBounds.width;
}
if (mContentSize.width != width
|| mContentSize.height != height) {
mContentSize.width = width;
mContentSize.height = height;
mContentPanel.setPreferredSize(mContentSize);
mContentPanel.revalidate();
}
}
private void initializeDragAndDrop(int dragAction, int dropAction) {
mDragActions = dragAction;
if (dragAction != DnDConstants.ACTION_NONE) {
new MoleculeDragAdapter(this) {
public Transferable getTransferable(Point p) {
if (mHighlightedIndex == -1)
return null;
setCursor(CursorHelper.getCursor(CursorHelper.cFistCursor));
mDragIndex = mHighlightedIndex;
return new MoleculeTransferable(mModel.getMolecule(mHighlightedIndex));
}
};
}
if (dropAction != DnDConstants.ACTION_NONE) {
MoleculeDropAdapter d = new MoleculeDropAdapter() {
@Override
public void onDropMolecule(StereoMolecule mol, Point pt) {
if (mIsEnabled && mIsEditable && mol != null && mol.getAllAtoms() != 0 && mDropIndex != -1) {
for (int atom=0; atom mDragIndex)
mDropIndex--;
}
mModel.addMolecule(mDropIndex, mol);
}
else {
JOptionPane.showMessageDialog(getParentFrame(),"The compound could not be added, because it doesn't qualify.");
}
}
updateDropPosition(-1);
}
@Override
public void dragEnter(DropTargetDragEvent e) {
boolean drop = mIsEnabled && mIsEditable && isDropOK(e) ;
if (!drop) {
e.rejectDrag();
}
else {
updateDropPosition(getDropIndex(e));
}
}
@Override
public void dragOver(DropTargetDragEvent e) {
mScroller.autoScroll();
updateDropPosition(getDropIndex(e));
}
@Override
public void dragExit(DropTargetEvent e) {
updateDropPosition(-1);
}
private int getDropIndex(DropTargetDragEvent e) {
int x = e.getLocation().x + (mIsVertical ? 0 : mCellSize.width / 2);
int y = e.getLocation().y + (mIsVertical ? mCellSize.height / 2 : 0);
int dropIndex = getMoleculeIndex(x, y);
if (dropIndex == -1)
dropIndex = mModel.getSize();
// if we move internally onto the same position, don't indicate it
if (mInternalDragAndDropIsMove
&& (dropIndex == mDragIndex || dropIndex == mDragIndex+1))
dropIndex = -1;
return dropIndex;
}
};
new DropTarget(this, dropAction, d, true, new OurFlavorMap());
}
}
private void updateDropPosition(int dropIndex) {
if (mIsEnabled && mIsEditable && mDropIndex != dropIndex) {
mDropIndex = dropIndex;
repaint();
}
}
private Component getParentFrame() {
Component c = this;
while (c != null && !(c instanceof Frame) && !(c instanceof Dialog))
c = c.getParent();
return c;
}
// This class is needed for inter-jvm drag&drop. Although not neccessary for standard environments, it prevents
// nasty "no native data was transfered" errors. It still might create ClassNotFoundException in the first place by
// the SystemFlavorMap, but as I found it does not hurt, since the context classloader will be installed after
// the first call. I know, that this depends heavely on a specific behaviour of the systemflavormap, but for now
// there's nothing I can do about it.
static class OurFlavorMap implements java.awt.datatransfer.FlavorMap {
public java.util.Map getNativesForFlavors(DataFlavor[] dfs) {
java.awt.datatransfer.FlavorMap m = java.awt.datatransfer.SystemFlavorMap.getDefaultFlavorMap();
return m.getNativesForFlavors(dfs);
}
public java.util.Map getFlavorsForNatives(String[] natives) {
java.awt.datatransfer.FlavorMap m = java.awt.datatransfer.SystemFlavorMap.getDefaultFlavorMap();
return m.getFlavorsForNatives(natives);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy