content: package com.actelion.research.gui.editor;
  
  import com.actelion.research.chem.StereoMolecule;
  import com.actelion.research.gui.clipboard.ClipboardHandler;
  import com.actelion.research.gui.dnd.MoleculeDropAdapter;
  import com.actelion.research.gui.generic.GenericCanvas;
  import com.actelion.research.gui.generic.GenericDrawContext;
  import com.actelion.research.gui.generic.GenericPoint;
  import com.actelion.research.gui.generic.GenericUIHelper;
  import com.actelion.research.gui.swing.SwingDrawContext;
  import com.actelion.research.gui.swing.SwingKeyHandler;
  import com.actelion.research.gui.swing.SwingMouseHandler;
  import com.actelion.research.gui.swing.SwingUIHelper;
  
  import javax.swing.*;
  import java.awt.*;
  import java.awt.datatransfer.DataFlavor;
  import java.awt.dnd.DnDConstants;
  import java.awt.dnd.DropTarget;
  
  public class SwingEditorArea extends JPanel implements GenericCanvas {
  	private static final int ALLOWED_DROP_ACTIONS = DnDConstants.ACTION_COPY_OR_MOVE;
  
  	private SwingEditorDrawArea mDrawArea;
  	private SwingKeyHandler mKeyHandler;
  
  	public class SwingEditorDrawArea extends GenericEditorArea {
  
  		public SwingEditorDrawArea(StereoMolecule mol, int mode, GenericUIHelper helper, GenericCanvas canvas) {
  			super(mol, mode, helper, canvas);
  		}
  
  		/**
  		 * Allows for proper disposal of the Graphics2D object after a synchronous
  		 * RepaintManager.repaintImmediately call during a drag-drop operation.
  		 * this.initializeDragAndDrop creates a MoleculeDropAdapter whose 
  		 * onDropMolecule method calls GenericEditorArea.addPastedOrDropped. 
  		 * That method comes back here to getDrawContext. 
  		 * which itself is called by MoleculeDropAdapter.  
  		 * Disposed of immediately following that call.
  		 * 
  		 */
  		private Graphics2D mTempGraphics;
  
  		public boolean addPastedOrDropped(StereoMolecule mol, GenericPoint p) {
  			// BH GenericEditorArea.addPastedOrDropped
  			// BH will create a new Graphics2D object and 
  			// BH never dispose of it. While this is not a 
  			// BH disaster in Java, in JavaScript it is critical
  			// BH to dispose properly of all Graphics2D objects.
  			// BH Otherwise, the offsets get messed up.
  			flushGraphics();
  			boolean ret = super.addPastedOrDropped(mol, p);
  			flushGraphics();
  			return ret;
  		}
  
  		private void flushGraphics() {
  			if (mTempGraphics != null) {
  				mTempGraphics.dispose();
  				mTempGraphics = null;
  			}
  		}
  
  		/**
  		 * called from super.addPastedOrDropped via get getDrawContext();
  		 */
  		protected GenericDrawContext getSwingDrawContext() {
  			return new SwingDrawContext(mTempGraphics = (Graphics2D) getGraphics());
  		}
  
  	}
  
  	public SwingEditorArea(StereoMolecule mol, int mode) {
  		setFocusable(true);
  
  		mDrawArea = new SwingEditorDrawArea(mol, mode, new SwingUIHelper(this), this);
  
  		initializeDragAndDrop(ALLOWED_DROP_ACTIONS);
  
  		SwingMouseHandler mouseHandler = new SwingMouseHandler(mDrawArea);
  		addMouseListener(mouseHandler);
  		addMouseMotionListener(mouseHandler);
  		mouseHandler.addListener(mDrawArea);
  
  		mKeyHandler = new SwingKeyHandler(mDrawArea);
  		addKeyListener(mKeyHandler);
  		mKeyHandler.addListener(mDrawArea);
  
  		getGenericDrawArea().setClipboardHandler(new ClipboardHandler());
  		}
  
  	public SwingKeyHandler getKeyHandler() {
  		return mKeyHandler;
  		}
  
  	public GenericEditorArea getGenericDrawArea() {
  		return mDrawArea;
  		}
  
  	@Override
  	public GenericDrawContext getDrawContext() {
  		// BH only called by GenericEditorArea.addPastedOrDropped
  		// BH this allows us to dispose of the graphics after the 
  		// BH repaint operation.
  		return mDrawArea.getSwingDrawContext();
  		}
  
  	@Override
  	public double getCanvasWidth() {
  		return getWidth();
  		}
  
  	@Override
  	public double getCanvasHeight() {
  		return getHeight();
  		}
  
  	@Override
  	public int getBackgroundRGB() {
  		return getBackground().getRGB();
  		}
  
  	@Override
  	public void paintComponent(Graphics g) {
  		super.paintComponent(g);
  		((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
  		((Graphics2D)g).setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
  
  		mDrawArea.paintContent(new SwingDrawContext((Graphics2D)g));
  		}
  
  	private void initializeDragAndDrop(int dropAction) {
  		if (dropAction != DnDConstants.ACTION_NONE) {
  			MoleculeDropAdapter d = new MoleculeDropAdapter() {
  				public void onDropMolecule(StereoMolecule mol, Point p) {
  					mDrawArea.addPastedOrDropped(mol, (p == null ? null : new GenericPoint(p.x, p.y)));
  				}
  			};
  
  			new DropTarget(this, dropAction, d, true, new OurFlavorMap());
  		}
  	}
  
  	// 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 {
  		@Override
  		public java.util.Map<DataFlavor, String> getNativesForFlavors(DataFlavor[] dfs) {
  			java.awt.datatransfer.FlavorMap m = java.awt.datatransfer.SystemFlavorMap.getDefaultFlavorMap();
  			return m.getNativesForFlavors(dfs);
  		}
  
  		@Override
  		public java.util.Map<String, DataFlavor> getFlavorsForNatives(String[] natives) {
  			java.awt.datatransfer.FlavorMap m = java.awt.datatransfer.SystemFlavorMap.getDefaultFlavorMap();
  			return m.getFlavorsForNatives(natives);
  		}
  	}
  }
  