/* * CCVisu is a tool for visual graph clustering * and general force-directed graph layout. * This file is part of CCVisu. * * Copyright (C) 2005-2012 Dirk Beyer * * CCVisu 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. * * CCVisu 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with CCVisu; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Please find the GNU Lesser General Public License in file * license_lgpl.txt or http://www.gnu.org/licenses/lgpl.txt * * Dirk Beyer (firstname.lastname@uni-passau.de) * University of Passau, Bavaria, Germany */ package org.sosy_lab.ccvisu.ui; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.RenderingHints; import java.awt.Window; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.InputEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; import java.util.EventObject; import java.util.HashSet; import java.util.Set; import javax.swing.JPanel; import javax.swing.event.MouseInputAdapter; import org.sosy_lab.ccvisu.Options; import org.sosy_lab.ccvisu.Options.OptionsEnum; import org.sosy_lab.ccvisu.graph.GraphData; import org.sosy_lab.ccvisu.graph.GraphEdge; import org.sosy_lab.ccvisu.graph.GraphVertex; import org.sosy_lab.ccvisu.graph.GraphicLayoutInfo; import org.sosy_lab.ccvisu.graph.Position; import org.sosy_lab.ccvisu.graph.interfaces.GraphEventListener; import org.sosy_lab.ccvisu.writers.WriterDataLayoutDISP; /** * Frame implementation for displaying the layout on the screen device. * Used by WriterDataGraphicsDISP. */ public class FrameDisplay implements GraphEventListener { private final Container mFrame; private final Options options; /** Canvas for graphics.*/ private final GraphCanvas canvas; // Coordinates for zooming rectangle. private Point rectTopLeft = new Point(0, 0); private Point rectBottomRight = new Point(0, 0); private boolean rectShow = false; // Coordinates of the mouse when MOUSE_PRESSED. private int mouseX; private int mouseY; private int tolerance; // Node id when MOUSE_PRESSED. private GraphVertex draggingVertex = null; private GraphVertex chosenVertex = null; /** * Constructor. * @param writer The writer that uses this frame to display the layout. */ public FrameDisplay(final WriterDataLayoutDISP writer, Container frame) { options = writer.getOptions(); if (options.frameDisplayMouseAdapter == null) { // Set default mouse listener that allows CCVisu to process all events by itself options.frameDisplayMouseAdapter = new AbstractFrameDisplayMouseAdapter(); } // The frame. this.mFrame = frame; // The graphics canvas. canvas = new GraphCanvas(writer, this); // Add canvas to the frames content pane. mFrame.add(canvas); mFrame.addComponentListener(new ComponentListener() { @Override public void componentResized(ComponentEvent pE) { // Repaint the canvas after the frame has been resized. canvas.updateAndPaint(); } @Override public void componentMoved(ComponentEvent e) { } @Override public void componentShown(ComponentEvent e) { } @Override public void componentHidden(ComponentEvent e) { } }); // Show canvas. if (mFrame instanceof Window) { ((Window)mFrame).setVisible(true); } } // constructor /** * repaint when the graph change * @param evt a GraphEvent */ @Override public void onGraphChangedEvent(EventObject evt) { this.canvas.updateAndPaint(); } /** * @return the canvas */ public GraphCanvas getCanvas() { return this.canvas; } public enum LoadDirection { NEXT, PREV } /** * Canvas implementation for displaying the layout on the screen. */ public class GraphCanvas extends JPanel { private static final long serialVersionUID = 200510192212L; private final Component parentFrame; private final WriterDataLayoutDISP writer; private FrameDisplayMenu frameDisplayMenu; private final GraphData graph; // image used for off-screen work private BufferedImage img; // dimension of the image used private Dimension size = new Dimension(0, 0); private boolean requiresReRender = true; /** * Constructor. * @param parentFrame The parent frame. * @param writer The writer that uses this object to draw on. * The painting is delegated to the writer object. */ private GraphCanvas(final WriterDataLayoutDISP writer, FrameDisplay parentFrameDisplay) { this.graph = writer.getOptions().graph; this.parentFrame = mFrame; this.writer = writer; // Creating the menu frameDisplayMenu = new FrameDisplayMenu(this, graph); add(frameDisplayMenu.createMenu()); // Adds MouseMotionListener for mouse event ''Mouse moved on vertex''. // Show the name(s) of the vertex(vertices) in the vertexNameDialog. addMouseMotionListener(new MouseInputAdapter() { @Override public void mouseMoved(MouseEvent evt) { if (!options.frameDisplayMouseAdapter.mouseMoved(evt, writer)) { return; } if (options.getOption(OptionsEnum.enableToolTips).getBool()) { Set<String> verticesSelected = writer.getNames(evt.getPoint(), false); String vertexToolTip = null; if (verticesSelected.size() > 0) { vertexToolTip = writer.getGraphData().getVertexByName(verticesSelected.iterator().next()).getTooltip(); if (vertexToolTip.length() > 100) { vertexToolTip = vertexToolTip.substring(0, 100) + "..."; } } writer.getDisplay().getCanvas().setToolTipText(vertexToolTip); } } @Override public void mouseDragged(MouseEvent evt) { if (rectShow) { // Zooming rectangle, set end corner. rectBottomRight.setLocation(evt.getPoint()); repaint(); } // Vertex dragging if ((draggingVertex != null) && (!options.getOption(OptionsEnum.disableVertexDragging).getBool())) { Dimension lSize = getDrawingAreaSize(); GraphicLayoutInfo graphicLayoutInfo = writer.calculateOffsetAndScale(lSize); Position eventPos = new Position(evt.getPoint().x, evt.getPoint().y, 0); Position vertexPos = graphicLayoutInfo.mapToLayout(eventPos); if (draggingVertex != null) { draggingVertex.setPosition(vertexPos); // Default implementation should always return true if (options.frameDisplayMouseAdapter.vertexDragged(evt, writer, draggingVertex)) { updateAndPaint(); } } } } }); // Adds MouseListener for mouse event ''Mouse clicked on vertex''. // Draw the name(s) of the vertex(vertices) as annotation on the canvas. addMouseListener(new MouseAdapter() { private void handlePopupTrigger(MouseEvent evt) { // MacOs X and Windows trigger different events when // the right mouse button is pressed. // Therefore the popup-menu code has been moved to a separate function. // See: http://developer.apple.com/library/mac/#documentation/Java/Conceptual/ // Java14Development/07-NativePlatformIntegration/NativePlatformIntegration.htm // Pop-up triggered -> show the PopupMenu Set<String> vertexRelations = new HashSet<String>(); Set<String> tmp = writer.getNames(evt.getPoint(), false); if (!tmp.isEmpty()) { // Vertex chosen with pop-up menu chosenVertex = writer.getGraphData().getVertexByName(tmp.iterator().next()); // Add relations to pop-up menu for (GraphEdge e : options.graph.getAdjacent(chosenVertex, ".*")) { vertexRelations.add(e.getRelName()); } } else { chosenVertex = null; } frameDisplayMenu.updateAndShowMenu(evt, vertexRelations, chosenVertex); } @Override public void mousePressed(MouseEvent evt) { if (!options.frameDisplayMouseAdapter.mousePressed(evt, writer)) { return; } if (evt.isPopupTrigger()) { handlePopupTrigger(evt); } else if (evt.getButton() == MouseEvent.BUTTON1) { mouseX = evt.getX(); mouseY = evt.getY(); tolerance = Math.max(getHeight(), getWidth()); tolerance /= 300; Set<String> tmp = writer.getNames(evt.getPoint(), false); if (tmp.isEmpty()) { rectTopLeft.setLocation(evt.getPoint()); rectShow = true; draggingVertex = null; } else { // Vertex dragging. draggingVertex = graph.getVertexByName(tmp.iterator().next()); } } } @Override public void mouseReleased(MouseEvent evt) { if (evt.isPopupTrigger()) { handlePopupTrigger(evt); } else if (evt.getButton() == MouseEvent.BUTTON1) { int x = evt.getX(); int y = evt.getY(); if (Math.abs(x - mouseX) > tolerance || Math.abs(y - mouseY) > tolerance) { if (rectShow) { rectShow = false; rectBottomRight.setLocation(evt.getPoint()); // Switch coordinates if top-left is not top-left. int xTl = Math.min(rectTopLeft.x, rectBottomRight.x); int xBr = Math.max(rectTopLeft.x, rectBottomRight.x); int yTl = Math.min(rectTopLeft.y, rectBottomRight.y); int yBr = Math.max(rectTopLeft.y, rectBottomRight.y); rectTopLeft.setLocation(xTl, yTl); rectBottomRight.setLocation(xBr, yBr); writer.restrictShowedVertices(rectTopLeft, rectBottomRight); updateAndPaint(); } } else { rectShow = false; if (writer.getOptions().frameDisplayMouseAdapter != null) { if (!writer.getOptions().frameDisplayMouseAdapter.mouseReleased(evt, writer)) { return; } } if (options.getOption(OptionsEnum.openURL).getBool() && (evt.getModifiersEx() == InputEvent.CTRL_DOWN_MASK)) { writer.openURL(evt.getPoint()); } else if (writer.toggleNames(evt.getPoint()) > 0) { //if something changed then recompute the img updateAndPaint(); } } } } }); } public Dimension getDrawingAreaSize() { int xSize = getSize().width - mFrame.getInsets().left - mFrame.getInsets().right; int ySize = getSize().height - mFrame.getInsets().top - mFrame.getInsets().bottom; Dimension size = null; if (options.getOption(OptionsEnum.stretchGraphToBounds).getBool()) { size = new Dimension(xSize, ySize); } else { int minSize = Math.min(xSize, ySize); size = new Dimension(minSize, minSize); } return size; } public void zoomOut() { writer.resetRestriction(); canvas.updateAndPaint(); } /** * Draws the layout on the screen. * @param area The graphics area for drawing. */ @Override public void paint(Graphics area) { // Size info. setSize(parentFrame.getSize()); // TODO: Enhance Performance. Some Tasks need only be done if // the graph data/layout has really changed. if (requiresReRender) { requiresReRender = false; // Adjust background color. setBackground(options.backColor.get()); // Draw "Refreshing..." in the center of the canvas. if (options.getOption(OptionsEnum.showRefreshingLabel).getBool()) { // Calculate the center position. int x = this.getWidth() / 2 - 70; int y = this.getHeight() / 2; // Draw it. area.setColor(canvas.writer.getWriteColor()); area.setFont(new Font("SansSerif", Font.ITALIC, 30)); area.drawString("Refreshing...", x, y); } setSize(parentFrame.getSize()); int xSize = getSize().width - mFrame.getInsets().left - mFrame.getInsets().right; int ySize = getSize().height - mFrame.getInsets().top - mFrame.getInsets().bottom; // (Re-)create the image buffer object. if (img == null || xSize != size.width || ySize != size.height) { size = new Dimension(xSize, ySize); img = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_RGB); } // Enable anti-aliasing. // WARNING GCJ: Graphics2D needs gcc 4 Graphics2D imgArea = (Graphics2D) img.getGraphics(); imgArea.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); imgArea.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // Set the font-style. imgArea.setFont(new Font("SansSerif", Font.PLAIN, options.getOption(OptionsEnum.fontSize).getInt())); // Fill the background. imgArea.setColor(this.getBackground()); imgArea.fillRect(0, 0, size.width, size.height); imgArea.setColor(Color.BLACK); // Get the size of the area to draw. Dimension drawingAreaSize = getDrawingAreaSize(); // Draw the graph. writer.writeDISP(drawingAreaSize, imgArea, size.width, size.height, mFrame.getInsets().left, mFrame.getInsets().bottom); //free some resources imgArea.dispose(); } //draw img on area area.drawImage(img, 0, 0, null); // Zooming rectangle. if (rectShow) { int x = (int) rectTopLeft.getX(); int y = (int) rectTopLeft.getY(); int width = (int) (rectBottomRight.getX() - rectTopLeft.getX()); int height = (int) (rectBottomRight.getY() - rectTopLeft.getY()); if (width < 0) { width = Math.abs(width); x = (int) rectBottomRight.getX(); } if (height < 0) { height = Math.abs(height); y = (int) rectBottomRight.getY(); } area.drawRect(x, y, width, height); } } // method paint /** * to use when changes are done and you want to display them * e.g. the user wants to zoom into the graph. */ public void updateAndPaint() { // Use of the graphics-object is only allowed inside the paint-method! // Graphics area = this.getGraphics(); // // this.updateImage(area); this.requiresReRender = true; this.invalidate(); this.repaint(); } /** * @return the writer */ public WriterDataLayoutDISP getWriter() { return this.writer; } /** * @return the parentFrame */ public Component getParentFrame() { return this.parentFrame; } } }