/*
* 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;
}
}
}