Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: jpicedt/graphic/PECanvas.java


1   /*
2    PECanvas.java - 1999 - jPicEdt 1.3.2, a picture editor for LaTeX.
3    Copyright (C) 1999-2002 Sylvain Reynal
4   
5    Département de Physique
6    Ecole Nationale Supérieure de l'Electronique et de ses Applications (ENSEA)
7    6, avenue du Ponceau
8    F-95014 CERGY CEDEX
9   
10   Tel : +33 130 736 245
11   Fax : +33 130 736 667
12   e-mail : reynal@ensea.fr
13   jPicEdt web page : http://www.jpicedt.org
14    
15   This program is free software; you can redistribute it and/or
16   modify it under the terms of the GNU General Public License
17   as published by the Free Software Foundation; either version 2
18   of the License, or any later version.
19    
20   This program is distributed in the hope that it will be useful,
21   but WITHOUT ANY WARRANTY; without even the implied warranty of
22   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23   GNU General Public License for more details.
24    
25   You should have received a copy of the GNU General Public License
26   along with this program; if not, write to the Free Software
27   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
28   */
29  
30  package jpicedt.graphic;
31  
32  import jpicedt.graphic.*;
33  import jpicedt.graphic.event.*;
34  import jpicedt.graphic.model.*;
35  import jpicedt.graphic.grid.Grid;
36  import jpicedt.graphic.toolkit.*;
37  import jpicedt.graphic.io.parser.*;
38  
39  import java.awt.*;
40  import java.awt.event.*;
41  import java.awt.geom.*;
42  import javax.swing.*;
43  import javax.swing.event.*;
44  import java.util.*;
45  import java.io.*;
46  import java.beans.*;
47  import java.awt.datatransfer.*;
48  import javax.swing.undo.*;
49  
50  /**
51   * This is a JComponent on which graphic elements are drawn. It's has an underlying model (a Drawing) to
52   * represent the content, an EditorKit to manipulate the content, and a View responsible for rendering
53   * the content. EditorKit, Drawing, and View's are pluggable : the EditorKit is responsible for creating
54   * 1) a default Drawing and 2) a ViewFactory that will populate the View tree associated with the Drawing, 
55   * by attaching a View to each element in the model.<p>
56   * Depending on the content type this Component is loaded with, it may plug a new EditorKit on-the-fly 
57   * that's suited for the given content type (e.g. LaTeX, Postscript, SVG-XML, etc...).
58   *  
59   *
60   * <h1>Model to View/View to Model</h1>
61   * <p>
62   * Graphic objects are stored (e.g. in a Drawing) 
63   * in natural coordinates, i.e. LaTeX/Postscript/... coordinates, using e.g. a "1 mm" unitlength : 
64   * that's what we call "model coordinate". Obviously, we've to translate these coordinates to
65   * screen coordinates (e.g. JViewport-coordinate or JPanel-coordinate) before rendering, 
66   * and this is done very simply by using an AffineTransform and adding it to the
67   * current Graphic2D context in the body of the "paintComponent" method. The following picture
68   * sums up the translation process that takes place b/w model- and view-coordinate.
69   * </p>
70   * <p><img src="model2view.png"></p>
71   * <p>The benefits of such an approach is to make it easy for someone willing to develop a parser/formater
72   * to handle objects coordinates with the fewest possible overhead, since objects coordinates 
73   * are "natively" available in natural (i.e. from left to right and from bottom to top) coordinates, and
74   * this is perfectly suited for formatting language like LaTeX, Postscript or SVG-XML. Besides,
75   * this makes sense with the grid ticks marks. 
76   * </p>
77   * <p>Margins are encapsulated in a PageFormat (an inner class) object. Contrary to previous jpicedt releases,
78   * it's no longer necessary, as of jpicedt 1.3.2, to provide formater methods with the ptOrg parameter.
79   * <p>In addition to the standard behaviour inherited from JPanel, PropertyChangeEvent's are triggered when :
80   * <ul>
81   * <li>changing the page format ;
82   * <li>changing the zoom factor ;
83   * <li>changing the editor kit ;
84   * </ul>
85   * </p>
86   * @author Sylvain Reynal
87   * @since PicEdt 1.0
88   */
89  
90  public class PECanvas extends JPanel implements Scrollable {
91  
92    //////////////////////////////////// PUBLIC FIELDS /////////////////////////////////
93  
94    public static final String[] PREDEFINED_ZOOM_STRINGS = {"100%", "200%", "400%", "800%"}; // [pending] use java.text.NumberFormat.getPercentInstance()
95    public static final double[] PREDEFINED_ZOOMS =     { 1.0,   2.0,  4.0,   8.0};
96    public static final double ZOOM_DEFAULT = 1.0;
97  
98    /** key for Properties's zoom value */
99    public static final String KEY_ZOOM = "canvas.zoom";
100   /** key for Properties's content-type value */
101   public static final String KEY_CONTENT_TYPE = "canvas.content-type";
102   /** key for Properties's nb of undoable steps value */
103   public static final String KEY_UNDOABLE_STEPS = "canvas.max-undoable-steps";
104   /** default undoable events to remember */
105   public static final int MAX_UNDOABLE_STEPS_DEFAULT = 100; // same as UndoManager
106   
107 
108   /** property name for drawing change */
109   public static final String DRAWING_CHANGE = "drawing-change";
110 
111   /** property name for editor kit change */
112   public static final String EDITOR_KIT_CHANGE = "editor-kit-change";
113   
114   /** property name for content-type change */
115   public static final String CONTENT_TYPE_CHANGE = "content-type-change";
116 
117   //////////////////////////////////// PROTECTED FIELDS /////////////////////////////////
118   
119   /** the model for this canvas */
120   protected Drawing drawing;
121 
122   /** pageFormat encapsulates board size and margin data */
123   protected PageFormat pageFormat;
124   
125   /** the current content-type for this PECanvas (determines the EditorKit behaviour) */
126   protected ContentType contentType;
127 
128   /** the AffineTransform used to translate from model-coordinates to view-coordinates ;
129    *  gets updated each time either the zoom factor or the page format changes */
130   protected AffineTransform model2ViewTransform;
131 
132   /** the AffineTransform used to translate from mouse-coordinates to model-coordinates ;
133    *  gets updated each time model2ViewTransform changes */
134   protected AffineTransform view2ModelTransform;
135 
136   /** the grid attached to this canvas */
137   protected Grid grid;
138 
139   /** the current editor kit for this component */
140   protected EditorKit kit;
141   
142   /** the UndoableEditSupport delegate for UndoableEditEvent firing */
143   protected UndoableEditSupport undoableEditSupport;
144   
145   /** the UndoManager delegate for undo/redo operation */
146   protected UndoManager undoManager;
147   
148   /** the UndoableEdit in progress */
149   protected StateEdit stateEdit;
150   
151   /** a Map storing RenderingHints to be applied to the graphic context when rendering the drawing */
152   protected RenderingHints renderingHints = new RenderingHints(null);
153 
154 
155   //////////////////////////////////// PRIVATE FIELDS /////////////////////////////////
156 
157   /** the zoom factor ; this determines the zoom factor to be applied to Graphics2D through an AffineTransform */
158   private double zoom;
159   
160   /** the total scale factor, including DPMM and zoom ; updated each time zoom changes */
161   private double scale;
162 
163   /** chained list of PEMouseInputListener's for this component */
164   private PEMouseInputListener mouseInputListener;
165 
166   /** tmp. buffer used by processMouseEvent */
167   private PicPoint peMousePoint = new PicPoint();
168   
169   /** tmp. buffer used by repaintFromModelRect */
170   private double[] tmpCoords = new double[2];
171   private Rectangle repaintRectangle = new Rectangle();
172 
173   
174   
175   //////////////////////////////////// CONSTRUCTORS /////////////////////////////////
176 
177   // [pending] add a default constructor and a constructor using Properties
178   
179   /**
180    * Construct a new PECanvas with the default editor-kit and drawing as content storage.
181    * @param zoom initial zoom factor
182    * @param initial page format (page size + page margins)
183    * @param content-type (e.g. LaTeX, PsTricks,...) ; this will determine the EditorKit for
184    *        editing the Drawing, and indirectly the ViewFactory that produces View's for the drawing,
185    *        (since the ViewFactory is obtained through the currently installed EditorKit) and the
186    *        FormatterFactory used to write the drawing to a writer.
187    *        If null, the default editor-kit/content-type is used.
188    * @author Sylvain Reynal
189    * @since PicEdt 1.0
190    */
191   public PECanvas(double zoom, PageFormat pageFormat, Grid grid, ContentType contentType){
192 
193     if (jpicedt.Log.DEBUG) jpicedt.Log.debug(this,"<init>","start");
194     this.zoom = zoom;
195     setPageFormat(pageFormat); // need zoom to be non-null
196     setZoomFactor(zoom);
197     // create a default drawing, and create View's for it using 
198     // the ViewFactory for the given content-type
199     setContentType(contentType);
200     if (jpicedt.Log.DEBUG) jpicedt.Log.debug(this,"<init>","Installing grid");
201     this.grid = grid;
202     // miscellaneous...
203     // [pending] setBorder(BorderFactory.createEtchedBorder());
204     setBackground(Color.white);
205     // undo/redo
206     undoableEditSupport = new UndoableEditSupport(this); // source for event = PECanvas 
207     undoManager = new UndoManager();
208     setUndoLimit(MAX_UNDOABLE_STEPS_DEFAULT);
209     undoableEditSupport.addUndoableEditListener(undoManager);
210     
211     if (jpicedt.Log.DEBUG) jpicedt.Log.debug(this,"<init>","completed !");
212 
213     // [pending] debug (press F3 to write a comment line for lisibility) 
214     // if (jpicedt.Log.DEBUG)
215     //if (jpicedt.Log.DEBUG)  
216     registerKeyboardAction(new ActionListener(){
217       public void actionPerformed(ActionEvent e){
218         System.out.println("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$");
219     }},KeyStroke.getKeyStroke(KeyEvent.VK_F3,0),WHEN_FOCUSED);
220     
221   }
222 
223   
224   
225   ///////////////////////////////////////////////////////////////////////////////
226   //// PAINT 
227   //////////////////////////////////////////////////////////////////////////////
228   
229   /**
230    * paintComponent(Graphics g) is called by AWTEventDispatchThread via "paint(g)"
231    * it's absolutely necessary to call super.paintComponent(g) so that the background gets properly painted (PECanvas is opaque)
232    */
233   public void paintComponent(Graphics g){
234 
235     super.paintComponent(g); // paint background
236     Graphics2D g2 = (Graphics2D)g;
237     g2.setRenderingHints(renderingHints);
238     g2.transform(model2ViewTransform); // Note : g2.setTransform(model2ViewTransform) -> bug when partial repaint !!! use g2.transform instead
239     Rectangle2D allocation = g2.getClip().getBounds2D();
240 
241     if (jpicedt.Log.DEBUG) { 
242       jpicedt.Log.debug(this,"paintComponent","clip=" + allocation);
243       g2.setPaint(Color.green);
244       g2.draw(allocation);
245     }
246     if (grid != null) grid.paint(g2,allocation,scale);
247     if (drawing != null && drawing.getRootView()!=null) 
248       drawing.getRootView().paint(g2,allocation); // security : if we are just installing a new drawing and its view-tree hasn't been set yet
249     if (kit != null) kit.paint(g2,allocation,scale);
250   }
251 
252   /**
253    * add the given rectangle, given in model-coordinates, to the list of dirty regions.
254    */
255   public void repaintFromModelRect(Rectangle2D rect){
256 
257     if (jpicedt.Log.DEBUG) jpicedt.Log.debug(this,"repaintFromModelRect");
258     if (jpicedt.Log.DEBUG) jpicedt.Log.debugAppendLn(this,"rect2D="+rect);
259     // fetch topleft corner :
260     tmpCoords[0] = rect.getX();
261     tmpCoords[1] = rect.getMaxY();
262     // translate it to view coord system :
263     model2ViewTransform.transform(tmpCoords,0,tmpCoords,0,1);
264     // set topleft corner for destination rectangle
265     repaintRectangle.x = (int)tmpCoords[0];
266     repaintRectangle.y = (int)tmpCoords[1];
267     // fetch double-precision width and height
268     tmpCoords[0] = rect.getWidth();
269     tmpCoords[1] = rect.getHeight();
270     // delta-translate it to view coord system :
271     model2ViewTransform.deltaTransform(tmpCoords,0,tmpCoords,0,1);
272     // set dimension for destination rectangle
273     repaintRectangle.width = (int)tmpCoords[0];
274     repaintRectangle.height = -(int)tmpCoords[1];
275     // add repaintRectangle to dirty region
276     if (jpicedt.Log.DEBUG) jpicedt.Log.debugAppendLn(this,"repaintRect="+repaintRectangle);
277     repaint(repaintRectangle);  
278   }
279 
280   /**
281    * Return the RenderingHints applied to the graphic context when rendering this component
282    */
283   public RenderingHints getRenderingHints(){
284     return renderingHints;
285   }
286 
287 
288 
289 
290   ////////////////////////////////////////////////////
291   //// CONTENT HANDLING
292   ////////////////////////////////////////////////////
293 
294   /**
295    * @return the model, i.e. a Drawing containing only non-selected objects
296    */
297   public Drawing getDrawing(){
298     return drawing;
299   }
300 
301   /**
302    * set the Drawing model for this component. The currently registered EditorKit is 
303    * used to build a viewtree for the drawing. A PropertyChange event (DRAWING_CHANGE) is sent
304    * to each listener.
305    */
306   public void setDrawing(Drawing dr){
307     if (jpicedt.Log.DEBUG) jpicedt.Log.debug(this,"setDrawing","drawing="+dr);
308     Drawing old = this.drawing;
309     this.drawing = dr;
310     this.drawing.setViewTree(getEditorKit().getViewFactory());
311     firePropertyChange(DRAWING_CHANGE, old, this.drawing); // [pending] doesn't seem to be fired !
312   }
313 
314   /**
315    * Fetches the currently installed kit for handling content.
316      * <code>createDefaultEditorKit</code> is called to set up a default, if no kit is currently installed.
317    * @return  the editor kit
318    * @since jPicEdt 1.3.2
319    */
320   public EditorKit getEditorKit(){
321     if (kit == null) {
322       kit = createDefaultEditorKit();
323     }
324     return kit;
325   }
326 
327   /**
328    * Creates the default editor kit (<code>EditorKit</code>) for when
329    * the component is first created.
330    * @return the editor kit
331    */
332   protected EditorKit createDefaultEditorKit() {
333     if (jpicedt.Log.DEBUG) jpicedt.Log.debug(this,"createDefaultEditorKit");
334     return new EditorKit(); 
335   }
336   
337   /**
338    * @return an EditorKit that's suited for the given contentType.
339    * [pending] improve this by providing a registry of editor kits, which plug-ins designer
340    *           can add their own EditorKit to.
341    * @param contentType 
342    * @see JEditorPane#createEditorKitForContentType
343    * @see JEditorPane#getEditorKitForContentType
344    */
345   public static EditorKit createEditorKitForContentType(ContentType contentType){
346     return contentType.createEditorKit(); // [pending] lookup kit registry
347   }
348 
349   /**
350    * Sets the currently installed kit for handling content.  This is the bound property that
351    * establishes the content type of the editor. Any old kit is first deinstalled, then if kit is
352    * non-<code>null</code>, the new kit is installed. <p>
353    * A default drawing is created from it if there was no drawing set in this canvas before, 
354    * otherwise the old drawing is reused : in both cases, <code>setDrawing</code> is called, but
355    * this allows the caller to change the ContentType w/o changing the Drawing if it deems it unnecessary
356    * (otherwise, it may call setDrawing() afterwards).
357    * A <code>PropertyChange</code> event (EDITOR_KIT_CHANGE) is always fired when
358    * <code>setEditorKit</code> is called.
359    * @param kit the desired editor behavior
360    * @see #getEditorKit
361    */
362   public void setEditorKit(EditorKit kit) {
363     if (jpicedt.Log.DEBUG) jpicedt.Log.debug(this,"setEditorKit","kit="+kit);
364     EditorKit old = this.kit;
365     if (old != null) {
366       old.deinstall(this);
367     }
368     this.kit = kit;
369     if (this.kit != null) {
370       this.kit.install(this); // add proper event listeners to PECanvas
371       if (drawing == null) setDrawing(this.kit.createDefaultDrawing());
372       else setDrawing(drawing); 
373     }
374     firePropertyChange(EDITOR_KIT_CHANGE, old, kit);
375   }
376 
377 
378 
379 
380   ///////////////////////////////////
381   ///// CONTENT TYPE
382   ///////////////////////////////////
383   /**
384    * @return the current content-type
385    */
386   public ContentType getContentType(){
387     return contentType;
388   }
389 
390   /**
391    * change the current content-type
392    */
393   public void setContentType(ContentType newContentType){
394     ContentType old = this.contentType;
395     this.contentType = newContentType;
396     // update editor kit
397     if (this.contentType == null) this.contentType = new DefaultContentType(); //setEditorKit(createDefaultEditorKit());
398     setEditorKit(createEditorKitForContentType(this.contentType)); // update view-tree as well
399     firePropertyChange(CONTENT_TYPE_CHANGE, old, this.contentType);
400   }
401 
402 
403   ///////////////////////////////////
404   ///// DRAWING BOARD SIZE
405   ///////////////////////////////////
406 
407   /**
408    * Set the size of the drawing board. Length are given in mm (this should approximately
409    * represent true mm on the screen, however this might slightly depend on the underlying platform).
410    * <br>
411    * This in turn sets the preferred size of the component. .
412    * @since jPicEdt 1.3.2
413    * @author Sylvain Reynal
414    */
415   public void setPageFormat(PageFormat pageFormat){
416 
417     if (jpicedt.Log.DEBUG) jpicedt.Log.debug(this,"setPageFormat","page-format="+pageFormat);
418     PageFormat oldFormat = this.pageFormat;
419     this.pageFormat = pageFormat;
420     model2ViewTransform = pageFormat.getModel2ViewTransform(zoom);
421     view2ModelTransform = pageFormat.getView2ModelTransform(zoom);
422     setPreferredSize(pageFormat.getSizePx(zoom));
423     //firePropertyChange(PAGE_FORMAT_CHANGE, oldFormat, this.pageFormat); //[pending] bug : not fired ! 
424     // give a chance
425     // to listeners to update their layout (e.g. ScrollPane)
426     invalidate();
427     fireZoomUpdate(this.zoom, this.zoom, null); // force e.g. a scrollpane to update rulers
428     //validate();
429     repaint();
430   }
431 
432   /**
433      * @return the page format for this drawing board
434      * @author Sylvain Reynal
435      * @since jPicEdt 1.3.2
436      */
437   public PageFormat getPageFormat(){
438     return pageFormat;
439   }
440 
441   /**
442    * @return the pixel-coordinates of the (0,0) model origin when zoom = 1.0
443    */
444   public PicPoint getSheetOrigin(){
445     return pageFormat.getOrgPx(1.0);
446   }
447 
448   /**
449    * @return the grid attached to this canvas
450    */
451   public Grid getGrid() {
452     return grid;
453   }
454 
455   ///////////////////////////////////
456   ///// Model <-> View
457   ///////////////////////////////////
458   
459 
460   /**
461    * @return an AffineTransform that represents the maping b/w the model-coordinate system
462    *         and the pixel coordinate system. Guaranteed not to change over time.
463    * @author Sylvain Reynal
464    * @since jPicEdt 1.3.2
465    */
466   public AffineTransform getModelToViewTransform(){
467     return (AffineTransform)model2ViewTransform.clone();
468   }
469   
470   /**
471    * @return an AffineTransform that represents the maping b/w the pixel-coordinate system
472    *         and the model-coordinate system. Guaranteed not to change over time.
473    * @author Sylvain Reynal
474    * @since jPicEdt 1.3.2
475    */
476   public AffineTransform getViewToModelTransform(){
477     return (AffineTransform)view2ModelTransform.clone();
478   }
479 
480   /**
481    * Converts a point from the model-coordinate system to the pixel-coordinate system.
482    * @param src the source point in model-coordinate
483    * @param dst the destination point ; if null, a new point is allocated, and returned for convenience.
484    * @return the result (same as dst if non-null)
485    * @author Sylvain Reynal
486    * @since jPicEdt 1.3.2
487    */
488   public PicPoint modelToView(PicPoint src, PicPoint dst){
489     if (dst==null) dst = new PicPoint();
490     // fetch topleft corner :
491     tmpCoords[0] = src.x;
492     tmpCoords[1] = src.y;
493     // translate it to view coord system :
494     model2ViewTransform.transform(tmpCoords,0,tmpCoords,0,1); // transform 1 point
495     dst.x = tmpCoords[0];
496     dst.y = tmpCoords[1];
497     return dst;
498   }
499     
500   /**
501    * Converts a point from the pixel-coordinate system to the model-coordinate system.
502    * @param src the source point in pixel-coordinate
503    * @param dst the destination point ; if null, a new point is allocated, and returned for convenience.
504    * @return the result (same as dst if non-null)
505    * @author Sylvain Reynal
506    * @since jPicEdt 1.3.2
507    */
508   public PicPoint view2Model(PicPoint src, PicPoint dst){
509     if (dst==null) dst = new PicPoint();
510     // fetch topleft corner :
511     tmpCoords[0] = src.x;
512     tmpCoords[1] = src.y;
513     // translate it to view coord system :
514     view2ModelTransform.transform(tmpCoords,0,tmpCoords,0,1); // transform 1 point
515     dst.x = tmpCoords[0];
516     dst.y = tmpCoords[1];
517     return dst;
518   }
519 
520   /**
521    * Converts a Shape from the model-coordinate system to the pixel-coordinate system.
522    * @return a new Shape corresponding to the given Shape once transformed to the pixel-coordinate system.
523    * @param src a Shape in the model-coordinate system
524    * @author Sylvain Reynal
525    * @since jPicEdt 1.3.2
526    */
527   public Shape modelToView(Shape src){
528     return model2ViewTransform.createTransformedShape(src);
529   }
530 
531   /**
532    * Converts a Shape from the pixel-coordinate system to the model-coordinate system.
533    * @return a new Shape corresponding to the given Shape once transformed to the model-coordinate system.
534    * @param src a Shape in the pixel-coordinate system
535    * @author Sylvain Reynal
536    * @since jPicEdt 1.3.2
537    */
538   public Shape viewToModel(Shape src){
539     return view2ModelTransform.createTransformedShape(src);
540   }
541   
542   ///////////////////////////////////
543   ///// I/O
544   ///////////////////////////////////
545 
546   /**
547    * read drawing content from a reader and erase old one. Listener's interested in
548    * DrawingEvent's should register their listener anew (this can be done systematically by
549    * registering a PropertyCHangeListener to this canvas, and waiting for DRAWING_CHANGE events).<br> 
550    * @param reader the reader to read content from
551    */
552    public void read(Reader reader, Parser parser) throws jpicedt.graphic.io.parser.ParserException {
553     getEditorKit().getSelectionHandler().unSelectAll(); // don't fire selection event
554     //jpicedt.graphic.io.parser.LaTeXParser parser = new jpicedt.graphic.io.parser.LaTeXParser(); // [pending] adapt parser to the type of content this EditorKit handles
555     Drawing parsed = parser.parse(reader); // takes some time...
556     setDrawing(parsed);
557     // [pending] update PageFormat and ContentType
558     repaint();
559   }
560  
561   /**
562    * insert content from a reader into the current drawing<br>
563    * @param reader the reader to insert content from
564    */
565    public void insert(Reader reader, Parser parser) throws jpicedt.graphic.io.parser.ParserException {
566 
567     //jpicedt.graphic.io.parser.LaTeXParser latexParser = new jpicedt.graphic.io.parser.LaTeXParser(); // adapt parser to the type of content this EditorKit handles
568     Drawing parsed = parser.parse(reader);
569     getEditorKit().getSelectionHandler().unSelectAll(); // don't fire selection event
570     // add parsed content to the current drawing
571     for (Iterator it=parsed.getRootElement().children(); it.hasNext(); ){
572       Element o = (Element)it.next();
573       o = (Element)o.clone();
574       drawing.addElement(o);
575       getEditorKit().getSelectionHandler().addToSelection(o);
576     }
577     fireSelectionUpdate(kit.getSelectionHandler().asArray(), SelectionEvent.EventType.SELECT);
578     // add more non-parsed command to current drawing, if applicable :
579     if (parsed.getNotparsedCommands().length()==0) return;
580     String s = drawing.getNotparsedCommands();
581     if (s==null || s.length()==0) drawing.setNotparsedCommands(s);
582     else {
583       s += "\n";
584       s += parsed.getNotparsedCommands();
585       drawing.setNotparsedCommands(s);
586     }
587   }
588 
589   /**
590      * Write drawing content to the given stream
591      * @param writer The writer to write to
592    * @param writeSelectionOnly if true, only write selection content
593      * @exception IOException on any I/O error
594    * @since jPicEdt 1.3.2
595      */
596    public void write(Writer writer, boolean writeSelectionOnly) throws IOException {
597 
598     String buffer;
599     if (writeSelectionOnly){
600       Drawing dr = new Drawing(getEditorKit().getSelectionHandler().asCollection()); // deep copy
601       // translate fragment to (0,0)
602       Rectangle2D bb = dr.getBoundingBox();
603       dr.getRootElement().translate(- bb.getX(), -bb.getY());
604       buffer = getEditorKit().getFormatterFactory().createFormatter(dr,null).format();
605     }
606     else { 
607       buffer = getEditorKit().getFormatterFactory().createFormatter(drawing,null).format();
608     } 
609     writer.write(buffer);
610   }
611 
612 
613 
614 
615 
616 
617   ///////////////////////////////////
618   ///// ZOOM
619   ///////////////////////////////////
620 
621   /**
622    * Convenience call to setZoomFactor(zoom,null)
623    */
624   public void setZoomFactor(double zoom){
625     setZoomFactor(zoom,null);
626   }
627   
628   /**
629    * sets the current zoom factor to the given double, 
630    * then updates various properties (model <-> view transforms, dimension, preferredSize...), finally,
631    * sources a ZoomEvent to give a chance to receiver to update their state accordingly (this may
632    * be used e.g. by a parent scrollpane to update its view port location, or by a GUI widget
633    * to reflect the new zoom value).
634    * @param zoom the new zoom factor
635    * @param ptClick this only makes sense if the parent of this component is aka ScrollPane ; 
636    *        <br> Coordinates for this point are in the model-coordinate system.
637    */
638   public void setZoomFactor(double zoom, PicPoint ptClick){
639 
640     if (jpicedt.Log.DEBUG) jpicedt.Log.debug(this,"setZoomFactor","zoom="+zoom);
641     double oldZoom = this.zoom; // store old value for further use by zoom event 
642     this.zoom = zoom; // [pending] test if zoom really changed !
643     
644     // update global variables according to new zoom value :
645      model2ViewTransform = pageFormat.getModel2ViewTransform(zoom);
646      view2ModelTransform = pageFormat.getView2ModelTransform(zoom);
647      scale = model2ViewTransform.getScaleX(); // assumption : scaleX = scaleY !
648     
649     // update preferred size according to new zoom :
650     setPreferredSize(pageFormat.getSizePx(zoom));
651     invalidate(); // JDK's documentation says : "revalidate()"
652     // give a change to a scrollpane to update the view location in the viewport and ruler sizes :
653     fireZoomUpdate(oldZoom, this.zoom, ptClick);
654     repaint();
655   }
656 
657   /**
658    * @return the current zoom factor
659    */
660   public double getZoomFactor(){
661     return zoom;
662   }
663 
664   /**
665    * @return the current scale factor between model- and view-coordinates, as given by the current
666    *         model2ViewTransform. This is usually the product of the current zoom factor, and the 
667    *         DotPerMilliMeter screen factor.
668    */
669   public double getScaleFactor(){
670     return scale;
671   }
672 
673   /**
674    * Notify all listeners that have registered interest for notification on this event type.
675    * @param oldZoom previous zoom value
676    * @param newZoom new zoom value
677    * @param ptClick the point (in model-coordinates) that is expected to be at the center of the view-port ;
678    *        can be null
679    */
680   protected void fireZoomUpdate(double oldZoom, double newZoom, PicPoint ptClick){
681     Object[] listeners = listenerList.getListenerList();
682     ZoomEvent e = null;
683     for (int i = listeners.length-2; i>=0; i-=2) {
684       // lazily create the event :
685       if (e==null) e = new ZoomEvent(this,oldZoom,newZoom,ptClick);
686       if (listeners[i]==ZoomListener.class) {
687         ((ZoomListener)listeners[i+1]).zoomUpdate(e);
688       }
689     }
690   }
691 
692   /**
693    * adds a ZoomListener to the Canvas
694    */
695   public void addZoomListener(ZoomListener l){
696     listenerList.add(ZoomListener.class, l);
697   }
698   
699   /**
700    * removes a ZoomListener from the Canvas
701    */
702   public void removeZoomListener(ZoomListener l){
703     listenerList.remove(ZoomListener.class, l);
704   }
705   
706   /**
707    * utilities to retrieve the index of the given zoom in PREDEFINED_ZOOMS ; this may be used by GUI widgets,
708    * e.g. JComboBox,...
709    * @return index of the given zoom in array "PREDEFINED_ZOOMS" ; returns -1 if not found.
710    */
711   public static int getZoomIndex(double zoom){
712     for(int i=0; i<PREDEFINED_ZOOMS.length; i++){
713       if (PREDEFINED_ZOOMS[i] == zoom) return i;
714     }
715     return -1; // not found
716   }
717   
718 
719   /////////////////////////////
720   ///// UNDO/REDO
721   /////////////////////////////
722 
723   /**
724    * set the number of undoable events to remember 
725    */
726   public void setUndoLimit(int limit){
727     undoManager.setLimit(limit);
728   }
729   
730   /**
731    * Undo last change [underway]
732    * @since PicEdt 1.1.3
733    */
734   public void undo() throws CannotUndoException {
735     selectAll(false);
736     undoManager.undo();
737   }
738 
739   /**
740    * Redo last change [underway]
741    * @since PicEdt 1.1.3
742    */
743   public void redo() throws CannotRedoException {
744     selectAll(false);
745     undoManager.redo();
746   }
747 
748   /**
749    * Register an UndoableEditListener for the Drawing hosted by this canvas.
750    */
751   public void addUndoableEditListener(UndoableEditListener l){
752     undoableEditSupport.addUndoableEditListener(l);
753   }
754 
755   /**
756    * Register an UndoableEditListener for the Drawing hosted by this canvas.
757    */
758   public void removeUndoableEditListener(UndoableEditListener l){
759     undoableEditSupport.removeUndoableEditListener(l);
760   }
761 
762   /**
763    * Create a new UndoableEdit that holds the current state of the Drawing. 
764    */
765   public void beginUndoableUpdate(String presentationName){
766     if (stateEdit != null) {
767       endUndoableUpdate(); // if end() was not called !
768     }
769     //stateEdit = new StateEdit(getDrawing(),presentationName);
770     stateEdit = new PEStateEdit(getDrawing(),presentationName);
771   }
772   
773   /**
774    * Ends the current UndoableEdit and fire an event to registered listeners.
775    */
776   public void endUndoableUpdate(){
777     if (stateEdit==null) {
778       return; // refuse two calls to end()
779     }
780     stateEdit.end();
781     undoableEditSupport.postEdit(stateEdit);
782     stateEdit = null; // flag for begin() to know we've ended !
783   }
784   
785   /**
786    * @return true if a "redo" operation would be successfull
787    */
788   public boolean canRedo(){
789     return undoManager.canRedo();
790   }
791 
792   /**
793    * @return true if a "undo" operation would be successfull
794    */
795   public boolean canUndo(){
796     return undoManager.canUndo();
797   }
798 
799   /**
800    * @return the presentation name of the next edit that can be redone
801    */
802   public String getRedoPresentationName(){
803     if (!canRedo()) return "";
804     return undoManager.getRedoPresentationName();
805   }
806 
807   /**
808    * @return the presentation name of the last edit that can be undone
809    */
810   public String getUndoPresentationName(){
811     if (!canUndo()) return "";
812     return undoManager.getUndoPresentationName();
813   }
814 
815   /** overriden so as to display our own undo- and redo- presentation names */
816   private class PEStateEdit extends StateEdit {
817     
818     PEStateEdit(StateEditable anObject, String name){
819       super(anObject, name);
820     }
821     
822       public String getUndoPresentationName() {
823       return getPresentationName();
824     }
825 
826       public String getRedoPresentationName() {
827       return getPresentationName();
828     }
829   }
830 
831   //////////////////////////////////
832   ////// SELECTION MANAGEMENT
833   //////////////////////////////////
834 
835   /**
836    * @return an Iterator over selected graphic elements
837    */
838   public Iterator selection(){ // [pending] change to  "getSelection"
839     return getEditorKit().getSelectionHandler().elements();
840   }
841   
842   /**
843    * @return the number of selected elements in the current selection
844    */
845   public int getSelectionSize(){ 
846     return getEditorKit().getSelectionHandler().size();
847   }
848   
849   /**
850    * @return whether the given element is selected or not
851    */
852   public boolean isSelected(Element e){
853     return getEditorKit().getSelectionHandler().isSelected(e, true);
854   }
855   
856   /**
857    * select or unselect every object in this drawing
858    * @param state if true, selectAll, otherwise unselect all.
859    */
860   public void selectAll(boolean state){
861     if (state == false){ // unselect all
862       if (getEditorKit().getSelectionHandler().size()==0) return; // already empty
863       Element[] deselected = getEditorKit().getSelectionHandler().asArray();
864       getEditorKit().getSelectionHandler().unSelectAll();
865       fireSelectionUpdate(deselected, SelectionEvent.EventType.UNSELECT);
866     }
867     else { // select all
868       kit.getSelectionHandler().selectAll(drawing); 
869       fireSelectionUpdate(kit.getSelectionHandler().asArray(), SelectionEvent.EventType.SELECT);
870     }
871   }
872 
873   /**
874    * select the elements in the given collection (if they belong to the drawing)
875    * @param incremental if true, add to the existing selection ; replace otherwise.
876    */
877   public void select(Collection c, boolean incremental){
878     if (incremental == false) getEditorKit().getSelectionHandler().unSelectAll();
879     ArrayList selected = new ArrayList();
880     for (Iterator it = c.iterator(); it.hasNext();){
881       Element obj = (Element)it.next();
882       if (isSelected(obj)) {
883         if (jpicedt.Log.DEBUG) jpicedt.Log.debug(this,"select","Already selected:elem="+obj);
884       }
885       else {
886         getEditorKit().getSelectionHandler().addToSelection(obj);
887         selected.add(obj);
888       }
889     }
890     Element[] selectedArray = (Element[])selected.toArray(new Element[0]); 
891     fireSelectionUpdate(selectedArray, SelectionEvent.EventType.SELECT);
892   }
893   
894   /**
895    * select the given object
896    * @param incremental if true, add to the existing selection ; replace otherwise.
897    */
898   public void select(Element obj, boolean incremental){
899     if (incremental == true) {
900       if (isSelected(obj)) {
901         if (jpicedt.Log.DEBUG) jpicedt.Log.debug(this,"select","Already selected:elem="+obj);
902         return; // already selected
903       }
904       getEditorKit().getSelectionHandler().addToSelection(obj);
905       fireSelectionUpdate(obj, SelectionEvent.EventType.SELECT);
906     }
907     else {
908       getEditorKit().getSelectionHandler().replaceSelection(obj);
909       fireSelectionUpdate(obj, SelectionEvent.EventType.SELECT); // don't fire "unselect"
910     }
911   }
912 
913   /**
914    * unselect the given object
915    */
916   public void unSelect(Element obj){
917     if (!isSelected(obj)) {
918       if (jpicedt.Log.DEBUG) jpicedt.Log.debug(this,"unSelect","Already unselected:elem="+obj);
919       return; // already unselected
920     }
921     getEditorKit().getSelectionHandler().unSelect(obj);
922     fireSelectionUpdate(obj, SelectionEvent.EventType.UNSELECT);
923   }
924 
925   /**
926    * remove all selected objects from the drawing
927    */
928   public void deleteSelection(){
929     getEditorKit().getSelectionHandler().delete(drawing);
930   }
931 
932   /**
933    * Add the content of the given ClipBoard to the current drawing, then select it.
934    * If only DataFlavor.stringFlavor is provided by the Transferable, 
935    * we try to parse the string and insert the parsed content. Otherwise, it's taken for granted
936    * that the ClipBoard content support the jpicedt.graphic.toolkit.TransferableGraphic.JPICEDT_DATA_FLAVOR
937    * data flavor.
938    * @param translate if true, translate the pasted content by a grid step so that it doesn't hide old one
939    */
940   public void paste(Clipboard clipbrd, boolean translate) throws ParserException, IOException, UnsupportedFlavorException {
941     getEditorKit().getSelectionHandler().unSelectAll(); // don't fire selection event
942     Transferable transferable = clipbrd.getContents(this); // according to doc. requestor is not used.
943     if (transferable==null) return; // clipboard is empty !
944     
945     // first check if it's a local clipboard supporting JPICEDT_DATA_FLAVOR :
946     if (transferable.isDataFlavorSupported(TransferableGraphic.JPICEDT_DATA_FLAVOR)){
947       Element[] content = (Element[])transferable.getTransferData(TransferableGraphic.JPICEDT_DATA_FLAVOR);
948       for (int i=0; i<content.length; i++){
949         // translate clipboard so that new content doesn't hide old one 
950         // Warning : this translates the source !!!
951         if (translate) content[i].translate(grid.getSnapStep(),-grid.getSnapStep());
952         // add (a copy of the) content to the current drawing
953         Element element = (Element)content[i].clone();
954         drawing.addElement(element);
955         getEditorKit().getSelectionHandler().addToSelection(element);
956       }
957       fireSelectionUpdate(kit.getSelectionHandler().asArray(), SelectionEvent.EventType.SELECT);
958     }
959     
960     // otherwise that may be the System Clipboard : only text is supported :
961     else {
962       StringReader reader = new StringReader(jpicedt.MiscUtilities.getClipboardStringContent(clipbrd));
963       Parser parser = jpicedt.MiscUtilities.createParser();
964       insert(reader,parser);
965     }
966     
967     // else does nothing
968   }
969 
970   /**
971    * Add the content of the System's ClipBoard to the current drawing, then select it.
972    * More specifically, we try to parse the string and insert the parsed content. 
973    * @param translate if true, translate the pasted content by a grid step so that it doesn't hide old one
974    */
975   public void paste(boolean translate) throws ParserException, IOException, UnsupportedFlavorException {
976     paste(Toolkit.getDefaultToolkit().getSystemClipboard(),translate);
977   }
978   
979   /**
980    * Copy the content of the current selection (through a GraphicTransferable) to the
981    * System's clipboard (after a formatting to text), AND to the given clipboard if non-null
982    * (the latter can be a local clipboard supporting more data-flavors than the system clipboard)
983    * @param clipbrd the target clipboard ; can be null, in which case only the System clipboard
984    *        is modified.
985    */
986   public void copy(Clipboard clipbrd) {
987     // if selection buffer is empty, don't modify clipboard's content
988     if (getSelectionSize() == 0) return;
989     // create array of selected Elements for GraphicTransferable
990     Element[] elements = new Element[getSelectionSize()];
991     int counter = 0;
992     for (Iterator it = selection(); it.hasNext();){
993       Element e = (Element)it.next();
994       elements[counter++] = e;
995     }
996     // create formatted string using "write" with selectionOnly = true :
997     StringWriter writer = new StringWriter();
998     try {
999       write(writer,true); 
1000      // create GraphicTransferable :
1001      TransferableGraphic transferable = new TransferableGraphic(elements,writer.toString());
1002      writer.close();
1003      if (clipbrd!=null) clipbrd.setContents(transferable,transferable);
1004      Clipboard systemClipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
1005      StringSelection ss = new StringSelection(writer.toString());
1006      systemClipboard.setContents(ss,ss);
1007    } catch (IOException ioEx){ioEx.printStackTrace();}
1008
1009  }
1010
1011  /**
1012   * Copy the content of the current selection to the
1013   * System's clipboard (after a formatting to text)
1014   */
1015  public void copy() {
1016    copy(null);
1017  }
1018
1019  /**
1020   * Cut the content of the current selection (through a GraphicTransferable) to the 
1021   * System clipboard, AND to the given ClipBoard if non-null.
1022   * @param clipbrd the target clipboard ; can be null, in which case only the System clipboard
1023   *        is modified.
1024   */
1025  public void cut(Clipboard clipbrd) {
1026    // if selection buffer is empty, don't modify clipboard's content
1027    if (getSelectionSize() == 0) return;
1028    copy(clipbrd);
1029    deleteSelection();
1030  }
1031
1032  /**
1033   * Cut the content of the current selection to the System clipboard, after formatting to text.
1034   */
1035  public void cut() {
1036    cut(null);
1037  }
1038  /**
1039   * group all selected objects into a new PicGroup and add it to the drawing.
1040   * @since jPicEdt 1.2.a
1041   */
1042  public void groupSelection(){
1043    PicGroup group = new PicGroup(getEditorKit().getSelectionHandler().asCollection());
1044    getEditorKit().getSelectionHandler().delete(drawing); // delete old selected elements
1045    drawing.addElement(group); 
1046    getEditorKit().getSelectionHandler().addToSelection(group); // select group
1047    fireSelectionUpdate(group, SelectionEvent.EventType.SELECT);
1048  }
1049
1050  /**
1051   * fetch all Element's belonging to the given PicGroup and add them to 
1052   * its parent, removing the given PicGroup from its parent afterward.
1053   */
1054  public void unGroup(PicGroup g){
1055    getEditorKit().getSelectionHandler().unSelectAll(); // otherwise the selectionHandler just gets confused
1056    BranchElement p = g.getParent();
1057    p.removeChild(g); 
1058    int max = g.getChildCount();
1059    for (int i=0; i<max; i++){
1060      Element o = (Element)g.getChildAt(0); // always fetch at position 0 
1061      p.addChild(o); // hence removed from group (was former parent)
1062      select(o,true); // incremental
1063    }
1064  }
1065  
1066  /**
1067   * Notify all listeners that have registered interest for notification on this event type.
1068   * @param element the Element that was (un)selected
1069   * @param type the event type
1070   */
1071  protected void fireSelectionUpdate(Element element, SelectionEvent.EventType type){
1072    Object[] listeners = listenerList.getListenerList();
1073    SelectionEvent e = null;
1074    for (int i = listeners.length-2; i>=0; i-=2) {
1075      // lazily create the event :
1076      if (e==null) e = new SelectionEvent(this,element,type);
1077      if (listeners[i]==SelectionListener.class) {
1078        ((SelectionListener)listeners[i+1]).selectionUpdate(e);
1079      }
1080    }
1081  }
1082  
1083  /**
1084   * Notify all listeners that have registered interest for notification on this event type.
1085   * @param elements the Element's that were (un)selected
1086   * @param type the event type
1087   */
1088  protected void fireSelectionUpdate(Element[] elements, SelectionEvent.EventType type){
1089    Object[] listeners = listenerList.getListenerList();
1090    SelectionEvent e = null;
1091    for (int i = listeners.length-2; i>=0; i-=2) {
1092      // lazily create the event :
1093      if (e==null) e = new SelectionEvent(this,elements,type);
1094      if (listeners[i]==SelectionListener.class) {
1095        ((SelectionListener)listeners[i+1]).selectionUpdate(e);
1096      }
1097    }
1098  }
1099
1100  /**
1101   * adds a SelectionListener to the Canvas
1102   */
1103  public void addSelectionListener(SelectionListener l){
1104    listenerList.add(SelectionListener.class, l);
1105  }
1106  
1107  /**
1108   * removes a SelectionListener from the Canvas
1109   */
1110  public void removeSelectionListener(SelectionListener l){
1111    listenerList.remove(SelectionListener.class, l);
1112  }
1113  
1114
1115  
1116  
1117  
1118  
1119  
1120  
1121  ////////////////////////////////////
1122  ///// SCROLLABLE INTERFACE and rel.
1123  ////////////////////////////////////
1124
1125  /**
1126   * @return the drawing board size (aka ScrollPane's View size, as opposed to ViewPort's size)
1127   */
1128  public Dimension getPreferredScrollableViewportSize() {
1129
1130    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
1131    Dimension componentSize = getPageFormat().getSizePx(zoom);
1132    // return half screen size at maximum
1133    return(new Dimension((int)Math.min(componentSize.width,screenSize.width/2),
1134                         (int)Math.min(componentSize.height,screenSize.height/2)));
1135  }
1136
1137  /**
1138   * @return a grid step
1139   */
1140  public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
1141
1142    return (int)(grid.getSnapStep() * zoom);
1143  }
1144
1145  /**
1146   * @return the viewport size minus a grid step
1147   */
1148  public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
1149
1150    if (orientation == SwingConstants.HORIZONTAL)
1151      return (int)(visibleRect.width - grid.getSnapStep() * zoom);
1152    else
1153      return (int)(visibleRect.height - grid.getSnapStep() * zoom);
1154  }
1155
1156  /**
1157   * @return false for this implementation
1158   */
1159  public boolean getScrollableTracksViewportWidth() {
1160    return false;
1161  }
1162
1163  /**
1164   * @return false for this implementation
1165   */
1166  public boolean getScrollableTracksViewportHeight() {
1167    return false;
1168  }
1169
1170
1171
1172
1173
1174
1175
1176
1177  //////////////////////////
1178  //// MISC
1179  //////////////////////////
1180
1181
1182  /**
1183   * Overriden from JComponent
1184   * Signals that this component can receive focus (useful for handling keyevents)
1185   */
1186  public boolean isRequestFocusEnabled(){
1187    return true;
1188  }
1189
1190
1191
1192
1193
1194  //////////////////////////
1195  //// PEMOUSE LISTENER
1196  //////////////////////////
1197
1198  /**
1199   * Adds the specified mouse listener to receive mouse events from this component.
1200   * If l is null, no exception is thrown and no action is performed.
1201   * @param l the mouse listener.
1202   */
1203  public synchronized void addPEMouseInputListener(PEMouseInputListener l) {
1204    if (l == null) {
1205      return;
1206    }
1207    mouseInputListener = PEEventMulticaster.add(mouseInputListener,l);
1208    this.enableEvents(AWTEvent.MOUSE_EVENT_MASK);
1209    this.enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK);    
1210  }
1211
1212  /**
1213   * Removes the specified mouse listener so that it no longer
1214   * receives mouse events from this component. This method performs 
1215   * no function, nor does it throw an exception, if the listener 
1216   * specified by the argument was not previously added to this component.
1217   * If l is null, no exception is thrown and no action is performed.
1218   *
1219   * @param l the mouse listener.
1220   */
1221  public synchronized void removePEMouseInputListener(PEMouseInputListener l) {
1222    if (l == null) {
1223      return;
1224    }
1225    mouseInputListener = PEEventMulticaster.remove(mouseInputListener, l);
1226  }
1227
1228  /**
1229   * Processes mouse events occurring on this component by
1230   * dispatching them to any registered
1231   * <code>PEMouseListener</code> objects.
1232   * @param e the mouse event.
1233   */
1234  protected void processMouseEvent(MouseEvent e) {
1235    super.processMouseEvent(e); // process "standar" mouse-events BEFORE (see pb with JPopupMenu for instance)
1236    if (mouseInputListener != null) {
1237      view2ModelTransform.transform(e.getPoint(),peMousePoint);
1238      PEMouseEvent me = new PEMouseEvent(e, this, peMousePoint);
1239      switch(e.getID()) {
1240      case MouseEvent.MOUSE_PRESSED:
1241        mouseInputListener.mousePressed(me);
1242        break;
1243      case MouseEvent.MOUSE_RELEASED:
1244        mouseInputListener.mouseReleased(me);
1245        break;
1246      case MouseEvent.MOUSE_CLICKED:
1247        mouseInputListener.mouseClicked(me);
1248        break;
1249      case MouseEvent.MOUSE_EXITED:
1250        mouseInputListener.mouseExited(me);
1251        break;
1252      case MouseEvent.MOUSE_ENTERED:
1253        mouseInputListener.mouseEntered(me);
1254        break;
1255      }
1256    }
1257    
1258  }
1259
1260  /**
1261   * Processes mouse motion events occurring on this component by
1262   * dispatching them to any registered
1263   * <code>PEMouseInputListener</code> objects.
1264   * @param e the mouse motion event.
1265   */
1266  protected void processMouseMotionEvent(MouseEvent e) {
1267    super.processMouseMotionEvent(e); // process "standar" mouse-events
1268    if (mouseInputListener != null) {
1269      view2ModelTransform.transform(e.getPoint(),peMousePoint);
1270      PEMouseEvent me = new PEMouseEvent(e, this, peMousePoint);
1271      switch(e.getID()) {
1272      case MouseEvent.MOUSE_MOVED:
1273        mouseInputListener.mouseMoved(me);
1274        break;
1275      case MouseEvent.MOUSE_DRAGGED:
1276        mouseInputListener.mouseDragged(me);
1277        break;
1278      }
1279    }
1280    
1281  }
1282} // class PECanvas
1283
1284