// Copyright (c) 2006 - 2008, Markus Strauch.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
package net.sf.sdedit.ui;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.util.LinkedList;
import java.util.List;
import javax.swing.JPanel;
import javax.swing.ToolTipManager;
import javax.swing.event.MouseInputListener;
import net.sf.sdedit.Constants;
import net.sf.sdedit.diagram.Diagram;
import net.sf.sdedit.diagram.Lifeline;
import net.sf.sdedit.diagram.PaintDevice;
import net.sf.sdedit.drawable.Drawable;
import net.sf.sdedit.drawable.ExtensibleDrawable;
import net.sf.sdedit.drawable.Fragment;
import net.sf.sdedit.ui.components.ZoomPane;
import net.sf.sdedit.ui.components.Zoomable;
/**
* A <tt>PanelPaintDevice</tt> is a <tt>PaintDevice</tt> implementation that
* uses a <tt>JPanel</tt> (see {@linkplain #getPanel()}) for displaying
* diagrams. Its main benefit is that - in contrast to other
* <tt>PaintDevice</tt> implementations - it does not allocate extra memory.
*
* @author Markus Strauch
*
*/
public class PanelPaintDevice extends PaintDevice implements
MouseInputListener, Constants {
/**
* FontMetrics of the normal font.
*/
private FontMetrics fontMetrics;
/**
* FontMetrics of the bold font.
*/
private FontMetrics boldFontMetrics;
/**
* The drawable object that the mouse move most recently over.
*/
private Drawable lastDrawableMovedOver;
/**
* The unscaled size of the diagram.
*/
private Dimension size;
/**
* A set of listeners.
*/
private final List<PanelPaintDeviceListener> listeners;
/**
* The panel for displaying the diagram.
*/
private final Panel panel;
/**
* Flag denoting if this is an interactive <tt>PanelPaintDevice</tt> that
* reacts to mouse movement and clicks.
*/
private final boolean interactive;
private boolean antialias;
private boolean blank;
/**
* Creates a new <tt>PanelPaintDevice</tt>.
*
* @param interactive
* flag denoting if the <tt>PanelPaintDevice</tt> is
* interactive, meaning that it reacts to mouse movement and
* clicks
*/
public PanelPaintDevice(boolean interactive) {
super();
this.interactive = interactive;
listeners = new LinkedList<PanelPaintDeviceListener>();
panel = new Panel();
ToolTipManager.sharedInstance().registerComponent(panel);
antialias = true;
blank = true;
}
public void setDiagram(Diagram diagram) {
super.setDiagram(diagram);
Graphics preGraphics = new BufferedImage(1, 1,
BufferedImage.TYPE_INT_RGB).getGraphics();
preGraphics.setFont(getFont(false));
fontMetrics = preGraphics.getFontMetrics();
Graphics boldGraphics = new BufferedImage(1, 1,
BufferedImage.TYPE_INT_RGB).getGraphics();
boldGraphics.setFont(getFont(true));
boldFontMetrics = boldGraphics.getFontMetrics();
}
/**
* This method is called when the mouse moved over the panel. If it has
* exited or entered a drawable object,
* {@linkplain PanelPaintDeviceListener}s are notified.
*
* @param e
*/
public void mouseMoved(MouseEvent e) {
if (interactive) {
JPanel zp = panel.getZoomPane().getPanel();
Point point = e.getPoint();
if (lastDrawableMovedOver != null) {
if (!lastDrawableMovedOver.contains(point)) {
for (PanelPaintDeviceListener listener : listeners) {
listener.mouseExitedDrawable(lastDrawableMovedOver);
zp.setCursor(Cursor.getDefaultCursor());
}
} else {
return;
}
}
lastDrawableMovedOver = null;
for (Drawable drawable : this) {
if (!(drawable instanceof Fragment)) {
if (drawable.contains(point)) {
lastDrawableMovedOver = drawable;
for (PanelPaintDeviceListener listener : listeners) {
if (listener.mouseEnteredDrawable(drawable)) {
zp.setCursor(HAND_CURSOR);
}
}
return;
}
}
}
}
}
/**
* Adds a <tt>PanelPaintDeviceListener</tt> that will be notified when the
* mouse enters, exits or clicks drawable objects
*
* @param ppdl
* a a <tt>PanelPaintDeviceListener</tt>
*/
public void addListener(PanelPaintDeviceListener ppdl) {
listeners.add(ppdl);
}
/**
* @see net.sf.sdedit.diagram.PaintDevice#close()
*/
public void close() {
super.close();
size = new Dimension(getWidth(), getHeight());
}
/**
* @see net.sf.sdedit.diagram.PaintDevice#getTextHeight(boolean)
*/
public int getTextHeight(boolean bold) {
return (bold ? boldFontMetrics : fontMetrics).getHeight();
}
/**
* @see net.sf.sdedit.diagram.PaintDevice#getTextWidth(java.lang.String,
* boolean)
*/
public int getTextWidth(String text, boolean bold) {
return (bold ? boldFontMetrics : fontMetrics).stringWidth(text);
}
/**
* Returns the panel for displaying the diagram.
*
* @return the panel for displaying the diagram
*/
public Zoomable<JPanel> getPanel() {
return panel;
}
public void setAntialiasing(boolean on) {
antialias = on;
}
public boolean isBlank() {
return blank;
}
/**
* Notifies all {@linkplain PanelPaintDeviceListener}s when the mouse has
* clicked a drawable object.
*
* @param e
*/
public void mouseClicked(MouseEvent e) {
if (lastDrawableMovedOver != null) {
for (PanelPaintDeviceListener listener : listeners) {
listener.mouseClickedDrawable(lastDrawableMovedOver);
}
}
}
/**
* @see java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent)
*/
public void mouseDragged(MouseEvent e) {
}
/**
* @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
*/
public void mouseEntered(MouseEvent e) {
}
/**
* @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
*/
public void mouseExited(MouseEvent e) {
}
/**
* @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
*/
public void mousePressed(MouseEvent e) {
}
/**
* @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
*/
public void mouseReleased(MouseEvent e) {
}
@SuppressWarnings("serial")
private final class Panel extends JPanel implements Zoomable<JPanel> {
Panel() {
addMouseMotionListener(PanelPaintDevice.this);
addMouseListener(PanelPaintDevice.this);
}
private ZoomPane zoomPane;
public ZoomPane getZoomPane() {
return zoomPane;
}
public void setZoomPane(ZoomPane zoomPane) {
this.zoomPane = zoomPane;
}
public JPanel asJComponent() {
return this;
}
public Dimension getPreferredSize() {
return getSize();
}
public Dimension getSize() {
if (size == null) {
return super.getSize();
}
return size;
}
@Override
public int getWidth() {
return getAbsoluteWidth();
}
@Override
public int getHeight() {
return getAbsoluteHeight();
}
public int getAbsoluteWidth() {
if (size == null) {
return super.getWidth();
}
return size.width;
}
public int getAbsoluteHeight() {
if (size == null) {
return super.getHeight();
}
return size.height;
}
@Override
public String getToolTipText(MouseEvent e) {
String text = null;
Point mousePoint = e.getPoint();
for (Drawable drawable : PanelPaintDevice.this) {
if (drawable instanceof ExtensibleDrawable) {
ExtensibleDrawable ed = (ExtensibleDrawable) drawable;
if (ed.contains(mousePoint)) {
Lifeline lifeline = ed.getLifeline();
text = lifeline.toString();
if (lifeline.getDiagram().getConfiguration()
.isThreaded()
&& !lifeline.isAlwaysActive()
&& (ed instanceof net.sf.sdedit.drawable.Rectangle)) {
text = text + " [thread=" + lifeline.getThread()
+ "]";
}
}
}
}
return text;
}
@Override
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g.create();
Rectangle clipBounds = g2.getClipBounds();
g2.setColor(Color.WHITE);
g2.fill(clipBounds);
blank = isEmpty();
if (!isEmpty()) {
g2.setFont(PanelPaintDevice.this.getFont(false));
if (antialias) {
g2.setRenderingHints(new RenderingHints(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON));
}
g2.setColor(Color.BLACK);
for (Drawable drawable : PanelPaintDevice.this) {
if (drawable.intersects(clipBounds)) {
drawable.draw(g2);
}
}
}
g2.dispose();
}
}
}