// PathVisio, // a tool for data visualization and analysis using Biological Pathways // Copyright 2006-2011 BiGCaT Bioinformatics // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // package org.pathvisio.gui.view; import com.jgoodies.forms.builder.DefaultFormBuilder; import com.jgoodies.forms.layout.FormLayout; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.swing.Action; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JWindow; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.TransferHandler; import javax.swing.UIManager; import org.pathvisio.core.gui.PathwayTransferable; import org.pathvisio.core.gui.SwingKeyEvent; import org.pathvisio.core.gui.SwingMouseEvent; import org.pathvisio.core.gui.ToolTipProvider; import org.pathvisio.core.model.Pathway; import org.pathvisio.core.model.PathwayElement; import org.pathvisio.core.preferences.GlobalPreference; import org.pathvisio.core.preferences.PreferenceManager; import org.pathvisio.core.view.GraphicsShape; import org.pathvisio.core.view.Handle; import org.pathvisio.core.view.Label; import org.pathvisio.core.view.VElementMouseEvent; import org.pathvisio.core.view.VElementMouseListener; import org.pathvisio.core.view.VPathway; import org.pathvisio.core.view.VPathwayElement; import org.pathvisio.core.view.VPathwayEvent; import org.pathvisio.core.view.VPathwayListener; import org.pathvisio.core.view.VPathwayWrapper; import org.pathvisio.gui.MainPanel; import org.pathvisio.gui.dnd.PathwayImportHandler; /** * swing-dependent implementation of VPathway. */ public class VPathwaySwing extends JPanel implements VPathwayWrapper, MouseMotionListener, MouseListener, KeyListener, VPathwayListener, VElementMouseListener, MouseWheelListener { protected VPathway child; protected JScrollPane container; public VPathwaySwing(JScrollPane parent) { super(); if (parent == null) throw new IllegalArgumentException("parent is null"); this.container = parent; addMouseListener(this); addMouseMotionListener(this); addKeyListener(this); addMouseWheelListener(this); setFocusable(true); setRequestFocusEnabled(true); setTransferHandler(new PathwayImportHandler()); setDoubleBuffered( PreferenceManager.getCurrent().getBoolean( GlobalPreference.ENABLE_DOUBLE_BUFFERING)); } public void setChild(VPathway c) { child = c; child.addVPathwayListener(this); child.addVElementMouseListener(this); } public VPathway getChild() { return child; } public Dimension getViewportSize() { if (container instanceof JScrollPane) { return ((JScrollPane) container).getViewport().getExtentSize(); } return getSize(); } /** * Schedule redraw of the entire visible area */ public void redraw() { repaint(); } /** * Draw immediately */ protected void paintComponent(Graphics g) { if(child != null) child.draw((Graphics2D) g); } /** * Schedule redraw of a certain part of the pathway */ public void redraw(Rectangle r) { repaint(r); } public void mouseClicked(MouseEvent e) { if(e.getClickCount() == 2) { child.mouseDoubleClick(new SwingMouseEvent(e)); } } public void mouseEntered(MouseEvent e) { //requestFocus(); //TODO: test if this is needed in applet. child.mouseEnter(new SwingMouseEvent(e)); } public void mouseExited(MouseEvent e) { child.mouseExit(new SwingMouseEvent(e)); } public void mousePressed(MouseEvent e) { requestFocus(); child.mouseDown(new SwingMouseEvent(e)); } public void mouseReleased(MouseEvent e) { child.mouseUp(new SwingMouseEvent(e)); } public void keyPressed(KeyEvent e) { child.keyPressed(new SwingKeyEvent(e)); } public void keyReleased(KeyEvent e) { child.keyReleased(new SwingKeyEvent(e)); } public void keyTyped(KeyEvent e) { // TODO: find out how to handle this one } public void mouseDragged(MouseEvent e) { Rectangle r = container.getViewport().getViewRect(); final int stepSize = 10; int newx = (int)r.getMinX(); int newy = (int)r.getMinY(); // scroll when dragging out of view if (e.getX() > r.getMaxX()) { newx += stepSize; } if (e.getX() < r.getMinX()) { newx = Math.max (newx - stepSize, 0); } if (e.getY() > r.getMaxY()) { newy += stepSize; } if (e.getY() < r.getMinY()) { newy = Math.max (newy - stepSize, 0); } container.getViewport().setViewPosition( new Point (newx, newy) ); child.mouseMove(new SwingMouseEvent(e)); } public void mouseMoved(MouseEvent e) { child.mouseMove(new SwingMouseEvent(e)); } public void mouseWheelMoved(MouseWheelEvent e) { int notches = e.getWheelRotation(); if(notches < 0) { child.zoomToCursor(child.getPctZoom() * 21 / 20, e.getPoint()); } else { child.zoomToCursor(child.getPctZoom() * 20 / 21, e.getPoint()); } Component comp = container.getParent().getParent(); if (comp instanceof MainPanel) ((MainPanel)comp).updateZoomCombo(); } public void registerKeyboardAction(KeyStroke k, Action a) { super.registerKeyboardAction(a, k, WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); //super.registerKeyboardAction(a, k, WHEN_IN_FOCUSED_WINDOW); } public VPathway createVPathway() { setChild(new VPathway(this)); return child; } public void vPathwayEvent(VPathwayEvent e) { switch(e.getType()) { case MODEL_LOADED: if(e.getSource() == child) { SwingUtilities.invokeLater(new Runnable() { public void run() { container.setViewportView(VPathwaySwing.this); resized(); VPathwaySwing.this.requestFocus(); } }); } break; case ELEMENT_HOVER: showToolTip(e); break; } } /** * paste from clip board with the common shift */ public void pasteFromClipboard() { Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard(); TransferHandler handler = getTransferHandler(); handler.importData(this, clip.getContents(this)); } /** * paste from clip board at the cursor position */ public void positionPasteFromClipboard(Point cursorPosition) { Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard(); PathwayImportHandler handler = (PathwayImportHandler) getTransferHandler(); handler.importDataAtCursorPosition(this, clip.getContents(this), cursorPosition); } List lastCopied; public void copyToClipboard(Pathway source, List copyElements) { Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard(); clip.setContents(new PathwayTransferable(source, copyElements), (PathwayImportHandler)getTransferHandler()); ((PathwayImportHandler)getTransferHandler()).obtainedOwnership(); } Set toolTipProviders = new HashSet(); public void addToolTipProvider(ToolTipProvider p) { toolTipProviders.add(p); } public void showToolTip(final VPathwayEvent e) { if(toolTipProviders.size() == 0) return; SwingUtilities.invokeLater(new Runnable() { public void run() { List elements = e.getAffectedElements(); if(elements.size() > 0) { PathwayToolTip tip = new PathwayToolTip(elements); if(!tip.hasContent()) return; final JWindow w = new JWindow(); w.setLayout(new BorderLayout()); w.add(tip, BorderLayout.CENTER); Point p = e.getMouseEvent().getLocation(); SwingUtilities.convertPointToScreen(p, VPathwaySwing.this); w.setLocation(p); //For some reason the mouse listener only works when //adding it directly to the scrollpane (the first child component). tip.getComponent(0).addMouseListener(new MouseAdapter() { public void mouseExited(MouseEvent e) { //Don't dispose if on scrollbars if(e.getComponent().contains(e.getPoint())) return; //Don't dispose if mouse is down (usually when scrolling) if((e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) != 0) return; w.dispose(); } }); w.setVisible(true); w.pack(); } } }); } class PathwayToolTip extends JPanel { private boolean hasContent; public PathwayToolTip(List elements) { applyToolTipStyle(this); setLayout(new BorderLayout()); DefaultFormBuilder builder = new DefaultFormBuilder(new FormLayout("pref")); for(ToolTipProvider p : toolTipProviders) { Component c = p.createToolTipComponent(this, elements); if(c != null) { hasContent = true; builder.append(c); builder.nextLine(); } } JPanel contents = builder.getPanel(); applyToolTipStyle(contents); JScrollPane scroll = new JScrollPane(contents, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); int w = contents.getPreferredSize().width + scroll.getVerticalScrollBar().getPreferredSize().width + 5; int h = contents.getPreferredSize().height + scroll.getHorizontalScrollBar().getPreferredSize().height + 5; w = Math.min(400, w); h = Math.min(500, h); setPreferredSize(new Dimension(w, h)); add(scroll, BorderLayout.CENTER); } public boolean hasContent() { return hasContent; } } /** * Apply the tooltip style (e.g. colors) to the given component. */ public static void applyToolTipStyle(JComponent c) { c.setForeground((Color)UIManager.get("ToolTip.foreground")); c.setBackground((Color)UIManager.get("ToolTip.background")); c.setFont((Font)UIManager.get("ToolTip.font")); } public void scrollTo(Rectangle r) { container.getViewport().scrollRectToVisible(r); } public void vElementMouseEvent(VElementMouseEvent e) { //Change mouse cursor based on underlying object if( e.getElement() instanceof Handle) { if(e.getType() == VElementMouseEvent.TYPE_MOUSEENTER) { Handle h = (Handle) e.getElement(); if(h.getAngle() == 1) setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); else setCursor(Cursor.getPredefinedCursor(calculateCursorStyle(e))); } else if(e.getType() == VElementMouseEvent.TYPE_MOUSEEXIT) { setCursor(Cursor.getDefaultCursor()); } } else if (e.getElement() instanceof Label) { if(e.getType() == VElementMouseEvent.TYPE_MOUSE_SHOWHAND) { setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); } else if(e.getType() == VElementMouseEvent.TYPE_MOUSE_NOTSHOWHAND) { setCursor(Cursor.getDefaultCursor()); } } } /** * calculates the corresponding cursor type * depending on the angle of the current handle object * and the rotation of the object * @param e * @return */ private int calculateCursorStyle(VElementMouseEvent e) { Handle h = (Handle) e.getElement(); if(h.getParent() instanceof GraphicsShape) { GraphicsShape gs = (GraphicsShape) h.getParent(); double rotation = gs.getPathwayElement().getRotation(); double degrees = h.getAngle() + (rotation * (180 / Math.PI)); if(degrees > 360) degrees = degrees - 360; if(h.getAngle() == 1) { return Cursor.MOVE_CURSOR; } else { switch (h.getFreedom()) { case X: return getXYCursorPosition(degrees); case Y: return getXYCursorPosition(degrees); case FREE: return getFREECursorPosition(degrees); case NEGFREE: return getFREECursorPosition(degrees); } } } return Cursor.DEFAULT_CURSOR; } private int getXYCursorPosition(double degrees) { if(degrees < 45 || degrees > 315) { return Cursor.E_RESIZE_CURSOR; } else if (degrees < 135) { return Cursor.S_RESIZE_CURSOR; } else if (degrees < 225) { return Cursor.W_RESIZE_CURSOR; } else if (degrees < 315) { return Cursor.N_RESIZE_CURSOR; } return Cursor.DEFAULT_CURSOR; } private int getFREECursorPosition(double degrees) { if(degrees < 90) { return Cursor.SE_RESIZE_CURSOR; } else if(degrees < 180) { return Cursor.SW_RESIZE_CURSOR; } else if (degrees < 270) { return Cursor.NW_RESIZE_CURSOR; } else if (degrees <= 360) { return Cursor.NE_RESIZE_CURSOR; } return Cursor.DEFAULT_CURSOR; } private boolean disposed = false; public void dispose() { assert (!disposed); if (container != null && container instanceof JScrollPane) ((JScrollPane)container).remove(this); child.removeVPathwayListener(this); child.removeVElementMouseListener(this); removeMouseListener(this); removeMouseMotionListener(this); removeKeyListener(this); setTransferHandler(null); // clean up actions, inputs registered earlier for this component // (otherwise VPathway won't get GC'ed, because actions contain reference to VPathay) getActionMap().clear(); getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).clear(); child = null; // free VPathway for GC disposed = true; } private int oldVWidth = 0; private int oldVHeight = 0; public void resized() { int vw = (int)child.getVWidth(); int vh = (int)child.getVHeight(); if (vw != oldVWidth || vh != oldVHeight) { oldVWidth = vw; oldVHeight = vh; setPreferredSize(new Dimension(vw, vh)); revalidate(); repaint(); } } public Rectangle getViewRect() { return container.getViewport().getViewRect(); } public void scrollCenterTo(int x, int y) { int w = container.getViewport().getWidth(); int h = container.getViewport().getHeight(); Rectangle r = new Rectangle(x - (w / 2), y - (h / 2), w - 1, h - 1); scrollRectToVisible(r); } }