package prefuse;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
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.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JComponent;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.JToolTip;
import javax.swing.KeyStroke;
import javax.swing.text.JTextComponent;
import prefuse.activity.Activity;
import prefuse.activity.SlowInSlowOutPacer;
import prefuse.controls.Control;
import prefuse.data.expression.AndPredicate;
import prefuse.data.expression.BooleanLiteral;
import prefuse.data.expression.Predicate;
import prefuse.data.expression.parser.ExpressionParser;
import prefuse.render.Renderer;
import prefuse.util.ColorLib;
import prefuse.util.StringLib;
import prefuse.util.UpdateListener;
import prefuse.util.collections.CopyOnWriteArrayList;
import prefuse.util.display.BackgroundPainter;
import prefuse.util.display.Clip;
import prefuse.util.display.DebugStatsPainter;
import prefuse.util.display.ExportDisplayAction;
import prefuse.util.display.ItemBoundsListener;
import prefuse.util.display.PaintListener;
import prefuse.util.display.RenderingQueue;
import prefuse.visual.VisualItem;
import prefuse.visual.expression.VisiblePredicate;
import prefuse.visual.sort.ItemSorter;
/**
* <p>User interface component that provides an interactive view onto
* a visualization. The Display is responsible for drawing items to the
* screen and providing callbacks for user interface actions such as
* mouse and keyboard events. A Display must be associated with an
* {@link prefuse.Visualization} from which it pulls the items to visualize.
* </p>
*
* <p>To control which {@link prefuse.visual.VisualItem} instances are
* drawn, the Display also maintains an optional
* {@link prefuse.data.expression.Predicate} for filtering items. The
* drawing order of items is
* controlled by an {@link prefuse.visual.sort.ItemSorter} instance,
* which calculates a score for each item. Items with higher scores
* are drawn later, and hence on top of lower scoring items.
* </p>
*
* <p>The {@link prefuse.controls.Control Control}
* interface provides the user interface callbacks for supporting
* interaction. The {@link prefuse.controls} package contains a number
* of pre-built <code>Control</code> implementations for common
* interactions.</p>
*
* <p>The Display class also supports arbitrary graphics transforms through
* the <code>java.awt.geom.AffineTransform</code> class. The
* {@link #setTransform(java.awt.geom.AffineTransform) setTransform} method
* allows arbitrary transforms to be applied, while the
* {@link #pan(double,double) pan} and
* {@link #zoom(java.awt.geom.Point2D,double) zoom}
* methods provide convenience methods that appropriately update the current
* transform to achieve panning and zooming of the presentation space.</p>
*
* <p>Additionally, each Display instance also supports use of a text editor
* to facilitate direct editing of text. See the various
* {@link #editText(prefuse.visual.VisualItem, String)} methods.</p>
*
* @version 1.0
* @author <a href="http://jheer.org">jeffrey heer</a>
* @see Visualization
* @see prefuse.controls.Control
* @see prefuse.controls
*/
public class Display extends JComponent {
private static final Logger s_logger
= Logger.getLogger(Display.class.getName());
// visual item source
protected Visualization m_vis;
protected AndPredicate m_predicate = new AndPredicate();
// listeners
protected CopyOnWriteArrayList m_controls = new CopyOnWriteArrayList();
protected CopyOnWriteArrayList m_painters;
protected CopyOnWriteArrayList m_bounders;
// display
protected BufferedImage m_offscreen;
protected Clip m_clip = new Clip();
protected Clip m_screen = new Clip();
protected Clip m_bounds = new Clip();
protected Rectangle2D m_rclip = new Rectangle2D.Double();
protected boolean m_damageRedraw = true;
protected boolean m_highQuality = false;
// optional background image
protected BackgroundPainter m_bgpainter = null;
// rendering queue
protected RenderingQueue m_queue = new RenderingQueue();
protected int m_visibleCount = 0;
// transform variables
protected AffineTransform m_transform = new AffineTransform();
protected AffineTransform m_itransform = new AffineTransform();
protected TransformActivity m_transact = new TransformActivity();
protected Point2D m_tmpPoint = new Point2D.Double();
// frame count and debugging output
protected double frameRate;
protected int nframes = 0;
private int sampleInterval = 10;
private long mark = -1L;
/* Custom tooltip, null to use regular tooltip mechanisms */
protected JToolTip m_customToolTip = null;
// text editing variables
private JTextComponent m_editor;
private boolean m_editing;
private VisualItem m_editItem;
private String m_editAttribute;
/**
* Creates a new Display instance. You will need to associate this
* Display with a {@link Visualization} for it to display anything.
*/
public Display() {
this(null);
}
/**
* Creates a new Display associated with the given Visualization.
* By default, all {@link prefuse.visual.VisualItem} instances in the
* {@link Visualization} will be drawn by the Display.
* @param visualization the {@link Visualization} backing this Display
*/
public Display(Visualization visualization) {
this(visualization, (Predicate)null);
}
/**
* Creates a new Display associated with the given Visualization that
* draws all VisualItems in the visualization that pass the given
* Predicate. The predicate string will be parsed by the
* {@link prefuse.data.expression.parser.ExpressionParser} to get a
* {@link prefuse.data.expression.Predicate} instance.
* @param visualization the {@link Visualization} backing this Display
* @param predicate a predicate expression in the prefuse expression
* language. This expression will be parsed; if the parsing fails or does
* not result in a Predicate instance, an exception will result.
*/
public Display(Visualization visualization, String predicate) {
this(visualization,
(Predicate)ExpressionParser.parse(predicate, true));
}
/**
* Creates a new Display associated with the given Visualization that
* draws all VisualItems in the visualization that pass the given
* Predicate.
* @param visualization the {@link Visualization} backing this Display
* @param predicate the filtering {@link prefuse.data.expression.Predicate}
*/
public Display(Visualization visualization, Predicate predicate) {
setDoubleBuffered(false);
setBackground(Color.WHITE);
// initialize text editor
m_editing = false;
m_editor = new JTextField();
m_editor.setBorder(null);
m_editor.setVisible(false);
this.add(m_editor);
// register input event capturer
InputEventCapturer iec = new InputEventCapturer();
addMouseListener(iec);
addMouseMotionListener(iec);
addMouseWheelListener(iec);
addKeyListener(iec);
registerDefaultCommands();
// invalidate the display when the filter changes
m_predicate.addExpressionListener(new UpdateListener() {
public void update(Object src) { damageReport(); }
});
setVisualization(visualization);
setPredicate(predicate);
setSize(400,400); // set a default size
}
/**
* Resets the display by clearing the offscreen buffer and flushing the
* internal rendering queue. This method can help reclaim memory when a
* Display is not visible.
*/
public void reset() {
m_offscreen = null;
m_queue.clean();
}
/**
* Registers default keystroke commands on the Display. The default
* commands are
* <ul><li><b>ctrl D</b> - Toggle debug info display</li>
* <li><b>ctrl H</b> - Toggle high quality rendering</li>
* <li><b>ctrl E</b> - Export display view to an image file</li></ul>
* Subclasses can override this method to prevent these commands from
* being set. Additional commands can be registered using the
* <code>registerKeyboardAction</code> method.
*/
protected void registerDefaultCommands() {
// add debugging output control
registerKeyboardAction(new ActionListener() {
private PaintListener m_debug = null;
public void actionPerformed(ActionEvent e) {
if (m_debug == null) {
m_debug = new DebugStatsPainter();
addPaintListener(m_debug);
} else {
removePaintListener(m_debug);
m_debug = null;
}
repaint();
}
}, "debug info", KeyStroke.getKeyStroke("ctrl D"), WHEN_FOCUSED);
// add quality toggle
registerKeyboardAction(new ActionListener() {
public void actionPerformed(ActionEvent e) {
setHighQuality(!isHighQuality());
repaint();
}
}, "toggle high-quality drawing", KeyStroke.getKeyStroke("ctrl H"),
WHEN_FOCUSED);
// add image output control, if this is not an applet
try {
registerKeyboardAction(new ExportDisplayAction(this),
"export display", KeyStroke.getKeyStroke("ctrl E"), WHEN_FOCUSED);
} catch (SecurityException se) {
}
}
/**
* Set the size of the Display.
* @param width the width of the Display in pixels
* @param height the height of the Display in pixels
* @see java.awt.Component#setSize(int, int)
*/
public void setSize(int width, int height) {
m_offscreen = null;
setPreferredSize(new Dimension(width, height));
super.setSize(width, height);
}
/**
* Set the size of the Display.
* @param d the dimensions of the Display in pixels
* @see java.awt.Component#setSize(java.awt.Dimension)
*/
public void setSize(Dimension d) {
m_offscreen = null;
setPreferredSize(d);
super.setSize(d);
}
/**
* Invalidates this component. Overridden to ensure that an
* internal damage report is generated.
* @see java.awt.Component#invalidate()
*/
public void invalidate() {
damageReport();
super.invalidate();
}
/**
* @see java.awt.Component#setBounds(int, int, int, int)
*/
public void setBounds(int x, int y, int w, int h) {
m_offscreen = null;
super.setBounds(x,y,w,h);
}
/**
* Sets the font used by this Display. This determines the font used
* by this Display's text editor and in any debugging text.
* @param f the Font to use
*/
public void setFont(Font f) {
super.setFont(f);
m_editor.setFont(f);
}
/**
* Returns the running average frame rate for this Display.
* @return the frame rate
*/
public double getFrameRate() {
return frameRate;
}
/**
* Determines if the Display uses a higher quality rendering, using
* anti-aliasing. This causes drawing to be much slower, however, and
* so is disabled by default.
* @param on true to enable anti-aliased rendering, false to disable it
*/
public void setHighQuality(boolean on) {
if ( m_highQuality != on )
damageReport();
m_highQuality = on;
}
/**
* Indicates if the Display is using high quality (return value true) or
* regular quality (return value false) rendering.
* @return true if high quality rendering is enabled, false otherwise
*/
public boolean isHighQuality() {
return m_highQuality;
}
/**
* Returns the Visualization backing this Display.
* @return this Display's {@link Visualization}
*/
public Visualization getVisualization() {
return m_vis;
}
/**
* Set the Visualiztion associated with this Display. This Display
* will render the items contained in the provided visualization. If this
* Display is already associated with a different Visualization, the
* Display unregisters itself with the previous one.
* @param vis the backing {@link Visualization} to use.
*/
public void setVisualization(Visualization vis) {
// TODO: synchronization?
if ( m_vis == vis ) {
// nothing need be done
return;
} else if ( m_vis != null ) {
// remove this display from it's previous registry
m_vis.removeDisplay(this);
}
m_vis = vis;
if ( m_vis != null )
m_vis.addDisplay(this);
}
/**
* Returns the filtering Predicate used to control what items are drawn
* by this display.
* @return the filtering {@link prefuse.data.expression.Predicate}
*/
public Predicate getPredicate() {
if ( m_predicate.size() == 1 ) {
return BooleanLiteral.TRUE;
} else {
return m_predicate.get(0);
}
}
/**
* Sets the filtering Predicate used to control what items are drawn by
* this Display.
* @param expr the filtering predicate to use. The predicate string will be
* parsed by the {@link prefuse.data.expression.parser.ExpressionParser}.
* If the parse fails or does not result in a
* {@link prefuse.data.expression.Predicate} instance, an exception will
* be thrown.
*/
public void setPredicate(String expr) {
Predicate p = (Predicate)ExpressionParser.parse(expr, true);
setPredicate(p);
}
/**
* Sets the filtering Predicate used to control what items are drawn by
* this Display.
* @param p the filtering {@link prefuse.data.expression.Predicate} to use
*/
public synchronized void setPredicate(Predicate p) {
if ( p == null ) {
m_predicate.set(VisiblePredicate.TRUE);
} else {
m_predicate.set(new Predicate[] {p, VisiblePredicate.TRUE});
}
}
/**
* Returns the number of visible items processed by this Display. This
* includes items not currently visible on screen due to the current
* panning or zooming state.
* @return the count of visible items
*/
public int getVisibleItemCount() {
return m_visibleCount;
}
/**
* Get the ItemSorter that determines the rendering order of the
* VisualItems. Items are drawn in ascending order of the scores provided
* by the ItemSorter.
* @return this Display's {@link prefuse.visual.sort.ItemSorter}
*/
public ItemSorter getItemSorter() {
return m_queue.sort;
}
/**
* Set the ItemSorter that determines the rendering order of the
* VisualItems. Items are drawn in ascending order of the scores provided
* by the ItemSorter.
* @return the {@link prefuse.visual.sort.ItemSorter} to use
*/
public synchronized void setItemSorter(ItemSorter cmp) {
damageReport();
m_queue.sort = cmp;
}
/**
* Set a background image for this display.
* @param image the background Image. If a null value is provided,
* than no background image will be shown.
* @param fixed true if the background image should stay in a fixed
* position, invariant to panning, zooming, or rotation; false if
* the image should be subject to view transforms
* @param tileImage true to tile the image across the visible background,
* false to only include the image once
*/
public synchronized void setBackgroundImage(Image image,
boolean fixed, boolean tileImage)
{
BackgroundPainter bg = null;
if ( image != null )
bg = new BackgroundPainter(image, fixed, tileImage);
setBackgroundPainter(bg);
}
/**
* Set a background image for this display.
* @param location a location String of where to retrieve the
* image file from. Uses
* {@link prefuse.util.io.IOLib#urlFromString(String)} to resolve
* the String. If a null value is provided, than no background
* image will be shown.
* @param fixed true if the background image should stay in a fixed
* position, invariant to panning, zooming, or rotation; false if
* the image should be subject to view transforms
* @param tileImage true to tile the image across the visible background,
* false to only include the image once
*/
public synchronized void setBackgroundImage(String location,
boolean fixed, boolean tileImage)
{
BackgroundPainter bg = null;
if ( location != null )
bg = new BackgroundPainter(location, fixed, tileImage);
setBackgroundPainter(bg);
}
private void setBackgroundPainter(BackgroundPainter bg) {
if ( m_bgpainter != null )
removePaintListener(m_bgpainter);
m_bgpainter = bg;
if ( bg != null )
addPaintListener(bg);
}
// ------------------------------------------------------------------------
// ToolTips
/**
* Returns the tooltip instance to use for this Display. By default, uses
* the normal Swing tooltips, returning the result of this same method
* invoked on the JComponent super-class. If a custom tooltip has been
* set, that is returned instead.
* @see #setCustomToolTip(JToolTip)
* @see javax.swing.JComponent#createToolTip()
*/
public JToolTip createToolTip() {
if ( m_customToolTip == null ) {
return super.createToolTip();
} else {
return m_customToolTip;
}
}
/**
* Set a custom tooltip to use for this Display. To trigger tooltip
* display, you must still use the <code>setToolTipText</code> method
* as usual. The actual text will no longer have any effect, other
* than that a null text value will result in no tooltip display
* while a non-null text value will result in a tooltip being
* shown. Clients are responsible for setting the tool tip
* text to enable/disable tooltips as well as updating the content
* of their own custom tooltip instance.
* @param tooltip the tooltip component to use
* @see prefuse.util.ui.JCustomTooltip
*/
public void setCustomToolTip(JToolTip tooltip) {
m_customToolTip = tooltip;
}
/**
* Get the custom tooltip used by this Display. Returns null if normal
* tooltips are being used.
* @return the custom tooltip used by this Display, or null if none
*/
public JToolTip getCustomToolTip() {
return m_customToolTip;
}
// ------------------------------------------------------------------------
// Clip / Bounds Management
/**
* Indicates if damage/redraw rendering is enabled. If enabled, the display
* will only redraw within the bounding box of all areas that have changed
* since the last rendering operation. For small changes, such as a single
* item being dragged, this can result in a significant performance
* increase. By default, the damage/redraw optimization is enabled. It can
* be disabled, however, if rendering artifacts are appearing in your
* visualization. Be careful though, as this may not be the best solution.
* Rendering artifacts may result because the item bounds returned by
* {@link prefuse.visual.VisualItem#getBounds()} are not accurate and the
* item's {@link prefuse.render.Renderer} is drawing outside of the
* reported bounds. In this case, there is usually a bug in the Renderer.
* One reported problem arises from Java itself, however, which
* inaccurately redraws images outside of their reported bounds. If you
* have a visulization with a number of images and are seeing rendering
* artifacts, try disabling damage/redraw.
* @return true if damage/redraw optimizations are enabled, false
* otherwise (in which case the entire Display is redrawn upon a repaint)
*/
public synchronized boolean isDamageRedraw() {
return m_damageRedraw;
}
/**
* Sets if damage/redraw rendering is enabled. If enabled, the display
* will only redraw within the bounding box of all areas that have changed
* since the last rendering operation. For small changes, such as a single
* item being dragged, this can result in a significant performance
* increase. By default, the damage/redraw optimization is enabled. It can
* be disabled, however, if rendering artifacts are appearing in your
* visualization. Be careful though, as this may not be the best solution.
* Rendering artifacts may result because the item bounds returned by
* {@link prefuse.visual.VisualItem#getBounds()} are not accurate and the
* item's {@link prefuse.render.Renderer} is drawing outside of the
* reported bounds. In this case, there is usually a bug in the Renderer.
* One reported problem arises from Java itself, however, which
* inaccurately redraws images outside of their reported bounds. If you
* have a visulization with a number of images and are seeing rendering
* artifacts, try disabling damage/redraw.
* @param b true to enable damage/redraw optimizations, false otherwise
* (in which case the entire Display will be redrawn upon a repaint)
*/
public synchronized void setDamageRedraw(boolean b) {
m_damageRedraw = b;
m_clip.invalidate();
}
/**
* Reports damage to the Display within in the specified region.
* @param region the damaged region, in absolute coordinates
*/
public synchronized void damageReport(Rectangle2D region) {
if ( m_damageRedraw )
m_clip.union(region);
}
/**
* Reports damage to the entire Display.
*/
public synchronized void damageReport() {
m_clip.invalidate();
}
/**
* Clears any reports of damaged regions, causing the Display to believe
* that the display contents are up-to-date. If used incorrectly this
* can cause inaccurate rendering. <strong>Call this method only
* if you know what you are doing.</strong>
*/
public synchronized void clearDamage() {
if ( m_damageRedraw )
m_clip.reset();
}
/**
* Returns the bounds, in absolute (item-space) coordinates, of the total
* bounds occupied by all currently visible VisualItems. This method
* allocates a new Rectangle2D instance for the result.
* @return the bounding box of all visibile VisualItems
* @see #getItemBounds(Rectangle2D)
*/
public synchronized Rectangle2D getItemBounds() {
return getItemBounds(new Rectangle2D.Double());
}
/**
* Returns the bounds, in absolute (item-space) coordinates, of the total
* bounds occupied by all currently visible VisualItems.
* @param b the Rectangle2D to use to store the return value
* @return the bounding box of all visibile VisualItems
*/
public synchronized Rectangle2D getItemBounds(Rectangle2D b) {
b.setFrameFromDiagonal(m_bounds.getMinX(), m_bounds.getMinY(),
m_bounds.getMaxX(), m_bounds.getMaxY());
return b;
}
// ------------------------------------------------------------------------
// Rendering
/**
* Returns the offscreen buffer used for double buffering.
* @return the offscreen buffer
*/
public BufferedImage getOffscreenBuffer() {
return m_offscreen;
}
/**
* Creates a new buffered image to use as an offscreen buffer.
*/
protected BufferedImage getNewOffscreenBuffer(int width, int height) {
BufferedImage img = null;
if ( !GraphicsEnvironment.isHeadless() ) {
try {
img = (BufferedImage)createImage(width, height);
} catch ( Exception e ) {
img = null;
}
}
if ( img == null ) {
return new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
}
return img;
}
/**
* Saves a copy of this display as an image to the specified output stream.
* @param output the output stream to write to.
* @param format the image format (e.g., "JPG", "PNG"). The number and kind
* of available formats varies by platform. See
* {@link javax.imageio.ImageIO} and related classes for more.
* @param scale how much to scale the image by. For example, a value of 2.0
* will result in an image with twice the pixel width and height of this
* Display.
* @return true if image was successfully saved, false if an error occurred.
*/
public boolean saveImage(OutputStream output, String format, double scale)
{
try {
// get an image to draw into
Dimension d = new Dimension((int)(scale*getWidth()),
(int)(scale*getHeight()));
BufferedImage img = getNewOffscreenBuffer(d.width, d.height);
Graphics2D g = (Graphics2D)img.getGraphics();
// set up the display, render, then revert to normal settings
Point2D p = new Point2D.Double(0,0);
zoom(p, scale); // also takes care of damage report
boolean q = isHighQuality();
setHighQuality(true);
paintDisplay(g, d);
setHighQuality(q);
zoom(p, 1/scale); // also takes care of damage report
// save the image and return
ImageIO.write(img, format, output);
return true;
} catch ( Exception e ) {
e.printStackTrace();
return false;
}
}
/**
* @see java.awt.Component#update(java.awt.Graphics)
*/
public void update(Graphics g) {
paint(g);
}
/**
* Paints the offscreen buffer to the provided graphics context.
* @param g the Graphics context to paint to
*/
protected void paintBufferToScreen(Graphics g) {
synchronized ( this ) {
g.drawImage(m_offscreen, 0, 0, null);
}
}
/**
* Immediately repaints the contents of the offscreen buffer
* to the screen. This bypasses the usual rendering loop.
*/
public void repaintImmediate() {
Graphics g = this.getGraphics();
if (g != null && m_offscreen != null) {
paintBufferToScreen(g);
}
}
/**
* Sets the transform of the provided Graphics context to be the
* transform of this Display and sets the desired rendering hints.
* @param g the Graphics context to prepare.
*/
protected void prepareGraphics(Graphics2D g) {
if ( m_transform != null )
g.transform(m_transform);
setRenderingHints(g);
}
/**
* Sets the rendering hints that should be used while drawing
* the visualization to the screen. Subclasses can override
* this method to set hints as desired. Such subclasses should
* consider honoring the high quality flag in one form or another.
* @param g the Graphics context on which to set the rendering hints
*/
protected void setRenderingHints(Graphics2D g) {
if ( m_highQuality ) {
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
} else {
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_OFF);
}
g.setRenderingHint(
RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
g.setRenderingHint(
RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BICUBIC);
}
/**
* @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
*/
public void paintComponent(Graphics g) {
if (m_offscreen == null) {
m_offscreen = getNewOffscreenBuffer(getWidth(), getHeight());
damageReport();
}
Graphics2D g2D = (Graphics2D)g;
Graphics2D buf_g2D = (Graphics2D) m_offscreen.getGraphics();
// Why not fire a pre-paint event here?
// Pre-paint events are fired by the clearRegion method
// paint the visualization
paintDisplay(buf_g2D, getSize());
paintBufferToScreen(g2D);
// fire post-paint events to any painters
firePostPaint(g2D);
buf_g2D.dispose();
// compute frame rate
nframes++;
if ( mark < 0 ) {
mark = System.currentTimeMillis();
nframes = 0;
} else if ( nframes == sampleInterval ){
long t = System.currentTimeMillis();
frameRate = (1000.0*nframes)/(t-mark);
mark = t;
nframes = 0;
}
}
/**
* Renders the display within the given graphics context and size bounds.
* @param g2D the <code>Graphics2D</code> context to use for rendering
* @param d the rendering width and height of the Display
*/
public void paintDisplay(Graphics2D g2D, Dimension d) {
// if double-locking *ALWAYS* lock on the visualization first
synchronized ( m_vis ) {
synchronized ( this ) {
if ( m_clip.isEmpty() )
return; // no damage, no render
// map the screen bounds to absolute coords
m_screen.setClip(0, 0, d.width+1, d.height+1);
m_screen.transform(m_itransform);
// compute the approximate size of an "absolute pixel"
// values too large are OK (though cause unnecessary rendering)
// values too small will cause incorrect rendering
double pixel = 1.0 + 1.0/getScale();
if ( m_damageRedraw ) {
if ( m_clip.isInvalid() ) {
// if clip is invalid, we clip to the entire screen
m_clip.setClip(m_screen);
} else {
// otherwise intersect damaged region with display bounds
m_clip.intersection(m_screen);
}
// expand the clip by the extra pixel margin
m_clip.expand(pixel);
// set the transform, rendering keys, etc
prepareGraphics(g2D);
// now set the actual rendering clip
m_rclip.setFrameFromDiagonal(
m_clip.getMinX(), m_clip.getMinY(),
m_clip.getMaxX(), m_clip.getMaxY());
g2D.setClip(m_rclip);
// finally, we want to clear the region we'll redraw. we clear
// a slightly larger area than the clip. if we don't do this,
// we sometimes get rendering artifacts, possibly due to
// scaling mismatches in the Java2D implementation
m_rclip.setFrameFromDiagonal(
m_clip.getMinX()-pixel, m_clip.getMinY()-pixel,
m_clip.getMaxX()+pixel, m_clip.getMaxY()+pixel);
} else {
// set the background region to clear
m_rclip.setFrame(m_screen.getMinX(), m_screen.getMinY(),
m_screen.getWidth(), m_screen.getHeight());
// set the item clip to the current screen
m_clip.setClip(m_screen);
// set the transform, rendering keys, etc
prepareGraphics(g2D);
}
// now clear the region
clearRegion(g2D, m_rclip);
// -- render ----------------------------
// the actual rendering loop
// copy current item bounds into m_rclip, reset item bounds
getItemBounds(m_rclip);
m_bounds.reset();
// fill the rendering and picking queues
m_queue.clear(); // clear the queue
Iterator items = m_vis.items(m_predicate);
for ( m_visibleCount=0; items.hasNext(); ++m_visibleCount ) {
VisualItem item = (VisualItem)items.next();
Rectangle2D bounds = item.getBounds();
m_bounds.union(bounds); // add to item bounds
if ( m_clip.intersects(bounds, pixel) )
m_queue.addToRenderQueue(item);
if ( item.isInteractive() )
m_queue.addToPickingQueue(item);
}
// sort the rendering queue
m_queue.sortRenderQueue();
// render each visual item
for ( int i=0; i<m_queue.rsize; ++i ) {
m_queue.ritems[i].render(g2D);
}
// no more damage so reset the clip
if ( m_damageRedraw )
m_clip.reset();
// fire bounds change, if appropriate
checkItemBoundsChanged(m_rclip);
}} // end synchronized block
}
/**
* Immediately render the given VisualItem to the screen. This method
* bypasses the Display's offscreen buffer.
* @param item the VisualItem to render immediately
*/
public void renderImmediate(VisualItem item) {
Graphics2D g2D = (Graphics2D)this.getGraphics();
prepareGraphics(g2D);
item.render(g2D);
}
/**
* Paints the graph to the provided graphics context, for output to a
* printer. This method does not double buffer the painting, in order to
* provide the maximum print quality.
*
* <b>This method may not be working correctly,
* and will be repaired at a later date.</b>
*
* @param g the printer graphics context.
*/
protected void printComponent(Graphics g) {
boolean wasHighQuality = m_highQuality;
try {
// Set the quality to high for the duration of the printing.
m_highQuality = true;
// Paint directly to the print graphics context.
paintDisplay((Graphics2D) g, getSize());
} finally {
// Reset the quality to the state it was in before printing.
m_highQuality = wasHighQuality;
}
}
/**
* Clears the specified region of the display
* in the display's offscreen buffer.
*/
protected void clearRegion(Graphics2D g, Rectangle2D r) {
g.setColor(getBackground());
g.fill(r);
// fire pre-paint events to any painters
firePrePaint(g);
}
// ------------------------------------------------------------------------
// Transformations
/**
* Set the 2D AffineTransform (e.g., scale, shear, pan, rotate) used by
* this display before rendering visual items. The provided transform
* must be invertible, otherwise an expection will be thrown. For simple
* panning and zooming transforms, you can instead use the provided
* pan() and zoom() methods.
*/
public synchronized void setTransform(AffineTransform transform)
throws NoninvertibleTransformException
{
damageReport();
m_transform = transform;
m_itransform = m_transform.createInverse();
}
/**
* Returns a reference to the AffineTransformation used by this Display.
* Changes made to this reference WILL corrupt the state of
* this display. Use setTransform() to safely update the transform state.
* @return the AffineTransform
*/
public AffineTransform getTransform() {
return m_transform;
}
/**
* Returns a reference to the inverse of the AffineTransformation used by
* this display. Direct changes made to this reference WILL corrupt the
* state of this display.
* @return the inverse AffineTransform
*/
public AffineTransform getInverseTransform() {
return m_itransform;
}
/**
* Gets the absolute co-ordinate corresponding to the given screen
* co-ordinate.
* @param screen the screen co-ordinate to transform
* @param abs a reference to put the result in. If this is the same
* object as the screen co-ordinate, it will be overridden safely. If
* this value is null, a new Point2D instance will be created and
* returned.
* @return the point in absolute co-ordinates
*/
public Point2D getAbsoluteCoordinate(Point2D screen, Point2D abs) {
return m_itransform.transform(screen, abs);
}
/**
* Returns the current scale (zoom) value.
* @return the current scale. This is the
* scaling factor along the x-dimension, so be careful when
* using this value in rare non-uniform scaling cases.
*/
public double getScale() {
return m_transform.getScaleX();
}
/**
* Returns the x-coordinate of the top-left of the display,
* in absolute (item-space) co-ordinates.
* @return the x co-ord of the top-left corner, in absolute coordinates
*/
public double getDisplayX() {
return -m_transform.getTranslateX();
}
/**
* Returns the y-coordinate of the top-left of the display,
* in absolute (item-space) co-ordinates.
* @return the y co-ord of the top-left corner, in absolute coordinates
*/
public double getDisplayY() {
return -m_transform.getTranslateY();
}
/**
* Pans the view provided by this display in screen coordinates.
* @param dx the amount to pan along the x-dimension, in pixel units
* @param dy the amount to pan along the y-dimension, in pixel units
*/
public synchronized void pan(double dx, double dy) {
m_tmpPoint.setLocation(dx, dy);
m_itransform.transform(m_tmpPoint, m_tmpPoint);
double panx = m_tmpPoint.getX();
double pany = m_tmpPoint.getY();
m_tmpPoint.setLocation(0, 0);
m_itransform.transform(m_tmpPoint, m_tmpPoint);
panx -= m_tmpPoint.getX();
pany -= m_tmpPoint.getY();
panAbs(panx, pany);
}
/**
* Pans the view provided by this display in absolute (i.e. item-space)
* coordinates.
* @param dx the amount to pan along the x-dimension, in absolute co-ords
* @param dy the amount to pan along the y-dimension, in absolute co-ords
*/
public synchronized void panAbs(double dx, double dy) {
damageReport();
m_transform.translate(dx, dy);
try {
m_itransform = m_transform.createInverse();
} catch ( Exception e ) { /*will never happen here*/ }
}
/**
* Pans the display view to center on the provided point in
* screen (pixel) coordinates.
* @param p the point to center on, in screen co-ords
*/
public synchronized void panTo(Point2D p) {
m_itransform.transform(p, m_tmpPoint);
panToAbs(m_tmpPoint);
}
/**
* Pans the display view to center on the provided point in
* absolute (i.e. item-space) coordinates.
* @param p the point to center on, in absolute co-ords
*/
public synchronized void panToAbs(Point2D p) {
double sx = m_transform.getScaleX();
double sy = m_transform.getScaleY();
double x = p.getX(); x = (Double.isNaN(x) ? 0 : x);
double y = p.getY(); y = (Double.isNaN(y) ? 0 : y);
x = getWidth() /(2*sx) - x;
y = getHeight()/(2*sy) - y;
double dx = x-(m_transform.getTranslateX()/sx);
double dy = y-(m_transform.getTranslateY()/sy);
damageReport();
m_transform.translate(dx, dy);
try {
m_itransform = m_transform.createInverse();
} catch ( Exception e ) { /*will never happen here*/ }
}
/**
* Zooms the view provided by this display by the given scale,
* anchoring the zoom at the specified point in screen coordinates.
* @param p the anchor point for the zoom, in screen coordinates
* @param scale the amount to zoom by
*/
public synchronized void zoom(final Point2D p, double scale) {
m_itransform.transform(p, m_tmpPoint);
zoomAbs(m_tmpPoint, scale);
}
/**
* Zooms the view provided by this display by the given scale,
* anchoring the zoom at the specified point in absolute coordinates.
* @param p the anchor point for the zoom, in absolute
* (i.e. item-space) co-ordinates
* @param scale the amount to zoom by
*/
public synchronized void zoomAbs(final Point2D p, double scale) {;
double zx = p.getX(), zy = p.getY();
damageReport();
m_transform.translate(zx, zy);
m_transform.scale(scale,scale);
m_transform.translate(-zx, -zy);
try {
m_itransform = m_transform.createInverse();
} catch ( Exception e ) { /*will never happen here*/ }
}
/**
* Rotates the view provided by this display by the given angle in radians,
* anchoring the rotation at the specified point in screen coordinates.
* @param p the anchor point for the rotation, in screen coordinates
* @param theta the angle to rotate by, in radians
*/
public synchronized void rotate(final Point2D p, double theta) {
m_itransform.transform(p, m_tmpPoint);
rotateAbs(m_tmpPoint, theta);
}
/**
* Rotates the view provided by this display by the given angle in radians,
* anchoring the rotation at the specified point in absolute coordinates.
* @param p the anchor point for the rotation, in absolute
* (i.e. item-space) co-ordinates
* @param theta the angle to rotation by, in radians
*/
public synchronized void rotateAbs(final Point2D p, double theta) {
double zx = p.getX(), zy = p.getY();
damageReport();
m_transform.translate(zx, zy);
m_transform.rotate(theta);
m_transform.translate(-zx, -zy);
try {
m_itransform = m_transform.createInverse();
} catch ( Exception e ) { /*will never happen here*/ }
}
/**
* Animate a pan along the specified distance in screen (pixel)
* co-ordinates using the provided duration.
* @param dx the amount to pan along the x-dimension, in pixel units
* @param dy the amount to pan along the y-dimension, in pixel units
* @param duration the duration of the animation, in milliseconds
*/
public synchronized void animatePan(double dx, double dy, long duration) {
double panx = dx / m_transform.getScaleX();
double pany = dy / m_transform.getScaleY();
animatePanAbs(panx,pany,duration);
}
/**
* Animate a pan along the specified distance in absolute (item-space)
* co-ordinates using the provided duration.
* @param dx the amount to pan along the x-dimension, in absolute co-ords
* @param dy the amount to pan along the y-dimension, in absolute co-ords
* @param duration the duration of the animation, in milliseconds
*/
public synchronized void animatePanAbs(double dx, double dy, long duration) {
m_transact.pan(dx,dy,duration);
}
/**
* Animate a pan to the specified location in screen (pixel)
* co-ordinates using the provided duration.
* @param p the point to pan to in screen (pixel) units
* @param duration the duration of the animation, in milliseconds
*/
public synchronized void animatePanTo(Point2D p, long duration) {
Point2D pp = new Point2D.Double();
m_itransform.transform(p,pp);
animatePanToAbs(pp,duration);
}
/**
* Animate a pan to the specified location in absolute (item-space)
* co-ordinates using the provided duration.
* @param p the point to pan to in absolute (item-space) units
* @param duration the duration of the animation, in milliseconds
*/
public synchronized void animatePanToAbs(Point2D p, long duration) {
m_tmpPoint.setLocation(0,0);
m_itransform.transform(m_tmpPoint,m_tmpPoint);
double x = p.getX(); x = (Double.isNaN(x) ? 0 : x);
double y = p.getY(); y = (Double.isNaN(y) ? 0 : y);
double w = getWidth() /(2*m_transform.getScaleX());
double h = getHeight()/(2*m_transform.getScaleY());
double dx = w-x+m_tmpPoint.getX();
double dy = h-y+m_tmpPoint.getY();
animatePanAbs(dx,dy,duration);
}
/**
* Animate a zoom centered on a given location in screen (pixel)
* co-ordinates by the given scale using the provided duration.
* @param p the point to center on in screen (pixel) units
* @param scale the scale factor to zoom by
* @param duration the duration of the animation, in milliseconds
*/
public synchronized void animateZoom(final Point2D p, double scale, long duration) {
Point2D pp = new Point2D.Double();
m_itransform.transform(p,pp);
animateZoomAbs(pp,scale,duration);
}
/**
* Animate a zoom centered on a given location in absolute (item-space)
* co-ordinates by the given scale using the provided duration.
* @param p the point to center on in absolute (item-space) units
* @param scale the scale factor to zoom by
* @param duration the duration of the animation, in milliseconds
*/
public synchronized void animateZoomAbs(final Point2D p, double scale, long duration) {
m_transact.zoom(p,scale,duration);
}
/**
* Animate a pan to the specified location in screen (pixel)
* co-ordinates and zoom to the given scale using the provided duration.
* @param p the point to center on in screen (pixel) units
* @param scale the scale factor to zoom by
* @param duration the duration of the animation, in milliseconds
*/
public synchronized void animatePanAndZoomTo(final Point2D p, double scale, long duration) {
Point2D pp = new Point2D.Double();
m_itransform.transform(p,pp);
animatePanAndZoomToAbs(pp,scale,duration);
}
/**
* Animate a pan to the specified location in absolute (item-space)
* co-ordinates and zoom to the given scale using the provided duration.
* @param p the point to center on in absolute (item-space) units
* @param scale the scale factor to zoom by
* @param duration the duration of the animation, in milliseconds
*/
public synchronized void animatePanAndZoomToAbs(final Point2D p, double scale, long duration) {
m_transact.panAndZoom(p,scale,duration);
}
/**
* Indicates if a view transformation is currently underway.
* @return true if a transform is in progress, false otherwise
*/
public boolean isTranformInProgress() {
return m_transact.isRunning();
}
/**
* Activity for conducting animated view transformations.
*/
protected class TransformActivity extends Activity {
// TODO: clean this up to be more general...
// TODO: change mechanism so that multiple transform
// activities can be running at once?
private double[] src, dst;
private AffineTransform m_at;
public TransformActivity() {
super(2000,20,0);
src = new double[6];
dst = new double[6];
m_at = new AffineTransform();
setPacingFunction(new SlowInSlowOutPacer());
}
private AffineTransform getTransform() {
if ( this.isScheduled() )
m_at.setTransform(dst[0],dst[1],dst[2],dst[3],dst[4],dst[5]);
else
m_at.setTransform(m_transform);
return m_at;
}
public void panAndZoom(final Point2D p, double scale, long duration) {
AffineTransform at = getTransform();
this.cancel();
setDuration(duration);
m_tmpPoint.setLocation(0,0);
m_itransform.transform(m_tmpPoint,m_tmpPoint);
double x = p.getX(); x = (Double.isNaN(x) ? 0 : x);
double y = p.getY(); y = (Double.isNaN(y) ? 0 : y);
double w = getWidth() /(2*m_transform.getScaleX());
double h = getHeight()/(2*m_transform.getScaleY());
double dx = w-x+m_tmpPoint.getX();
double dy = h-y+m_tmpPoint.getY();
at.translate(dx,dy);
at.translate(p.getX(), p.getY());
at.scale(scale,scale);
at.translate(-p.getX(), -p.getY());
at.getMatrix(dst);
m_transform.getMatrix(src);
this.run();
}
public void pan(double dx, double dy, long duration) {
AffineTransform at = getTransform();
this.cancel();
setDuration(duration);
at.translate(dx,dy);
at.getMatrix(dst);
m_transform.getMatrix(src);
this.run();
}
public void zoom(final Point2D p, double scale, long duration) {
AffineTransform at = getTransform();
this.cancel();
setDuration(duration);
double zx = p.getX(), zy = p.getY();
at.translate(zx, zy);
at.scale(scale,scale);
at.translate(-zx, -zy);
at.getMatrix(dst);
m_transform.getMatrix(src);
this.run();
}
protected void run(long elapsedTime) {
double f = getPace(elapsedTime);
damageReport();
m_transform.setTransform(
src[0] + f*(dst[0]-src[0]),
src[1] + f*(dst[1]-src[1]),
src[2] + f*(dst[2]-src[2]),
src[3] + f*(dst[3]-src[3]),
src[4] + f*(dst[4]-src[4]),
src[5] + f*(dst[5]-src[5])
);
try {
m_itransform = m_transform.createInverse();
} catch ( Exception e ) { /* won't happen */ }
repaint();
}
} // end of inner class TransformActivity
// ------------------------------------------------------------------------
// Paint Listeners
/**
* Add a PaintListener to this Display to receive notifications
* about paint events.
* @param pl the {@link prefuse.util.display.PaintListener} to add
*/
public void addPaintListener(PaintListener pl) {
if ( m_painters == null )
m_painters = new CopyOnWriteArrayList();
m_painters.add(pl);
}
/**
* Remove a PaintListener from this Display.
* @param pl the {@link prefuse.util.display.PaintListener} to remove
*/
public void removePaintListener(PaintListener pl) {
m_painters.remove(pl);
}
/**
* Fires a pre-paint notification to PaintListeners.
* @param g the current graphics context
*/
protected void firePrePaint(Graphics2D g) {
if ( m_painters != null && m_painters.size() > 0 ) {
Object[] lstnrs = m_painters.getArray();
for ( int i=0; i<lstnrs.length; ++i ) {
try {
((PaintListener)lstnrs[i]).prePaint(this, g);
} catch ( Exception e ) {
s_logger.warning(
"Exception thrown by PaintListener: " + e + "\n" +
StringLib.getStackTrace(e));
}
}
}
}
/**
* Fires a post-paint notification to PaintListeners.
* @param g the current graphics context
*/
protected void firePostPaint(Graphics2D g) {
if ( m_painters != null && m_painters.size() > 0 ) {
Object[] lstnrs = m_painters.getArray();
for ( int i=0; i<lstnrs.length; ++i ) {
try {
((PaintListener)lstnrs[i]).postPaint(this, g);
} catch ( Exception e ) {
s_logger.warning(
"Exception thrown by PaintListener: " + e + "\n" +
StringLib.getStackTrace(e));
}
}
}
}
// ------------------------------------------------------------------------
// Item Bounds Listeners
/**
* Add an ItemBoundsListener to receive notifications when the bounds
* occupied by the VisualItems in this Display change.
* @param ibl the {@link prefuse.util.display.ItemBoundsListener} to add
*/
public void addItemBoundsListener(ItemBoundsListener ibl) {
if ( m_bounders == null )
m_bounders = new CopyOnWriteArrayList();
m_bounders.add(ibl);
}
/**
* Remove an ItemBoundsListener to receive notifications when the bounds
* occupied by the VisualItems in this Display change.
* @param ibl the {@link prefuse.util.display.ItemBoundsListener} to remove
*/
public void removeItemBoundsListener(ItemBoundsListener ibl) {
m_bounders.remove(ibl);
}
/**
* Check if the item bounds has changed, and if so, fire a notification.
* @param prev the previous item bounds of the Display
*/
protected void checkItemBoundsChanged(Rectangle2D prev) {
if ( m_bounds.equals(prev) )
return; // nothing to do
if ( m_bounders != null && m_bounders.size() > 0 ) {
Object[] lstnrs = m_bounders.getArray();
for ( int i=0; i<lstnrs.length; ++i ) {
try {
((ItemBoundsListener)lstnrs[i]).itemBoundsChanged(this);
} catch ( Exception e ) {
s_logger.warning(
"Exception thrown by ItemBoundsListener: " + e + "\n" +
StringLib.getStackTrace(e));
}
}
}
}
// ------------------------------------------------------------------------
// Control Listeners
/**
* Adds a ControlListener to receive all input events on VisualItems.
* @param cl the listener to add.
*/
public void addControlListener(Control cl) {
m_controls.add(cl);
}
/**
* Removes a registered ControlListener.
* @param cl the listener to remove.
*/
public void removeControlListener(Control cl) {
m_controls.remove(cl);
}
/**
* Returns the VisualItem located at the given point.
* @param p the Point at which to look
* @return the VisualItem located at the given point, if any
*/
public synchronized VisualItem findItem(Point p) {
// transform mouse point from screen space to item space
Point2D p2 = (m_itransform==null ? p :
m_itransform.transform(p, m_tmpPoint));
// ensure that the picking queue has been z-sorted
if ( !m_queue.psorted )
m_queue.sortPickingQueue();
// walk queue from front to back looking for hits
for ( int i = m_queue.psize; --i >= 0; ) {
VisualItem vi = m_queue.pitems[i];
if ( !vi.isValid() ) continue; // in case tuple went invalid
Renderer r = vi.getRenderer();
if (r!=null && vi.isInteractive() && r.locatePoint(p2, vi)) {
return vi;
}
}
return null;
}
/**
* Captures all mouse and key events on the display, detects relevant
* VisualItems, and informs ControlListeners.
*/
public class InputEventCapturer implements MouseMotionListener,
MouseWheelListener, MouseListener, KeyListener
{
private VisualItem activeItem = null;
private boolean mouseDown = false;
private boolean validityCheck() {
if ( activeItem.isValid() )
return true;
activeItem = null;
return false;
}
public void mouseDragged(MouseEvent e) {
synchronized ( m_vis ) {
if ( activeItem != null ) {
if ( validityCheck() )
fireItemDragged(activeItem, e);
} else {
fireMouseDragged(e);
}
}
}
public void mouseMoved(MouseEvent e) {
synchronized ( m_vis ) {
boolean earlyReturn = false;
//check if we've gone over any item
VisualItem vi = findItem(e.getPoint());
if ( activeItem != null && activeItem != vi ) {
if ( validityCheck() )
fireItemExited(activeItem, e);
earlyReturn = true;
}
if ( vi != null && vi != activeItem ) {
fireItemEntered(vi, e);
earlyReturn = true;
}
activeItem = vi;
if ( earlyReturn ) return;
if ( vi != null && vi == activeItem ) {
fireItemMoved(vi, e);
}
if ( vi == null ) {
fireMouseMoved(e);
}
}
}
public void mouseWheelMoved(MouseWheelEvent e) {
synchronized ( m_vis ) {
if ( activeItem != null ) {
if ( validityCheck() )
fireItemWheelMoved(activeItem, e);
} else {
fireMouseWheelMoved(e);
}
}
}
public void mouseClicked(MouseEvent e) {
synchronized ( m_vis ) {
if ( activeItem != null ) {
if ( validityCheck() )
fireItemClicked(activeItem, e);
} else {
fireMouseClicked(e);
}
}
}
public void mousePressed(MouseEvent e) {
synchronized ( m_vis ) {
mouseDown = true;
if ( activeItem != null ) {
if ( validityCheck() )
fireItemPressed(activeItem, e);
} else {
fireMousePressed(e);
}
}
}
public void mouseReleased(MouseEvent e) {
synchronized ( m_vis ) {
if ( activeItem != null ) {
if ( validityCheck() )
fireItemReleased(activeItem, e);
} else {
fireMouseReleased(e);
}
if ( activeItem != null && mouseDown && isOffComponent(e) ) {
// mouse was dragged off of the component,
// then released, so register an exit
fireItemExited(activeItem, e);
activeItem = null;
}
mouseDown = false;
}
}
public void mouseEntered(MouseEvent e) {
synchronized ( m_vis ) {
fireMouseEntered(e);
}
}
public void mouseExited(MouseEvent e) {
synchronized ( m_vis ) {
if ( !mouseDown && activeItem != null ) {
// we've left the component and an item
// is active but not being dragged, deactivate it
fireItemExited(activeItem, e);
activeItem = null;
}
fireMouseExited(e);
}
}
public void keyPressed(KeyEvent e) {
synchronized ( m_vis ) {
if ( activeItem != null ) {
if ( validityCheck() )
fireItemKeyPressed(activeItem, e);
} else {
fireKeyPressed(e);
}
}
}
public void keyReleased(KeyEvent e) {
synchronized ( m_vis ) {
if ( activeItem != null ) {
if ( validityCheck() )
fireItemKeyReleased(activeItem, e);
} else {
fireKeyReleased(e);
}
}
}
public void keyTyped(KeyEvent e) {
synchronized ( m_vis ) {
if ( activeItem != null ) {
if ( validityCheck() )
fireItemKeyTyped(activeItem, e);
} else {
fireKeyTyped(e);
}
}
}
private boolean isOffComponent(MouseEvent e) {
int x = e.getX(), y = e.getY();
return ( x<0 || x>getWidth() || y<0 || y>getHeight() );
}
// --------------------------------------------------------------------
// Fire Event Notifications
private void fireItemDragged(VisualItem item, MouseEvent e) {
Object[] lstnrs = m_controls.getArray();
for (int i = 0; i < lstnrs.length; ++i) {
Control ctrl = (Control) lstnrs[i];
if (ctrl.isEnabled())
try {
ctrl.itemDragged(item, e);
} catch ( Exception ex ) {
s_logger.warning(
"Exception thrown by Control: " + ex + "\n" +
StringLib.getStackTrace(ex));
}
}
}
private void fireItemMoved(VisualItem item, MouseEvent e) {
Object[] lstnrs = m_controls.getArray();
for (int i = 0; i < lstnrs.length; ++i) {
Control ctrl = (Control) lstnrs[i];
if (ctrl.isEnabled())
try {
ctrl.itemMoved(item, e);
} catch ( Exception ex ) {
s_logger.warning(
"Exception thrown by Control: " + ex + "\n" +
StringLib.getStackTrace(ex));
}
}
}
private void fireItemWheelMoved(VisualItem item, MouseWheelEvent e) {
Object[] lstnrs = m_controls.getArray();
for (int i = 0; i < lstnrs.length; ++i) {
Control ctrl = (Control) lstnrs[i];
if (ctrl.isEnabled())
try {
ctrl.itemWheelMoved(item, e);
} catch ( Exception ex ) {
s_logger.warning(
"Exception thrown by Control: " + ex + "\n" +
StringLib.getStackTrace(ex));
}
}
}
private void fireItemClicked(VisualItem item, MouseEvent e) {
Object[] lstnrs = m_controls.getArray();
for (int i = 0; i < lstnrs.length; ++i) {
Control ctrl = (Control) lstnrs[i];
if (ctrl.isEnabled())
try {
ctrl.itemClicked(item, e);
} catch ( Exception ex ) {
s_logger.warning(
"Exception thrown by Control: " + ex + "\n" +
StringLib.getStackTrace(ex));
}
}
}
private void fireItemPressed(VisualItem item, MouseEvent e) {
Object[] lstnrs = m_controls.getArray();
for (int i = 0; i < lstnrs.length; ++i) {
Control ctrl = (Control) lstnrs[i];
if (ctrl.isEnabled())
try {
ctrl.itemPressed(item, e);
} catch ( Exception ex ) {
s_logger.warning(
"Exception thrown by Control: " + ex + "\n" +
StringLib.getStackTrace(ex));
}
}
}
private void fireItemReleased(VisualItem item, MouseEvent e) {
Object[] lstnrs = m_controls.getArray();
for (int i = 0; i < lstnrs.length; ++i) {
Control ctrl = (Control) lstnrs[i];
if (ctrl.isEnabled())
try {
ctrl.itemReleased(item, e);
} catch ( Exception ex ) {
s_logger.warning(
"Exception thrown by Control: " + ex + "\n" +
StringLib.getStackTrace(ex));
}
}
}
private void fireItemEntered(VisualItem item, MouseEvent e) {
item.setHover(true);
Object[] lstnrs = m_controls.getArray();
for (int i = 0; i < lstnrs.length; ++i) {
Control ctrl = (Control) lstnrs[i];
if (ctrl.isEnabled())
try {
ctrl.itemEntered(item, e);
} catch ( Exception ex ) {
s_logger.warning(
"Exception thrown by Control: " + ex + "\n" +
StringLib.getStackTrace(ex));
}
}
}
private void fireItemExited(VisualItem item, MouseEvent e) {
if ( item.isValid() ) item.setHover(false);
Object[] lstnrs = m_controls.getArray();
for (int i = 0; i < lstnrs.length; ++i) {
Control ctrl = (Control) lstnrs[i];
if (ctrl.isEnabled())
try {
ctrl.itemExited(item, e);
} catch ( Exception ex ) {
s_logger.warning(
"Exception thrown by Control: " + ex + "\n" +
StringLib.getStackTrace(ex));
}
}
}
private void fireItemKeyPressed(VisualItem item, KeyEvent e) {
Object[] lstnrs = m_controls.getArray();
if (lstnrs.length == 0)
return;
for (int i = 0; i < lstnrs.length; ++i) {
Control ctrl = (Control) lstnrs[i];
if (ctrl.isEnabled())
try {
ctrl.itemKeyPressed(item, e);
} catch ( Exception ex ) {
s_logger.warning(
"Exception thrown by Control: " + ex + "\n" +
StringLib.getStackTrace(ex));
}
}
}
private void fireItemKeyReleased(VisualItem item, KeyEvent e) {
Object[] lstnrs = m_controls.getArray();
for (int i = 0; i < lstnrs.length; ++i) {
Control ctrl = (Control) lstnrs[i];
if (ctrl.isEnabled())
try {
ctrl.itemKeyReleased(item, e);
} catch ( Exception ex ) {
s_logger.warning(
"Exception thrown by Control: " + ex + "\n" +
StringLib.getStackTrace(ex));
}
}
}
private void fireItemKeyTyped(VisualItem item, KeyEvent e) {
Object[] lstnrs = m_controls.getArray();
for (int i = 0; i < lstnrs.length; ++i) {
Control ctrl = (Control) lstnrs[i];
if (ctrl.isEnabled())
try {
ctrl.itemKeyTyped(item, e);
} catch ( Exception ex ) {
s_logger.warning(
"Exception thrown by Control: " + ex + "\n" +
StringLib.getStackTrace(ex));
}
}
}
private void fireMouseEntered(MouseEvent e) {
Object[] lstnrs = m_controls.getArray();
for (int i = 0; i < lstnrs.length; ++i) {
Control ctrl = (Control) lstnrs[i];
if (ctrl.isEnabled())
try {
ctrl.mouseEntered(e);
} catch ( Exception ex ) {
s_logger.warning(
"Exception thrown by Control: " + ex + "\n" +
StringLib.getStackTrace(ex));
}
}
}
private void fireMouseExited(MouseEvent e) {
Object[] lstnrs = m_controls.getArray();
for (int i = 0; i < lstnrs.length; ++i) {
Control ctrl = (Control) lstnrs[i];
if (ctrl.isEnabled())
try {
ctrl.mouseExited(e);
} catch ( Exception ex ) {
s_logger.warning(
"Exception thrown by Control: " + ex + "\n" +
StringLib.getStackTrace(ex));
}
}
}
private void fireMousePressed(MouseEvent e) {
Object[] lstnrs = m_controls.getArray();
for (int i = 0; i < lstnrs.length; ++i) {
Control ctrl = (Control) lstnrs[i];
if (ctrl.isEnabled())
try {
ctrl.mousePressed(e);
} catch ( Exception ex ) {
s_logger.warning(
"Exception thrown by Control: " + ex + "\n" +
StringLib.getStackTrace(ex));
}
}
}
private void fireMouseReleased(MouseEvent e) {
Object[] lstnrs = m_controls.getArray();
for (int i = 0; i < lstnrs.length; ++i) {
Control ctrl = (Control) lstnrs[i];
if (ctrl.isEnabled())
try {
ctrl.mouseReleased(e);
} catch ( Exception ex ) {
s_logger.warning(
"Exception thrown by Control: " + ex + "\n" +
StringLib.getStackTrace(ex));
}
}
}
private void fireMouseClicked(MouseEvent e) {
Object[] lstnrs = m_controls.getArray();
for (int i = 0; i < lstnrs.length; ++i) {
Control ctrl = (Control) lstnrs[i];
if (ctrl.isEnabled())
try {
ctrl.mouseClicked(e);
} catch ( Exception ex ) {
s_logger.warning(
"Exception thrown by Control: " + ex + "\n" +
StringLib.getStackTrace(ex));
}
}
}
private void fireMouseDragged(MouseEvent e) {
Object[] lstnrs = m_controls.getArray();
for (int i = 0; i < lstnrs.length; ++i) {
Control ctrl = (Control) lstnrs[i];
if (ctrl.isEnabled())
try {
ctrl.mouseDragged(e);
} catch ( Exception ex ) {
s_logger.warning(
"Exception thrown by Control: " + ex + "\n" +
StringLib.getStackTrace(ex));
}
}
}
private void fireMouseMoved(MouseEvent e) {
Object[] lstnrs = m_controls.getArray();
for (int i = 0; i < lstnrs.length; ++i) {
Control ctrl = (Control) lstnrs[i];
if (ctrl.isEnabled())
try {
ctrl.mouseMoved(e);
} catch ( Exception ex ) {
s_logger.warning(
"Exception thrown by Control: " + ex + "\n" +
StringLib.getStackTrace(ex));
}
}
}
private void fireMouseWheelMoved(MouseWheelEvent e) {
Object[] lstnrs = m_controls.getArray();
for (int i = 0; i < lstnrs.length; ++i) {
Control ctrl = (Control) lstnrs[i];
if (ctrl.isEnabled())
try {
ctrl.mouseWheelMoved(e);
} catch ( Exception ex ) {
s_logger.warning(
"Exception thrown by Control: " + ex + "\n" +
StringLib.getStackTrace(ex));
}
}
}
private void fireKeyPressed(KeyEvent e) {
Object[] lstnrs = m_controls.getArray();
for (int i = 0; i < lstnrs.length; ++i) {
Control ctrl = (Control) lstnrs[i];
if (ctrl.isEnabled())
try {
ctrl.keyPressed(e);
} catch ( Exception ex ) {
s_logger.warning(
"Exception thrown by Control: " + ex + "\n" +
StringLib.getStackTrace(ex));
}
}
}
private void fireKeyReleased(KeyEvent e) {
Object[] lstnrs = m_controls.getArray();
for (int i = 0; i < lstnrs.length; ++i) {
Control ctrl = (Control) lstnrs[i];
if (ctrl.isEnabled())
try {
ctrl.keyReleased(e);
} catch ( Exception ex ) {
s_logger.warning(
"Exception thrown by Control: " + ex + "\n" +
StringLib.getStackTrace(ex));
}
}
}
private void fireKeyTyped(KeyEvent e) {
Object[] lstnrs = m_controls.getArray();
for (int i = 0; i < lstnrs.length; ++i) {
Control ctrl = (Control) lstnrs[i];
if (ctrl.isEnabled())
try {
ctrl.keyTyped(e);
} catch ( Exception ex ) {
s_logger.warning(
"Exception thrown by Control: " + ex + "\n" +
StringLib.getStackTrace(ex));
}
}
}
} // end of inner class MouseEventCapturer
// ------------------------------------------------------------------------
// Text Editing
/**
* Returns the TextComponent used for on-screen text editing.
* @return the TextComponent used for text editing
*/
public JTextComponent getTextEditor() {
return m_editor;
}
/**
* Sets the TextComponent used for on-screen text editing.
* @param tc the TextComponent to use for text editing
*/
public void setTextEditor(JTextComponent tc) {
this.remove(m_editor);
m_editor = tc;
this.add(m_editor, 1);
}
/**
* Edit text for the given VisualItem and attribute. Presents a text
* editing widget spaning the item's bounding box. Use stopEditing()
* to hide the text widget. When stopEditing() is called, the data field
* will automatically be updated with the VisualItem.
* @param item the VisualItem to edit
* @param attribute the attribute to edit
*/
public void editText(VisualItem item, String attribute) {
if ( m_editing ) { stopEditing(); }
Rectangle2D b = item.getBounds();
Rectangle r = m_transform.createTransformedShape(b).getBounds();
// hacky placement code that attempts to keep text in same place
// configured under Windows XP and Java 1.4.2b
if ( m_editor instanceof JTextArea ) {
r.y -= 2; r.width += 22; r.height += 2;
} else {
r.x += 3; r.y += 1; r.width -= 5; r.height -= 2;
}
Font f = getFont();
int size = (int)Math.round(f.getSize()*m_transform.getScaleX());
Font nf = new Font(f.getFontName(), f.getStyle(), size);
m_editor.setFont(nf);
editText(item, attribute, r);
}
/**
* Edit text for the given VisualItem and field. Presents a text
* editing widget spaning the given bounding box. Use stopEditing()
* to hide the text widget. When stopEditing() is called, the field
* will automatically be updated with the VisualItem.
* @param item the VisualItem to edit
* @param attribute the attribute to edit
* @param r Rectangle representing the desired bounding box of the text
* editing widget
*/
public void editText(VisualItem item, String attribute, Rectangle r) {
if ( m_editing ) { stopEditing(); }
String txt = item.getString(attribute);
m_editItem = item;
m_editAttribute = attribute;
Color tc = ColorLib.getColor(item.getTextColor());
Color fc = ColorLib.getColor(item.getFillColor());
m_editor.setForeground(tc);
m_editor.setBackground(fc);
editText(txt, r);
}
/**
* Show a text editing widget containing the given text and spanning the
* specified bounding box. Use stopEditing() to hide the text widget. Use
* the method calls getTextEditor().getText() to get the resulting edited
* text.
* @param txt the text string to display in the text widget
* @param r Rectangle representing the desired bounding box of the text
* editing widget
*/
public void editText(String txt, Rectangle r) {
if ( m_editing ) { stopEditing(); }
m_editing = true;
m_editor.setBounds(r.x,r.y,r.width,r.height);
m_editor.setText(txt);
m_editor.setVisible(true);
m_editor.setCaretPosition(txt.length());
m_editor.requestFocus();
}
/**
* Stops text editing on the display, hiding the text editing widget. If
* the text editor was associated with a specific VisualItem (ie one of the
* editText() methods which include a VisualItem as an argument was called),
* the item is updated with the edited text.
*/
public void stopEditing() {
m_editor.setVisible(false);
if ( m_editItem != null ) {
String txt = m_editor.getText();
m_editItem.set(m_editAttribute, txt);
m_editItem = null;
m_editAttribute = null;
m_editor.setBackground(null);
m_editor.setForeground(null);
}
m_editing = false;
}
} // end of class Display