All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.actelion.research.gui.CompoundCollectionPane Maven / Gradle / Ivy

There is a newer version: 2024.12.1
Show newest version
/*
 * 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 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			        mDisplayMode,mSelectedIndex,mHighlightedIndex,
	                            mEditedIndex,mFileSupport,mStructureSize;
	private Dimension           mContentSize,mCellSize;
	private JPanel              mContentPanel;
	private boolean             mIsVertical,mIsEditable,mIsSelectable,mCreateFragments,
	                            mShowDropBorder,mIsEnabled,mShowValidationError;

	/**
	 * 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;
		init();
        initializeDragAndDrop(dragAction, dropAction);
		}

	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 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;
            StereoMolecule mol = mClipboardHandler.pasteMolecule();
	        if (mol != null) {
	        	mol.setFragment(mCreateFragments);
	        	if (mCompoundFilter == null || mCompoundFilter.moleculeQualifies(mol))
		            mModel.addMolecule(index, mol);
	        	else
					JOptionPane.showMessageDialog(getParentFrame(),"The compound could not be added, because it 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= 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;
                repaint();
                }
            }
        }

    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 qualify.");
				}
    		}
    	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 changes don't qualify.");
				}
    		}
        }

    /**
     * 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) {
        if (dragAction != DnDConstants.ACTION_NONE) {
            new MoleculeDragAdapter(this) {
                public Transferable getTransferable(Point p) {
                    if (mHighlightedIndex == -1)
                        return null;
                    return new MoleculeTransferable(mModel.getMolecule(mHighlightedIndex));
                    }
                };
            }

        if (dropAction != DnDConstants.ACTION_NONE) {
            MoleculeDropAdapter d = new MoleculeDropAdapter() {
                public void onDropMolecule(StereoMolecule mol, Point pt) {
                    if (mIsEnabled && mIsEditable && mol != null && mol.getAllAtoms() != 0) {
                        for (int atom=0; atom 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