/* Copyright (C) 2006 EBI This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the itmplied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.biomart.builder.view.gui.diagrams.components; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Font; import java.awt.Graphics2D; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.dnd.DnDConstants; import java.awt.dnd.DragGestureEvent; import java.awt.dnd.DragGestureListener; import java.awt.dnd.DragSource; import java.awt.dnd.DragSourceContext; import java.awt.dnd.DragSourceDragEvent; import java.awt.dnd.DragSourceDropEvent; import java.awt.dnd.DragSourceEvent; import java.awt.dnd.DragSourceListener; import java.awt.dnd.DropTarget; import java.awt.dnd.DropTargetDragEvent; import java.awt.dnd.DropTargetDropEvent; import java.awt.dnd.DropTargetEvent; import java.awt.dnd.DropTargetListener; import java.awt.image.BufferedImage; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.IOException; import java.util.Arrays; import java.util.List; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.SwingUtilities; import org.biomart.builder.model.Column; import org.biomart.builder.model.Key; import org.biomart.builder.model.DataSet.DataSetColumn; import org.biomart.builder.model.Key.PrimaryKey; import org.biomart.builder.view.gui.diagrams.Diagram; import org.biomart.common.view.gui.dialogs.StackTrace; /** * Represents a key by listing out in a label each column in the key. * <p> * Drag-and-drop code courtesy of <a * href="http://www.javaworld.com/javaworld/jw-03-1999/jw-03-dragndrop.html?page=1">JavaWorld</a>. * * @author Richard Holland <holland@ebi.ac.uk> * @version $Revision: 1.28 $, $Date: 2007-11-09 11:36:28 $, modified by * $Author: rh4 $ * @since 0.5 */ public class KeyComponent extends BoxShapedComponent { private static final long serialVersionUID = 1; private static final Color PK_BACKGROUND_COLOUR = Color.CYAN; private static final Color FK_BACKGROUND_COLOUR = Color.YELLOW; /** * Constant referring to handmade key colour. */ public static Color HANDMADE_COLOUR = Color.GREEN; /** * Constant referring to incorrect key colour. */ public static Color INCORRECT_COLOUR = Color.LIGHT_GRAY; /** * Constant referring to normal key colour. */ public static Color NORMAL_COLOUR = Color.DARK_GRAY; private static final Font PLAIN_FONT = Font.decode("SansSerif-PLAIN-10"); private GridBagConstraints constraints; private GridBagLayout layout; private final PropertyChangeListener repaintListener = new PropertyChangeListener() { public void propertyChange(final PropertyChangeEvent e) { KeyComponent.this.needsRepaint = true; } }; private final PropertyChangeListener recalcListener = new PropertyChangeListener() { public void propertyChange(final PropertyChangeEvent e) { KeyComponent.this.needsRecalc = true; } }; private BufferedImage image = null; private final Rectangle rect2D = new Rectangle(); /** * The constructor constructs a key component around a given key object, and * associates it with the given display. * * @param key * the key to represent. * @param diagram * the diagram to draw the key on. */ public KeyComponent(final Key key, final Diagram diagram) { super(key, diagram); // Key components are set out in a vertical list. this.layout = new GridBagLayout(); this.setLayout(this.layout); // Constraints for each column in the key. this.constraints = new GridBagConstraints(); this.constraints.gridwidth = GridBagConstraints.REMAINDER; this.constraints.fill = GridBagConstraints.HORIZONTAL; this.constraints.anchor = GridBagConstraints.CENTER; this.constraints.insets = new Insets(0, 1, 0, 2); // Create the background colour. if (key instanceof PrimaryKey) this.setBackground(KeyComponent.PK_BACKGROUND_COLOUR); else this.setBackground(KeyComponent.FK_BACKGROUND_COLOUR); // Calculate the component layout. this.recalculateDiagramComponent(); // Repaint events. key.addPropertyChangeListener("directModified", this.repaintListener); // Recalc events. key.addPropertyChangeListener("columns", this.recalcListener); // Set up drag-and-drop capabilities. final DragSource dragSource = DragSource.getDefaultDragSource(); final DragSourceListener dsListener = new DragSourceListener() { public void dragEnter(DragSourceDragEvent e) { KeyComponent.this.paintImage(e.getLocation()); DragSourceContext context = e.getDragSourceContext(); int myaction = e.getDropAction(); if ((myaction & DnDConstants.ACTION_LINK) != 0) context.setCursor(DragSource.DefaultLinkDrop); else context.setCursor(DragSource.DefaultLinkNoDrop); } public void dragDropEnd(DragSourceDropEvent e) { KeyComponent.this.clearImage(); } public void dragExit(DragSourceEvent dse) { KeyComponent.this.paintImage(dse.getLocation()); DragSourceContext context = dse.getDragSourceContext(); context.setCursor(DragSource.DefaultLinkNoDrop); } public void dragOver(DragSourceDragEvent dsde) { this.dragEnter(dsde); } public void dropActionChanged(DragSourceDragEvent dsde) { this.dragEnter(dsde); } }; final DragGestureListener dgListener = new DragGestureListener() { public void dragGestureRecognized(DragGestureEvent e) { if (KeyComponent.this.isDraggable()) try { Transferable transferable = new KeyTransferable( ((KeyComponent) e.getComponent()).getKey()); JComponent lbl = (JComponent) e.getComponent(); KeyComponent.this.image = new BufferedImage(lbl .getWidth(), lbl.getHeight(), BufferedImage.TYPE_INT_ARGB_PRE); Graphics2D graphics = KeyComponent.this.image .createGraphics(); graphics.setComposite(AlphaComposite.getInstance( AlphaComposite.SRC_OVER, 0.5f)); lbl.paint(graphics); graphics.dispose(); e.startDrag(DragSource.DefaultLinkNoDrop, KeyComponent.this.image, new Point(0, 0), transferable, dsListener); } catch (final Throwable t) { SwingUtilities.invokeLater(new Runnable() { public void run() { StackTrace.showStackTrace(t); } }); } } }; final DropTargetListener dtListener = new DropTargetListener() { public void dragEnter(DropTargetDragEvent e) { if (this.isDragOk(e) == false) { e.rejectDrag(); return; } e.acceptDrag(DnDConstants.ACTION_LINK); } public void dragOver(DropTargetDragEvent e) { if (this.isDragOk(e) == false) { e.rejectDrag(); return; } e.acceptDrag(DnDConstants.ACTION_LINK); } public void dropActionChanged(DropTargetDragEvent e) { if (this.isDragOk(e) == false) { e.rejectDrag(); return; } e.acceptDrag(DnDConstants.ACTION_LINK); } public void dragExit(DropTargetEvent e) { } private boolean isDragOk(DropTargetDragEvent e) { DataFlavor[] flavors = KeyTransferable.flavors; DataFlavor chosen = null; for (int i = 0; i < flavors.length; i++) if (e.isDataFlavorSupported(flavors[i])) { chosen = flavors[i]; break; } if (chosen == null) return false; int sa = e.getSourceActions(); if ((sa & DnDConstants.ACTION_LINK) == 0) return false; return true; } public void drop(DropTargetDropEvent e) { DataFlavor[] flavors = KeyTransferable.flavors; DataFlavor chosen = null; for (int i = 0; i < flavors.length; i++) if (e.isDataFlavorSupported(flavors[i])) { chosen = flavors[i]; break; } if (chosen == null) { e.rejectDrop(); return; } int sa = e.getSourceActions(); if ((sa & DnDConstants.ACTION_LINK) == 0) { e.rejectDrop(); return; } Object data = null; try { data = e.getTransferable().getTransferData(chosen); if (data instanceof Key) { final Key sourceKey = (Key) data; final Key targetKey = KeyComponent.this.getKey(); if (!sourceKey.equals(targetKey)) { KeyComponent.this.getDiagram().getMartTab() .getSchemaTabSet().requestCreateRelation( sourceKey, targetKey); e.acceptDrop(DnDConstants.ACTION_LINK); e.dropComplete(true); } } } catch (final Throwable t) { SwingUtilities.invokeLater(new Runnable() { public void run() { StackTrace.showStackTrace(t); } }); e.rejectDrop(); e.dropComplete(false); } } }; new DropTarget(this, DnDConstants.ACTION_LINK, dtListener, true); dragSource.createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_LINK, dgListener); } private final void paintImage(final Point pt) { SwingUtilities.convertPointFromScreen(pt, this.getDiagram()); this.getDiagram().paintImmediately(this.rect2D.getBounds()); this.rect2D.setRect((int) pt.getX(), (int) pt.getY(), this.image .getWidth(), this.image.getHeight()); this.getDiagram().getGraphics().drawImage(this.image, (int) pt.getX(), (int) pt.getY(), this); } private final void clearImage() { this.getDiagram().paintImmediately(this.rect2D.getBounds()); } private Key getKey() { return (Key) this.getObject(); } public String getDisplayName() { return ""; } protected void doRecalculateDiagramComponent() { // Calculate new label. final StringBuffer sb = new StringBuffer(); for (int i = 0; i < this.getKey().getColumns().length; i++) { if (i > 0) sb.append(", "); final Column column = this.getKey().getColumns()[i]; sb .append(column instanceof DataSetColumn ? ((DataSetColumn) column) .getModifiedName() : column.getName()); } // Add the label. final JLabel label = new JLabel(sb.toString()); label.setFont(KeyComponent.PLAIN_FONT); this.layout.setConstraints(label, this.constraints); this.add(label); } private static class KeyTransferable implements Transferable { private static final DataFlavor keyFlavor = new DataFlavor(Key.class, "MartBuilder Schema Key") { private static final long serialVersionUID = 1L; }; private static final DataFlavor[] flavors = { KeyTransferable.keyFlavor }; private static final List flavorList = Arrays .asList(KeyTransferable.flavors); public synchronized DataFlavor[] getTransferDataFlavors() { return KeyTransferable.flavors; } public boolean isDataFlavorSupported(final DataFlavor flavor) { return KeyTransferable.flavorList.contains(flavor); } private Key key; private KeyTransferable(final Key key) { this.key = key; } public Object getTransferData(final DataFlavor flavor) throws UnsupportedFlavorException, IOException { if (KeyTransferable.keyFlavor.equals(flavor)) return this.key; else throw new UnsupportedFlavorException(flavor); } } }