KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > prefuse > Display


1 package prefuse;
2
3 import java.awt.Color JavaDoc;
4 import java.awt.Dimension JavaDoc;
5 import java.awt.Font JavaDoc;
6 import java.awt.Graphics JavaDoc;
7 import java.awt.Graphics2D JavaDoc;
8 import java.awt.GraphicsEnvironment JavaDoc;
9 import java.awt.Image JavaDoc;
10 import java.awt.Point JavaDoc;
11 import java.awt.Rectangle JavaDoc;
12 import java.awt.RenderingHints JavaDoc;
13 import java.awt.event.ActionEvent JavaDoc;
14 import java.awt.event.ActionListener JavaDoc;
15 import java.awt.event.KeyEvent JavaDoc;
16 import java.awt.event.KeyListener JavaDoc;
17 import java.awt.event.MouseEvent JavaDoc;
18 import java.awt.event.MouseListener JavaDoc;
19 import java.awt.event.MouseMotionListener JavaDoc;
20 import java.awt.event.MouseWheelEvent JavaDoc;
21 import java.awt.event.MouseWheelListener JavaDoc;
22 import java.awt.geom.AffineTransform JavaDoc;
23 import java.awt.geom.NoninvertibleTransformException JavaDoc;
24 import java.awt.geom.Point2D JavaDoc;
25 import java.awt.geom.Rectangle2D JavaDoc;
26 import java.awt.image.BufferedImage JavaDoc;
27 import java.io.OutputStream JavaDoc;
28 import java.util.Iterator JavaDoc;
29 import java.util.logging.Logger JavaDoc;
30
31 import javax.imageio.ImageIO JavaDoc;
32 import javax.swing.JComponent JavaDoc;
33 import javax.swing.JTextArea JavaDoc;
34 import javax.swing.JTextField JavaDoc;
35 import javax.swing.JToolTip JavaDoc;
36 import javax.swing.KeyStroke JavaDoc;
37 import javax.swing.text.JTextComponent JavaDoc;
38
39 import prefuse.activity.Activity;
40 import prefuse.activity.SlowInSlowOutPacer;
41 import prefuse.controls.Control;
42 import prefuse.data.expression.AndPredicate;
43 import prefuse.data.expression.BooleanLiteral;
44 import prefuse.data.expression.Predicate;
45 import prefuse.data.expression.parser.ExpressionParser;
46 import prefuse.render.Renderer;
47 import prefuse.util.ColorLib;
48 import prefuse.util.StringLib;
49 import prefuse.util.UpdateListener;
50 import prefuse.util.collections.CopyOnWriteArrayList;
51 import prefuse.util.display.BackgroundPainter;
52 import prefuse.util.display.Clip;
53 import prefuse.util.display.DebugStatsPainter;
54 import prefuse.util.display.ExportDisplayAction;
55 import prefuse.util.display.ItemBoundsListener;
56 import prefuse.util.display.PaintListener;
57 import prefuse.util.display.RenderingQueue;
58 import prefuse.visual.VisualItem;
59 import prefuse.visual.expression.VisiblePredicate;
60 import prefuse.visual.sort.ItemSorter;
61
62
63 /**
64  * <p>User interface component that provides an interactive view onto
65  * a visualization. The Display is responsible for drawing items to the
66  * screen and providing callbacks for user interface actions such as
67  * mouse and keyboard events. A Display must be associated with an
68  * {@link prefuse.Visualization} from which it pulls the items to visualize.
69  * </p>
70  *
71  * <p>To control which {@link prefuse.visual.VisualItem} instances are
72  * drawn, the Display also maintains an optional
73  * {@link prefuse.data.expression.Predicate} for filtering items. The
74  * drawing order of items is
75  * controlled by an {@link prefuse.visual.sort.ItemSorter} instance,
76  * which calculates a score for each item. Items with higher scores
77  * are drawn later, and hence on top of lower scoring items.
78  * </p>
79  *
80  * <p>The {@link prefuse.controls.Control Control}
81  * interface provides the user interface callbacks for supporting
82  * interaction. The {@link prefuse.controls} package contains a number
83  * of pre-built <code>Control</code> implementations for common
84  * interactions.</p>
85  *
86  * <p>The Display class also supports arbitrary graphics transforms through
87  * the <code>java.awt.geom.AffineTransform</code> class. The
88  * {@link #setTransform(java.awt.geom.AffineTransform) setTransform} method
89  * allows arbitrary transforms to be applied, while the
90  * {@link #pan(double,double) pan} and
91  * {@link #zoom(java.awt.geom.Point2D,double) zoom}
92  * methods provide convenience methods that appropriately update the current
93  * transform to achieve panning and zooming of the presentation space.</p>
94  *
95  * <p>Additionally, each Display instance also supports use of a text editor
96  * to facilitate direct editing of text. See the various
97  * {@link #editText(prefuse.visual.VisualItem, String)} methods.</p>
98  *
99  * @version 1.0
100  * @author <a HREF="http://jheer.org">jeffrey heer</a>
101  * @see Visualization
102  * @see prefuse.controls.Control
103  * @see prefuse.controls
104  */

105 public class Display extends JComponent JavaDoc {
106
107     private static final Logger JavaDoc s_logger
108         = Logger.getLogger(Display.class.getName());
109     
110     // visual item source
111
protected Visualization m_vis;
112     protected AndPredicate m_predicate = new AndPredicate();
113     
114     // listeners
115
protected CopyOnWriteArrayList m_controls = new CopyOnWriteArrayList();
116     protected CopyOnWriteArrayList m_painters;
117     protected CopyOnWriteArrayList m_bounders;
118     
119     // display
120
protected BufferedImage JavaDoc m_offscreen;
121     protected Clip m_clip = new Clip();
122     protected Clip m_screen = new Clip();
123     protected Clip m_bounds = new Clip();
124     protected Rectangle2D JavaDoc m_rclip = new Rectangle2D.Double JavaDoc();
125     protected boolean m_damageRedraw = true;
126     protected boolean m_highQuality = false;
127     
128     // optional background image
129
protected BackgroundPainter m_bgpainter = null;
130     
131     // rendering queue
132
protected RenderingQueue m_queue = new RenderingQueue();
133     protected int m_visibleCount = 0;
134     
135     // transform variables
136
protected AffineTransform JavaDoc m_transform = new AffineTransform JavaDoc();
137     protected AffineTransform JavaDoc m_itransform = new AffineTransform JavaDoc();
138     protected TransformActivity m_transact = new TransformActivity();
139     protected Point2D JavaDoc m_tmpPoint = new Point2D.Double JavaDoc();
140     
141     // frame count and debugging output
142
protected double frameRate;
143     protected int nframes = 0;
144     private int sampleInterval = 10;
145     private long mark = -1L;
146     
147     /* Custom tooltip, null to use regular tooltip mechanisms */
148     protected JToolTip JavaDoc m_customToolTip = null;
149     
150     // text editing variables
151
private JTextComponent JavaDoc m_editor;
152     private boolean m_editing;
153     private VisualItem m_editItem;
154     private String JavaDoc m_editAttribute;
155     
156     /**
157      * Creates a new Display instance. You will need to associate this
158      * Display with a {@link Visualization} for it to display anything.
159      */

160     public Display() {
161         this(null);
162     }
163     
164     /**
165      * Creates a new Display associated with the given Visualization.
166      * By default, all {@link prefuse.visual.VisualItem} instances in the
167      * {@link Visualization} will be drawn by the Display.
168      * @param visualization the {@link Visualization} backing this Display
169      */

170     public Display(Visualization visualization) {
171         this(visualization, (Predicate)null);
172     }
173     
174     /**
175      * Creates a new Display associated with the given Visualization that
176      * draws all VisualItems in the visualization that pass the given
177      * Predicate. The predicate string will be parsed by the
178      * {@link prefuse.data.expression.parser.ExpressionParser} to get a
179      * {@link prefuse.data.expression.Predicate} instance.
180      * @param visualization the {@link Visualization} backing this Display
181      * @param predicate a predicate expression in the prefuse expression
182      * language. This expression will be parsed; if the parsing fails or does
183      * not result in a Predicate instance, an exception will result.
184      */

185     public Display(Visualization visualization, String JavaDoc predicate) {
186         this(visualization,
187                 (Predicate)ExpressionParser.parse(predicate, true));
188     }
189     
190     /**
191      * Creates a new Display associated with the given Visualization that
192      * draws all VisualItems in the visualization that pass the given
193      * Predicate.
194      * @param visualization the {@link Visualization} backing this Display
195      * @param predicate the filtering {@link prefuse.data.expression.Predicate}
196      */

197     public Display(Visualization visualization, Predicate predicate) {
198         setDoubleBuffered(false);
199         setBackground(Color.WHITE);
200         
201         // initialize text editor
202
m_editing = false;
203         m_editor = new JTextField JavaDoc();
204         m_editor.setBorder(null);
205         m_editor.setVisible(false);
206         this.add(m_editor);
207         
208         // register input event capturer
209
InputEventCapturer iec = new InputEventCapturer();
210         addMouseListener(iec);
211         addMouseMotionListener(iec);
212         addMouseWheelListener(iec);
213         addKeyListener(iec);
214         
215         registerDefaultCommands();
216         
217         // invalidate the display when the filter changes
218
m_predicate.addExpressionListener(new UpdateListener() {
219             public void update(Object JavaDoc src) { damageReport(); }
220         });
221         
222         setVisualization(visualization);
223         setPredicate(predicate);
224         setSize(400,400); // set a default size
225
}
226     
227     /**
228      * Registers default keystroke commands on the Display. The default
229      * commands are
230      * <ul><li><b>ctrl D</b> - Toggle debug info display</li>
231      * <li><b>ctrl H</b> - Toggle high quality rendering</li>
232      * <li><b>ctrl E</b> - Export display view to an image file</li></ul>
233      * Subclasses can override this method to prevent these commands from
234      * being set. Additional commands can be registered using the
235      * <code>registerKeyboardAction</code> method.
236      */

237     protected void registerDefaultCommands() {
238         // add debugging output control
239
registerKeyboardAction(new ActionListener JavaDoc() {
240             private PaintListener m_debug = null;
241
242             public void actionPerformed(ActionEvent JavaDoc e) {
243                 if (m_debug == null) {
244                     m_debug = new DebugStatsPainter();
245                     addPaintListener(m_debug);
246                 } else {
247                     removePaintListener(m_debug);
248                     m_debug = null;
249                 }
250                 repaint();
251             }
252         }, "debug info", KeyStroke.getKeyStroke("ctrl D"), WHEN_FOCUSED);
253         
254         // add quality toggle
255
registerKeyboardAction(new ActionListener JavaDoc() {
256             public void actionPerformed(ActionEvent JavaDoc e) {
257                 setHighQuality(!isHighQuality());
258                 repaint();
259             }
260         }, "toggle high-quality drawing", KeyStroke.getKeyStroke("ctrl H"),
261                 WHEN_FOCUSED);
262         
263         // add image output control, if this is not an applet
264
try {
265             registerKeyboardAction(new ExportDisplayAction(this),
266              "export display", KeyStroke.getKeyStroke("ctrl E"), WHEN_FOCUSED);
267         } catch (SecurityException JavaDoc se) {
268         }
269     }
270     
271     /**
272      * Set the size of the Display.
273      * @param width the width of the Display in pixels
274      * @param height the height of the Display in pixels
275      * @see java.awt.Component#setSize(int, int)
276      */

277     public void setSize(int width, int height) {
278         m_offscreen = null;
279         setPreferredSize(new Dimension JavaDoc(width, height));
280         super.setSize(width, height);
281     }
282     
283     /**
284      * Set the size of the Display.
285      * @param d the dimensions of the Display in pixels
286      * @see java.awt.Component#setSize(java.awt.Dimension)
287      */

288     public void setSize(Dimension JavaDoc d) {
289         m_offscreen = null;
290         setPreferredSize(d);
291         super.setSize(d);
292     }
293
294     /**
295      * Invalidates this component. Overridden to ensure that an
296      * internal damage report is generated.
297      * @see java.awt.Component#invalidate()
298      */

299     public void invalidate() {
300         damageReport();
301         super.invalidate();
302     }
303     
304     /**
305      * @see java.awt.Component#setBounds(int, int, int, int)
306      */

307     public void setBounds(int x, int y, int w, int h) {
308         m_offscreen = null;
309         super.setBounds(x,y,w,h);
310     }
311     
312     /**
313      * Sets the font used by this Display. This determines the font used
314      * by this Display's text editor and in any debugging text.
315      * @param f the Font to use
316      */

317     public void setFont(Font JavaDoc f) {
318         super.setFont(f);
319         m_editor.setFont(f);
320     }
321     
322     /**
323      * Returns the running average frame rate for this Display.
324      * @return the frame rate
325      */

326     public double getFrameRate() {
327         return frameRate;
328     }
329     
330     /**
331      * Determines if the Display uses a higher quality rendering, using
332      * anti-aliasing. This causes drawing to be much slower, however, and
333      * so is disabled by default.
334      * @param on true to enable anti-aliased rendering, false to disable it
335      */

336     public void setHighQuality(boolean on) {
337         if ( m_highQuality != on )
338             damageReport();
339         m_highQuality = on;
340     }
341     
342     /**
343      * Indicates if the Display is using high quality (return value true) or
344      * regular quality (return value false) rendering.
345      * @return true if high quality rendering is enabled, false otherwise
346      */

347     public boolean isHighQuality() {
348         return m_highQuality;
349     }
350     
351     /**
352      * Returns the Visualization backing this Display.
353      * @return this Display's {@link Visualization}
354      */

355     public Visualization getVisualization() {
356         return m_vis;
357     }
358     
359     /**
360      * Set the Visualiztion associated with this Display. This Display
361      * will render the items contained in the provided visualization. If this
362      * Display is already associated with a different Visualization, the
363      * Display unregisters itself with the previous one.
364      * @param vis the backing {@link Visualization} to use.
365      */

366     public void setVisualization(Visualization vis) {
367         // TODO: synchronization?
368
if ( m_vis == vis ) {
369             // nothing need be done
370
return;
371         } else if ( m_vis != null ) {
372             // remove this display from it's previous registry
373
m_vis.removeDisplay(this);
374         }
375         m_vis = vis;
376         if ( m_vis != null )
377             m_vis.addDisplay(this);
378     }
379     
380     /**
381      * Returns the filtering Predicate used to control what items are drawn
382      * by this display.
383      * @return the filtering {@link prefuse.data.expression.Predicate}
384      */

385     public Predicate getPredicate() {
386         if ( m_predicate.size() == 1 ) {
387             return BooleanLiteral.TRUE;
388         } else {
389             return m_predicate.get(0);
390         }
391     }
392
393     /**
394      * Sets the filtering Predicate used to control what items are drawn by
395      * this Display.
396      * @param expr the filtering predicate to use. The predicate string will be
397      * parsed by the {@link prefuse.data.expression.parser.ExpressionParser}.
398      * If the parse fails or does not result in a
399      * {@link prefuse.data.expression.Predicate} instance, an exception will
400      * be thrown.
401      */

402     public void setPredicate(String JavaDoc expr) {
403         Predicate p = (Predicate)ExpressionParser.parse(expr, true);
404         setPredicate(p);
405     }
406     
407     /**
408      * Sets the filtering Predicate used to control what items are drawn by
409      * this Display.
410      * @param p the filtering {@link prefuse.data.expression.Predicate} to use
411      */

412     public synchronized void setPredicate(Predicate p) {
413         if ( p == null ) {
414             m_predicate.set(VisiblePredicate.TRUE);
415         } else {
416             m_predicate.set(new Predicate[] {p, VisiblePredicate.TRUE});
417         }
418     }
419     
420     /**
421      * Returns the number of visible items processed by this Display. This
422      * includes items not currently visible on screen due to the current
423      * panning or zooming state.
424      * @return the count of visible items
425      */

426     public int getVisibleItemCount() {
427         return m_visibleCount;
428     }
429     
430     /**
431      * Get the ItemSorter that determines the rendering order of the
432      * VisualItems. Items are drawn in ascending order of the scores provided
433      * by the ItemSorter.
434      * @return this Display's {@link prefuse.visual.sort.ItemSorter}
435      */

436     public ItemSorter getItemSorter() {
437         return m_queue.sort;
438     }
439
440     /**
441      * Set the ItemSorter that determines the rendering order of the
442      * VisualItems. Items are drawn in ascending order of the scores provided
443      * by the ItemSorter.
444      * @return the {@link prefuse.visual.sort.ItemSorter} to use
445      */

446     public synchronized void setItemSorter(ItemSorter cmp) {
447         damageReport();
448         m_queue.sort = cmp;
449     }
450     
451
452     /**
453      * Set a background image for this display.
454      * @param image the background Image. If a null value is provided,
455      * than no background image will be shown.
456      * @param fixed true if the background image should stay in a fixed
457      * position, invariant to panning, zooming, or rotation; false if
458      * the image should be subject to view transforms
459      * @param tileImage true to tile the image across the visible background,
460      * false to only include the image once
461      */

462     public synchronized void setBackgroundImage(Image JavaDoc image,
463                                       boolean fixed, boolean tileImage)
464     {
465         BackgroundPainter bg = null;
466         if ( image != null )
467             bg = new BackgroundPainter(image, fixed, tileImage);
468         setBackgroundPainter(bg);
469     }
470
471     /**
472      * Set a background image for this display.
473      * @param location a location String of where to retrieve the
474      * image file from. Uses
475      * {@link prefuse.util.io.IOLib#urlFromString(String)} to resolve
476      * the String. If a null value is provided, than no background
477      * image will be shown.
478      * @param fixed true if the background image should stay in a fixed
479      * position, invariant to panning, zooming, or rotation; false if
480      * the image should be subject to view transforms
481      * @param tileImage true to tile the image across the visible background,
482      * false to only include the image once
483      */

484     public synchronized void setBackgroundImage(String JavaDoc location,
485                                       boolean fixed, boolean tileImage)
486     {
487         BackgroundPainter bg = null;
488         if ( location != null )
489             bg = new BackgroundPainter(location, fixed, tileImage);
490         setBackgroundPainter(bg);
491     }
492     
493     private void setBackgroundPainter(BackgroundPainter bg) {
494         if ( m_bgpainter != null )
495             removePaintListener(m_bgpainter);
496         m_bgpainter = bg;
497         if ( bg != null )
498             addPaintListener(bg);
499     }
500     
501     // ------------------------------------------------------------------------
502
// ToolTips
503

504     /**
505      * Returns the tooltip instance to use for this Display. By default, uses
506      * the normal Swing tooltips, returning the result of this same method
507      * invoked on the JComponent super-class. If a custom tooltip has been
508      * set, that is returned instead.
509      * @see #setCustomToolTip(JToolTip)
510      * @see javax.swing.JComponent#createToolTip()
511      */

512     public JToolTip JavaDoc createToolTip() {
513         if ( m_customToolTip == null ) {
514             return super.createToolTip();
515         } else {
516             return m_customToolTip;
517         }
518     }
519     
520     /**
521      * Set a custom tooltip to use for this Display. To trigger tooltip
522      * display, you must still use the <code>setToolTipText</code> method
523      * as usual. The actual text will no longer have any effect, other
524      * than that a null text value will result in no tooltip display
525      * while a non-null text value will result in a tooltip being
526      * shown. Clients are responsible for setting the tool tip
527      * text to enable/disable tooltips as well as updating the content
528      * of their own custom tooltip instance.
529      * @param tooltip the tooltip component to use
530      * @see prefuse.util.ui.JCustomTooltip
531      */

532     public void setCustomToolTip(JToolTip JavaDoc tooltip) {
533         m_customToolTip = tooltip;
534     }
535     
536     /**
537      * Get the custom tooltip used by this Display. Returns null if normal
538      * tooltips are being used.
539      * @return the custom tooltip used by this Display, or null if none
540      */

541     public JToolTip JavaDoc getCustomToolTip() {
542         return m_customToolTip;
543     }
544     
545     // ------------------------------------------------------------------------
546
// Clip / Bounds Management
547

548     /**
549      * Indicates if damage/redraw rendering is enabled. If enabled, the display
550      * will only redraw within the bounding box of all areas that have changed
551      * since the last rendering operation. For small changes, such as a single
552      * item being dragged, this can result in a significant performance
553      * increase. By default, the damage/redraw optimization is enabled. It can
554      * be disabled, however, if rendering artifacts are appearing in your
555      * visualization. Be careful though, as this may not be the best solution.
556      * Rendering artifacts may result because the item bounds returned by
557      * {@link prefuse.visual.VisualItem#getBounds()} are not accurate and the
558      * item's {@link prefuse.render.Renderer} is drawing outside of the
559      * reported bounds. In this case, there is usually a bug in the Renderer.
560      * One reported problem arises from Java itself, however, which
561      * inaccurately redraws images outside of their reported bounds. If you
562      * have a visulization with a number of images and are seeing rendering
563      * artifacts, try disabling damage/redraw.
564      * @return true if damage/redraw optimizations are enabled, false
565      * otherwise (in which case the entire Display is redrawn upon a repaint)
566      */

567     public synchronized boolean isDamageRedraw() {
568         return m_damageRedraw;
569     }
570     
571     /**
572      * Sets if damage/redraw rendering is enabled. If enabled, the display
573      * will only redraw within the bounding box of all areas that have changed
574      * since the last rendering operation. For small changes, such as a single
575      * item being dragged, this can result in a significant performance
576      * increase. By default, the damage/redraw optimization is enabled. It can
577      * be disabled, however, if rendering artifacts are appearing in your
578      * visualization. Be careful though, as this may not be the best solution.
579      * Rendering artifacts may result because the item bounds returned by
580      * {@link prefuse.visual.VisualItem#getBounds()} are not accurate and the
581      * item's {@link prefuse.render.Renderer} is drawing outside of the
582      * reported bounds. In this case, there is usually a bug in the Renderer.
583      * One reported problem arises from Java itself, however, which
584      * inaccurately redraws images outside of their reported bounds. If you
585      * have a visulization with a number of images and are seeing rendering
586      * artifacts, try disabling damage/redraw.
587      * @param b true to enable damage/redraw optimizations, false otherwise
588      * (in which case the entire Display will be redrawn upon a repaint)
589      */

590     public synchronized void setDamageRedraw(boolean b) {
591         m_damageRedraw = b;
592         m_clip.invalidate();
593     }
594     
595     /**
596      * Reports damage to the Display within in the specified region.
597      * @param region the damaged region, in absolute coordinates
598      */

599     public synchronized void damageReport(Rectangle2D JavaDoc region) {
600         if ( m_damageRedraw )
601             m_clip.union(region);
602     }
603     
604     /**
605      * Reports damage to the entire Display.
606      */

607     public synchronized void damageReport() {
608         m_clip.invalidate();
609     }
610    
611     /**
612      * Clears any reports of damaged regions, causing the Display to believe
613      * that the display contents are up-to-date. If used incorrectly this
614      * can cause inaccurate rendering. <strong>Call this method only
615      * if you know what you are doing.</strong>
616      */

617     public synchronized void clearDamage() {
618         if ( m_damageRedraw )
619             m_clip.reset();
620     }
621     
622     /**
623      * Returns the bounds, in absolute (item-space) coordinates, of the total
624      * bounds occupied by all currently visible VisualItems. This method
625      * allocates a new Rectangle2D instance for the result.
626      * @return the bounding box of all visibile VisualItems
627      * @see #getItemBounds(Rectangle2D)
628      */

629     public synchronized Rectangle2D JavaDoc getItemBounds() {
630         return getItemBounds(new Rectangle2D.Double JavaDoc());
631     }
632
633     /**
634      * Returns the bounds, in absolute (item-space) coordinates, of the total
635      * bounds occupied by all currently visible VisualItems.
636      * @param b the Rectangle2D to use to store the return value
637      * @return the bounding box of all visibile VisualItems
638      */

639     public synchronized Rectangle2D JavaDoc getItemBounds(Rectangle2D JavaDoc b) {
640         b.setFrameFromDiagonal(m_bounds.getMinX(), m_bounds.getMinY(),
641                                m_bounds.getMaxX(), m_bounds.getMaxY());
642         return b;
643     }
644     
645     // ------------------------------------------------------------------------
646
// Rendering
647

648     /**
649      * Returns the offscreen buffer used for double buffering.
650      * @return the offscreen buffer
651      */

652     public BufferedImage JavaDoc getOffscreenBuffer() {
653         return m_offscreen;
654     }
655     
656     /**
657      * Creates a new buffered image to use as an offscreen buffer.
658      */

659     protected BufferedImage JavaDoc getNewOffscreenBuffer(int width, int height) {
660         BufferedImage JavaDoc img = null;
661         if ( !GraphicsEnvironment.isHeadless() ) {
662             try {
663                 img = (BufferedImage JavaDoc)createImage(width, height);
664             } catch ( Exception JavaDoc e ) {
665                 img = null;
666             }
667         }
668         if ( img == null ) {
669             return new BufferedImage JavaDoc(width, height,
670                                      BufferedImage.TYPE_INT_RGB);
671         }
672         return img;
673     }
674     
675     /**
676      * Saves a copy of this display as an image to the specified output stream.
677      * @param output the output stream to write to.
678      * @param format the image format (e.g., "JPG", "PNG"). The number and kind
679      * of available formats varies by platform. See
680      * {@link javax.imageio.ImageIO} and related classes for more.
681      * @param scale how much to scale the image by. For example, a value of 2.0
682      * will result in an image with twice the pixel width and height of this
683      * Display.
684      * @return true if image was successfully saved, false if an error occurred.
685      */

686     public boolean saveImage(OutputStream JavaDoc output, String JavaDoc format, double scale)
687     {
688         try {
689             // get an image to draw into
690
Dimension JavaDoc d = new Dimension JavaDoc((int)(scale*getWidth()),
691                                         (int)(scale*getHeight()));
692             BufferedImage JavaDoc img = getNewOffscreenBuffer(d.width, d.height);
693             Graphics2D JavaDoc g = (Graphics2D JavaDoc)img.getGraphics();
694             
695             // set up the display, render, then revert to normal settings
696
Point2D JavaDoc p = new Point2D.Double JavaDoc(0,0);
697             zoom(p, scale); // also takes care of damage report
698
boolean q = isHighQuality();
699             setHighQuality(true);
700             paintDisplay(g, d);
701             setHighQuality(q);
702             zoom(p, 1/scale); // also takes care of damage report
703

704             // save the image and return
705
ImageIO.write(img, format, output);
706             return true;
707         } catch ( Exception JavaDoc e ) {
708             e.printStackTrace();
709             return false;
710         }
711     }
712
713     /**
714      * @see java.awt.Component#update(java.awt.Graphics)
715      */

716     public void update(Graphics JavaDoc g) {
717         paint(g);
718     }
719     
720     /**
721      * Paints the offscreen buffer to the provided graphics context.
722      * @param g the Graphics context to paint to
723      */

724     protected void paintBufferToScreen(Graphics JavaDoc g) {
725         synchronized ( this ) {
726             g.drawImage(m_offscreen, 0, 0, null);
727         }
728     }
729
730     /**
731      * Immediately repaints the contents of the offscreen buffer
732      * to the screen. This bypasses the usual rendering loop.
733      */

734     public void repaintImmediate() {
735         Graphics JavaDoc g = this.getGraphics();
736         if (g != null && m_offscreen != null) {
737             paintBufferToScreen(g);
738         }
739     }
740
741     /**
742      * Sets the transform of the provided Graphics context to be the
743      * transform of this Display and sets the desired rendering hints.
744      * @param g the Graphics context to prepare.
745      */

746     protected void prepareGraphics(Graphics2D JavaDoc g) {
747         if ( m_transform != null )
748             g.transform(m_transform);
749         setRenderingHints(g);
750     }
751     
752     /**
753      * Sets the rendering hints that should be used while drawing
754      * the visualization to the screen. Subclasses can override
755      * this method to set hints as desired. Such subclasses should
756      * consider honoring the high quality flag in one form or another.
757      * @param g the Graphics context on which to set the rendering hints
758      */

759     protected void setRenderingHints(Graphics2D JavaDoc g) {
760         if ( m_highQuality ) {
761             g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
762                     RenderingHints.VALUE_ANTIALIAS_ON);
763         } else {
764             g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
765                     RenderingHints.VALUE_ANTIALIAS_OFF);
766         }
767         g.setRenderingHint(
768             RenderingHints.KEY_RENDERING,
769             RenderingHints.VALUE_RENDER_QUALITY);
770         g.setRenderingHint(
771             RenderingHints.KEY_INTERPOLATION,
772             RenderingHints.VALUE_INTERPOLATION_BICUBIC);
773     }
774
775
776     /**
777      * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
778      */

779     public void paintComponent(Graphics JavaDoc g) {
780         if (m_offscreen == null) {
781             m_offscreen = getNewOffscreenBuffer(getWidth(), getHeight());
782             damageReport();
783         }
784         Graphics2D JavaDoc g2D = (Graphics2D JavaDoc)g;
785         Graphics2D JavaDoc buf_g2D = (Graphics2D JavaDoc) m_offscreen.getGraphics();
786         
787         // Why not fire a pre-paint event here?
788
// Pre-paint events are fired by the clearRegion method
789

790         // paint the visualization
791
paintDisplay(buf_g2D, getSize());
792         paintBufferToScreen(g2D);
793         
794         // fire post-paint events to any painters
795
firePostPaint(g2D);
796         
797         buf_g2D.dispose();
798         
799         // compute frame rate
800
nframes++;
801         if ( mark < 0 ) {
802             mark = System.currentTimeMillis();
803             nframes = 0;
804         } else if ( nframes == sampleInterval ){
805             long t = System.currentTimeMillis();
806             frameRate = (1000.0*nframes)/(t-mark);
807             mark = t;
808             nframes = 0;
809         }
810     }
811     
812     /**
813      * Renders the display within the given graphics context and size bounds.
814      * @param g2D the <code>Graphics2D</code> context to use for rendering
815      * @param d the rendering width and height of the Display
816      */

817     public void paintDisplay(Graphics2D JavaDoc g2D, Dimension JavaDoc d) {
818         // if double-locking *ALWAYS* lock on the visualization first
819
synchronized ( m_vis ) {
820         synchronized ( this ) {
821             
822             if ( m_clip.isEmpty() )
823                 return; // no damage, no render
824

825             // map the screen bounds to absolute coords
826
m_screen.setClip(0, 0, d.width+1, d.height+1);
827             m_screen.transform(m_itransform);
828             
829             // compute the approximate size of an "absolute pixel"
830
// values too large are OK (though cause unnecessary rendering)
831
// values too small will cause incorrect rendering
832
double pixel = 1.0 + 1.0/getScale();
833             
834             if ( m_damageRedraw ) {
835                 if ( m_clip.isInvalid() ) {
836                     // if clip is invalid, we clip to the entire screen
837
m_clip.setClip(m_screen);
838                 } else {
839                     // otherwise intersect damaged region with display bounds
840
m_clip.intersection(m_screen);
841                 }
842   
843                 // expand the clip by the extra pixel margin
844
m_clip.expand(pixel);
845                 
846                 // set the transform, rendering keys, etc
847
prepareGraphics(g2D);
848                 
849                 // now set the actual rendering clip
850
m_rclip.setFrameFromDiagonal(
851                         m_clip.getMinX(), m_clip.getMinY(),
852                         m_clip.getMaxX(), m_clip.getMaxY());
853                 g2D.setClip(m_rclip);
854                 
855                 // finally, we want to clear the region we'll redraw. we clear
856
// a slightly larger area than the clip. if we don't do this,
857
// we sometimes get rendering artifacts, possibly due to
858
// scaling mismatches in the Java2D implementation
859
m_rclip.setFrameFromDiagonal(
860                         m_clip.getMinX()-pixel, m_clip.getMinY()-pixel,
861                         m_clip.getMaxX()+pixel, m_clip.getMaxY()+pixel);
862
863             } else {
864                 // set the background region to clear
865
m_rclip.setFrame(0, 0, getWidth(), getHeight());
866                 // set the item clip to the current screen
867
m_clip.setClip(m_screen);
868                 // set the transform, rendering keys, etc
869
prepareGraphics(g2D);
870             }
871
872             // now clear the region
873
clearRegion(g2D, m_rclip);
874             
875             // -- render ----------------------------
876
// the actual rendering loop
877

878             // copy current item bounds into m_rclip, reset item bounds
879
getItemBounds(m_rclip);
880             m_bounds.reset();
881             
882             // fill the rendering and picking queues
883
m_queue.clear(); // clear the queue
884
Iterator JavaDoc items = m_vis.items(m_predicate);
885             for ( m_visibleCount=0; items.hasNext(); ++m_visibleCount ) {
886                 VisualItem item = (VisualItem)items.next();
887                 Rectangle2D JavaDoc bounds = item.getBounds();
888                 m_bounds.union(bounds); // add to item bounds
889

890                 if ( m_clip.intersects(bounds, pixel) )
891                     m_queue.addToRenderQueue(item);
892                 if ( item.isInteractive() )
893                     m_queue.addToPickingQueue(item);
894             }
895             
896             // sort the rendering queue
897
m_queue.sortRenderQueue();
898             
899             // render each visual item
900
for ( int i=0; i<m_queue.rsize; ++i ) {
901                 m_queue.ritems[i].render(g2D);
902             }
903             
904             // no more damage so reset the clip
905
if ( m_damageRedraw )
906                 m_clip.reset();
907             
908             // fire bounds change, if appropriate
909
checkItemBoundsChanged(m_rclip);
910             
911         }} // end synchronized block
912
}
913     
914     /**
915      * Immediately render the given VisualItem to the screen. This method
916      * bypasses the Display's offscreen buffer.
917      * @param item the VisualItem to render immediately
918      */

919     public void renderImmediate(VisualItem item) {
920         Graphics2D JavaDoc g2D = (Graphics2D JavaDoc)this.getGraphics();
921         prepareGraphics(g2D);
922         item.render(g2D);
923     }
924     
925     /**
926      * Paints the graph to the provided graphics context, for output to a
927      * printer. This method does not double buffer the painting, in order to
928      * provide the maximum print quality.
929      *
930      * <b>This method may not be working correctly,
931      * and will be repaired at a later date.</b>
932      *
933      * @param g the printer graphics context.
934      */

935     protected void printComponent(Graphics JavaDoc g) {
936         boolean wasHighQuality = m_highQuality;
937         try {
938             // Set the quality to high for the duration of the printing.
939
m_highQuality = true;
940             // Paint directly to the print graphics context.
941
paintDisplay((Graphics2D JavaDoc) g, getSize());
942         } finally {
943             // Reset the quality to the state it was in before printing.
944
m_highQuality = wasHighQuality;
945         }
946     }
947     
948     /**
949      * Clears the specified region of the display
950      * in the display's offscreen buffer.
951      */

952     protected void clearRegion(Graphics2D JavaDoc g, Rectangle2D JavaDoc r) {
953         g.setColor(getBackground());
954         g.fill(r);
955         // fire pre-paint events to any painters
956
firePrePaint(g);
957     }
958
959     // ------------------------------------------------------------------------
960
// Transformations
961

962     /**
963      * Set the 2D AffineTransform (e.g., scale, shear, pan, rotate) used by
964      * this display before rendering visual items. The provided transform
965      * must be invertible, otherwise an expection will be thrown. For simple
966      * panning and zooming transforms, you can instead use the provided
967      * pan() and zoom() methods.
968      */

969     public synchronized void setTransform(AffineTransform JavaDoc transform)
970         throws NoninvertibleTransformException JavaDoc
971     {
972         damageReport();
973         m_transform = transform;
974         m_itransform = m_transform.createInverse();
975     }
976     
977     /**
978      * Returns a reference to the AffineTransformation used by this Display.
979      * Changes made to this reference WILL corrupt the state of
980      * this display. Use setTransform() to safely update the transform state.
981      * @return the AffineTransform
982      */

983     public AffineTransform JavaDoc getTransform() {
984         return m_transform;
985     }
986     
987     /**
988      * Returns a reference to the inverse of the AffineTransformation used by
989      * this display. Direct changes made to this reference WILL corrupt the
990      * state of this display.
991      * @return the inverse AffineTransform
992      */

993     public AffineTransform JavaDoc getInverseTransform() {
994         return m_itransform;
995     }
996     
997     /**
998      * Gets the absolute co-ordinate corresponding to the given screen
999      * co-ordinate.
1000     * @param screen the screen co-ordinate to transform
1001     * @param abs a reference to put the result in. If this is the same
1002     * object as the screen co-ordinate, it will be overridden safely. If
1003     * this value is null, a new Point2D instance will be created and
1004     * returned.
1005     * @return the point in absolute co-ordinates
1006     */

1007    public Point2D JavaDoc getAbsoluteCoordinate(Point2D JavaDoc screen, Point2D JavaDoc abs) {
1008        return m_itransform.transform(screen, abs);
1009    }
1010    
1011    /**
1012     * Returns the current scale (zoom) value.
1013     * @return the current scale. This is the
1014     * scaling factor along the x-dimension, so be careful when
1015     * using this value in rare non-uniform scaling cases.
1016     */

1017    public double getScale() {
1018        return m_transform.getScaleX();
1019    }
1020    
1021    /**
1022     * Returns the x-coordinate of the top-left of the display,
1023     * in absolute (item-space) co-ordinates.
1024     * @return the x co-ord of the top-left corner, in absolute coordinates
1025     */

1026    public double getDisplayX() {
1027        return -m_transform.getTranslateX();
1028    }
1029    
1030    /**
1031     * Returns the y-coordinate of the top-left of the display,
1032     * in absolute (item-space) co-ordinates.
1033     * @return the y co-ord of the top-left corner, in absolute coordinates
1034     */

1035    public double getDisplayY() {
1036        return -m_transform.getTranslateY();
1037    }
1038    
1039    /**
1040     * Pans the view provided by this display in screen coordinates.
1041     * @param dx the amount to pan along the x-dimension, in pixel units
1042     * @param dy the amount to pan along the y-dimension, in pixel units
1043     */

1044    public synchronized void pan(double dx, double dy) {
1045        double panx = dx / m_transform.getScaleX();
1046        double pany = dy / m_transform.getScaleY();
1047        panAbs(panx,pany);
1048    }
1049    
1050    /**
1051     * Pans the view provided by this display in absolute (i.e. item-space)
1052     * coordinates.
1053     * @param dx the amount to pan along the x-dimension, in absolute co-ords
1054     * @param dy the amount to pan along the y-dimension, in absolute co-ords
1055     */

1056    public synchronized void panAbs(double dx, double dy) {
1057        damageReport();
1058        m_transform.translate(dx, dy);
1059        try {
1060            m_itransform = m_transform.createInverse();
1061        } catch ( Exception JavaDoc e ) { /*will never happen here*/ }
1062    }
1063    
1064    /**
1065     * Pans the display view to center on the provided point in
1066     * screen (pixel) coordinates.
1067     * @param p the point to center on, in screen co-ords
1068     */

1069    public synchronized void panTo(Point2D JavaDoc p) {
1070        m_itransform.transform(p, m_tmpPoint);
1071        panToAbs(m_tmpPoint);
1072    }
1073    
1074    /**
1075     * Pans the display view to center on the provided point in
1076     * absolute (i.e. item-space) coordinates.
1077     * @param p the point to center on, in absolute co-ords
1078     */

1079    public synchronized void panToAbs(Point2D JavaDoc p) {
1080        double sx = m_transform.getScaleX();
1081        double sy = m_transform.getScaleY();
1082        double x = p.getX(); x = (Double.isNaN(x) ? 0 : x);
1083        double y = p.getY(); y = (Double.isNaN(y) ? 0 : y);
1084        x = getWidth() /(2*sx) - x;
1085        y = getHeight()/(2*sy) - y;
1086        
1087        double dx = x-(m_transform.getTranslateX()/sx);
1088        double dy = y-(m_transform.getTranslateY()/sy);
1089
1090        damageReport();
1091        m_transform.translate(dx, dy);
1092        try {
1093            m_itransform = m_transform.createInverse();
1094        } catch ( Exception JavaDoc e ) { /*will never happen here*/ }
1095    }
1096
1097    /**
1098     * Zooms the view provided by this display by the given scale,
1099     * anchoring the zoom at the specified point in screen coordinates.
1100     * @param p the anchor point for the zoom, in screen coordinates
1101     * @param scale the amount to zoom by
1102     */

1103    public synchronized void zoom(final Point2D JavaDoc p, double scale) {
1104        m_itransform.transform(p, m_tmpPoint);
1105        zoomAbs(m_tmpPoint, scale);
1106    }
1107    
1108    /**
1109     * Zooms the view provided by this display by the given scale,
1110     * anchoring the zoom at the specified point in absolute coordinates.
1111     * @param p the anchor point for the zoom, in absolute
1112     * (i.e. item-space) co-ordinates
1113     * @param scale the amount to zoom by
1114     */

1115    public synchronized void zoomAbs(final Point2D JavaDoc p, double scale) {;
1116        double zx = p.getX(), zy = p.getY();
1117        damageReport();
1118        m_transform.translate(zx, zy);
1119        m_transform.scale(scale,scale);
1120        m_transform.translate(-zx, -zy);
1121        try {
1122            m_itransform = m_transform.createInverse();
1123        } catch ( Exception JavaDoc e ) { /*will never happen here*/ }
1124    }
1125    
1126    /**
1127     * Rotates the view provided by this display by the given angle in radians,
1128     * anchoring the rotation at the specified point in screen coordinates.
1129     * @param p the anchor point for the rotation, in screen coordinates
1130     * @param theta the angle to rotate by, in radians
1131     */

1132    public synchronized void rotate(final Point2D JavaDoc p, double theta) {
1133        m_itransform.transform(p, m_tmpPoint);
1134        rotateAbs(m_tmpPoint, theta);
1135    }
1136    
1137    /**
1138     * Rotates the view provided by this display by the given angle in radians,
1139     * anchoring the rotation at the specified point in absolute coordinates.
1140     * @param p the anchor point for the rotation, in absolute
1141     * (i.e. item-space) co-ordinates
1142     * @param theta the angle to rotation by, in radians
1143     */

1144    public synchronized void rotateAbs(final Point2D JavaDoc p, double theta) {
1145        double zx = p.getX(), zy = p.getY();
1146        damageReport();
1147        m_transform.translate(zx, zy);
1148        m_transform.rotate(theta);
1149        m_transform.translate(-zx, -zy);
1150        try {
1151            m_itransform = m_transform.createInverse();
1152        } catch ( Exception JavaDoc e ) { /*will never happen here*/ }
1153    }
1154
1155    /**
1156     * Animate a pan along the specified distance in screen (pixel)
1157     * co-ordinates using the provided duration.
1158     * @param dx the amount to pan along the x-dimension, in pixel units
1159     * @param dy the amount to pan along the y-dimension, in pixel units
1160     * @param duration the duration of the animation, in milliseconds
1161     */

1162    public synchronized void animatePan(double dx, double dy, long duration) {
1163        double panx = dx / m_transform.getScaleX();
1164        double pany = dy / m_transform.getScaleY();
1165        animatePanAbs(panx,pany,duration);
1166    }
1167    
1168    /**
1169     * Animate a pan along the specified distance in absolute (item-space)
1170     * co-ordinates using the provided duration.
1171     * @param dx the amount to pan along the x-dimension, in absolute co-ords
1172     * @param dy the amount to pan along the y-dimension, in absolute co-ords
1173     * @param duration the duration of the animation, in milliseconds
1174     */

1175    public synchronized void animatePanAbs(double dx, double dy, long duration) {
1176        m_transact.pan(dx,dy,duration);
1177    }
1178    
1179    /**
1180     * Animate a pan to the specified location in screen (pixel)
1181     * co-ordinates using the provided duration.
1182     * @param p the point to pan to in screen (pixel) units
1183     * @param duration the duration of the animation, in milliseconds
1184     */

1185    public synchronized void animatePanTo(Point2D JavaDoc p, long duration) {
1186        Point2D JavaDoc pp = new Point2D.Double JavaDoc();
1187        m_itransform.transform(p,pp);
1188        animatePanToAbs(pp,duration);
1189    }
1190    
1191    /**
1192     * Animate a pan to the specified location in absolute (item-space)
1193     * co-ordinates using the provided duration.
1194     * @param p the point to pan to in absolute (item-space) units
1195     * @param duration the duration of the animation, in milliseconds
1196     */

1197    public synchronized void animatePanToAbs(Point2D JavaDoc p, long duration) {
1198        m_tmpPoint.setLocation(0,0);
1199        m_itransform.transform(m_tmpPoint,m_tmpPoint);
1200        double x = p.getX(); x = (Double.isNaN(x) ? 0 : x);
1201        double y = p.getY(); y = (Double.isNaN(y) ? 0 : y);
1202        double w = getWidth() /(2*m_transform.getScaleX());
1203        double h = getHeight()/(2*m_transform.getScaleY());
1204        double dx = w-x+m_tmpPoint.getX();
1205        double dy = h-y+m_tmpPoint.getY();
1206        animatePanAbs(dx,dy,duration);
1207    }
1208    
1209    /**
1210     * Animate a zoom centered on a given location in screen (pixel)
1211     * co-ordinates by the given scale using the provided duration.
1212     * @param p the point to center on in screen (pixel) units
1213     * @param scale the scale factor to zoom by
1214     * @param duration the duration of the animation, in milliseconds
1215     */

1216    public synchronized void animateZoom(final Point2D JavaDoc p, double scale, long duration) {
1217        Point2D JavaDoc pp = new Point2D.Double JavaDoc();
1218        m_itransform.transform(p,pp);
1219        animateZoomAbs(pp,scale,duration);
1220    }
1221    
1222    /**
1223     * Animate a zoom centered on a given location in absolute (item-space)
1224     * co-ordinates by the given scale using the provided duration.
1225     * @param p the point to center on in absolute (item-space) units
1226     * @param scale the scale factor to zoom by
1227     * @param duration the duration of the animation, in milliseconds
1228     */

1229    public synchronized void animateZoomAbs(final Point2D JavaDoc p, double scale, long duration) {
1230        m_transact.zoom(p,scale,duration);
1231    }
1232    
1233    /**
1234     * Animate a pan to the specified location in screen (pixel)
1235     * co-ordinates and zoom to the given scale using the provided duration.
1236     * @param p the point to center on in screen (pixel) units
1237     * @param scale the scale factor to zoom by
1238     * @param duration the duration of the animation, in milliseconds
1239     */

1240    public synchronized void animatePanAndZoomTo(final Point2D JavaDoc p, double scale, long duration) {
1241        Point2D JavaDoc pp = new Point2D.Double JavaDoc();
1242        m_itransform.transform(p,pp);
1243        animatePanAndZoomToAbs(pp,scale,duration);
1244    }
1245    
1246    /**
1247     * Animate a pan to the specified location in absolute (item-space)
1248     * co-ordinates and zoom to the given scale using the provided duration.
1249     * @param p the point to center on in absolute (item-space) units
1250     * @param scale the scale factor to zoom by
1251     * @param duration the duration of the animation, in milliseconds
1252     */

1253    public synchronized void animatePanAndZoomToAbs(final Point2D JavaDoc p, double scale, long duration) {
1254        m_transact.panAndZoom(p,scale,duration);
1255    }
1256    
1257    /**
1258     * Indicates if a view transformation is currently underway.
1259     * @return true if a transform is in progress, false otherwise
1260     */

1261    public boolean isTranformInProgress() {
1262        return m_transact.isRunning();
1263    }
1264    
1265    /**
1266     * Activity for conducting animated view transformations.
1267     */

1268    private class TransformActivity extends Activity {
1269        // TODO: clean this up to be more general...
1270
// TODO: change mechanism so that multiple transform
1271
// activities can be running at once?
1272

1273        private double[] src, dst;
1274        private AffineTransform JavaDoc m_at;
1275        public TransformActivity() {
1276            super(2000,20,0);
1277            src = new double[6];
1278            dst = new double[6];
1279            m_at = new AffineTransform JavaDoc();
1280            setPacingFunction(new SlowInSlowOutPacer());
1281        }
1282        private AffineTransform JavaDoc getTransform() {
1283            if ( this.isScheduled() )
1284                m_at.setTransform(dst[0],dst[1],dst[2],dst[3],dst[4],dst[5]);
1285            else
1286                m_at.setTransform(m_transform);
1287            return m_at;
1288        }
1289        public void panAndZoom(final Point2D JavaDoc p, double scale, long duration) {
1290            AffineTransform JavaDoc at = getTransform();
1291            this.cancel();
1292            setDuration(duration);
1293            
1294            m_tmpPoint.setLocation(0,0);
1295            m_itransform.transform(m_tmpPoint,m_tmpPoint);
1296            double x = p.getX(); x = (Double.isNaN(x) ? 0 : x);
1297            double y = p.getY(); y = (Double.isNaN(y) ? 0 : y);
1298            double w = getWidth() /(2*m_transform.getScaleX());
1299            double h = getHeight()/(2*m_transform.getScaleY());
1300            double dx = w-x+m_tmpPoint.getX();
1301            double dy = h-y+m_tmpPoint.getY();
1302            at.translate(dx,dy);
1303
1304            at.translate(p.getX(), p.getY());
1305            at.scale(scale,scale);
1306            at.translate(-p.getX(), -p.getY());
1307            
1308            at.getMatrix(dst);
1309            m_transform.getMatrix(src);
1310            this.run();
1311        }
1312        public void pan(double dx, double dy, long duration) {
1313            AffineTransform JavaDoc at = getTransform();
1314            this.cancel();
1315            setDuration(duration);
1316            at.translate(dx,dy);
1317            at.getMatrix(dst);
1318            m_transform.getMatrix(src);
1319            this.run();
1320        }
1321        public void zoom(final Point2D JavaDoc p, double scale, long duration) {
1322            AffineTransform JavaDoc at = getTransform();
1323            this.cancel();
1324            setDuration(duration);
1325            double zx = p.getX(), zy = p.getY();
1326            at.translate(zx, zy);
1327            at.scale(scale,scale);
1328            at.translate(-zx, -zy);
1329            at.getMatrix(dst);
1330            m_transform.getMatrix(src);
1331            this.run();
1332        }
1333        protected void run(long elapsedTime) {
1334            double f = getPace(elapsedTime);
1335            damageReport();
1336            m_transform.setTransform(
1337                src[0] + f*(dst[0]-src[0]),
1338                src[1] + f*(dst[1]-src[1]),
1339                src[2] + f*(dst[2]-src[2]),
1340                src[3] + f*(dst[3]-src[3]),
1341                src[4] + f*(dst[4]-src[4]),
1342                src[5] + f*(dst[5]-src[5])
1343            );
1344            try {
1345                m_itransform = m_transform.createInverse();
1346            } catch ( Exception JavaDoc e ) { /* won't happen */ }
1347            repaint();
1348        }
1349    } // end of inner class TransformActivity
1350

1351    // ------------------------------------------------------------------------
1352
// Paint Listeners
1353

1354    /**
1355     * Add a PaintListener to this Display to receive notifications
1356     * about paint events.
1357     * @param pl the {@link prefuse.util.display.PaintListener} to add
1358     */

1359    public void addPaintListener(PaintListener pl) {
1360        if ( m_painters == null )
1361            m_painters = new CopyOnWriteArrayList();
1362        m_painters.add(pl);
1363    }
1364    
1365    /**
1366     * Remove a PaintListener from this Display.
1367     * @param pl the {@link prefuse.util.display.PaintListener} to remove
1368     */

1369    public void removePaintListener(PaintListener pl) {
1370        m_painters.remove(pl);
1371    }
1372    
1373    /**
1374     * Fires a pre-paint notification to PaintListeners.
1375     * @param g the current graphics context
1376     */

1377    protected void firePrePaint(Graphics2D JavaDoc g) {
1378        if ( m_painters != null && m_painters.size() > 0 ) {
1379            Object JavaDoc[] lstnrs = m_painters.getArray();
1380            for ( int i=0; i<lstnrs.length; ++i ) {
1381                try {
1382                    ((PaintListener)lstnrs[i]).prePaint(this, g);
1383                } catch ( Exception JavaDoc e ) {
1384                    s_logger.warning(
1385                        "Exception thrown by PaintListener: " + e + "\n" +
1386                        StringLib.getStackTrace(e));
1387                }
1388            }
1389        }
1390    }
1391    
1392    /**
1393     * Fires a post-paint notification to PaintListeners.
1394     * @param g the current graphics context
1395     */

1396    protected void firePostPaint(Graphics2D JavaDoc g) {
1397        if ( m_painters != null && m_painters.size() > 0 ) {
1398            Object JavaDoc[] lstnrs = m_painters.getArray();
1399            for ( int i=0; i<lstnrs.length; ++i ) {
1400                try {
1401                    ((PaintListener)lstnrs[i]).postPaint(this, g);
1402                } catch ( Exception JavaDoc e ) {
1403                    s_logger.warning(
1404                        "Exception thrown by PaintListener: " + e + "\n" +
1405                        StringLib.getStackTrace(e));
1406                }
1407            }
1408        }
1409    }
1410    
1411    
1412    // ------------------------------------------------------------------------
1413
// Item Bounds Listeners
1414

1415    /**
1416     * Add an ItemBoundsListener to receive notifications when the bounds
1417     * occupied by the VisualItems in this Display change.
1418     * @param ibl the {@link prefuse.util.display.ItemBoundsListener} to add
1419     */

1420    public void addItemBoundsListener(ItemBoundsListener ibl) {
1421        if ( m_bounders == null )
1422            m_bounders = new CopyOnWriteArrayList();
1423        m_bounders.add(ibl);
1424    }
1425    
1426    /**
1427     * Remove an ItemBoundsListener to receive notifications when the bounds
1428     * occupied by the VisualItems in this Display change.
1429     * @param ibl the {@link prefuse.util.display.ItemBoundsListener} to remove
1430     */

1431    public void removeItemBoundsListener(ItemBoundsListener ibl) {
1432        m_bounders.remove(ibl);
1433    }
1434    
1435    /**
1436     * Check if the item bounds has changed, and if so, fire a notification.
1437     * @param prev the previous item bounds of the Display
1438     */

1439    protected void checkItemBoundsChanged(Rectangle2D JavaDoc prev) {
1440        if ( m_bounds.equals(prev) )
1441            return; // nothing to do
1442

1443        if ( m_bounders != null && m_bounders.size() > 0 ) {
1444            Object JavaDoc[] lstnrs = m_bounders.getArray();
1445            for ( int i=0; i<lstnrs.length; ++i ) {
1446                try {
1447                    ((ItemBoundsListener)lstnrs[i]).itemBoundsChanged(this);
1448                } catch ( Exception JavaDoc e ) {
1449                    s_logger.warning(
1450                        "Exception thrown by ItemBoundsListener: " + e + "\n" +
1451                        StringLib.getStackTrace(e));
1452                }
1453            }
1454        }
1455    }
1456    
1457    
1458    // ------------------------------------------------------------------------
1459
// Control Listeners
1460

1461    /**
1462     * Adds a ControlListener to receive all input events on VisualItems.
1463     * @param cl the listener to add.
1464     */

1465    public void addControlListener(Control cl) {
1466        m_controls.add(cl);
1467    }
1468
1469    /**
1470     * Removes a registered ControlListener.
1471     * @param cl the listener to remove.
1472     */

1473    public void removeControlListener(Control cl) {
1474        m_controls.remove(cl);
1475    }
1476    
1477    /**
1478     * Returns the VisualItem located at the given point.
1479     * @param p the Point at which to look
1480     * @return the VisualItem located at the given point, if any
1481     */

1482    public synchronized VisualItem findItem(Point JavaDoc p) {
1483        // transform mouse point from screen space to item space
1484
Point2D JavaDoc p2 = (m_itransform==null ? p :
1485                      m_itransform.transform(p, m_tmpPoint));
1486        // ensure that the picking queue has been z-sorted
1487
if ( !m_queue.psorted )
1488            m_queue.sortPickingQueue();
1489        // walk queue from front to back looking for hits
1490
for ( int i = m_queue.psize; --i >= 0; ) {
1491            VisualItem vi = m_queue.pitems[i];
1492            if ( !vi.isValid() ) continue; // in case tuple went invalid
1493
Renderer r = vi.getRenderer();
1494            if (r!=null && vi.isInteractive() && r.locatePoint(p2, vi)) {
1495                return vi;
1496            }
1497        }
1498        return null;
1499    }
1500    
1501    /**
1502     * Captures all mouse and key events on the display, detects relevant
1503     * VisualItems, and informs ControlListeners.
1504     */

1505    public class InputEventCapturer implements MouseMotionListener JavaDoc,
1506        MouseWheelListener JavaDoc, MouseListener JavaDoc, KeyListener JavaDoc
1507    {
1508        private VisualItem activeItem = null;
1509        private boolean mouseDown = false;
1510
1511        private boolean validityCheck() {
1512            if ( activeItem.isValid() )
1513                return true;
1514            activeItem = null;
1515            return false;
1516        }
1517        
1518        public void mouseDragged(MouseEvent JavaDoc e) {
1519            synchronized ( m_vis ) {
1520                if ( activeItem != null ) {
1521                    if ( validityCheck() )
1522                        fireItemDragged(activeItem, e);
1523                } else {
1524                    fireMouseDragged(e);
1525                }
1526            }
1527        }
1528
1529        public void mouseMoved(MouseEvent JavaDoc e) {
1530            synchronized ( m_vis ) {
1531                boolean earlyReturn = false;
1532                //check if we've gone over any item
1533
VisualItem vi = findItem(e.getPoint());
1534                if ( activeItem != null && activeItem != vi ) {
1535                    if ( validityCheck() )
1536                        fireItemExited(activeItem, e);
1537                    earlyReturn = true;
1538                }
1539                if ( vi != null && vi != activeItem ) {
1540                    fireItemEntered(vi, e);
1541                    earlyReturn = true;
1542                }
1543                activeItem = vi;
1544                if ( earlyReturn ) return;
1545                
1546                if ( vi != null && vi == activeItem ) {
1547                    fireItemMoved(vi, e);
1548                }
1549                if ( vi == null ) {
1550                    fireMouseMoved(e);
1551                }
1552            }
1553        }
1554
1555        public void mouseWheelMoved(MouseWheelEvent JavaDoc e) {
1556            synchronized ( m_vis ) {
1557                if ( activeItem != null ) {
1558                    if ( validityCheck() )
1559                        fireItemWheelMoved(activeItem, e);
1560                } else {
1561                    fireMouseWheelMoved(e);
1562                }
1563            }
1564        }
1565
1566        public void mouseClicked(MouseEvent JavaDoc e) {
1567            synchronized ( m_vis ) {
1568                if ( activeItem != null ) {
1569                    if ( validityCheck() )
1570                        fireItemClicked(activeItem, e);
1571                } else {
1572                    fireMouseClicked(e);
1573                }
1574            }
1575        }
1576        
1577
1578        public void mousePressed(MouseEvent JavaDoc e) {
1579            synchronized ( m_vis ) {
1580                mouseDown = true;
1581                if ( activeItem != null ) {
1582                    if ( validityCheck() )
1583                        fireItemPressed(activeItem, e);
1584                } else {
1585                    fireMousePressed(e);
1586                }
1587            }
1588        }
1589
1590        public void mouseReleased(MouseEvent JavaDoc e) {
1591            synchronized ( m_vis ) {
1592                if ( activeItem != null ) {
1593                    if ( validityCheck() )
1594                        fireItemReleased(activeItem, e);
1595                } else {
1596                    fireMouseReleased(e);
1597                }
1598                if ( activeItem != null && mouseDown && isOffComponent(e) ) {
1599                    // mouse was dragged off of the component,
1600
// then released, so register an exit
1601
fireItemExited(activeItem, e);
1602                    activeItem = null;
1603                }
1604                mouseDown = false;
1605            }
1606        }
1607
1608        public void mouseEntered(MouseEvent JavaDoc e) {
1609            synchronized ( m_vis ) {
1610                fireMouseEntered(e);
1611            }
1612        }
1613
1614        public void mouseExited(MouseEvent JavaDoc e) {
1615            synchronized ( m_vis ) {
1616                if ( !mouseDown && activeItem != null ) {
1617                    // we've left the component and an item
1618
// is active but not being dragged, deactivate it
1619
fireItemExited(activeItem, e);
1620                    activeItem = null;
1621                }
1622                fireMouseExited(e);
1623            }
1624        }
1625
1626        public void keyPressed(KeyEvent JavaDoc e) {
1627            synchronized ( m_vis ) {
1628                if ( activeItem != null ) {
1629                    if ( validityCheck() )
1630                        fireItemKeyPressed(activeItem, e);
1631                } else {
1632                    fireKeyPressed(e);
1633                }
1634            }
1635        }
1636
1637        public void keyReleased(KeyEvent JavaDoc e) {
1638            synchronized ( m_vis ) {
1639                if ( activeItem != null ) {
1640                    if ( validityCheck() )
1641                        fireItemKeyReleased(activeItem, e);
1642                } else {
1643                    fireKeyReleased(e);
1644                }
1645            }
1646        }
1647
1648        public void keyTyped(KeyEvent JavaDoc e) {
1649            synchronized ( m_vis ) {
1650                if ( activeItem != null ) {
1651                    if ( validityCheck() )
1652                        fireItemKeyTyped(activeItem, e);
1653                } else {
1654                    fireKeyTyped(e);
1655                }
1656            }
1657        }
1658        
1659        private boolean isOffComponent(MouseEvent JavaDoc e) {
1660            int x = e.getX(), y = e.getY();
1661            return ( x<0 || x>getWidth() || y<0 || y>getHeight() );
1662        }
1663        
1664        // --------------------------------------------------------------------
1665
// Fire Event Notifications
1666

1667        private void fireItemDragged(VisualItem item, MouseEvent JavaDoc e) {
1668            Object JavaDoc[] lstnrs = m_controls.getArray();
1669            for (int i = 0; i < lstnrs.length; ++i) {
1670                Control ctrl = (Control) lstnrs[i];
1671                if (ctrl.isEnabled())
1672                    try {
1673                        ctrl.itemDragged(item, e);
1674                    } catch ( Exception JavaDoc ex ) {
1675                        s_logger.warning(
1676                            "Exception thrown by Control: " + ex + "\n" +
1677                            StringLib.getStackTrace(ex));
1678                    }
1679            }
1680        }
1681
1682        private void fireItemMoved(VisualItem item, MouseEvent JavaDoc e) {
1683            Object JavaDoc[] lstnrs = m_controls.getArray();
1684            for (int i = 0; i < lstnrs.length; ++i) {
1685                Control ctrl = (Control) lstnrs[i];
1686                if (ctrl.isEnabled())
1687                    try {
1688                        ctrl.itemMoved(item, e);
1689                    } catch ( Exception JavaDoc ex ) {
1690                        s_logger.warning(
1691                            "Exception thrown by Control: " + ex + "\n" +
1692                            StringLib.getStackTrace(ex));
1693                    }
1694            }
1695        }
1696
1697        private void fireItemWheelMoved(VisualItem item, MouseWheelEvent JavaDoc e) {
1698            Object JavaDoc[] lstnrs = m_controls.getArray();
1699            for (int i = 0; i < lstnrs.length; ++i) {
1700                Control ctrl = (Control) lstnrs[i];
1701                if (ctrl.isEnabled())
1702                    try {
1703                        ctrl.itemWheelMoved(item, e);
1704                    } catch ( Exception JavaDoc ex ) {
1705                        s_logger.warning(
1706                            "Exception thrown by Control: " + ex + "\n" +
1707                            StringLib.getStackTrace(ex));
1708                    }
1709            }
1710        }
1711
1712        private void fireItemClicked(VisualItem item, MouseEvent JavaDoc e) {
1713            Object JavaDoc[] lstnrs = m_controls.getArray();
1714            for (int i = 0; i < lstnrs.length; ++i) {
1715                Control ctrl = (Control) lstnrs[i];
1716                if (ctrl.isEnabled())
1717                    try {
1718                        ctrl.itemClicked(item, e);
1719                    } catch ( Exception JavaDoc ex ) {
1720                        s_logger.warning(
1721                            "Exception thrown by Control: " + ex + "\n" +
1722                            StringLib.getStackTrace(ex));
1723                    }
1724            }
1725        }
1726
1727        private void fireItemPressed(VisualItem item, MouseEvent JavaDoc e) {
1728            Object JavaDoc[] lstnrs = m_controls.getArray();
1729            for (int i = 0; i < lstnrs.length; ++i) {
1730                Control ctrl = (Control) lstnrs[i];
1731                if (ctrl.isEnabled())
1732                    try {
1733                        ctrl.itemPressed(item, e);
1734                    } catch ( Exception JavaDoc ex ) {
1735                        s_logger.warning(
1736                            "Exception thrown by Control: " + ex + "\n" +
1737                            StringLib.getStackTrace(ex));
1738                    }
1739            }
1740        }
1741
1742        private void fireItemReleased(VisualItem item, MouseEvent JavaDoc e) {
1743            Object JavaDoc[] lstnrs = m_controls.getArray();
1744            for (int i = 0; i < lstnrs.length; ++i) {
1745                Control ctrl = (Control) lstnrs[i];
1746                if (ctrl.isEnabled())
1747                    try {
1748                        ctrl.itemReleased(item, e);
1749                    } catch ( Exception JavaDoc ex ) {
1750                        s_logger.warning(
1751                            "Exception thrown by Control: " + ex + "\n" +
1752                            StringLib.getStackTrace(ex));
1753                    }
1754            }
1755        }
1756
1757        private void fireItemEntered(VisualItem item, MouseEvent JavaDoc e) {
1758            item.setHover(true);
1759            Object JavaDoc[] lstnrs = m_controls.getArray();
1760            for (int i = 0; i < lstnrs.length; ++i) {
1761                Control ctrl = (Control) lstnrs[i];
1762                if (ctrl.isEnabled())
1763                    try {
1764                        ctrl.itemEntered(item, e);
1765                    } catch ( Exception JavaDoc ex ) {
1766                        s_logger.warning(
1767                            "Exception thrown by Control: " + ex + "\n" +
1768                            StringLib.getStackTrace(ex));
1769                    }
1770            }
1771        }
1772
1773        private void fireItemExited(VisualItem item, MouseEvent JavaDoc e) {
1774            if ( item.isValid() ) item.setHover(false);
1775            Object JavaDoc[] lstnrs = m_controls.getArray();
1776            for (int i = 0; i < lstnrs.length; ++i) {
1777                Control ctrl = (Control) lstnrs[i];
1778                if (ctrl.isEnabled())
1779                    try {
1780                        ctrl.itemExited(item, e);
1781                    } catch ( Exception JavaDoc ex ) {
1782                        s_logger.warning(
1783                            "Exception thrown by Control: " + ex + "\n" +
1784                            StringLib.getStackTrace(ex));
1785                    }
1786            }
1787        }
1788
1789        private void fireItemKeyPressed(VisualItem item, KeyEvent JavaDoc e) {
1790            Object JavaDoc[] lstnrs = m_controls.getArray();
1791            if (lstnrs.length == 0)
1792                return;
1793            for (int i = 0; i < lstnrs.length; ++i) {
1794                Control ctrl = (Control) lstnrs[i];
1795                if (ctrl.isEnabled())
1796                    try {
1797                        ctrl.itemKeyPressed(item, e);
1798                    } catch ( Exception JavaDoc ex ) {
1799                        s_logger.warning(
1800                            "Exception thrown by Control: " + ex + "\n" +
1801                            StringLib.getStackTrace(ex));
1802                    }
1803            }
1804        }
1805
1806        private void fireItemKeyReleased(VisualItem item, KeyEvent JavaDoc e) {
1807            Object JavaDoc[] lstnrs = m_controls.getArray();
1808            for (int i = 0; i < lstnrs.length; ++i) {
1809                Control ctrl = (Control) lstnrs[i];
1810                if (ctrl.isEnabled())
1811                    try {
1812                        ctrl.itemKeyReleased(item, e);
1813                    } catch ( Exception JavaDoc ex ) {
1814                        s_logger.warning(
1815                            "Exception thrown by Control: " + ex + "\n" +
1816                            StringLib.getStackTrace(ex));
1817                    }
1818            }
1819        }
1820
1821        private void fireItemKeyTyped(VisualItem item, KeyEvent JavaDoc e) {
1822            Object JavaDoc[] lstnrs = m_controls.getArray();
1823            for (int i = 0; i < lstnrs.length; ++i) {
1824                Control ctrl = (Control) lstnrs[i];
1825                if (ctrl.isEnabled())
1826                    try {
1827                        ctrl.itemKeyTyped(item, e);
1828                    } catch ( Exception JavaDoc ex ) {
1829                        s_logger.warning(
1830                            "Exception thrown by Control: " + ex + "\n" +
1831                            StringLib.getStackTrace(ex));
1832                    }
1833            }
1834        }
1835
1836        private void fireMouseEntered(MouseEvent JavaDoc e) {
1837            Object JavaDoc[] lstnrs = m_controls.getArray();
1838            for (int i = 0; i < lstnrs.length; ++i) {
1839                Control ctrl = (Control) lstnrs[i];
1840                if (ctrl.isEnabled())
1841                    try {
1842                        ctrl.mouseEntered(e);
1843                    } catch ( Exception JavaDoc ex ) {
1844                        s_logger.warning(
1845                            "Exception thrown by Control: " + ex + "\n" +
1846                            StringLib.getStackTrace(ex));
1847                    }
1848            }
1849        }
1850
1851        private void fireMouseExited(MouseEvent JavaDoc e) {
1852            Object JavaDoc[] lstnrs = m_controls.getArray();
1853            for (int i = 0; i < lstnrs.length; ++i) {
1854                Control ctrl = (Control) lstnrs[i];
1855                if (ctrl.isEnabled())
1856                    try {
1857                        ctrl.mouseExited(e);
1858                    } catch ( Exception JavaDoc ex ) {
1859                        s_logger.warning(
1860                            "Exception thrown by Control: " + ex + "\n" +
1861                            StringLib.getStackTrace(ex));
1862                    }
1863            }
1864        }
1865
1866        private void fireMousePressed(MouseEvent JavaDoc e) {
1867            Object JavaDoc[] lstnrs = m_controls.getArray();
1868            for (int i = 0; i < lstnrs.length; ++i) {
1869                Control ctrl = (Control) lstnrs[i];
1870                if (ctrl.isEnabled())
1871                    try {
1872                        ctrl.mousePressed(e);
1873                    } catch ( Exception JavaDoc ex ) {
1874                        s_logger.warning(
1875                            "Exception thrown by Control: " + ex + "\n" +
1876                            StringLib.getStackTrace(ex));
1877                    }
1878            }
1879        }
1880
1881        private void fireMouseReleased(MouseEvent JavaDoc e) {
1882            Object JavaDoc[] lstnrs = m_controls.getArray();
1883            for (int i = 0; i < lstnrs.length; ++i) {
1884                Control ctrl = (Control) lstnrs[i];
1885                if (ctrl.isEnabled())
1886                    try {
1887                        ctrl.mouseReleased(e);
1888                    } catch ( Exception JavaDoc ex ) {
1889                        s_logger.warning(
1890                            "Exception thrown by Control: " + ex + "\n" +
1891                            StringLib.getStackTrace(ex));
1892                    }
1893            }
1894        }
1895
1896        private void fireMouseClicked(MouseEvent JavaDoc e) {
1897            Object JavaDoc[] lstnrs = m_controls.getArray();
1898            for (int i = 0; i < lstnrs.length; ++i) {
1899                Control ctrl = (Control) lstnrs[i];
1900                if (ctrl.isEnabled())
1901                    try {
1902                        ctrl.mouseClicked(e);
1903                    } catch ( Exception JavaDoc ex ) {
1904                        s_logger.warning(
1905                            "Exception thrown by Control: " + ex + "\n" +
1906                            StringLib.getStackTrace(ex));
1907                    }
1908            }
1909        }
1910
1911        private void fireMouseDragged(MouseEvent JavaDoc e) {
1912            Object JavaDoc[] lstnrs = m_controls.getArray();
1913            for (int i = 0; i < lstnrs.length; ++i) {
1914                Control ctrl = (Control) lstnrs[i];
1915                if (ctrl.isEnabled())
1916                    try {
1917                        ctrl.mouseDragged(e);
1918                    } catch ( Exception JavaDoc ex ) {
1919                        s_logger.warning(
1920                            "Exception thrown by Control: " + ex + "\n" +
1921                            StringLib.getStackTrace(ex));
1922                    }
1923            }
1924        }
1925
1926        private void fireMouseMoved(MouseEvent JavaDoc e) {
1927            Object JavaDoc[] lstnrs = m_controls.getArray();
1928            for (int i = 0; i < lstnrs.length; ++i) {
1929                Control ctrl = (Control) lstnrs[i];
1930                if (ctrl.isEnabled())
1931                    try {
1932                        ctrl.mouseMoved(e);
1933                    } catch ( Exception JavaDoc ex ) {
1934                        s_logger.warning(
1935                            "Exception thrown by Control: " + ex + "\n" +
1936                            StringLib.getStackTrace(ex));
1937                    }
1938            }
1939        }
1940
1941        private void fireMouseWheelMoved(MouseWheelEvent JavaDoc e) {
1942            Object JavaDoc[] lstnrs = m_controls.getArray();
1943            for (int i = 0; i < lstnrs.length; ++i) {
1944                Control ctrl = (Control) lstnrs[i];
1945                if (ctrl.isEnabled())
1946                    try {
1947                        ctrl.mouseWheelMoved(e);
1948                    } catch ( Exception JavaDoc ex ) {
1949                        s_logger.warning(
1950                            "Exception thrown by Control: " + ex + "\n" +
1951                            StringLib.getStackTrace(ex));
1952                    }
1953            }
1954        }
1955
1956        private void fireKeyPressed(KeyEvent JavaDoc e) {
1957            Object JavaDoc[] lstnrs = m_controls.getArray();
1958            for (int i = 0; i < lstnrs.length; ++i) {
1959                Control ctrl = (Control) lstnrs[i];
1960                if (ctrl.isEnabled())
1961                    try {
1962                        ctrl.keyPressed(e);
1963                    } catch ( Exception JavaDoc ex ) {
1964                        s_logger.warning(
1965                            "Exception thrown by Control: " + ex + "\n" +
1966                            StringLib.getStackTrace(ex));
1967                    }
1968            }
1969        }
1970
1971        private void fireKeyReleased(KeyEvent JavaDoc e) {
1972            Object JavaDoc[] lstnrs = m_controls.getArray();
1973            for (int i = 0; i < lstnrs.length; ++i) {
1974                Control ctrl = (Control) lstnrs[i];
1975                if (ctrl.isEnabled())
1976                    try {
1977                        ctrl.keyReleased(e);
1978                    } catch ( Exception JavaDoc ex ) {
1979                        s_logger.warning(
1980                            "Exception thrown by Control: " + ex + "\n" +
1981                            StringLib.getStackTrace(ex));
1982                    }
1983            }
1984        }
1985
1986        private void fireKeyTyped(KeyEvent JavaDoc e) {
1987            Object JavaDoc[] lstnrs = m_controls.getArray();
1988            for (int i = 0; i < lstnrs.length; ++i) {
1989                Control ctrl = (Control) lstnrs[i];
1990                if (ctrl.isEnabled())
1991                    try {
1992                        ctrl.keyTyped(e);
1993                    } catch ( Exception JavaDoc ex ) {
1994                        s_logger.warning(
1995                            "Exception thrown by Control: " + ex + "\n" +
1996                            StringLib.getStackTrace(ex));
1997                    }
1998            }
1999        }
2000        
2001    } // end of inner class MouseEventCapturer
2002

2003    
2004    // ------------------------------------------------------------------------
2005
// Text Editing
2006

2007    /**
2008     * Returns the TextComponent used for on-screen text editing.
2009     * @return the TextComponent used for text editing
2010     */

2011    public JTextComponent JavaDoc getTextEditor() {
2012        return m_editor;
2013    }
2014    
2015    /**
2016     * Sets the TextComponent used for on-screen text editing.
2017     * @param tc the TextComponent to use for text editing
2018     */

2019    public void setTextEditor(JTextComponent JavaDoc tc) {
2020        this.remove(m_editor);
2021        m_editor = tc;
2022        this.add(m_editor, 1);
2023    }
2024    
2025    /**
2026     * Edit text for the given VisualItem and attribute. Presents a text
2027     * editing widget spaning the item's bounding box. Use stopEditing()
2028     * to hide the text widget. When stopEditing() is called, the data field
2029     * will automatically be updated with the VisualItem.
2030     * @param item the VisualItem to edit
2031     * @param attribute the attribute to edit
2032     */

2033    public void editText(VisualItem item, String JavaDoc attribute) {
2034        if ( m_editing ) { stopEditing(); }
2035        Rectangle2D JavaDoc b = item.getBounds();
2036        Rectangle JavaDoc r = m_transform.createTransformedShape(b).getBounds();
2037        
2038        // hacky placement code that attempts to keep text in same place
2039
// configured under Windows XP and Java 1.4.2b
2040
if ( m_editor instanceof JTextArea JavaDoc ) {
2041            r.y -= 2; r.width += 22; r.height += 2;
2042        } else {
2043            r.x += 3; r.y += 1; r.width -= 5; r.height -= 2;
2044        }
2045        
2046        Font JavaDoc f = getFont();
2047        int size = (int)Math.round(f.getSize()*m_transform.getScaleX());
2048        Font JavaDoc nf = new Font JavaDoc(f.getFontName(), f.getStyle(), size);
2049        m_editor.setFont(nf);
2050        
2051        editText(item, attribute, r);
2052    }
2053    
2054    /**
2055     * Edit text for the given VisualItem and field. Presents a text
2056     * editing widget spaning the given bounding box. Use stopEditing()
2057     * to hide the text widget. When stopEditing() is called, the field
2058     * will automatically be updated with the VisualItem.
2059     * @param item the VisualItem to edit
2060     * @param attribute the attribute to edit
2061     * @param r Rectangle representing the desired bounding box of the text
2062     * editing widget
2063     */

2064    public void editText(VisualItem item, String JavaDoc attribute, Rectangle JavaDoc r) {
2065        if ( m_editing ) { stopEditing(); }
2066        String JavaDoc txt = item.getString(attribute);
2067        m_editItem = item;
2068        m_editAttribute = attribute;
2069        Color JavaDoc tc = ColorLib.getColor(item.getTextColor());
2070        Color JavaDoc fc = ColorLib.getColor(item.getFillColor());
2071        m_editor.setForeground(tc);
2072        m_editor.setBackground(fc);
2073        editText(txt, r);
2074    }
2075    
2076    /**
2077     * Show a text editing widget containing the given text and spanning the
2078     * specified bounding box. Use stopEditing() to hide the text widget. Use
2079     * the method calls getTextEditor().getText() to get the resulting edited
2080     * text.
2081     * @param txt the text string to display in the text widget
2082     * @param r Rectangle representing the desired bounding box of the text
2083     * editing widget
2084     */

2085    public void editText(String JavaDoc txt, Rectangle JavaDoc r) {
2086        if ( m_editing ) { stopEditing(); }
2087        m_editing = true;
2088        m_editor.setBounds(r.x,r.y,r.width,r.height);
2089        m_editor.setText(txt);
2090        m_editor.setVisible(true);
2091        m_editor.setCaretPosition(txt.length());
2092        m_editor.requestFocus();
2093    }
2094    
2095    /**
2096     * Stops text editing on the display, hiding the text editing widget. If
2097     * the text editor was associated with a specific VisualItem (ie one of the
2098     * editText() methods which include a VisualItem as an argument was called),
2099     * the item is updated with the edited text.
2100     */

2101    public void stopEditing() {
2102        m_editor.setVisible(false);
2103        if ( m_editItem != null ) {
2104            String JavaDoc txt = m_editor.getText();
2105            m_editItem.set(m_editAttribute, txt);
2106            m_editItem = null;
2107            m_editAttribute = null;
2108            m_editor.setBackground(null);
2109            m_editor.setForeground(null);
2110        }
2111        m_editing = false;
2112    }
2113    
2114} // end of class Display
2115
Popular Tags