View Javadoc

1   // Copyright 2003-2005, FreeHEP.
2   package hep.wired.heprep.graphicspanel;
3   
4   import java.awt.*;
5   import java.awt.event.*;
6   import java.awt.geom.*;
7   import java.util.*;
8   import java.util.List;
9   import javax.swing.*;
10  import javax.swing.event.*;
11  import javax.swing.undo.*;
12  
13  import org.jdom.Element;
14  
15  import org.freehep.application.Application;
16  import org.freehep.application.studio.Studio;
17  import org.freehep.graphics2d.BufferedPanel;
18  import org.freehep.graphics2d.VectorGraphics;
19  import org.freehep.graphics2d.PixelGraphics2D;
20  import org.freehep.xml.io.XMLIO;
21  import org.freehep.xml.io.XMLIOManager;
22  
23  import hep.graphics.heprep.HepRep;
24  import hep.graphics.heprep.HepRepAttValue;
25  import hep.graphics.heprep.HepRepInstanceTree;
26  import hep.graphics.heprep.HepRepInstance;
27  import hep.graphics.heprep.HepRepIterator;
28  import hep.graphics.heprep.HepRepType;
29  import hep.graphics.heprep.util.HepRepUtil;
30  
31  import hep.wired.feature.HasBoundingBox;
32  import hep.wired.feature.Resetable;
33  import hep.wired.feature.Rotateable3D;
34  import hep.wired.feature.Scaleable;
35  import hep.wired.feature.Scaleable2D;
36  import hep.wired.feature.Scaleable3D;
37  import hep.wired.feature.Translateable;
38  import hep.wired.feature.Translateable2D;
39  import hep.wired.feature.Translateable3D;
40  import hep.wired.viewport.RectangularViewPort;
41  import hep.wired.services.Feature;
42  import hep.wired.services.GraphicsPanel;
43  import hep.wired.services.RecordPlot;
44  import hep.wired.services.ViewPort;
45  import hep.wired.util.WiredRegistry;
46  
47  import hep.wired.heprep.edit.SetProjection;
48  import hep.wired.heprep.feature.HasNearestPoint;
49  import hep.wired.heprep.interaction.HepRepInfoPanel;
50  import hep.wired.heprep.projection.AbstractProjection;
51  import hep.wired.heprep.projection.ParallelProjection;
52  import hep.wired.heprep.plugin.WiredPlugin;
53  import hep.wired.heprep.representation.DrawAsCache;
54  import hep.wired.heprep.representation.DrawAsPoint;
55  import hep.wired.heprep.services.DrawAs;
56  import hep.wired.heprep.services.GraphicsMode;
57  import hep.wired.heprep.services.Projection;
58  import hep.wired.heprep.tree.TreePanelModel;
59  import hep.wired.heprep.util.BoundingBoxGraphics2D;
60  import hep.wired.heprep.util.NearestPointPathConstructor;
61  import hep.wired.heprep.util.NullGraphics2D;
62  import hep.wired.heprep.util.PathGraphics2D;
63  
64  /***
65   * Panel to display HepReps.
66   *
67   * @author Mark Donszelmann
68   * @version $Id: HepRepPanel.java 2136 2005-07-30 00:25:21Z duns $
69   */
70  
71  public class HepRepPanel extends BufferedPanel 
72                           implements GraphicsPanel, 
73                                      Resetable, 
74                                      HasBoundingBox, 
75                                      HasNearestPoint,
76                                      XMLIO {
77  
78      private HepRepGraphicsMode mode;    
79      private Projection projection;
80      private ViewPort viewPort;
81      private HepRepPanelCommandHandler commandHandler;
82      
83      private HepRep heprep;
84      private TreePanelModel treePanelModel;
85      
86      private transient HepRepGraphicsMode usedMode;
87      private transient HepRepGraphicsMode fastMode;
88      private transient Set /*<String>*/ missingDrawAsValues;
89      private transient DrawAsCache drawAsCache;
90      private transient boolean fast;
91      private transient InstanceListener instanceListener;
92      private transient Set/*<HepRepInstance>*/ highlight;
93      
94      private static final long nDraws = 10;
95      private transient long tMin = 200;
96      private transient long tMax = 200;
97      private transient long tAverage = 200;     // reasonable value
98  
99      private transient HepRepAttValue initialPhi, initialTheta;
100     private transient HepRepAttValue initialScaleX, initialScaleY, initialScaleZ;
101     private transient HepRepAttValue initialTranslateX, initialTranslateY, initialTranslateZ;
102 
103     private HepRepPanel() {
104         this(null, null);
105     }
106 
107     public HepRepPanel(HepRepGraphicsMode mode) {
108         this(mode, null);
109     }
110 
111     /***
112      * Creates a HepRep panel of dimension 800x600.
113      */
114     public HepRepPanel(HepRepGraphicsMode mode, ViewPort viewPort) {
115         this(mode, viewPort, new Dimension(800,600), Color.BLACK, 
116              new ParallelProjection.XY());
117     }
118     
119     private HepRepPanel(HepRepGraphicsMode mode, ViewPort viewPort, 
120                         Dimension preferredSize, Color background,
121                         Projection projection) {
122         super(true);
123         
124         this.mode = mode;        
125         if (viewPort == null) viewPort = new RectangularViewPort(0, 0, 1000, 1000, getWidth(), getHeight());
126         this.viewPort = viewPort;
127         this.projection = projection;
128         
129         heprep = null;
130         
131         setPreferredSize(preferredSize);
132         setBackground(background);
133         
134         setOpaque(true);
135 
136         fastMode = new HepRepGraphicsMode(true);
137         fast = false;
138         
139         highlight = null;
140 
141         treePanelModel = new TreePanelModel();
142         treePanelModel.addChangeListener(new ChangeListener() {
143             public void stateChanged(ChangeEvent event) {
144                 repaint();
145             }
146         });
147     }
148 
149     public void setHepRepInstanceListener(InstanceListener listener) {
150         instanceListener = listener;
151     }
152     
153     /***
154      * Returns a copy of this HepRep panel.
155      */
156     public GraphicsPanel copy() {
157         return new HepRepPanel((HepRepGraphicsMode)mode.copy(), viewPort.copy(), 
158                                getPreferredSize(), getBackground(),
159                                getProjection().copy());
160     }
161     
162     /***
163      * Sets the HepRep to be displayed in this panel and repaints.
164      */
165     public void setRecord(Object object) {        
166         heprep = (HepRep)object;
167 
168         treePanelModel.setHepRep(heprep);       
169         
170         // save reset state if available
171         if (heprep != null) {
172             HepRepIterator iterator = HepRepUtil.getInstances(heprep.getInstanceTreeList(), null, null, false);
173             if (iterator.hasNext()) {
174                 HepRepInstance instance = iterator.nextInstance();
175 
176                 initialPhi = instance.getAttValue("ViewPhi");
177                 initialTheta = instance.getAttValue("ViewTheta");
178                 
179                 initialScaleX = instance.getAttValue("ViewScaleX");
180                 if (initialScaleX == null) {
181                     initialScaleX = instance.getAttValue("ViewScale");
182                 }
183                 initialScaleY = instance.getAttValue("ViewScaleY");
184                 initialScaleZ = instance.getAttValue("ViewScaleZ");
185         
186                 if (initialScaleY == null) {
187                     initialScaleY = initialScaleX;
188                 }
189                 if (initialScaleZ == null) {
190                     initialScaleZ = initialScaleX;
191                 }
192         
193                 initialTranslateX = instance.getAttValue("ViewTranslateX");
194                 initialTranslateY = instance.getAttValue("ViewTranslateY");
195                 initialTranslateZ = instance.getAttValue("ViewTranslateZ");
196             }
197         }
198                                
199         repaint();
200     }
201    
202     /***
203      * Returns the HepRep displayed in this panel.
204      */
205     public Object getRecord() {
206         return heprep;
207     }
208        
209     public TreePanelModel getTreePanelModel() {
210         return treePanelModel;
211     }
212        
213     public void setSelectedTypes(Set/*<HepRepType>*/ selectedTypes) {
214         System.err.println("Selected: "+getSelectedTypes().size());
215         System.err.println("ignored");
216 //        for (Iterator i=selectedTypes.iterator(); i.hasNext(); ) {
217 //            HepRepType type = (HepRepType)i.next();
218 //            System.err.println(type.hashCode()+" "+type.getFullName());
219 //        }
220 //        this.selectedTypes = selectedTypes;
221 //        repaint();
222     }
223 
224     public Set/*<HepRepType>*/ getSelectedTypes() {
225         return treePanelModel.getSelectedTypes();
226     }
227         
228     public boolean supports(Class featureClass) {
229         return getFeature(featureClass) != null;
230     }
231     
232     /***
233      * Returns the feature for a specific featureClass, or null if that featureClass if not 
234      * in the projection of this panel.
235      */
236     public Feature getFeature(Class featureClass) {
237         if (featureClass.isAssignableFrom(this.getClass())) return this;
238         return projection.getFeature(featureClass);
239     }
240 
241     //
242     // Resetable
243     //
244     public Object reset(Object newState) {
245         Resetable resetable = (Resetable)projection.getFeature(Resetable.class);
246         Object oldState = (resetable != null) ? resetable.reset(newState) : null;
247                
248         // ViewScale
249         if (initialScaleX != null) {
250             Scaleable3D scaleable3d = (Scaleable3D)getFeature(Scaleable3D.class);
251             if (scaleable3d != null) {
252                 scaleable3d.setScale(initialScaleX.getDouble(), initialScaleY.getDouble(), initialScaleZ.getDouble());
253             } else {
254                 Scaleable2D scaleable2d = (Scaleable2D)getFeature(Scaleable2D.class);
255                 if (scaleable2d != null) {
256                     scaleable2d.setScale(initialScaleX.getDouble(), initialScaleY.getDouble());
257                 } else {
258                     Scaleable scaleable = (Scaleable)getFeature(Scaleable.class);
259                     if (scaleable != null) {
260                         scaleable.setScale(initialScaleX.getDouble());
261                     }   
262                 }
263             }
264         }
265 
266         // ViewRotation
267         Rotateable3D rotateable3d = (Rotateable3D)getFeature(Rotateable3D.class);
268         if (rotateable3d != null) {          
269             if (initialPhi != null) {
270                 rotateable3d.rotate(initialPhi.getDouble(), 0.0, 0.0, 1.0);
271             }
272             if (initialTheta != null) {
273                 rotateable3d.rotate(initialTheta.getDouble(), 0.0, 1.0, 0.0);
274             }
275         }
276                 
277         // ViewTranslate
278         double stx = (initialTranslateX != null) ? initialTranslateX.getDouble() : 0;
279         double sty = (initialTranslateY != null) ? initialTranslateY.getDouble() : 0;
280         double stz = (initialTranslateZ != null) ? initialTranslateZ.getDouble() : 0;
281 
282         Translateable3D translateable3D = (Translateable3D)getFeature(Translateable3D.class);
283         if (translateable3D != null) {
284             translateable3D.setTranslate(stx, sty, stz);
285         } else {
286             Translateable2D translateable2D = (Translateable2D)getFeature(Translateable2D.class);
287             if (translateable2D != null) {
288                 translateable2D.setTranslate(stx, sty);
289             }                    
290         }
291 
292         return oldState;
293     }
294 
295     /***
296      * Sets the projection for this panel.
297      */
298     public void setProjection(Projection projection) {
299         this.projection = projection;
300         
301         WiredPlugin plugin = WiredPlugin.getPlugin();
302         
303         // disable any buttons due to new projection
304         plugin.getInteractionPanel().update();
305         
306         // make sure interaction handler is valid
307         if ((plugin.getInteractionHandler() != null) && !plugin.getInteractionHandler().isSupportedBy(this)) {
308             plugin.setInteractionHandler(null);
309         }
310 
311         // make sure variable panel is up to date
312         plugin.getVariablePanel().update();
313     }
314     
315     /***
316      * Returns the projection for this panel.
317      */
318     public Projection getProjection() {
319         return projection;
320     }
321 
322     /***
323      * Returns the bounding box for this plot in its current projection/viewpoint.
324      */
325     public Rectangle2D getBoundingBoxForPlot() {
326         BoundingBoxGraphics2D bb = new BoundingBoxGraphics2D(true);
327         draw(bb, true, false);
328         return bb.getBoundingBox();
329     }
330 
331     public Point2D getNearestPoint(Point2D p) {
332         NearestPointPathConstructor pathConstructor = new NearestPointPathConstructor(p);
333         PathGraphics2D pathGraphics = new PathGraphics2D(pathConstructor);
334         draw(pathGraphics, true, false);
335         return pathConstructor.getNearestPoint();
336     }
337 
338     public void setFastMode(boolean fast) {
339         this.fast = fast;
340     }
341 
342     public void setSelected(RecordPlot plot, boolean selected) {       
343         if (selected) {
344             if (commandHandler == null) {
345                 commandHandler = new HepRepPanelCommandHandler(plot);
346                 UndoableEditSupport undoSupport = plot.getUndoableEditSupport();
347                 if (undoSupport != null) undoSupport.addUndoableEditListener(commandHandler);
348             }
349             ((Studio)Application.getApplication()).getCommandTargetManager().add(commandHandler);        
350         } else if (commandHandler != null) {
351             ((Studio)Application.getApplication()).getCommandTargetManager().remove(commandHandler);        
352         }
353     }
354 
355     public void highlight(Set/*<HepRepInstance>*/ highlight) {
356         this.highlight = highlight;
357     }
358 
359     public void repaint() {
360         // update toolbar...
361         if (commandHandler != null) commandHandler.setChanged();
362         super.repaint();
363     } 
364 
365     /***
366      * Paints the HepRep into this panel.
367      */
368     public void paintComponent(VectorGraphics graphics) {
369         try {
370             // clear background
371             Color c = graphics.getColor();
372             graphics.setColor(getBackground());
373             Rectangle r = getBounds();
374             graphics.fillRect(r.x, r.y, r.width, r.height);
375             graphics.setColor(c);
376                 
377             // FIXME: WIRED-380, blitting does not always work
378             graphics.setRenderingHint(PixelGraphics2D.KEY_SYMBOL_BLIT, PixelGraphics2D.VALUE_SYMBOL_BLIT_OFF);
379     
380             // set miterlimit to 1 to avoid jaggy edges
381             BasicStroke cs = (BasicStroke)graphics.getStroke();
382             graphics.setStroke(new BasicStroke(cs.getLineWidth(),
383                                                cs.getEndCap(),
384                                                cs.getLineJoin(),
385                                                1.0f,
386                                                cs.getDashArray(),
387                                                cs.getDashPhase()));
388     
389             long t = System.currentTimeMillis();
390     
391             // now draw all the elements
392             draw(graphics, fast, false);
393             
394             // do some timing measurements, calculating approximate moving averages, minima and maxima
395             if (false) {
396                 t = System.currentTimeMillis()-t;
397                 if (t != 0) {
398                     long tNow = t/nDraws;
399                     tMin = t < tAverage ? tMin + tNow - tMin/nDraws : tMin;
400                     tMax = t > tAverage ? tMax + tNow - tMax/nDraws : tMax;
401                     tAverage = tAverage + tNow - tAverage/nDraws;
402                     if (usedMode != fastMode) {
403                         System.err.println("Draw took: "+tMin+" <= "+tAverage+" <= "+tMax+" ms averaged over "+nDraws+" redraws.");
404                     }
405                 }
406             }
407         } catch (Throwable t) {
408 // FIXME, we need to disentangle this from Application...            
409 //            Application.getApplication().error(t.toString(), t);
410             System.err.println(t.toString());
411             t.printStackTrace();
412         }
413     }
414 
415     private void draw(VectorGraphics graphics, boolean fast, boolean pick) {
416         if (heprep == null) return;
417         List layers = (fast || !mode.useLayering) ? null : heprep.getLayerOrder();
418         draw(graphics, fast, layers, null, pick);
419     }
420     
421     public void draw(VectorGraphics graphics, boolean fast, List layers, Set types, boolean pick) {
422         if (heprep == null) return;
423         
424         // make types the intersection of types and selectedTypes.
425         if (types == null) {
426             types = getSelectedTypes();
427         } else {
428             types = new HashSet(types);
429             types.retainAll(getSelectedTypes());
430         }
431         
432         usedMode = (fast) ? fastMode : mode;
433         missingDrawAsValues = new HashSet();
434 
435         // create a new Cache
436         drawAsCache = new DrawAsCache();
437 
438         // set rendering mode
439         if (usedMode.antiAlias) {
440             graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
441                                          RenderingHints.VALUE_ANTIALIAS_ON);
442             graphics.setRenderingHint(RenderingHints.KEY_RENDERING,
443                                          RenderingHints.VALUE_RENDER_QUALITY);
444         } else {
445             graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
446                                          RenderingHints.VALUE_ANTIALIAS_OFF);
447             graphics.setRenderingHint(RenderingHints.KEY_RENDERING,
448                                          RenderingHints.VALUE_RENDER_SPEED);
449         }
450     
451         draw(graphics, heprep.getInstanceTreeList(), layers, types, pick);
452 
453         if (!missingDrawAsValues.isEmpty()) {
454             System.err.print("Missing DrawAs values: ");
455             for (Iterator i=missingDrawAsValues.iterator(); i.hasNext(); ) {
456                 System.err.print(i.next()+" ");
457             }
458             System.err.println();
459         }
460     }
461 
462     private void draw(VectorGraphics graphics, List/*<HepRepInstanceTree>*/ trees, List layers, Set types, boolean pick) {
463 //        System.err.println(trees.size()+" "+(layers != null ? ""+layers.size() : "-") +" "+(types != null ? ""+types.size() : "-"));
464 
465         HepRepIterator iterator = HepRepUtil.getInstances(trees, layers, types, usedMode.drawFrames);
466         AttributeCache atts = new AttributeCache(graphics, usedMode, iterator);
467 
468         projection.setViewPort(viewPort);
469 
470         int i=0;
471         while (iterator.hasNext()) {
472             HepRepInstance instance = iterator.nextInstance();
473             if (pick && !atts.isPickable()) continue;
474             if (atts.isVisible()) {
475                 if (instanceListener != null) instanceListener.currentHepRepInstance(instance);
476                 
477                 // highlight if necessary
478                 // FIXME: WIRED-381, we could maybe do something smarter here...
479                 if ((highlight != null) && !highlight.contains(instance)) {
480                     atts.overrideColor(Color.GRAY);
481                     atts.overrideFrameColor(Color.BLACK);
482                     atts.overrideFillColor(Color.GRAY);
483                 }
484                 draw(graphics, atts, instance, iterator.drawAsFrame());
485                 i++;
486                 // highlight if necessary
487                 if ((highlight != null) && !highlight.contains(instance)) {
488                     atts.overrideColor(null);
489                     atts.overrideFrameColor(null);
490                     atts.overrideFillColor(null);
491                 }                
492             }
493         }
494 //        System.err.println("Drawn: "+i+" instances");
495     }
496 
497     private void draw(VectorGraphics graphics, AttributeCache atts, HepRepInstance instance, boolean drawAsFrame) {
498 // Extra check
499 //        if (!atts.getDrawAs().equalsIgnoreCase(instance.getAttValue("drawas").getString())) {
500 //            System.err.println("DrawAs incorrect: found cache to be "+atts.getDrawAs()+", while real value is: "+instance.getAttValue("drawas").getString());
501 //        }
502         
503         DrawAs handler = drawAsCache.lookup(atts.getDrawAs());
504         if (handler == null) {
505             handler = drawAsCache.lookup("point");
506             if (handler == null) {
507                 System.err.println("WARNING: DrawAsPoint handler seems to be missing");
508                 handler = new DrawAsPoint();
509             }
510             missingDrawAsValues.add(atts.getDrawAs());
511         }
512         if (drawAsFrame) {
513             handler.frame(graphics, instance, atts, usedMode, projection, viewPort);
514         } else {
515             handler.draw(graphics, instance, atts, usedMode, projection, viewPort);
516         }
517     }
518 
519     public static interface InstanceListener {
520         public void currentHepRepInstance(HepRepInstance instance); 
521     }
522 
523     public void setBounds(int x, int y, int width, int height) {
524         super.setBounds(x, y, width, height);
525 
526         if (height < width) {
527             viewPort = new RectangularViewPort((width - height)/2, 0, height, height, width, height);
528         } else {
529             viewPort = new RectangularViewPort(0, (height - width) / 2, width, width, width, height);
530         }
531     }
532     
533     /***
534      * Returns the viewport for this panel.
535      */
536     public ViewPort getViewPort() {
537         return viewPort;
538     }
539 
540     /***
541      * Returns the GraphicsMode
542      */
543     public GraphicsMode getGraphicsMode() {
544         return mode;
545     }
546 
547     /***
548      * Returns a set of DrawAs values for which no DrawAs service was available. These were drawn as 
549      * Points.
550      */
551     public Set/*<String>*/ getMissingDrawAsValues() {
552         NullGraphics2D ng = new NullGraphics2D();
553         draw(ng, true, false);
554         return missingDrawAsValues;
555     }
556 
557     public JPopupMenu modifyPopupMenu(JPopupMenu menu, final RecordPlot plot) {
558   
559 // FIXME WIRED-291: re-add when merging really works based on name.      
560 //        Application.getApplication().getXMLMenuBuilder().mergePopupMenu("wiredPopupMenu", menu);
561 
562         // add projections dynamically
563         menu.addSeparator();
564         
565         final JMenu projectionMenu = new JMenu("Projection");
566         menu.add(projectionMenu);
567         projectionMenu.addMenuListener(new MenuListener() {
568             
569             public void menuCanceled(MenuEvent event) {
570             }
571                 
572             public void menuSelected(MenuEvent event) {
573                 ButtonGroup group = new ButtonGroup();
574                 
575                 // FIXME, WIRED-242 pick up from preferences
576                 String[] names = AbstractProjection.defaultProjections.split(":");
577 
578                 Collection allProjections = WiredRegistry.allInstances(Projection.class);
579                 
580                 for (int i=0; i<names.length; i++) {
581                     addMenu(group, projectionMenu, names[i]);
582                 }
583             }
584               
585             public void menuDeselected(MenuEvent event) {
586                 projectionMenu.removeAll();
587             }
588             
589             //
590             // Add a menu/item for the part of the name between '$' (or start of the string) and '.' or end of the string.
591             // Recursive!
592             //
593             private void addMenu(ButtonGroup group, JMenu menu, String name) {
594                 int dollar = name.lastIndexOf('$');
595                 int dot = name.indexOf('.', dollar+1);
596                 String menuName = dot < 0 ? name.substring(dollar+1) : name.substring(dollar+1, dot);
597                 if (dot >= 0) {
598                     // add submenu or to submenu
599                     JMenu subMenu = null;
600                     for (int i=0; i<menu.getItemCount(); i++) {
601                         JMenuItem item = menu.getItem(i);
602                         if ((item instanceof JMenu) && item.getText().equals(menuName)) {
603                             subMenu = (JMenu)item;
604                             break;
605                         }
606                     }
607                     if (subMenu == null) {
608                         subMenu = new JMenu(menuName);
609                         menu.add(subMenu);
610                     }
611                     addMenu(group, subMenu, name.substring(0,dot)+"$"+name.substring(dot+1)); 
612                 } else {
613                     // add here
614                     JMenuItem item = new JRadioButtonMenuItem(menuName);
615                     menu.add(item);
616                     group.add(item);
617                     item.setEnabled(false);
618                     
619                     // lookup in projection services
620                     final Projection projection = (Projection)WiredRegistry.lookup(Projection.class, name); 
621                     if (projection != null) {
622                         item.setSelected(name.equals(getProjection().getID()));
623                         item.setEnabled(true);
624                         item.addActionListener(new ActionListener() {
625                             public void actionPerformed(ActionEvent event) {
626                                 plot.postEdit(new SetProjection(projection));
627                             }
628                         });
629                     }
630                 }
631             }
632         });
633                
634         return menu;
635     }
636 
637 //
638 // XMLIO
639 //
640     public void save(XMLIOManager xmlioManager,
641                      org.jdom.Element nodeEl) {
642         nodeEl.addContent(xmlioManager.save(mode));
643         nodeEl.addContent(xmlioManager.save(projection));
644         nodeEl.addContent(xmlioManager.save(viewPort));
645     }
646 
647     public void restore(XMLIOManager xmlioManager,
648                         org.jdom.Element nodeEl) { 
649         Iterator i = nodeEl.getChildren().iterator();
650         mode = (HepRepGraphicsMode)xmlioManager.restore((Element)i.next());
651         projection = (Projection)xmlioManager.restore((Element)i.next());
652         viewPort = (ViewPort)xmlioManager.restore((Element)i.next());
653     }     
654 }