001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
006     *
007     * Project Info:  http://www.jfree.org/jfreechart/index.html
008     *
009     * This library is free software; you can redistribute it and/or modify it 
010     * under the terms of the GNU Lesser General Public License as published by 
011     * the Free Software Foundation; either version 2.1 of the License, or 
012     * (at your option) any later version.
013     *
014     * This library is distributed in the hope that it will be useful, but 
015     * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
016     * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
017     * License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this library; if not, write to the Free Software
021     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
022     * USA.  
023     *
024     * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
025     * in the United States and other countries.]
026     *
027     * -------------------
028     * ChartComposite.java
029     * -------------------
030     * (C) Copyright 2006, 2007, by Henry Proudhon and Contributors.
031     *
032     * Original Author:  Henry Proudhon (henry.proudhon AT ensmp.fr);
033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
034     *                   Cedric Chabanois (cchabanois AT no-log.org);
035     *                   Christoph Beck;
036     *
037     * Changes
038     * -------
039     * 19-Jun-2006 : New class (HP);
040     * 06-Nov-2006 : Added accessor methods for zoomInFactor and zoomOutFactor (DG);
041     * 28-Nov-2006 : Added support for trace lines (HP);
042     * 30-Nov-2006 : Improved zoom box handling (HP);
043     * 06-Dec-2006 : Added (simplified) tool tip support (HP);
044     * 11-Dec-2006 : Fixed popup menu location by fgiust, bug 1612770 (HP);
045     * 31-Jan-2007 : Fixed some issues with the trace lines, fixed cross hair not 
046     *               being drawn, added getter and setter methods for the trace 
047     *               lines (HP); 
048     * 07-Apr-2007 : Changed this.redraw() into canvas.redraw() to fix redraw 
049     *               problems (HP);
050     * 19-May-2007 : Small fix in paintControl to check for null charts, bug 
051     *               1719260 (HP);
052     * 19-May-2007 : Corrected bug with scaling when the drawing region is larger 
053     *               than maximum draw width/height (HP);
054     * 23-May-2007 : Added some dispose call to free SWT resources, patch sent by 
055     *               Cédric Chabanois (CC);
056     * 06-Jun-2007 : Fixed minor issues with tooltips. bug reported and fix 
057     *               proposed by Christoph Beck, bug 1726404 (HP);
058     * 22-Oct-2007 : Added addChartMouseListener and removeChartMouseListener 
059     *               methods as suggested by Christoph Beck, bug 1742002 (HP);
060     * 22-Oct-2007 : Fixed bug in zooming with multiple plots (HP);
061     * 22-Oct-2007 : Check for null zoom point when restoring auto range and domain
062     *               bounds (HP);
063     * 22-Oct-2007 : Pass mouse moved events to listening ChartMouseListeners (HP);
064     * 22-Oct-2007 : Refactored class, now implements PaintListener, MouseListener, 
065     *               MouseMoveListener. Made the chart field be private again and 
066     *               added new method addSWTListener to allow custom behavior.
067     * 14-Nov-2007 : Create canvas with SWT.DOUBLE_BUFFER, added 
068     *               getChartRenderingInfo(), is/setDomainZoomable() and 
069     *               is/setRangeZoomable() as per feature request (DG);
070     */
071    
072    package org.jfree.experimental.chart.swt;
073    
074    import java.awt.Graphics;
075    import java.awt.Point;
076    import java.awt.RenderingHints;
077    import java.awt.geom.Point2D;
078    import java.awt.geom.Rectangle2D;
079    import java.awt.print.PageFormat;
080    import java.awt.print.Printable;
081    import java.awt.print.PrinterException;
082    import java.awt.print.PrinterJob;
083    import java.io.File;
084    import java.io.IOException;
085    import java.util.ResourceBundle;
086    
087    import javax.swing.event.EventListenerList;
088    
089    import org.eclipse.swt.SWT;
090    import org.eclipse.swt.events.ControlListener;
091    import org.eclipse.swt.events.DisposeListener;
092    import org.eclipse.swt.events.FocusListener;
093    import org.eclipse.swt.events.HelpListener;
094    import org.eclipse.swt.events.KeyListener;
095    import org.eclipse.swt.events.MouseEvent;
096    import org.eclipse.swt.events.MouseListener;
097    import org.eclipse.swt.events.MouseMoveListener;
098    import org.eclipse.swt.events.MouseTrackListener;
099    import org.eclipse.swt.events.PaintEvent;
100    import org.eclipse.swt.events.PaintListener;
101    import org.eclipse.swt.events.SelectionEvent;
102    import org.eclipse.swt.events.SelectionListener;
103    import org.eclipse.swt.events.TraverseListener;
104    import org.eclipse.swt.graphics.GC;
105    import org.eclipse.swt.graphics.Rectangle;
106    import org.eclipse.swt.internal.SWTEventListener;
107    import org.eclipse.swt.layout.FillLayout;
108    import org.eclipse.swt.widgets.Canvas;
109    import org.eclipse.swt.widgets.Composite;
110    import org.eclipse.swt.widgets.Event;
111    import org.eclipse.swt.widgets.FileDialog;
112    import org.eclipse.swt.widgets.Menu;
113    import org.eclipse.swt.widgets.MenuItem;
114    import org.eclipse.swt.widgets.MessageBox;
115    import org.jfree.chart.ChartMouseEvent;
116    import org.jfree.chart.ChartMouseListener;
117    import org.jfree.chart.ChartRenderingInfo;
118    import org.jfree.chart.ChartUtilities;
119    import org.jfree.chart.JFreeChart;
120    import org.jfree.chart.entity.ChartEntity;
121    import org.jfree.chart.entity.EntityCollection;
122    import org.jfree.chart.event.ChartChangeEvent;
123    import org.jfree.chart.event.ChartChangeListener;
124    import org.jfree.chart.event.ChartProgressEvent;
125    import org.jfree.chart.event.ChartProgressListener;
126    import org.jfree.chart.plot.Plot;
127    import org.jfree.chart.plot.PlotOrientation;
128    import org.jfree.chart.plot.PlotRenderingInfo;
129    import org.jfree.chart.plot.ValueAxisPlot;
130    import org.jfree.chart.plot.Zoomable;
131    import org.jfree.experimental.chart.swt.editor.SWTChartEditor;
132    import org.jfree.experimental.swt.SWTGraphics2D;
133    import org.jfree.experimental.swt.SWTUtils;
134    
135    /**
136     * A SWT GUI composite for displaying a {@link JFreeChart} object.
137     * <p>
138     * The composite listens to the chart to receive notification of changes to any
139     * component of the chart.  The chart is redrawn automatically whenever this 
140     * notification is received.
141     */
142    public class ChartComposite extends Composite implements ChartChangeListener,
143                                                             ChartProgressListener,
144                                                             PaintListener,
145                                                             SelectionListener,
146                                                             MouseListener,
147                                                             MouseMoveListener,
148                                                             Printable
149    {
150        /** Default setting for buffer usage. */
151        public static final boolean DEFAULT_BUFFER_USED = false;
152    
153        /** The default panel width. */
154        public static final int DEFAULT_WIDTH = 680;
155    
156        /** The default panel height. */
157        public static final int DEFAULT_HEIGHT = 420;
158    
159        /** The default limit below which chart scaling kicks in. */
160        public static final int DEFAULT_MINIMUM_DRAW_WIDTH = 300;
161    
162        /** The default limit below which chart scaling kicks in. */
163        public static final int DEFAULT_MINIMUM_DRAW_HEIGHT = 200;
164    
165        /** The default limit below which chart scaling kicks in. */
166        public static final int DEFAULT_MAXIMUM_DRAW_WIDTH = 800;
167    
168        /** The default limit below which chart scaling kicks in. */
169        public static final int DEFAULT_MAXIMUM_DRAW_HEIGHT = 600;
170    
171        /** The minimum size required to perform a zoom on a rectangle */
172        public static final int DEFAULT_ZOOM_TRIGGER_DISTANCE = 10;
173    
174        /** Properties action command. */
175        public static final String PROPERTIES_COMMAND = "PROPERTIES";
176    
177        /** Save action command. */
178        public static final String SAVE_COMMAND = "SAVE";
179    
180        /** Print action command. */
181        public static final String PRINT_COMMAND = "PRINT";
182    
183        /** Zoom in (both axes) action command. */
184        public static final String ZOOM_IN_BOTH_COMMAND = "ZOOM_IN_BOTH";
185    
186        /** Zoom in (domain axis only) action command. */
187        public static final String ZOOM_IN_DOMAIN_COMMAND = "ZOOM_IN_DOMAIN";
188    
189        /** Zoom in (range axis only) action command. */
190        public static final String ZOOM_IN_RANGE_COMMAND = "ZOOM_IN_RANGE";
191    
192        /** Zoom out (both axes) action command. */
193        public static final String ZOOM_OUT_BOTH_COMMAND = "ZOOM_OUT_BOTH";
194    
195        /** Zoom out (domain axis only) action command. */
196        public static final String ZOOM_OUT_DOMAIN_COMMAND = "ZOOM_DOMAIN_BOTH";
197    
198        /** Zoom out (range axis only) action command. */
199        public static final String ZOOM_OUT_RANGE_COMMAND = "ZOOM_RANGE_BOTH";
200    
201        /** Zoom reset (both axes) action command. */
202        public static final String ZOOM_RESET_BOTH_COMMAND = "ZOOM_RESET_BOTH";
203    
204        /** Zoom reset (domain axis only) action command. */
205        public static final String ZOOM_RESET_DOMAIN_COMMAND = "ZOOM_RESET_DOMAIN";
206    
207        /** Zoom reset (range axis only) action command. */
208        public static final String ZOOM_RESET_RANGE_COMMAND = "ZOOM_RESET_RANGE";
209    
210        /** The chart that is displayed in the panel. */
211        private JFreeChart chart;
212    
213        /** The canvas to display the chart. */
214        private Canvas canvas;
215        
216        /** Storage for registered (chart) mouse listeners. */
217        private EventListenerList chartMouseListeners;
218    
219        /** A flag that controls whether or not the off-screen buffer is used. */
220        private boolean useBuffer;
221    
222        /** A flag that indicates that the buffer should be refreshed. */
223        private boolean refreshBuffer;
224    
225        /** A flag that indicates that the tooltips should be displayed. */
226        private boolean displayToolTips;
227    
228        /** A buffer for the rendered chart. */
229        private org.eclipse.swt.graphics.Image chartBuffer;
230    
231        /** The height of the chart buffer. */
232        private int chartBufferHeight;
233    
234        /** The width of the chart buffer. */
235        private int chartBufferWidth;
236    
237        /** 
238         * The minimum width for drawing a chart (uses scaling for smaller widths). 
239         */
240        private int minimumDrawWidth;
241    
242        /** 
243         * The minimum height for drawing a chart (uses scaling for smaller 
244         * heights). 
245         */
246        private int minimumDrawHeight;
247    
248        /** 
249         * The maximum width for drawing a chart (uses scaling for bigger 
250         * widths). 
251         */
252        private int maximumDrawWidth;
253    
254        /** 
255         * The maximum height for drawing a chart (uses scaling for bigger 
256         * heights). 
257         */
258        private int maximumDrawHeight;
259    
260        /** The popup menu for the frame. */
261        private Menu popup;
262    
263        /** The drawing info collected the last time the chart was drawn. */
264        private ChartRenderingInfo info;
265        
266        /** The chart anchor point. */
267        private Point2D anchor;
268    
269        /** The scale factor used to draw the chart. */
270        private double scaleX;
271    
272        /** The scale factor used to draw the chart. */
273        private double scaleY;
274    
275        /** The plot orientation. */
276        private PlotOrientation orientation = PlotOrientation.VERTICAL;
277        
278        /** A flag that controls whether or not domain zooming is enabled. */
279        private boolean domainZoomable = false;
280    
281        /** A flag that controls whether or not range zooming is enabled. */
282        private boolean rangeZoomable = false;
283    
284        /** 
285         * The zoom rectangle starting point (selected by the user with a mouse 
286         * click).  This is a point on the screen, not the chart (which may have
287         * been scaled up or down to fit the panel).  
288         */
289        private org.eclipse.swt.graphics.Point zoomPoint = null;
290    
291        /** The zoom rectangle (selected by the user with the mouse). */
292        private transient Rectangle zoomRectangle = null;
293    
294        /** Controls if the zoom rectangle is drawn as an outline or filled. */
295        //TODO private boolean fillZoomRectangle = true;
296    
297        /** The minimum distance required to drag the mouse to trigger a zoom. */
298        private int zoomTriggerDistance;
299        
300        /** A flag that controls whether or not horizontal tracing is enabled. */
301        private boolean horizontalAxisTrace = false;
302    
303        /** A flag that controls whether or not vertical tracing is enabled. */
304        private boolean verticalAxisTrace = false;
305    
306        /** A vertical trace line. */
307        private transient int verticalTraceLineX;
308    
309        /** A horizontal trace line. */
310        private transient int horizontalTraceLineY;
311    
312        /** Menu item for zooming in on a chart (both axes). */
313        private MenuItem zoomInBothMenuItem;
314    
315        /** Menu item for zooming in on a chart (domain axis). */
316        private MenuItem zoomInDomainMenuItem;
317    
318        /** Menu item for zooming in on a chart (range axis). */
319        private MenuItem zoomInRangeMenuItem;
320    
321        /** Menu item for zooming out on a chart. */
322        private MenuItem zoomOutBothMenuItem;
323    
324        /** Menu item for zooming out on a chart (domain axis). */
325        private MenuItem zoomOutDomainMenuItem;
326    
327        /** Menu item for zooming out on a chart (range axis). */
328        private MenuItem zoomOutRangeMenuItem;
329    
330        /** Menu item for resetting the zoom (both axes). */
331        private MenuItem zoomResetBothMenuItem;
332    
333        /** Menu item for resetting the zoom (domain axis only). */
334        private MenuItem zoomResetDomainMenuItem;
335    
336        /** Menu item for resetting the zoom (range axis only). */
337        private MenuItem zoomResetRangeMenuItem;
338    
339        /** A flag that controls whether or not file extensions are enforced. */
340        private boolean enforceFileExtensions;
341    
342        /** The factor used to zoom in on an axis range. */
343        private double zoomInFactor = 0.5;
344        
345        /** The factor used to zoom out on an axis range. */
346        private double zoomOutFactor = 2.0;
347        
348        /** The resourceBundle for the localization. */
349        protected static ResourceBundle localizationResources 
350            = ResourceBundle.getBundle("org.jfree.chart.LocalizationBundle");
351    
352        /**
353         * Create a new chart composite with a default FillLayout. 
354         * This way, when drawn, the chart will fill all the space.
355         * @param comp The parent.
356         * @param style The style of the composite.
357         */
358        public ChartComposite(Composite comp, int style) {
359            this(comp, 
360                    style,
361                    null,
362                    DEFAULT_WIDTH,
363                    DEFAULT_HEIGHT,
364                    DEFAULT_MINIMUM_DRAW_WIDTH,
365                    DEFAULT_MINIMUM_DRAW_HEIGHT,
366                    DEFAULT_MAXIMUM_DRAW_WIDTH,
367                    DEFAULT_MAXIMUM_DRAW_HEIGHT,
368                    DEFAULT_BUFFER_USED,
369                    true,  // properties
370                    true,  // save
371                    true,  // print
372                    true,  // zoom
373                    true   // tooltips
374            );
375        }
376    
377        /**
378         * Constructs a panel that displays the specified chart.
379         *
380         * @param comp The parent.
381         * @param style The style of the composite.
382         * @param chart  the chart.
383         */
384        public ChartComposite(Composite comp, int style, JFreeChart chart) {
385            this( 
386                    comp, 
387                    style,
388                    chart,
389                    DEFAULT_WIDTH,
390                    DEFAULT_HEIGHT,
391                    DEFAULT_MINIMUM_DRAW_WIDTH,
392                    DEFAULT_MINIMUM_DRAW_HEIGHT,
393                    DEFAULT_MAXIMUM_DRAW_WIDTH,
394                    DEFAULT_MAXIMUM_DRAW_HEIGHT,
395                    DEFAULT_BUFFER_USED,
396                    true,  // properties
397                    true,  // save
398                    true,  // print
399                    true,  // zoom
400                    true   // tooltips
401            );
402        }
403    
404        /**
405         * Constructs a panel containing a chart.
406         *
407         * @param comp The parent.
408         * @param style The style of the composite.
409         * @param chart  the chart.
410         * @param useBuffer  a flag controlling whether or not an off-screen buffer
411         *                   is used.
412         */
413        public ChartComposite(Composite comp, int style, JFreeChart chart, 
414                boolean useBuffer) {
415            
416            this(comp, style, chart,
417                    DEFAULT_WIDTH,
418                    DEFAULT_HEIGHT,
419                    DEFAULT_MINIMUM_DRAW_WIDTH,
420                    DEFAULT_MINIMUM_DRAW_HEIGHT,
421                    DEFAULT_MAXIMUM_DRAW_WIDTH,
422                    DEFAULT_MAXIMUM_DRAW_HEIGHT,
423                    useBuffer,
424                    true,  // properties
425                    true,  // save
426                    true,  // print
427                    true,  // zoom
428                    true   // tooltips
429                    );
430        }
431        
432        /**
433         * Constructs a JFreeChart panel.
434         *
435         * @param comp The parent.
436         * @param style The style of the composite.
437         * @param chart  the chart.
438         * @param properties  a flag indicating whether or not the chart property
439         *                    editor should be available via the popup menu.
440         * @param save  a flag indicating whether or not save options should be
441         *              available via the popup menu.
442         * @param print  a flag indicating whether or not the print option
443         *               should be available via the popup menu.
444         * @param zoom  a flag indicating whether or not zoom options should
445         *              be added to the popup menu.
446         * @param tooltips  a flag indicating whether or not tooltips should be
447         *                  enabled for the chart.
448         */
449        public ChartComposite(
450                Composite comp, 
451                int style,
452                JFreeChart chart,
453                boolean properties,
454                boolean save,
455                boolean print,
456                boolean zoom,
457                boolean tooltips) {
458            this(
459                    comp,
460                    style,
461                    chart,
462                    DEFAULT_WIDTH,
463                    DEFAULT_HEIGHT,
464                    DEFAULT_MINIMUM_DRAW_WIDTH,
465                    DEFAULT_MINIMUM_DRAW_HEIGHT,
466                    DEFAULT_MAXIMUM_DRAW_WIDTH,
467                    DEFAULT_MAXIMUM_DRAW_HEIGHT,
468                    DEFAULT_BUFFER_USED,
469                    properties,
470                    save,
471                    print,
472                    zoom,
473                    tooltips
474                    );
475        }
476    
477        /**
478         * Constructs a JFreeChart panel.
479         *
480         * @param comp The parent.
481         * @param style The style of the composite.
482         * @param jfreechart  the chart.
483         * @param width  the preferred width of the panel.
484         * @param height  the preferred height of the panel.
485         * @param minimumDrawW  the minimum drawing width.
486         * @param minimumDrawH  the minimum drawing height.
487         * @param maximumDrawW  the maximum drawing width.
488         * @param maximumDrawH  the maximum drawing height.
489         * @param usingBuffer  a flag that indicates whether to use the off-screen
490         *                   buffer to improve performance (at the expense of 
491         *                   memory).
492         * @param properties  a flag indicating whether or not the chart property
493         *                    editor should be available via the popup menu.
494         * @param save  a flag indicating whether or not save options should be
495         *              available via the popup menu.
496         * @param print  a flag indicating whether or not the print option
497         *               should be available via the popup menu.
498         * @param zoom  a flag indicating whether or not zoom options should be 
499         *              added to the popup menu.
500         * @param tooltips  a flag indicating whether or not tooltips should be 
501         *                  enabled for the chart.
502         */
503        public ChartComposite(Composite comp, 
504                int style,
505                JFreeChart jfreechart,
506                int width,
507                int height,
508                int minimumDrawW,
509                int minimumDrawH,
510                int maximumDrawW,
511                int maximumDrawH,
512                boolean usingBuffer,
513                boolean properties,
514                boolean save,
515                boolean print,
516                boolean zoom,
517                boolean tooltips) {
518            super(comp, style);
519            this.setChart(jfreechart);
520            this.chartMouseListeners = new EventListenerList();
521            this.setLayout(new FillLayout());
522            this.info = new ChartRenderingInfo();
523            this.useBuffer = usingBuffer;
524            this.refreshBuffer = false;
525            this.minimumDrawWidth = minimumDrawW;
526            this.minimumDrawHeight = minimumDrawH;
527            this.maximumDrawWidth = maximumDrawW;
528            this.maximumDrawHeight = maximumDrawH;
529            this.zoomTriggerDistance = DEFAULT_ZOOM_TRIGGER_DISTANCE;
530            this.setDisplayToolTips(tooltips);
531            // create the canvas and add the required listeners
532            this.canvas = new Canvas(this, SWT.DOUBLE_BUFFERED | SWT.NO_BACKGROUND);
533            this.canvas.addPaintListener(this);
534            this.canvas.addMouseListener(this);
535            this.canvas.addMouseMoveListener(this);
536                
537            if (this.chart != null) {
538                this.chart.addChangeListener(this);
539                Plot plot = this.chart.getPlot();
540                this.domainZoomable = false;
541                this.rangeZoomable = false;
542                if (plot instanceof Zoomable) {
543                    Zoomable z = (Zoomable) plot;
544                    this.domainZoomable = z.isDomainZoomable();
545                    this.rangeZoomable = z.isRangeZoomable();
546                    this.orientation = z.getOrientation();
547                }
548            }
549    
550            // set up popup menu...
551            this.popup = null;
552            if (properties || save || print || zoom)
553                this.popup = createPopupMenu(properties, save, print, zoom);
554    
555            this.enforceFileExtensions = true;
556        }
557            
558        /**
559         * Returns the X scale factor for the chart.  This will be 1.0 if no 
560         * scaling has been used.
561         * 
562         * @return The scale factor.
563         */
564        public double getScaleX() {
565            return this.scaleX;
566        }
567        
568        /**
569         * Returns the Y scale factory for the chart.  This will be 1.0 if no 
570         * scaling has been used.
571         * 
572         * @return The scale factor.
573         */
574        public double getScaleY() {
575            return this.scaleY;
576        }
577        
578        /**
579         * Returns the anchor point.
580         * 
581         * @return The anchor point (possibly <code>null</code>).
582         */
583        public Point2D getAnchor() {
584            return this.anchor;   
585        }
586        
587        /**
588         * Sets the anchor point.  This method is provided for the use of 
589         * subclasses, not end users.
590         * 
591         * @param anchor  the anchor point (<code>null</code> permitted).
592         */
593        protected void setAnchor(Point2D anchor) {
594            this.anchor = anchor;   
595        }
596    
597        /**
598         * Returns the chart contained in the panel.
599         *
600         * @return The chart (possibly <code>null</code>).
601         */
602        public JFreeChart getChart() {
603            return this.chart;
604        }
605    
606        /**
607         * Sets the chart that is displayed in the panel.
608         *
609         * @param chart  the chart (<code>null</code> permitted).
610         */
611        public void setChart(JFreeChart chart) {
612            // stop listening for changes to the existing chart
613            if (this.chart != null) {
614                this.chart.removeChangeListener(this);
615                this.chart.removeProgressListener(this);
616            }
617    
618            // add the new chart
619            this.chart = chart;
620            if (chart != null) {
621                this.chart.addChangeListener(this);
622                this.chart.addProgressListener(this);
623                Plot plot = chart.getPlot();
624                this.domainZoomable = false;
625                this.rangeZoomable = false;
626                if (plot instanceof Zoomable) {
627                    Zoomable z = (Zoomable) plot;
628                    this.domainZoomable = z.isDomainZoomable();
629                    this.rangeZoomable = z.isRangeZoomable();
630                    this.orientation = z.getOrientation();
631                }
632            }
633            else {
634                this.domainZoomable = false;
635                this.rangeZoomable = false;
636            }
637            if (this.useBuffer) {
638                this.refreshBuffer = true;
639            }
640        }
641    
642        /**
643         * Returns the chart rendering info from the most recent chart redraw.
644         *
645         * @return The chart rendering info (possibly <code>null</code>).
646         */
647        public ChartRenderingInfo getChartRenderingInfo() {
648            return this.info;
649        }
650        
651        /**
652         * Returns the flag that determines whether or not zooming is enabled for 
653         * the domain axis.
654         * 
655         * @return A boolean.
656         */
657        public boolean isDomainZoomable() {
658            return this.domainZoomable;
659        }
660        
661        /**
662         * Sets the flag that controls whether or not zooming is enable for the 
663         * domain axis.  A check is made to ensure that the current plot supports
664         * zooming for the domain values.
665         *
666         * @param flag  <code>true</code> enables zooming if possible.
667         */
668        public void setDomainZoomable(boolean flag) {
669            if (flag) {
670                Plot plot = this.chart.getPlot();
671                if (plot instanceof Zoomable) {
672                    Zoomable z = (Zoomable) plot;
673                    this.domainZoomable = flag && (z.isDomainZoomable());  
674                }
675            }
676            else {
677                this.domainZoomable = false;
678            }
679        }
680    
681        /**
682         * Returns the flag that determines whether or not zooming is enabled for 
683         * the range axis.
684         * 
685         * @return A boolean.
686         */
687        public boolean isRangeZoomable() {
688            return this.rangeZoomable;
689        }
690        
691        /**
692         * A flag that controls mouse-based zooming on the vertical axis.
693         *
694         * @param flag  <code>true</code> enables zooming.
695         */
696        public void setRangeZoomable(boolean flag) {
697            if (flag) {
698                Plot plot = this.chart.getPlot();
699                if (plot instanceof Zoomable) {
700                    Zoomable z = (Zoomable) plot;
701                    this.rangeZoomable = flag && (z.isRangeZoomable());  
702                }
703            }
704            else {
705                this.rangeZoomable = false;
706            }
707        }
708    
709        /**
710         * Returns the zoom in factor.
711         * 
712         * @return The zoom in factor.
713         * 
714         * @see #setZoomInFactor(double)
715         */
716        public double getZoomInFactor() {
717            return this.zoomInFactor;   
718        }
719        
720        /**
721         * Sets the zoom in factor.
722         * 
723         * @param factor  the factor.
724         * 
725         * @see #getZoomInFactor()
726         */
727        public void setZoomInFactor(double factor) {
728            this.zoomInFactor = factor;
729        }
730        
731        /**
732         * Returns the zoom out factor.
733         * 
734         * @return The zoom out factor.
735         * 
736         * @see #setZoomOutFactor(double)
737         */
738        public double getZoomOutFactor() {
739            return this.zoomOutFactor;   
740        }
741        
742        /**
743         * Sets the zoom out factor.
744         * 
745         * @param factor  the factor.
746         * 
747         * @see #getZoomOutFactor()
748         */
749        public void setZoomOutFactor(double factor) {
750            this.zoomOutFactor = factor;
751        }
752        
753        /**
754         * Displays a dialog that allows the user to edit the properties for the
755         * current chart.
756         */
757        private void attemptEditChartProperties() {
758            SWTChartEditor editor = new SWTChartEditor(this.canvas.getDisplay(), 
759                    this.chart);
760            //ChartEditorManager.getChartEditor(canvas.getDisplay(), this.chart);
761            editor.open();
762        }
763    
764        /**
765         * Returns <code>true</code> if file extensions should be enforced, and 
766         * <code>false</code> otherwise.
767         *
768         * @return The flag.
769         */
770        public boolean isEnforceFileExtensions() {
771            return this.enforceFileExtensions;
772        }
773    
774        /**
775         * Sets a flag that controls whether or not file extensions are enforced.
776         *
777         * @param enforce  the new flag value.
778         */
779        public void setEnforceFileExtensions(boolean enforce) {
780            this.enforceFileExtensions = enforce;
781        }
782    
783        /**
784         * Opens a file chooser and gives the user an opportunity to save the chart
785         * in PNG format.
786         *
787         * @throws IOException if there is an I/O error.
788         */
789        public void doSaveAs() throws IOException {
790            FileDialog fileDialog = new FileDialog(this.canvas.getShell(), 
791                    SWT.SAVE);
792            String[] extensions = { "*.png" };
793            fileDialog.setFilterExtensions(extensions);
794            String filename = fileDialog.open();
795            if (filename != null) {
796                if (isEnforceFileExtensions()) {
797                    if (!filename.endsWith(".png")) {
798                        filename = filename + ".png";
799                    }
800                }
801                //TODO replace getSize by getBounds ?
802                ChartUtilities.saveChartAsPNG(new File(filename), this.chart, 
803                        this.canvas.getSize().x, this.canvas.getSize().y);
804            }
805        }
806    
807        /**
808         * Returns a point based on (x, y) but constrained to be within the bounds
809         * of the given rectangle.  This method could be moved to JCommon.
810         * 
811         * @param x  the x-coordinate.
812         * @param y  the y-coordinate.
813         * @param area  the rectangle (<code>null</code> not permitted).
814         * 
815         * @return A point within the rectangle.
816         */
817        private org.eclipse.swt.graphics.Point getPointInRectangle(int x, int y, 
818                Rectangle area) {
819            x = Math.max(area.x, Math.min(x, area.x + area.width));   
820            y = Math.max(area.y, Math.min(y, area.y + area.height));
821            return new org.eclipse.swt.graphics.Point(x, y);
822        }
823    
824        /**
825         * Zooms in on an anchor point (specified in screen coordinate space).
826         *
827         * @param x  the x value (in screen coordinates).
828         * @param y  the y value (in screen coordinates).
829         */
830        public void zoomInBoth(double x, double y) {
831            zoomInDomain(x, y);
832            zoomInRange(x, y);
833        }
834    
835        /**
836         * Decreases the length of the domain axis, centered about the given
837         * coordinate on the screen.  The length of the domain axis is reduced
838         * by the value of {@link #getZoomInFactor()}.
839         *
840         * @param x  the x coordinate (in screen coordinates).
841         * @param y  the y-coordinate (in screen coordinates).
842         */
843        public void zoomInDomain(double x, double y) {
844            Plot p = this.chart.getPlot();
845            if (p instanceof Zoomable) 
846            {
847                Zoomable plot = (Zoomable) p;
848                plot.zoomDomainAxes(this.zoomInFactor, this.info.getPlotInfo(), 
849                        translateScreenToJava2D(new Point((int) x, (int) y)));
850            }
851        }
852    
853        /**
854         * Decreases the length of the range axis, centered about the given
855         * coordinate on the screen.  The length of the range axis is reduced by
856         * the value of {@link #getZoomInFactor()}.
857         *
858         * @param x  the x-coordinate (in screen coordinates).
859         * @param y  the y coordinate (in screen coordinates).
860         */
861        public void zoomInRange(double x, double y) {
862            Plot p = this.chart.getPlot();
863            if (p instanceof Zoomable) {
864                Zoomable z = (Zoomable) p;
865                z.zoomRangeAxes(this.zoomInFactor, this.info.getPlotInfo(), 
866                        translateScreenToJava2D(new Point((int) x, (int) y)));
867            }
868        }
869    
870        /**
871         * Zooms out on an anchor point (specified in screen coordinate space).
872         *
873         * @param x  the x value (in screen coordinates).
874         * @param y  the y value (in screen coordinates).
875         */
876        public void zoomOutBoth(double x, double y) {
877            zoomOutDomain(x, y);
878            zoomOutRange(x, y);
879        }
880    
881        /**
882         * Increases the length of the domain axis, centered about the given
883         * coordinate on the screen.  The length of the domain axis is increased
884         * by the value of {@link #getZoomOutFactor()}.
885         *
886         * @param x  the x coordinate (in screen coordinates).
887         * @param y  the y-coordinate (in screen coordinates).
888         */
889        public void zoomOutDomain(double x, double y) {
890            Plot p = this.chart.getPlot();
891            if (p instanceof Zoomable) {
892                Zoomable z = (Zoomable) p;
893                z.zoomDomainAxes(this.zoomOutFactor, this.info.getPlotInfo(), 
894                        translateScreenToJava2D(new Point((int) x, (int) y)));
895            }
896        }
897    
898        /**
899         * Increases the length the range axis, centered about the given
900         * coordinate on the screen.  The length of the range axis is increased
901         * by the value of {@link #getZoomOutFactor()}.
902         *
903         * @param x  the x coordinate (in screen coordinates).
904         * @param y  the y-coordinate (in screen coordinates).
905         */
906        public void zoomOutRange(double x, double y) {
907            Plot p = this.chart.getPlot();
908            if (p instanceof Zoomable) {
909                Zoomable z = (Zoomable) p;
910                z.zoomRangeAxes(this.zoomOutFactor, this.info.getPlotInfo(), 
911                        translateScreenToJava2D(new Point((int) x, (int) y)));
912            }
913        }
914    
915        /**
916         * Zooms in on a selected region.
917         *
918         * @param selection  the selected region.
919         */
920        public void zoom(Rectangle selection) {
921    
922            // get the origin of the zoom selection in the Java2D space used for
923            // drawing the chart (that is, before any scaling to fit the panel)
924            Point2D selectOrigin = translateScreenToJava2D(
925                    new Point(selection.x, selection.y));
926            PlotRenderingInfo plotInfo = this.info.getPlotInfo();
927            Rectangle scaledDataArea = getScreenDataArea(
928                    (selection.x + selection.width / 2), 
929                    (selection.y + selection.height/2));
930            if ((selection.height > 0) && (selection.width > 0)) {
931    
932                double hLower = (selection.x - scaledDataArea.x) 
933                    / (double) scaledDataArea.width;
934                double hUpper = (selection.x + selection.width - scaledDataArea.x) 
935                    / (double) scaledDataArea.width;
936                double vLower = (scaledDataArea.y + scaledDataArea.height 
937                        - selection.y - selection.height) 
938                        / (double) scaledDataArea.height;
939                double vUpper = (scaledDataArea.y + scaledDataArea.height 
940                        - selection.y) / (double) scaledDataArea.height;
941                Plot p = this.chart.getPlot();
942                if (p instanceof Zoomable) {
943                    Zoomable z = (Zoomable) p;
944                    if (z.getOrientation() == PlotOrientation.HORIZONTAL) {
945                        z.zoomDomainAxes(vLower, vUpper, plotInfo, selectOrigin);
946                        z.zoomRangeAxes(hLower, hUpper, plotInfo, selectOrigin);
947                    }
948                    else {
949                        z.zoomDomainAxes(hLower, hUpper, plotInfo, selectOrigin);
950                        z.zoomRangeAxes(vLower, vUpper, plotInfo, selectOrigin);
951                    }
952                }
953    
954            }
955    
956        }
957    
958        /**
959         * Receives notification of changes to the chart, and redraws the chart.
960         *
961         * @param event  details of the chart change event.
962         */
963        public void chartChanged(ChartChangeEvent event) {
964            this.refreshBuffer = true;
965            Plot plot = this.chart.getPlot();
966            if (plot instanceof Zoomable) {
967                Zoomable z = (Zoomable) plot;
968                this.orientation = z.getOrientation();
969            }
970            this.canvas.redraw();
971        }
972    
973        /**
974         * Forces a redraw of the canvas by invoking a new PaintEvent.
975         */
976        public void forceRedraw() {
977            Event ev = new Event();
978            ev.gc = new GC(this.canvas);
979            ev.x = 0;
980            ev.y = 0;
981            ev.width = this.canvas.getBounds().width;
982            ev.height = this.canvas.getBounds().height;
983            ev.count = 0;
984            this.canvas.notifyListeners(SWT.Paint, ev);
985            ev.gc.dispose();
986        }
987        
988        /**
989         * Adds a listener to the list of objects listening for chart mouse events.
990         *
991         * @param listener  the listener (<code>null</code> not permitted).
992         */
993        public void addChartMouseListener(ChartMouseListener listener) {
994            this.chartMouseListeners.add(ChartMouseListener.class, listener);
995        }
996        
997        /**
998         * Removes a listener from the list of objects listening for chart mouse 
999         * events.
1000         *
1001         * @param listener  the listener.
1002         */
1003        public void removeChartMouseListener(ChartMouseListener listener) {
1004            this.chartMouseListeners.remove(ChartMouseListener.class, listener);
1005        }
1006        
1007        /**
1008         * Receives notification of a chart progress event.
1009         *
1010         * @param event  the event.
1011         */
1012        public void chartProgress(ChartProgressEvent event) {
1013            // does nothing - override if necessary
1014        }
1015    
1016        /**
1017         * Restores the auto-range calculation on both axes.
1018         */
1019        public void restoreAutoBounds() {
1020            restoreAutoDomainBounds();
1021            restoreAutoRangeBounds();
1022        }
1023    
1024        /**
1025         * Restores the auto-range calculation on the domain axis.
1026         */
1027        public void restoreAutoDomainBounds() {
1028            Plot p = this.chart.getPlot();
1029            if (p instanceof Zoomable) {
1030                Zoomable z = (Zoomable) p;
1031                // we need to guard against this.zoomPoint being null
1032                org.eclipse.swt.graphics.Point zp = 
1033                    (this.zoomPoint != null ? this.zoomPoint : 
1034                        new org.eclipse.swt.graphics.Point(0,0));
1035                z.zoomDomainAxes(0.0, this.info.getPlotInfo(), 
1036                        SWTUtils.toAwtPoint(zp));
1037            }
1038        }
1039    
1040        /**
1041         * Restores the auto-range calculation on the range axis.
1042         */
1043        public void restoreAutoRangeBounds() {
1044            Plot p = this.chart.getPlot();
1045            if (p instanceof ValueAxisPlot) {
1046                Zoomable z = (Zoomable) p;
1047                // we need to guard against this.zoomPoint being null
1048                org.eclipse.swt.graphics.Point zp = 
1049                    (this.zoomPoint != null ? this.zoomPoint : 
1050                        new org.eclipse.swt.graphics.Point(0,0));
1051                z.zoomRangeAxes(0.0, this.info.getPlotInfo(), 
1052                        SWTUtils.toAwtPoint(zp)); 
1053            }
1054        }
1055    
1056        /**
1057         * Applies any scaling that is in effect for the chart drawing to the
1058         * given rectangle.
1059         *  
1060         * @param rect  the rectangle.
1061         * 
1062         * @return A new scaled rectangle.
1063         */
1064        public Rectangle scale(Rectangle2D rect) {
1065            Rectangle insets = this.getClientArea();
1066            int x = (int) Math.round(rect.getX() * getScaleX()) + insets.x;
1067            int y = (int) Math.round(rect.getY() * this.getScaleY()) + insets.y;
1068            int w = (int) Math.round(rect.getWidth() * this.getScaleX());
1069            int h = (int) Math.round(rect.getHeight() * this.getScaleY());
1070            return new Rectangle(x, y, w, h);
1071        }
1072    
1073        /**
1074         * Returns the data area for the chart (the area inside the axes) with the
1075         * current scaling applied (that is, the area as it appears on screen).
1076         *
1077         * @return The scaled data area.
1078         */
1079        public Rectangle getScreenDataArea() {
1080            Rectangle2D dataArea = this.info.getPlotInfo().getDataArea();
1081            Rectangle clientArea = this.getClientArea();
1082            int x = (int) (dataArea.getX() * this.scaleX + clientArea.x);
1083            int y = (int) (dataArea.getY() * this.scaleY + clientArea.y);
1084            int w = (int) (dataArea.getWidth() * this.scaleX);
1085            int h = (int) (dataArea.getHeight() * this.scaleY);
1086            return new Rectangle(x, y, w, h);
1087        }
1088        
1089        /**
1090         * Returns the data area (the area inside the axes) for the plot or subplot,
1091         * with the current scaling applied.
1092         *
1093         * @param x  the x-coordinate (for subplot selection).
1094         * @param y  the y-coordinate (for subplot selection).
1095         * 
1096         * @return The scaled data area.
1097         */
1098        public Rectangle getScreenDataArea(int x, int y) {
1099            PlotRenderingInfo plotInfo = this.info.getPlotInfo();
1100            Rectangle result;
1101            if (plotInfo.getSubplotCount() == 0)
1102                result = getScreenDataArea();
1103            else {
1104                // get the origin of the zoom selection in the Java2D space used for
1105                // drawing the chart (that is, before any scaling to fit the panel)
1106                Point2D selectOrigin = translateScreenToJava2D(new Point(x, y));
1107                int subplotIndex = plotInfo.getSubplotIndex(selectOrigin);
1108                if (subplotIndex == -1) {
1109                    return null;
1110                }
1111                result = scale(plotInfo.getSubplotInfo(subplotIndex).getDataArea());
1112            }
1113            return result;
1114        }
1115    
1116        /**
1117         * Translates a Java2D point on the chart to a screen location.
1118         *
1119         * @param java2DPoint  the Java2D point.
1120         *
1121         * @return The screen location.
1122         */
1123        public Point translateJava2DToScreen(Point2D java2DPoint) {
1124            Rectangle insets = this.getClientArea();
1125            int x = (int) (java2DPoint.getX() * this.scaleX + insets.x);
1126            int y = (int) (java2DPoint.getY() * this.scaleY + insets.y);
1127            return new Point(x, y);
1128        }
1129    
1130        /**
1131         * Translates a screen location to a Java SWT point.
1132         *
1133         * @param screenPoint  the screen location.
1134         *
1135         * @return The Java2D coordinates.
1136         */
1137        public Point translateScreenToJavaSWT(Point screenPoint) {
1138            Rectangle insets = this.getClientArea();
1139            int x = (int) ((screenPoint.x - insets.x) / this.scaleX);
1140            int y = (int) ((screenPoint.y - insets.y) / this.scaleY);
1141            return new Point(x, y);
1142        }
1143    
1144        /**
1145         * Translates a screen location to a Java2D point.
1146         *
1147         * @param screenPoint  the screen location.
1148         *
1149         * @return The Java2D coordinates.
1150         */
1151        public Point2D translateScreenToJava2D(Point screenPoint) {
1152            Rectangle insets = this.getClientArea();
1153            int x = (int) ((screenPoint.x - insets.x) / this.scaleX);
1154            int y = (int) ((screenPoint.y - insets.y) / this.scaleY);
1155            return new Point2D.Double(x, y);
1156        }
1157    
1158        /**
1159         * Returns the flag that controls whether or not a horizontal axis trace
1160         * line is drawn over the plot area at the current mouse location.
1161         * 
1162         * @return A boolean.
1163         */
1164        public boolean getHorizontalAxisTrace() {
1165            return this.horizontalAxisTrace;    
1166        }
1167        
1168        /**
1169         * A flag that controls trace lines on the horizontal axis.
1170         *
1171         * @param flag  <code>true</code> enables trace lines for the mouse
1172         *      pointer on the horizontal axis.
1173         */
1174        public void setHorizontalAxisTrace(boolean flag) {
1175            this.horizontalAxisTrace = flag;
1176        }
1177        
1178        /**
1179         * Returns the flag that controls whether or not a vertical axis trace
1180         * line is drawn over the plot area at the current mouse location.
1181         * 
1182         * @return A boolean.
1183         */
1184        public boolean getVerticalAxisTrace() {
1185            return this.verticalAxisTrace;    
1186        }
1187        
1188        /**
1189         * A flag that controls trace lines on the vertical axis.
1190         *
1191         * @param flag  <code>true</code> enables trace lines for the mouse
1192         *              pointer on the vertical axis.
1193         */
1194        public void setVerticalAxisTrace(boolean flag) {
1195            this.verticalAxisTrace = flag;
1196        }
1197    
1198        /**
1199         * @param displayToolTips the displayToolTips to set
1200         */
1201        public void setDisplayToolTips( boolean displayToolTips ) {
1202            this.displayToolTips = displayToolTips;
1203        }
1204    
1205        /**
1206         * Returns a string for the tooltip.
1207         *
1208         * @param e  the mouse event.
1209         *
1210         * @return A tool tip or <code>null</code> if no tooltip is available.
1211         */
1212        public String getToolTipText(org.eclipse.swt.events.MouseEvent e) {
1213            String result = null;
1214            if (this.info != null) {
1215                EntityCollection entities = this.info.getEntityCollection();
1216                if (entities != null) {
1217                    Rectangle insets = getClientArea();
1218                    ChartEntity entity = entities.getEntity(
1219                            (int) ((e.x - insets.x) / this.scaleX),
1220                            (int) ((e.y - insets.y) / this.scaleY));
1221                    if (entity != null) {
1222                        result = entity.getToolTipText();
1223                    }
1224                }
1225            }
1226            return result;
1227    
1228        }
1229    
1230        /**
1231         * The idea is to modify the zooming options depending on the type of chart 
1232         * being displayed by the panel.
1233         *
1234         * @param x  horizontal position of the popup.
1235         * @param y  vertical position of the popup.
1236         */
1237        protected void displayPopupMenu(int x, int y) {
1238            if (this.popup != null) {
1239                // go through each zoom menu item and decide whether or not to 
1240                // enable it...
1241                Plot plot = this.chart.getPlot();
1242                boolean isDomainZoomable = false;
1243                boolean isRangeZoomable = false;
1244                if (plot instanceof Zoomable) {
1245                    Zoomable z = (Zoomable) plot;
1246                    isDomainZoomable = z.isDomainZoomable();
1247                    isRangeZoomable = z.isRangeZoomable();
1248                }
1249                if (this.zoomInDomainMenuItem != null) {
1250                    this.zoomInDomainMenuItem.setEnabled(isDomainZoomable);
1251                }
1252                if (this.zoomOutDomainMenuItem != null) {
1253                    this.zoomOutDomainMenuItem.setEnabled(isDomainZoomable);
1254                } 
1255                if (this.zoomResetDomainMenuItem != null) {
1256                    this.zoomResetDomainMenuItem.setEnabled(isDomainZoomable);
1257                }
1258    
1259                if (this.zoomInRangeMenuItem != null) {
1260                    this.zoomInRangeMenuItem.setEnabled(isRangeZoomable);
1261                }
1262                if (this.zoomOutRangeMenuItem != null) {
1263                    this.zoomOutRangeMenuItem.setEnabled(isRangeZoomable);
1264                }
1265    
1266                if (this.zoomResetRangeMenuItem != null) {
1267                    this.zoomResetRangeMenuItem.setEnabled(isRangeZoomable);
1268                }
1269    
1270                if (this.zoomInBothMenuItem != null) {
1271                    this.zoomInBothMenuItem.setEnabled(
1272                        isDomainZoomable & isRangeZoomable
1273                    );
1274                }
1275                if (this.zoomOutBothMenuItem != null) {
1276                    this.zoomOutBothMenuItem.setEnabled(isDomainZoomable 
1277                            & isRangeZoomable);
1278                }
1279                if (this.zoomResetBothMenuItem != null) {
1280                    this.zoomResetBothMenuItem.setEnabled(isDomainZoomable 
1281                            & isRangeZoomable);
1282                }
1283    
1284                this.popup.setLocation(x, y);
1285                this.popup.setVisible(true);
1286            }
1287    
1288        }
1289    
1290        /**
1291         * Creates a print job for the chart.
1292         */
1293        public void createChartPrintJob() {
1294            //FIXME try to replace swing print stuff by swt
1295            PrinterJob job = PrinterJob.getPrinterJob();
1296            PageFormat pf = job.defaultPage();
1297            PageFormat pf2 = job.pageDialog(pf);
1298            if (pf2 != pf) {
1299                job.setPrintable(this, pf2);
1300                if (job.printDialog()) {
1301                    try {
1302                        job.print();
1303                    }
1304                    catch (PrinterException e) {
1305                        MessageBox messageBox = new MessageBox( 
1306                                this.canvas.getShell(), SWT.OK | SWT.ICON_ERROR );
1307                        messageBox.setMessage( e.getMessage() );
1308                        messageBox.open();
1309                    }
1310                }
1311            }
1312        }
1313    
1314        /**
1315         * Creates a popup menu for the canvas.
1316         *
1317         * @param properties  include a menu item for the chart property editor.
1318         * @param save  include a menu item for saving the chart.
1319         * @param print  include a menu item for printing the chart.
1320         * @param zoom  include menu items for zooming.
1321         *
1322         * @return The popup menu.
1323         */
1324        protected Menu createPopupMenu(boolean properties, boolean save, 
1325                boolean print, boolean zoom) {
1326            
1327            Menu result = new Menu(this);
1328            boolean separator = false;
1329    
1330            if (properties) {
1331                MenuItem propertiesItem = new MenuItem(result, SWT.PUSH);
1332                propertiesItem.setText(localizationResources.getString(
1333                        "Properties..."));
1334                propertiesItem.setData(PROPERTIES_COMMAND);
1335                propertiesItem.addSelectionListener(this);
1336                separator = true;
1337            }
1338            if (save) {
1339                if (separator) {
1340                    new MenuItem(result, SWT.SEPARATOR);
1341                    separator = false;
1342                }
1343                MenuItem saveItem = new MenuItem(result, SWT.NONE);
1344                saveItem.setText(localizationResources.getString("Save_as..."));
1345                saveItem.setData(SAVE_COMMAND);
1346                saveItem.addSelectionListener(this);
1347                separator = true;
1348            }
1349            if (print) {
1350                if (separator) {
1351                    new MenuItem(result, SWT.SEPARATOR);
1352                    separator = false;
1353                }
1354                MenuItem printItem = new MenuItem(result, SWT.NONE);
1355                printItem.setText(localizationResources.getString("Print..."));
1356                printItem.setData(PRINT_COMMAND);
1357                printItem.addSelectionListener(this);
1358                separator = true;
1359            }
1360            if (zoom) {
1361                if (separator) {
1362                    new MenuItem(result, SWT.SEPARATOR);
1363                    separator = false;
1364                }
1365    
1366                Menu zoomInMenu = new Menu(result);
1367                MenuItem zoomInMenuItem = new MenuItem(result, SWT.CASCADE);
1368                zoomInMenuItem.setText(localizationResources.getString("Zoom_In"));
1369                zoomInMenuItem.setMenu(zoomInMenu);
1370    
1371                this.zoomInBothMenuItem = new MenuItem(zoomInMenu, SWT.PUSH);
1372                this.zoomInBothMenuItem.setText(localizationResources.getString(
1373                        "All_Axes"));
1374                this.zoomInBothMenuItem.setData(ZOOM_IN_BOTH_COMMAND);
1375                this.zoomInBothMenuItem.addSelectionListener(this);
1376    
1377                new MenuItem(zoomInMenu, SWT.SEPARATOR);
1378    
1379                this.zoomInDomainMenuItem = new MenuItem(zoomInMenu, SWT.PUSH);
1380                this.zoomInDomainMenuItem.setText(localizationResources.getString(
1381                        "Domain_Axis"));
1382                this.zoomInDomainMenuItem.setData(ZOOM_IN_DOMAIN_COMMAND);
1383                this.zoomInDomainMenuItem.addSelectionListener(this);
1384    
1385                this.zoomInRangeMenuItem = new MenuItem(zoomInMenu, SWT.PUSH);
1386                this.zoomInRangeMenuItem.setText(localizationResources.getString(
1387                        "Range_Axis"));
1388                this.zoomInRangeMenuItem.setData(ZOOM_IN_RANGE_COMMAND);
1389                this.zoomInRangeMenuItem.addSelectionListener(this);
1390    
1391                Menu zoomOutMenu = new Menu(result);
1392                MenuItem zoomOutMenuItem = new MenuItem(result, SWT.CASCADE);
1393                zoomOutMenuItem.setText(localizationResources.getString(
1394                        "Zoom_Out"));
1395                zoomOutMenuItem.setMenu(zoomOutMenu);
1396    
1397                this.zoomOutBothMenuItem = new MenuItem(zoomOutMenu, SWT.PUSH);
1398                this.zoomOutBothMenuItem.setText(localizationResources.getString(
1399                        "All_Axes"));
1400                this.zoomOutBothMenuItem.setData(ZOOM_OUT_BOTH_COMMAND);
1401                this.zoomOutBothMenuItem.addSelectionListener(this);
1402                
1403                new MenuItem(zoomOutMenu, SWT.SEPARATOR);
1404    
1405                this.zoomOutDomainMenuItem = new MenuItem(zoomOutMenu, SWT.PUSH);
1406                this.zoomOutDomainMenuItem.setText(localizationResources.getString(
1407                        "Domain_Axis"));
1408                this.zoomOutDomainMenuItem.setData(ZOOM_OUT_DOMAIN_COMMAND);
1409                this.zoomOutDomainMenuItem.addSelectionListener(this);
1410    
1411                this.zoomOutRangeMenuItem = new MenuItem(zoomOutMenu, SWT.PUSH);
1412                this.zoomOutRangeMenuItem.setText(
1413                        localizationResources.getString("Range_Axis"));
1414                this.zoomOutRangeMenuItem.setData(ZOOM_OUT_RANGE_COMMAND);
1415                this.zoomOutRangeMenuItem.addSelectionListener(this);
1416    
1417                Menu autoRangeMenu = new Menu(result);
1418                MenuItem autoRangeMenuItem = new MenuItem(result, SWT.CASCADE);
1419                autoRangeMenuItem.setText(localizationResources.getString(
1420                        "Auto_Range"));
1421                autoRangeMenuItem.setMenu(autoRangeMenu);
1422    
1423                this.zoomResetBothMenuItem = new MenuItem(autoRangeMenu, SWT.PUSH);
1424                this.zoomResetBothMenuItem.setText(localizationResources.getString(
1425                        "All_Axes"));
1426                this.zoomResetBothMenuItem.setData(ZOOM_RESET_BOTH_COMMAND);
1427                this.zoomResetBothMenuItem.addSelectionListener(this);
1428                
1429                new MenuItem(autoRangeMenu, SWT.SEPARATOR);
1430    
1431                this.zoomResetDomainMenuItem = new MenuItem(autoRangeMenu, 
1432                        SWT.PUSH);
1433                this.zoomResetDomainMenuItem.setText(
1434                        localizationResources.getString("Domain_Axis"));
1435                this.zoomResetDomainMenuItem.setData(ZOOM_RESET_DOMAIN_COMMAND);
1436                this.zoomResetDomainMenuItem.addSelectionListener(this);
1437                   
1438                this.zoomResetRangeMenuItem = new MenuItem(autoRangeMenu, SWT.PUSH);
1439                this.zoomResetRangeMenuItem.setText(
1440                        localizationResources.getString("Range_Axis"));
1441                this.zoomResetRangeMenuItem.setData(ZOOM_RESET_RANGE_COMMAND);
1442                this.zoomResetRangeMenuItem.addSelectionListener(this);
1443            }
1444            
1445            return result;
1446        }
1447    
1448        /**
1449         * Handles action events generated by the popup menu.
1450         *
1451         * @see org.eclipse.swt.events.SelectionListener#widgetDefaultSelected(
1452         * org.eclipse.swt.events.SelectionEvent)
1453         */
1454        public void widgetDefaultSelected(SelectionEvent e) {
1455            widgetSelected(e);
1456        }
1457    
1458        /**
1459         * Handles action events generated by the popup menu.
1460         *
1461         * @see org.eclipse.swt.events.SelectionListener#widgetSelected(
1462         * org.eclipse.swt.events.SelectionEvent)
1463         */
1464        public void widgetSelected(SelectionEvent e) {
1465            String command = (String) ((MenuItem) e.getSource()).getData();
1466            if (command.equals(PROPERTIES_COMMAND)) {
1467                attemptEditChartProperties();
1468            }
1469            else if (command.equals(SAVE_COMMAND)) {
1470                try {
1471                    doSaveAs();
1472                }
1473                catch (IOException ex) {
1474                    ex.printStackTrace();
1475                }
1476            }
1477            else if (command.equals(PRINT_COMMAND)) {
1478                createChartPrintJob();
1479            }
1480            /* in the next zoomPoint.x and y replace by e.x and y for now. 
1481             * this helps to handle the mouse events and besides, 
1482             * those values are unused AFAIK. */
1483            else if (command.equals(ZOOM_IN_BOTH_COMMAND)) {
1484                zoomInBoth( e.x, e.y );
1485            }
1486            else if (command.equals(ZOOM_IN_DOMAIN_COMMAND)) {
1487                zoomInDomain( e.x, e.y );
1488            }
1489            else if (command.equals(ZOOM_IN_RANGE_COMMAND)) {
1490                zoomInRange( e.x, e.y );
1491            }
1492            else if (command.equals(ZOOM_OUT_BOTH_COMMAND)) {
1493                zoomOutBoth( e.x, e.y );
1494            }
1495            else if (command.equals(ZOOM_OUT_DOMAIN_COMMAND)) {
1496                zoomOutDomain( e.x, e.y );
1497            }
1498            else if (command.equals(ZOOM_OUT_RANGE_COMMAND)) {
1499                zoomOutRange( e.x, e.y );
1500            }
1501            else if (command.equals(ZOOM_RESET_BOTH_COMMAND)) {
1502                restoreAutoBounds();
1503            }
1504            else if (command.equals(ZOOM_RESET_DOMAIN_COMMAND)) {
1505                restoreAutoDomainBounds();
1506            }
1507            else if (command.equals(ZOOM_RESET_RANGE_COMMAND)) {
1508                restoreAutoRangeBounds();
1509            }
1510            this.forceRedraw();
1511        }
1512    
1513        public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) 
1514            throws PrinterException {
1515            if (pageIndex != 0) {
1516                return NO_SUCH_PAGE;
1517            }
1518            /*
1519            CairoImage image = new CairoImage( 
1520                    this.getBounds().width, this.getBounds().height );
1521            Graphics2D g2 = image.createGraphics2D();
1522            double x = pageFormat.getImageableX();
1523            double y = pageFormat.getImageableY();
1524            double w = pageFormat.getImageableWidth();
1525            double h = pageFormat.getImageableHeight();
1526            this.chart.draw(
1527                g2, new Rectangle2D.Double(x, y, w, h), this.anchor, null
1528            );
1529            */
1530            return PAGE_EXISTS;
1531        }
1532    
1533        /**
1534         * Hook an SWT listener on the canvas where the chart is drawn.
1535         * The purpose of this method is to allow some degree of customization.
1536         * @param listener The SWT listener to attach to the canvas.
1537         */
1538        public void addSWTListener(SWTEventListener listener) {
1539            if (listener instanceof ControlListener) {
1540                this.canvas.addControlListener((ControlListener) listener);
1541            } else if (listener instanceof DisposeListener) {
1542                this.canvas.addDisposeListener((DisposeListener) listener);
1543    //      } else if (listener instanceof DragDetectListener) {
1544    //          this.canvas.addDragDetectListener((DragDetectListener) listener);
1545            } else if (listener instanceof FocusListener) {
1546                this.canvas.addFocusListener((FocusListener) listener);
1547            } else if (listener instanceof HelpListener) {
1548                this.canvas.addHelpListener((HelpListener) listener);
1549            } else if (listener instanceof KeyListener) {
1550                this.canvas.addKeyListener((KeyListener) listener);
1551    //      } else if (listener instanceof MenuDetectListener) {
1552    //          this.canvas.addMenuDetectListener((MenuDetectListener) listener);
1553            } else if (listener instanceof MouseListener) {
1554                this.canvas.addMouseListener((MouseListener) listener);
1555            } else if (listener instanceof MouseMoveListener) {
1556                this.canvas.addMouseMoveListener((MouseMoveListener) listener);
1557            } else if (listener instanceof MouseTrackListener) {
1558                this.canvas.addMouseTrackListener((MouseTrackListener) listener);
1559    //      } else if (listener instanceof MouseWheelListener) {
1560    //          this.canvas.addMouseWheelListener((MouseWheelListener) listener);
1561            } else if (listener instanceof PaintListener) {
1562                this.canvas.addPaintListener((PaintListener) listener);
1563            } else if (listener instanceof TraverseListener) {
1564                this.canvas.addTraverseListener((TraverseListener) listener);
1565            } 
1566        }
1567        
1568        /* (non-Javadoc)
1569         * @see org.eclipse.swt.events.MouseListener#mouseDoubleClick(
1570         * org.eclipse.swt.events.MouseEvent)
1571         */
1572        public void mouseDoubleClick(MouseEvent event) {
1573            // do nothing, override if necessary
1574        }
1575    
1576        /* (non-Javadoc)
1577         * @see org.eclipse.swt.events.MouseListener#mouseDown(
1578         * org.eclipse.swt.events.MouseEvent)
1579         */
1580        public void mouseDown(MouseEvent event) {
1581            
1582            Rectangle scaledDataArea = getScreenDataArea(event.x, event.y);
1583            if (scaledDataArea == null) return;
1584            this.zoomPoint = getPointInRectangle(event.x, event.y, scaledDataArea);
1585            int x = (int) ((event.x - getClientArea().x) / this.scaleX);
1586            int y = (int) ((event.y - getClientArea().y) / this.scaleY);
1587    
1588            this.anchor = new Point2D.Double(x, y);
1589            this.chart.setNotify(true);  // force a redraw 
1590            this.canvas.redraw();
1591            
1592            // new entity code
1593            ChartEntity entity = null;
1594            if (this.info != null) {
1595                EntityCollection entities = this.info.getEntityCollection();
1596                if (entities != null) {
1597                    entity = entities.getEntity(x, y);
1598                }
1599            }
1600            
1601            Object[] listeners = this.chartMouseListeners.getListeners(
1602                    ChartMouseListener.class);
1603            if (listeners.length == 0) {
1604                return;
1605            }
1606    
1607            // pass mouse down event if some ChartMouseListener are listening
1608            java.awt.event.MouseEvent mouseEvent = SWTUtils.toAwtMouseEvent(event); 
1609            ChartMouseEvent chartEvent = new ChartMouseEvent(getChart(), 
1610                    mouseEvent, entity);
1611            for (int i = listeners.length - 1; i >= 0; i -= 1) {
1612                ((ChartMouseListener) listeners[i]).chartMouseClicked(chartEvent);
1613            }
1614        }
1615    
1616        /* (non-Javadoc)
1617         * @see org.eclipse.swt.events.MouseListener#mouseUp(
1618         * org.eclipse.swt.events.MouseEvent)
1619         */
1620        public void mouseUp(MouseEvent event) {
1621    
1622            boolean hZoom, vZoom;
1623            if (this.zoomRectangle == null) {
1624                Rectangle screenDataArea = getScreenDataArea(event.x, event.y);
1625                if (screenDataArea != null) {
1626                    this.zoomPoint = getPointInRectangle(event.x, event.y, 
1627                            screenDataArea);
1628                }
1629                if (this.popup != null && event.button == 3) {
1630                    org.eclipse.swt.graphics.Point pt = this.canvas.toDisplay(
1631                            event.x, event.y);
1632                    displayPopupMenu(pt.x, pt.y);
1633                }
1634            }
1635            else {
1636                hZoom = false;
1637                vZoom = false;
1638                if (this.orientation == PlotOrientation.HORIZONTAL) {
1639                    hZoom = this.rangeZoomable;
1640                    vZoom = this.domainZoomable;
1641                }
1642                else {
1643                    hZoom = this.domainZoomable;              
1644                    vZoom = this.rangeZoomable;
1645                }
1646                boolean zoomTrigger1 = hZoom && Math.abs(this.zoomRectangle.width) 
1647                        >= this.zoomTriggerDistance;
1648                boolean zoomTrigger2 = vZoom 
1649                        && Math.abs(this.zoomRectangle.height) 
1650                        >= this.zoomTriggerDistance;
1651                if (zoomTrigger1 || zoomTrigger2) {
1652                    // if the box has been drawn backwards, restore the auto bounds
1653                    if ((hZoom && (this.zoomRectangle.x + this.zoomRectangle.width 
1654                            < this.zoomPoint.x)) || (vZoom && (this.zoomRectangle.y
1655                            + this.zoomRectangle.height < this.zoomPoint.y))) 
1656                        restoreAutoBounds();
1657                    else {
1658                        zoom(this.zoomRectangle);
1659                    }
1660                    this.canvas.redraw();
1661                }
1662            }
1663            this.zoomPoint = null;
1664            this.zoomRectangle = null;
1665        }
1666    
1667        /* (non-Javadoc)
1668         * @see org.eclipse.swt.events.MouseMoveListener#mouseMove(
1669         * org.eclipse.swt.events.MouseEvent)
1670         */
1671        public void mouseMove(MouseEvent event) {
1672    
1673            // handle axis trace
1674            if (this.horizontalAxisTrace || this.verticalAxisTrace) {
1675                this.horizontalTraceLineY = event.y;
1676                this.verticalTraceLineX = event.x;
1677                this.canvas.redraw();
1678            }
1679    
1680            // handle tool tips in a simple way
1681            if (this.displayToolTips) {                            
1682                String s = getToolTipText(event);
1683                if (s == null && this.canvas.getToolTipText() != null
1684                        || s!=null && !s.equals(this.canvas.getToolTipText()))
1685                    this.canvas.setToolTipText(s);
1686            }
1687    
1688            // handle zoom box
1689            boolean hZoom, vZoom;
1690            if (this.zoomPoint != null) {
1691                Rectangle scaledDataArea = getScreenDataArea(this.zoomPoint.x, 
1692                        this.zoomPoint.y);
1693                org.eclipse.swt.graphics.Point movingPoint 
1694                        = getPointInRectangle(event.x, event.y, scaledDataArea);
1695                if (this.orientation == PlotOrientation.HORIZONTAL) {
1696                    hZoom = this.rangeZoomable;
1697                    vZoom = this.domainZoomable;
1698                }
1699                else {
1700                    hZoom = this.domainZoomable;              
1701                    vZoom = this.rangeZoomable;
1702                }
1703                if (hZoom && vZoom) {
1704                    // selected rectangle shouldn't extend outside the data area...
1705                    this.zoomRectangle = new Rectangle(this.zoomPoint.x, 
1706                            this.zoomPoint.y, movingPoint.x - this.zoomPoint.x, 
1707                            movingPoint.y - this.zoomPoint.y);                            
1708                }
1709                else if (hZoom) {
1710                    this.zoomRectangle = new Rectangle(this.zoomPoint.x, 
1711                            scaledDataArea.y, movingPoint.x - this.zoomPoint.x, 
1712                            scaledDataArea.height);
1713                }
1714                else if (vZoom) {
1715                    int ymax = Math.max(movingPoint.y, scaledDataArea.y);
1716                    this.zoomRectangle = new Rectangle(
1717                            scaledDataArea.x, this.zoomPoint.y,
1718                            scaledDataArea.width, ymax - this.zoomPoint.y);
1719                }
1720                this.canvas.redraw();
1721            }
1722    
1723            // new entity code
1724            ChartEntity entity = null;
1725            int x = (int) ((event.x - getClientArea().x) / this.scaleX);
1726            int y = (int) ((event.y - getClientArea().y) / this.scaleY);
1727    
1728            if (this.info != null) {
1729                EntityCollection entities = this.info.getEntityCollection();
1730                if (entities != null) {
1731                    entity = entities.getEntity(x, y);
1732                }
1733            }
1734    
1735            Object[] listeners = this.chartMouseListeners.getListeners(
1736                    ChartMouseListener.class);
1737            if (listeners.length == 0) {
1738                return;
1739            }
1740    
1741            // pass mouse move event if some ChartMouseListener are listening
1742            java.awt.event.MouseEvent mouseEvent = SWTUtils.toAwtMouseEvent(event); 
1743            ChartMouseEvent chartEvent = new ChartMouseEvent(getChart(), 
1744                    mouseEvent, entity);
1745            for (int i = listeners.length - 1; i >= 0; i -= 1) {
1746                ((ChartMouseListener) listeners[i]).chartMouseMoved(chartEvent);
1747            }
1748        }
1749    
1750        /* (non-Javadoc)
1751         * @see org.eclipse.swt.events.PaintListener#paintComponent(
1752         * org.eclipse.swt.events.PaintEvent)
1753         */
1754        public void paintControl(PaintEvent e) {
1755            // first determine the size of the chart rendering area...
1756            // TODO workout insets for SWT
1757            Rectangle available = getBounds();
1758            // skip if chart is null
1759            if (this.chart == null) {
1760                this.canvas.drawBackground(e.gc, available.x, available.y, 
1761                        available.width, available.height);
1762                return;
1763            }
1764            SWTGraphics2D sg2 = new SWTGraphics2D(e.gc);
1765    
1766            // work out if scaling is required...
1767            boolean scale = false;
1768            int drawWidth = available.width;
1769            int drawHeight = available.height;
1770            if ( drawWidth == 0.0 || drawHeight == 0.0 ) return;
1771            this.scaleX = 1.0;
1772            this.scaleY = 1.0;
1773            if (drawWidth < this.minimumDrawWidth) {
1774                this.scaleX = (double) drawWidth / this.minimumDrawWidth;
1775                drawWidth = this.minimumDrawWidth;
1776                scale = true;
1777            }
1778            else if (drawWidth > this.maximumDrawWidth) {
1779                this.scaleX = (double) drawWidth / this.maximumDrawWidth;
1780                drawWidth = this.maximumDrawWidth;
1781                scale = true;
1782            }
1783            if (drawHeight < this.minimumDrawHeight) {
1784                this.scaleY = (double) drawHeight / this.minimumDrawHeight;
1785                drawHeight = this.minimumDrawHeight;
1786                scale = true;
1787            }
1788            else if (drawHeight > this.maximumDrawHeight) {
1789                this.scaleY = (double) drawHeight / this.maximumDrawHeight;
1790                drawHeight = this.maximumDrawHeight;
1791                scale = true;
1792            }
1793            // are we using the chart buffer?
1794            if (this.useBuffer) {
1795                //SwtGraphics2D sg2 = new SwtGraphics2D( e.gc );
1796                this.chartBuffer = (org.eclipse.swt.graphics.Image) 
1797                        this.canvas.getData("double-buffer-image");
1798                // do we need to fill the buffer?
1799                if (this.chartBuffer == null
1800                        || this.chartBufferWidth != available.width
1801                        || this.chartBufferHeight != available.height ) {
1802                    this.chartBufferWidth = available.width;
1803                    this.chartBufferHeight = available.height;
1804                    if (this.chartBuffer != null) {
1805                        this.chartBuffer.dispose();
1806                    }
1807                    this.chartBuffer = new org.eclipse.swt.graphics.Image( 
1808                            getDisplay(), this.chartBufferWidth, 
1809                            this.chartBufferHeight);
1810                    this.refreshBuffer = true;
1811                }
1812    
1813                // do we need to redraw the buffer?
1814                if (this.refreshBuffer) {
1815                    // Performs the actual drawing here ...
1816                    GC gci = new GC(this.chartBuffer);
1817                    // anti-aliasing
1818                    if (this.chart.getAntiAlias()) {
1819                        gci.setAntialias(SWT.ON);
1820                    }
1821                    if (this.chart.getTextAntiAlias() 
1822                            == RenderingHints.KEY_TEXT_ANTIALIASING) {
1823                        gci.setTextAntialias(SWT.ON);
1824                    }
1825                    SWTGraphics2D sg2d = new SWTGraphics2D(gci);
1826                    if (scale) {
1827                        sg2d.scale(this.scaleX, this.scaleY);
1828                        this.chart.draw(sg2d, new Rectangle2D.Double(0, 0, 
1829                                drawWidth, drawHeight), getAnchor(), this.info);                            
1830                    } 
1831                    else {
1832                        this.chart.draw(sg2d, new Rectangle2D.Double(0, 0, 
1833                                drawWidth, drawHeight), getAnchor(), this.info);                            
1834                    }
1835                    this.canvas.setData("double-buffer-image", this.chartBuffer);
1836                    sg2d.dispose();
1837                    gci.dispose();
1838                    this.refreshBuffer = false;
1839                }
1840    
1841                // zap the buffer onto the canvas...
1842                sg2.drawImage(this.chartBuffer, 0, 0);
1843            }
1844            // or redrawing the chart every time...
1845            else {
1846                if (this.chart.getAntiAlias()) {
1847                    e.gc.setAntialias(SWT.ON);
1848                }
1849                if (this.chart.getTextAntiAlias() 
1850                        == RenderingHints.KEY_TEXT_ANTIALIASING) {
1851                    e.gc.setTextAntialias(SWT.ON);
1852                }
1853                this.chart.draw(sg2, new Rectangle2D.Double(0, 0, 
1854                        getBounds().width, getBounds().height), getAnchor(), 
1855                        this.info);
1856            }
1857            Rectangle area = getScreenDataArea();
1858            // TODO see if we need to apply some line color and style to the 
1859            // axis traces
1860            if (this.verticalAxisTrace && area.x < this.verticalTraceLineX 
1861                    && area.x + area.width > this.verticalTraceLineX) {
1862                e.gc.drawLine(this.verticalTraceLineX, area.y, 
1863                        this.verticalTraceLineX, area.y + area.height);
1864            }
1865            if (this.horizontalAxisTrace && area.y < this.horizontalTraceLineY 
1866                    && area.y + area.height > this.horizontalTraceLineY) {
1867                e.gc.drawLine(area.x, this.horizontalTraceLineY, 
1868                        area.x + area.width, this.horizontalTraceLineY);
1869            }
1870            this.verticalTraceLineX = 0;
1871            this.horizontalTraceLineY = 0;
1872            if (this.zoomRectangle != null) {
1873                e.gc.drawRectangle(this.zoomRectangle);
1874            }
1875            sg2.dispose();
1876        }
1877    
1878    }