View Javadoc
1   // ******************************************************************************
2   //
3   // Title:       Force Field X.
4   // Description: Force Field X - Software for Molecular Biophysics.
5   // Copyright:   Copyright (c) Michael J. Schnieders 2001-2024.
6   //
7   // This file is part of Force Field X.
8   //
9   // Force Field X is free software; you can redistribute it and/or modify it
10  // under the terms of the GNU General Public License version 3 as published by
11  // the Free Software Foundation.
12  //
13  // Force Field X is distributed in the hope that it will be useful, but WITHOUT
14  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16  // details.
17  //
18  // You should have received a copy of the GNU General Public License along with
19  // Force Field X; if not, write to the Free Software Foundation, Inc., 59 Temple
20  // Place, Suite 330, Boston, MA 02111-1307 USA
21  //
22  // Linking this library statically or dynamically with other modules is making a
23  // combined work based on this library. Thus, the terms and conditions of the
24  // GNU General Public License cover the whole combination.
25  //
26  // As a special exception, the copyright holders of this library give you
27  // permission to link this library with independent modules to produce an
28  // executable, regardless of the license terms of these independent modules, and
29  // to copy and distribute the resulting executable under terms of your choice,
30  // provided that you also meet, for each linked independent module, the terms
31  // and conditions of the license of that module. An independent module is a
32  // module which is not derived from or based on this library. If you modify this
33  // library, you may extend this exception to your version of the library, but
34  // you are not obligated to do so. If you do not wish to do so, delete this
35  // exception statement from your version.
36  //
37  // ******************************************************************************
38  package ffx.ui;
39  
40  import static ffx.potential.bonded.RendererCache.pickingColor;
41  import static ffx.potential.bonded.RendererCache.selectionColor;
42  import static ffx.potential.bonded.RendererCache.userColor;
43  import static java.lang.String.format;
44  
45  import ffx.potential.MolecularAssembly;
46  import ffx.potential.bonded.MSNode;
47  import ffx.potential.bonded.RendererCache;
48  import ffx.potential.bonded.RendererCache.ColorModel;
49  import ffx.potential.bonded.RendererCache.ViewModel;
50  
51  import java.awt.Color;
52  import java.awt.Font;
53  import java.awt.GraphicsConfiguration;
54  import java.awt.GraphicsEnvironment;
55  import java.awt.Rectangle;
56  import java.awt.Toolkit;
57  import java.awt.event.ActionEvent;
58  import java.awt.event.ActionListener;
59  import java.awt.image.BufferedImage;
60  import java.io.File;
61  import java.io.IOException;
62  import java.util.ArrayList;
63  import java.util.HashMap;
64  import java.util.logging.Level;
65  import java.util.logging.Logger;
66  import java.util.prefs.Preferences;
67  import javax.imageio.ImageIO;
68  import javax.swing.JButton;
69  import javax.swing.JCheckBoxMenuItem;
70  import javax.swing.JColorChooser;
71  import javax.swing.JFileChooser;
72  import javax.swing.JLabel;
73  import javax.swing.JOptionPane;
74  
75  import org.jogamp.java3d.AmbientLight;
76  import org.jogamp.java3d.Background;
77  import org.jogamp.java3d.BoundingSphere;
78  import org.jogamp.java3d.Bounds;
79  import org.jogamp.java3d.BranchGroup;
80  import org.jogamp.java3d.Canvas3D;
81  import org.jogamp.java3d.DirectionalLight;
82  import org.jogamp.java3d.GraphicsConfigTemplate3D;
83  import org.jogamp.java3d.GraphicsContext3D;
84  import org.jogamp.java3d.ImageComponent;
85  import org.jogamp.java3d.ImageComponent2D;
86  import org.jogamp.java3d.J3DGraphics2D;
87  import org.jogamp.java3d.Raster;
88  import org.jogamp.java3d.Transform3D;
89  import org.jogamp.java3d.TransformGroup;
90  import org.jogamp.java3d.View;
91  import org.jogamp.java3d.utils.universe.SimpleUniverse;
92  import org.jogamp.vecmath.Color3f;
93  import org.jogamp.vecmath.Point3d;
94  import org.jogamp.vecmath.Point3f;
95  import org.jogamp.vecmath.Vector3d;
96  import org.jogamp.vecmath.Vector3f;
97  
98  /**
99   * The GraphicsCanvas class provides a Canvas on which to render 3D Graphics. The following display
100  * types are currently supported: Wireframe, Ball and Stick, Spacefill/CPK, RMIN and Tube.
101  *
102  * @author Michael J. Schnieders
103  */
104 @SuppressWarnings("serial")
105 public class GraphicsCanvas extends Canvas3D implements ActionListener {
106 
107   private static final Logger logger = Logger.getLogger(GraphicsCanvas.class.getName());
108   /**
109    * Constant <code>imageFormatHash</code>
110    */
111   private static final HashMap<String, ImageFormat> imageFormatHash = new HashMap<>();
112   /**
113    * Save preferences to the user node.
114    */
115   private static final Preferences prefs = Preferences.userNodeForPackage(GraphicsCanvas.class);
116 
117   static {
118     ImageFormat[] values = ImageFormat.values();
119     for (ImageFormat value : values) {
120       imageFormatHash.put(value.toString(), value);
121     }
122   }
123 
124   // Controller Classes
125   private ffx.potential.Renderer renderer;
126   private GraphicsEvents graphicsEvents;
127   private GraphicsPicking rendererPicking;
128   private MainPanel mainPanel;
129   private GraphicsAxis graphicsAxis;
130   private GraphicsFullScreen fullScreenWindow;
131   // 3D Universe Variables
132   private SimpleUniverse universe;
133   private Background background;
134   private BranchGroup baseBranchGroup;
135   private TransformGroup baseTransformGroup;
136   private Transform3D baseTransform3D = new Transform3D();
137   private Bounds bounds;
138   // State Variables
139   private GraphicsPrefs graphics3DPrefs = null;
140   private MouseMode mouseMode = MouseMode.ACTIVESYSTEM;
141   private ImageFormat imageFormat = ImageFormat.PNG;
142   private LeftButtonMode leftButtonMode = LeftButtonMode.ROTATE;
143   private boolean imageCapture = false;
144   private File imageName;
145 
146   /**
147    * The GraphicsCanvas constructor initializes the Java3D Universe and Behaviors.
148    *
149    * @param config    a {@link java.awt.GraphicsConfiguration} object.
150    * @param mainPanel a {@link ffx.ui.MainPanel} object.
151    */
152   GraphicsCanvas(GraphicsConfiguration config, MainPanel mainPanel) {
153     super(config);
154     this.mainPanel = mainPanel;
155     initialize();
156   }
157 
158   /**
159    * Constructor for GraphicsCanvas.
160    *
161    * @param mainlPanel a {@link ffx.ui.MainPanel} object.
162    */
163   public GraphicsCanvas(MainPanel mainlPanel) {
164     this(
165         GraphicsEnvironment.getLocalGraphicsEnvironment()
166             .getDefaultScreenDevice()
167             .getBestConfiguration(new GraphicsConfigTemplate3D()),
168         mainlPanel);
169   }
170 
171   /**
172    * {@inheritDoc}
173    *
174    * <p>Handles ActionEvents from the Selection, Display, Color, Options, and Picking Menus.
175    */
176   @Override
177   public void actionPerformed(ActionEvent evt) {
178     String arg = evt.getActionCommand();
179     // Selection Menu
180     if (arg.equals("LabelSelectedAtoms")) {
181       labelSelectedAtoms();
182     } else if (arg.equals("LabelSelectedResidues")) {
183       labelSelectedResidues();
184     } else if (arg.equals("SetLabelFontColor")) {
185       setLabelFontColor();
186     } else if (arg.equals("SetLabelFontSize")) {
187       setLabelFontSize();
188       // Display Menu
189     } else if (RendererCache.viewModelHash.containsKey(arg.toUpperCase())) {
190       setViewModel(arg);
191     } else if (arg.equals("Preferences")) {
192       preferences();
193       // Color Menu
194     } else if (RendererCache.colorModelHash.containsKey(arg.toUpperCase())) {
195       setColorModel(arg);
196     } else if (arg.equals("SetSelectionColor")) {
197       setSelectionColor();
198     } else if (arg.equals("SetUserColor")) {
199       setUserColor();
200       // Options
201     } else if (arg.equals("SystemBelowMouse")) {
202       mouseMode = MouseMode.SYSTEMBELOWMOUSE;
203     } else if (arg.equals("ActiveSystem")) {
204       mouseMode = MouseMode.ACTIVESYSTEM;
205     } else if (arg.equals("Rotate")) {
206       leftButtonMode = LeftButtonMode.ROTATE;
207     } else if (arg.equals("Translate")) {
208       leftButtonMode = LeftButtonMode.TRANSLATE;
209     } else if (arg.equals("Zoom")) {
210       leftButtonMode = LeftButtonMode.ZOOM;
211     } else if (arg.equals("ResetRotation")) {
212       resetRotation();
213     } else if (arg.equals("ResetTranslation")) {
214       resetTranslation();
215     } else if (arg.equals("ResetRotationAndTranslation")) {
216       resetRotationAndTranslation();
217     } else if (arg.equalsIgnoreCase("RotateAboutPick")) {
218       rotateAboutPick();
219     } else if (arg.equalsIgnoreCase("RotateAboutCenter")) {
220       rotateAboutCenter();
221     } else if (arg.equalsIgnoreCase("ResetGlobalTranslation")) {
222       resetGlobalTranslation();
223     } else if (arg.equalsIgnoreCase("ResetGlobalRotation")) {
224       resetGlobalRotation();
225     } else if (arg.equalsIgnoreCase("ResetGlobalZoom")) {
226       resetGlobalZoom();
227     } else if (arg.equalsIgnoreCase("ResetGlobalView")) {
228       resetGlobalView();
229     } else if (arg.equals("FullScreen")) {
230       fullScreen();
231     } else if (arg.equals("SetBackgroundColor")) {
232       setBackgroundColor();
233     } else if (arg.equalsIgnoreCase("ZoomIn")) {
234       zoomIn();
235     } else if (arg.equalsIgnoreCase("ZoomOut")) {
236       zoomOut();
237       // Picking Menu
238     } else if (arg.equalsIgnoreCase("GraphicsPicking")) {
239       graphicsPicking(evt);
240     } else if (imageFormatHash.containsKey(arg.toUpperCase())) {
241       setImageFormat(arg);
242     } else if (arg.equals("CaptureGraphics")) {
243       captureGraphics();
244     } else if (GraphicsPicking.pickLevelHash.containsKey(arg.toUpperCase())) {
245       setPickingLevel(arg);
246     } else if (arg.equals("SetGraphicsPickingColor")) {
247       setGraphicsPickingColor();
248     } else {
249       logger.warning(format("Graphics Menu command not found: %s.", arg));
250     }
251   }
252 
253   /**
254    * colorWait
255    *
256    * @param colorMode a {@link java.lang.String} object.
257    */
258   public void colorWait(String colorMode) {
259     if (colorMode == null) {
260       logger.info("Null color.");
261       return;
262     }
263     try {
264       ColorModel colorModel = ColorModel.valueOf(colorMode.toUpperCase());
265       colorWait(colorModel);
266     } catch (Exception e) {
267       logger.info("Unknown color command.");
268     }
269   }
270 
271   /**
272    * getNavigation
273    *
274    * @return a {@link ffx.ui.GraphicsAxis} object.
275    */
276   public GraphicsAxis getNavigation() {
277     return graphicsAxis;
278   }
279 
280   /**
281    * {@inheritDoc}
282    */
283   @Override
284   public void paint(java.awt.Graphics g) {
285     super.paint(g);
286     Toolkit.getDefaultToolkit().sync();
287   }
288 
289   /**
290    * {@inheritDoc}
291    *
292    * <p>Labels are drawn in postRender.
293    */
294   @Override
295   public void postRender() {
296     if (RendererCache.labelAtoms || RendererCache.labelResidues) {
297       J3DGraphics2D g2D = getGraphics2D();
298       synchronized (mainPanel.getHierarchy()) {
299         ArrayList<MSNode> nodes = mainPanel.getHierarchy().getActiveNodes();
300         if (nodes != null && !nodes.isEmpty()) {
301           for (MSNode node : nodes) {
302             MolecularAssembly sys = node.getMSNode(MolecularAssembly.class);
303             if (sys != null) {
304               node.drawLabel(this, g2D, sys.getWireFrame());
305             }
306           }
307         } else {
308           return;
309         }
310       }
311       g2D.flush(true);
312     }
313   }
314 
315   /**
316    * {@inheritDoc}
317    *
318    * <p>Image capture from the 3D Canvas is done in postSwap.
319    */
320   @Override
321   public void postSwap() {
322     if (!imageCapture || mainPanel.getHierarchy().getActive() == null) {
323       return;
324     }
325     GraphicsContext3D ctx = getGraphicsContext3D();
326     Rectangle rect = getBounds();
327     BufferedImage img = new BufferedImage(rect.width, rect.height, BufferedImage.TYPE_INT_RGB);
328     ImageComponent2D comp = new ImageComponent2D(ImageComponent.FORMAT_RGB, img);
329     Raster ras = new Raster(new Point3f(-1.0f, -1.0f, -1.0f),
330         Raster.RASTER_COLOR, 0, 0, rect.width, rect.height, comp, null);
331     ctx.readRaster(ras);
332     img = ras.getImage().getImage();
333     try {
334       if (!ImageIO.write(img, imageFormat.toString(), imageName)) {
335         logger.warning(
336             format(
337                 " No image writer was found for %s.\n Please try a different image format.\n",
338                 imageFormat.toString()));
339         imageName.delete();
340       } else {
341         logger.info(format(" %s was captured.", imageName));
342       }
343     } catch (IOException e) {
344       logger.warning(e.getMessage());
345     }
346     imageCapture = false;
347   }
348 
349   /**
350    * selected
351    */
352   public void selected() {
353     validate();
354     repaint();
355   }
356 
357   /**
358    * setCaptures
359    *
360    * @param c a boolean.
361    */
362   public void setCaptures(boolean c) {
363     imageCapture = c;
364   }
365 
366   /**
367    * setColor
368    *
369    * @param model a {@link java.lang.String} object.
370    */
371   public void setColor(String model) {
372     setColorModel(model);
373   }
374 
375   /**
376    * Operates on the passed node.
377    *
378    * @param model String
379    * @param node  a {@link ffx.potential.bonded.MSNode} object.
380    */
381   public void setColorModel(String model, MSNode node) {
382     if (node == null) {
383       return;
384     }
385     if (!RendererCache.colorModelHash.containsKey(model.toUpperCase())) {
386       return;
387     }
388     ColorModel colorModel = RendererCache.colorModelHash.get(model.toUpperCase());
389     renderer.arm(node, false, false, null, true, colorModel);
390   }
391 
392   /**
393    * setPosition
394    */
395   public void setPosition() {
396     setPosition(mainPanel.getHierarchy().getActive());
397   }
398 
399   /**
400    * setPosition
401    *
402    * @param node a {@link ffx.potential.bonded.MSNode} object.
403    */
404   public void setPosition(MSNode node) {
405     updateScene(node, true, false, null, true, null);
406   }
407 
408   /**
409    * @param model a {@link java.lang.String} object.
410    */
411   public void setView(String model) {
412     setViewModel(model);
413   }
414 
415   /**
416    * Operates on the supplied node.
417    *
418    * @param model String
419    * @param node  a {@link ffx.potential.bonded.MSNode} object.
420    */
421   public void setViewModel(String model, MSNode node) {
422     if (node == null) {
423       return;
424     }
425     if (!RendererCache.viewModelHash.containsKey(model.toUpperCase())) {
426       return;
427     }
428     RendererCache.ViewModel viewModel = RendererCache.viewModelHash.get(model.toUpperCase());
429     renderer.arm(node, false, true, viewModel, false, null);
430   }
431 
432   /**
433    * {@inheritDoc}
434    */
435   @Override
436   public String toString() {
437     return "3D Graphics";
438   }
439 
440   // *********************************************************************
441   // Selection Commands
442 
443   /**
444    * updateScene
445    *
446    * @param n             a {@link java.util.ArrayList} object.
447    * @param t             a boolean.
448    * @param v             a boolean.
449    * @param newViewModel  a {@link ffx.potential.bonded.RendererCache.ViewModel} object.
450    * @param c             a boolean.
451    * @param newColorModel a {@link ffx.potential.bonded.RendererCache.ColorModel} object.
452    */
453   public void updateScene(
454       ArrayList<MSNode> n,
455       boolean t,
456       boolean v,
457       ViewModel newViewModel,
458       boolean c,
459       ColorModel newColorModel) {
460     if (n != null) {
461       renderer.arm(n, t, v, newViewModel, c, newColorModel);
462     }
463   }
464 
465   /**
466    * updateScene
467    *
468    * @param n             a {@link ffx.potential.bonded.MSNode} object.
469    * @param t             a boolean.
470    * @param v             a boolean.
471    * @param newViewModel  a {@link ffx.potential.bonded.RendererCache.ViewModel} object.
472    * @param c             a boolean.
473    * @param newColorModel a {@link ffx.potential.bonded.RendererCache.ColorModel} object.
474    */
475   public void updateScene(
476       MSNode n, boolean t, boolean v, ViewModel newViewModel, boolean c, ColorModel newColorModel) {
477     if (n != null) {
478       renderer.arm(n, t, v, newViewModel, c, newColorModel);
479     }
480   }
481 
482   // *********************************************************************
483   // The following three methods modify default Canvas3D methods.
484 
485   /**
486    * viewWait
487    *
488    * @param viewMode a {@link java.lang.String} object.
489    */
490   public void viewWait(String viewMode) {
491     if (viewMode == null) {
492       logger.info("Null view.");
493       return;
494     }
495     try {
496       ViewModel viewModel = ViewModel.valueOf(viewMode.toUpperCase());
497       viewWait(viewModel);
498     } catch (Exception e) {
499       logger.info("Unknown view command.");
500     }
501   }
502 
503   /**
504    * This attaches a MolecularAssembly to the Scene BranchGroup.
505    *
506    * @param s MolecularAssembly to attach.
507    */
508   void attachModel(MolecularAssembly s) {
509     if (s == null) {
510       return;
511     }
512     synchronized (this) {
513       BranchGroup bg = s.getBranchGroup();
514       resetGlobalView();
515       baseBranchGroup.addChild(bg);
516     }
517   }
518 
519   private void captureGraphics() {
520     MolecularAssembly active = mainPanel.getHierarchy().getActive();
521     if (active == null) {
522       return;
523     }
524     imageName = null;
525     String name = active.getName();
526     JFileChooser fileChooser = MainPanel.resetFileChooser();
527     fileChooser.setAcceptAllFileFilterUsed(true);
528     if (mainPanel.getHierarchy().getActive() != null) {
529       imageName = mainPanel.getHierarchy().getActive().getFile();
530     } else {
531       imageName = null;
532     }
533     if (imageName != null) {
534       if (name.indexOf(".") > 0) {
535         name = name.substring(0, name.indexOf("."));
536       }
537       imageName = new File(imageName.getParentFile() + File.separator + name + "." + imageFormat);
538       fileChooser.setSelectedFile(imageName);
539     }
540     fileChooser.setDialogTitle("Select Name for Screen Capture " + "(" + imageFormat + ")");
541     fileChooser.setCurrentDirectory(MainPanel.getPWD());
542     int result = fileChooser.showSaveDialog(this);
543     if (result == JFileChooser.APPROVE_OPTION) {
544       imageName = fileChooser.getSelectedFile();
545       mainPanel.setCWD(fileChooser.getCurrentDirectory());
546       imageCapture = true;
547       repaint();
548     }
549   }
550 
551   /**
552    * fullScreen
553    */
554   private void fullScreen() {
555     if (fullScreenWindow == null) {
556       fullScreenWindow = new GraphicsFullScreen(mainPanel.getFrame(), this);
557     }
558     fullScreenWindow.enterFullScreen();
559   }
560 
561   /**
562    * Getter for the field <code>mouseMode</code>.
563    *
564    * @return a {@link ffx.ui.GraphicsCanvas.MouseMode} object.
565    */
566   MouseMode getMouseMode() {
567     return mouseMode;
568   }
569 
570   /**
571    * Getter for the field <code>leftButtonMode</code>.
572    *
573    * @return a {@link ffx.ui.GraphicsCanvas.LeftButtonMode} object.
574    */
575   LeftButtonMode getLeftButtonMode() {
576     return leftButtonMode;
577   }
578 
579   /**
580    * getStatusBar
581    *
582    * @return a {@link javax.swing.JLabel} object.
583    */
584   private JLabel getStatusBar() {
585     return mainPanel.getStatusBar();
586   }
587 
588   /**
589    * graphicsPicking
590    *
591    * @param evt a {@link java.awt.event.ActionEvent} object.
592    */
593   private void graphicsPicking(ActionEvent evt) {
594     if (evt.getSource() instanceof JButton) {
595       MainMenu m = mainPanel.getMainMenu();
596       boolean picking = m.getPicking();
597       if (picking) {
598         rendererPicking.clear();
599         rendererPicking.setPicking(false);
600         m.setPickBehavior(false);
601       } else {
602         rendererPicking.setPicking(true);
603         m.setPickBehavior(true);
604       }
605     } else if (evt.getSource() instanceof JCheckBoxMenuItem) {
606       JCheckBoxMenuItem jcbmi = (JCheckBoxMenuItem) evt.getSource();
607       if (jcbmi.isSelected()) {
608         rendererPicking.setPicking(true);
609       } else {
610         rendererPicking.setPicking(false);
611       }
612     }
613   }
614 
615   // *********************************************************************
616   // Options Commands
617 
618   /**
619    * Initialization of the GraphisCanvas.
620    */
621   private void initialize() {
622     setBackground(Color.black);
623     universe = new SimpleUniverse(this);
624     SimpleUniverse.setJ3DThreadPriority(Thread.MAX_PRIORITY);
625     universe.getViewingPlatform().setNominalViewingTransform();
626     // Create the Scene Root BranchGroup
627     BranchGroup objRoot = new BranchGroup();
628     baseTransformGroup = new TransformGroup();
629     Transform3D t3d = new Transform3D();
630     t3d.setScale(0.1d);
631     baseTransformGroup.setTransform(t3d);
632     baseTransformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
633     baseTransformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
634     // Set the Background
635     background = new Background(RendererCache.BLACK);
636     background.setCapability(Background.ALLOW_COLOR_READ);
637     background.setCapability(Background.ALLOW_COLOR_WRITE);
638     bounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0), 2000.0);
639     background.setApplicationBounds(bounds);
640     // Create lights
641     AmbientLight aLgt =
642         new AmbientLight(new Color3f(Color.darkGray.getRGBColorComponents(new float[3])));
643     aLgt.setInfluencingBounds(bounds);
644     Vector3f dir = new Vector3f(0.0f, -1.0f, -1.0f);
645     Color3f dLgtColor = new Color3f(Color.lightGray.getRGBColorComponents(new float[3]));
646     DirectionalLight dLgt = new DirectionalLight(dLgtColor, dir);
647     dLgt.setInfluencingBounds(bounds);
648     dir = new Vector3f(0.0f, 1.0f, -1.0f);
649     dLgtColor = new Color3f(0.1f, 0.1f, 0.1f);
650     DirectionalLight dLgt2 = new DirectionalLight(dLgtColor, dir);
651     dLgt2.setInfluencingBounds(bounds);
652     // Create the Base of the Molecular Scene
653     baseBranchGroup = new BranchGroup();
654     baseBranchGroup.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
655     baseBranchGroup.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
656     baseBranchGroup.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
657     baseBranchGroup.setCapability(BranchGroup.ALLOW_BOUNDS_READ);
658     // Add children created above to the base TransformGroup
659     baseTransformGroup.addChild(background);
660     baseTransformGroup.addChild(baseBranchGroup);
661     objRoot.addChild(baseTransformGroup);
662     // Position the view platmform and add lights
663     View v = universe.getViewer().getView();
664     v.setProjectionPolicy(View.PARALLEL_PROJECTION);
665     v.setFrontClipPolicy(View.VIRTUAL_EYE);
666     v.setFrontClipDistance(1.0);
667     v.setBackClipPolicy(View.VIRTUAL_EYE);
668     v.setBackClipDistance(10.0);
669     v.setTransparencySortingPolicy(View.TRANSPARENCY_SORT_NONE);
670     Transform3D trans = new Transform3D();
671     trans.set(new Vector3d(0.0d, 0.0d, 2.0d));
672     TransformGroup vptg = universe.getViewingPlatform().getViewPlatformTransform();
673     vptg.setTransform(trans);
674     BranchGroup viewBranch = new BranchGroup();
675     viewBranch.addChild(aLgt);
676     viewBranch.addChild(dLgt);
677     viewBranch.addChild(dLgt2);
678     vptg.addChild(viewBranch);
679     // Initialize Behaviors
680     graphicsAxis = new GraphicsAxis(universe.getViewingPlatform(), bounds);
681     graphicsEvents =
682         new GraphicsEvents(
683             mainPanel, this, graphicsAxis, universe, bounds, baseBranchGroup, baseTransformGroup);
684     baseBranchGroup.addChild(graphicsEvents);
685     rendererPicking = new GraphicsPicking(baseBranchGroup, bounds, this, mainPanel);
686     baseBranchGroup.addChild(rendererPicking);
687     renderer = new ffx.potential.Renderer(bounds, mainPanel.getStatusBar());
688     baseBranchGroup.addChild(renderer);
689     // Compile the Root BranchGroup and add it to the Universe
690     objRoot.compile();
691     universe.addBranchGraph(objRoot);
692   }
693 
694   /**
695    * isCacheFull
696    *
697    * @return a boolean.
698    */
699   boolean isCacheFull() {
700     return renderer.isCacheFull();
701   }
702 
703   /**
704    * isSceneRendering
705    *
706    * @return a boolean.
707    */
708   boolean isSceneRendering() {
709     return renderer.isArmed();
710   }
711 
712   /**
713    * labelSelectedAtoms
714    */
715   private void labelSelectedAtoms() {
716     if (RendererCache.labelAtoms) {
717       RendererCache.labelAtoms = false;
718       getStatusBar().setText("  Atom Labeling Turned Off");
719     } else {
720       RendererCache.labelAtoms = true;
721       getStatusBar().setText("  Atom Labeling Turned On");
722     }
723     repaint();
724   }
725 
726   /**
727    * Label selected residues.
728    */
729   private void labelSelectedResidues() {
730     if (RendererCache.labelResidues) {
731       RendererCache.labelResidues = false;
732       getStatusBar().setText("  Residue Labeling Turned Off");
733     } else {
734       RendererCache.labelResidues = true;
735       getStatusBar().setText("  Residue Labeling Turned On");
736     }
737     repaint();
738   }
739 
740   // ********************************************************************
741   // The following three methods modify default Canvas3D methods.
742 
743   /**
744    * Load preferences from the user node.
745    */
746   void loadPrefs() {
747     String c = GraphicsCanvas.class.getName();
748     RendererCache.bondwidth = prefs.getInt(c + ".bondwidth", 3);
749     RendererCache.detail = prefs.getInt(c + ".detail", 3);
750     RendererCache.radius = prefs.getDouble(c + ".radius", 1.0d);
751     String s = prefs.get(c + ".mouse", MouseMode.ACTIVESYSTEM.name());
752     if (s.equalsIgnoreCase("ACTIVESYSTEM") || s.equalsIgnoreCase("SYSTEMBELOWMOUSE")) {
753       mouseMode = MouseMode.valueOf(s);
754     } else {
755       mouseMode = MouseMode.ACTIVESYSTEM;
756     }
757     mainPanel.getMainMenu().setMouseMode(mouseMode);
758     RendererCache.highlightSelections = prefs.getBoolean(c + ".highlight", false);
759     mainPanel.getMainMenu().setHighlighting(RendererCache.highlightSelections);
760     String[] hlColor = prefs.get(c + ".highlightColor", "153 153 255").trim().split(" +");
761     selectionColor =
762         new Color3f(
763             Float.parseFloat(hlColor[0]),
764             Float.parseFloat(hlColor[1]),
765             Float.parseFloat(hlColor[2]));
766     RendererCache.labelAtoms = prefs.getBoolean(c + ".labelAtoms", false);
767     mainPanel.getMainMenu().setAtomLabels(RendererCache.labelAtoms);
768     RendererCache.labelResidues = prefs.getBoolean(c + ".labelResidues", false);
769     mainPanel.getMainMenu().setResidueLabels(RendererCache.labelResidues);
770     /*
771      * int fontSize = prefs.getInt("Graphics_labelSize", 12); J3DGraphics2D
772      * j2D = getOffscreenCanvas3D().getGraphics2D(); Font currentFont =
773      * j2D.getFont(); Font newFont = new Font(currentFont.getName(),
774      * currentFont.getStyle(), fontSize); j2D.setFont(newFont); String[]
775      * fontColor = prefs.get("Graphics_labelColor", "255 255 255")
776      * .trim().split(" +"); newColor = new
777      * Color(Integer.parseInt(fontColor[0]), Integer
778      * .parseInt(fontColor[1]), Integer.parseInt(fontColor[2]));
779      * j2D.setPaint(newColor);
780      */
781     String[] pickColor = prefs.get(c + ".pickColor", "102 255 102").trim().split(" +");
782     pickingColor =
783         new Color3f(
784             Float.parseFloat(pickColor[0]),
785             Float.parseFloat(pickColor[1]),
786             Float.parseFloat(pickColor[2]));
787     String pickLevel = prefs.get(c + ".pickLevel", "PickAtom");
788     mainPanel.getMainMenu().setPickLevel(pickLevel);
789     boolean pickMode = prefs.getBoolean(c + ".picking", false);
790     if (pickMode) {
791       rendererPicking.setPicking(true);
792     }
793     mainPanel.getMainMenu().setPickBehavior(pickMode);
794     String[] userColor = prefs.get(c + ".userColor", "255 255 255").trim().split(" +");
795     RendererCache.userColor =
796         new Color3f(
797             Float.parseFloat(userColor[0]),
798             Float.parseFloat(userColor[1]),
799             Float.parseFloat(userColor[2]));
800     /*
801      * String[] bgColor = prefs.get("Graphics_backgroundColor", "0 0 0")
802      * .trim().split(" +"); newColor = new
803      * Color(Integer.parseInt(bgColor[0]), Integer .parseInt(bgColor[1]),
804      * Integer.parseInt(bgColor[2])); background.setColor(new
805      * Color3f(newColor));
806      */
807   }
808 
809   /**
810    * preferences
811    */
812   void preferences() {
813     if (graphics3DPrefs == null) {
814       graphics3DPrefs = new GraphicsPrefs(mainPanel.getFrame(), mainPanel.getDataRoot());
815       graphics3DPrefs.setModal(false);
816     }
817     graphics3DPrefs.setVisible(true);
818     graphics3DPrefs.toFront();
819   }
820 
821   /**
822    * resetGlobalRotation
823    */
824   private void resetGlobalRotation() {
825     graphicsEvents.centerView(true, false, false);
826   }
827 
828   /**
829    * resetGlobalTranslation
830    */
831   private void resetGlobalTranslation() {
832     graphicsEvents.centerView(false, true, false);
833   }
834 
835   /**
836    * This functions centers the scene.
837    */
838   private void resetGlobalView() {
839     double radius = mainPanel.getDataRoot().getExtent();
840     Transform3D t3d = new Transform3D();
841     t3d.setScale(1.0d / (1.2d * radius));
842     baseTransformGroup.setTransform(t3d);
843     graphicsEvents.centerView(true, true, true);
844   }
845 
846   /**
847    * resetGlobalZoom
848    */
849   private void resetGlobalZoom() {
850     double radius = mainPanel.getDataRoot().getExtent();
851     baseTransformGroup.getTransform(baseTransform3D);
852     baseTransform3D.setScale(1.0d / (1.2d * radius));
853     baseTransformGroup.setTransform(baseTransform3D);
854   }
855 
856   /**
857    * Reset rotation.
858    */
859   private void resetRotation() {
860     MolecularAssembly sys = mainPanel.getHierarchy().getActive();
861     if (sys != null) {
862       sys.centerView(true, false);
863     }
864   }
865 
866   // **********************************************************************
867   // Color Commands
868 
869   /**
870    * resetRotationAndTranslation
871    */
872   private void resetRotationAndTranslation() {
873     MolecularAssembly sys = mainPanel.getHierarchy().getActive();
874     if (sys != null) {
875       sys.centerView(true, true);
876     }
877   }
878 
879   /**
880    * resetTranslation
881    */
882   private void resetTranslation() {
883     MolecularAssembly sys = mainPanel.getHierarchy().getActive();
884     if (sys != null) {
885       sys.centerView(false, true);
886     }
887   }
888 
889   /**
890    * rotateAboutCenter
891    */
892   private void rotateAboutCenter() {
893     MolecularAssembly sys = mainPanel.getHierarchy().getActive();
894     double[] center = sys.getMultiScaleCenter(false);
895     sys.rotateAbout(new Vector3d(center));
896   }
897 
898   // *********************************************************************
899   // Export Commands
900 
901   /**
902    * rotateAboutPick
903    */
904   private void rotateAboutPick() {
905     MSNode node = rendererPicking.getPick();
906     if (node != null) {
907       double[] center = node.getCenter(false);
908       MolecularAssembly m = node.getMSNode(MolecularAssembly.class);
909       m.rotateAbout(new Vector3d(center));
910     }
911   }
912 
913   /**
914    * savePrefs
915    */
916   void savePrefs() {
917     String c = GraphicsCanvas.class.getName();
918     prefs.putInt(c + ".bondwidth", RendererCache.bondwidth);
919     prefs.putInt(c + ".detail", RendererCache.detail);
920     prefs.putDouble(c + ".radius", RendererCache.radius);
921     prefs.put(c + ".mouse", mouseMode.name());
922     prefs.putBoolean(c + ".highlight", RendererCache.highlightSelections);
923     prefs.put(c + ".highlightColor", selectionColor.x + " " + selectionColor.y + " " + selectionColor.z);
924     prefs.putBoolean(c + ".labelAtoms", RendererCache.labelAtoms);
925     prefs.putBoolean(c + ".labelResidues", RendererCache.labelResidues);
926     prefs.putInt(c + ".labelSize", getGraphics2D().getFont().getSize());
927     Color fontColor = getGraphics2D().getColor();
928     prefs.put(c + ".labelColor", fontColor.getRed() + " " + fontColor.getGreen() + " " + fontColor.getBlue());
929     prefs.put(c + ".pickColor", pickingColor.x + " " + pickingColor.y + " " + pickingColor.x);
930     prefs.putBoolean(c + ".picking", rendererPicking.getPicking());
931     prefs.put(c + ".pickLevel", rendererPicking.getPickLevel());
932     prefs.put(c + ".userColor", userColor.x + " " + userColor.y + " " + userColor.z);
933     Color3f temp = new Color3f();
934     background.getColor(temp);
935     prefs.put(c + ".backgroundColor", temp.x + " " + temp.y + " " + temp.z);
936   }
937 
938   /**
939    * setAxisShowing
940    *
941    * @param b a boolean.
942    */
943   void setAxisShowing(boolean b) {
944     if (b && graphicsAxis == null) {
945       graphicsAxis = new GraphicsAxis(universe.getViewingPlatform(), bounds);
946     } else if (graphicsAxis != null) {
947       graphicsAxis.showAxis(b);
948     }
949   }
950 
951   /**
952    * setBackgroundColor
953    */
954   private void setBackgroundColor() {
955     Color3f col = new Color3f();
956     background.getColor(col);
957     Color currentColor = new Color(col.x, col.y, col.z);
958     Color newcolor = JColorChooser.showDialog(this, "Choose Background Color", currentColor);
959     if (newcolor != null) {
960       background.setColor(new Color3f(newcolor.getRGBColorComponents(new float[3])));
961     }
962   }
963 
964   // *********************************************************************
965   // Picking Commands
966 
967   /**
968    * Operates on the Active nodes.
969    *
970    * @param model String
971    */
972   private void setColorModel(String model) {
973     if (!RendererCache.colorModelHash.containsKey(model.toUpperCase())) {
974       return;
975     }
976     ColorModel colorModel = RendererCache.colorModelHash.get(model.toUpperCase());
977     ArrayList<MSNode> active = mainPanel.getHierarchy().getActiveNodes();
978     if (active == null) {
979       return;
980     }
981     renderer.arm(active, false, false, null, true, colorModel);
982   }
983 
984   /**
985    * setGraphicsPickingColor
986    */
987   private void setGraphicsPickingColor() {
988     Color newcolor =
989         JColorChooser.showDialog(
990             this,
991             "Choose Picking Color",
992             new Color(pickingColor.x, pickingColor.y, pickingColor.z));
993     if (newcolor != null) {
994       pickingColor = new Color3f(newcolor.getRGBColorComponents(new float[3]));
995     }
996   }
997 
998   /**
999    * Set the image format.
1000    *
1001    * @param format a {@link java.lang.String} object.
1002    */
1003   private void setImageFormat(String format) {
1004     if (format == null) {
1005       return;
1006     }
1007     format = format.toUpperCase();
1008     if (!imageFormatHash.containsKey(format)) {
1009       return;
1010     }
1011     imageFormat = imageFormatHash.get(format);
1012   }
1013 
1014   /**
1015    * setLabelFontColor
1016    */
1017   private void setLabelFontColor() {
1018     Color color = getGraphics2D().getColor();
1019     Color newColor = JColorChooser.showDialog(this, "Choose Font Color", color);
1020     if (newColor != null && newColor != color) {
1021       getGraphics2D().setPaint(newColor);
1022       if (RendererCache.labelAtoms || RendererCache.labelResidues) {
1023         repaint();
1024       }
1025       getStatusBar()
1026           .setText(
1027               "  Label Font Color Changed to ("
1028                   + newColor.getRed()
1029                   + ","
1030                   + newColor.getGreen()
1031                   + ","
1032                   + newColor.getBlue()
1033                   + ")");
1034     }
1035   }
1036 
1037   /**
1038    * setLabelFontSize
1039    */
1040   private void setLabelFontSize() {
1041     Font currentFont = getGraphics2D().getFont();
1042     int currentSize = currentFont.getSize();
1043     String size = Integer.toString(currentSize);
1044     size = JOptionPane.showInputDialog("Set the Font Size (8 to 64)", size);
1045     try {
1046       int f = Integer.parseInt(size);
1047       if (f < 8 || f > 64 || f == currentSize) {
1048         return;
1049       }
1050       Font newFont = new Font(currentFont.getName(), currentFont.getStyle(), f);
1051       getGraphics2D().setFont(newFont);
1052       if (RendererCache.labelAtoms || RendererCache.labelResidues) {
1053         repaint();
1054       }
1055       getStatusBar().setText("  Label Font Size Changed to " + newFont.getSize());
1056     } catch (NumberFormatException e) {
1057       logger.warning(e.getMessage());
1058     }
1059   }
1060 
1061   // *********************************************************************
1062   // Display Commands
1063 
1064   /**
1065    * Update labels.
1066    */
1067   void setLabelsUpdated() {
1068     repaint();
1069   }
1070 
1071   /**
1072    * Set the picking level.
1073    *
1074    * @param level a {@link java.lang.String} object.
1075    */
1076   private void setPickingLevel(String level) {
1077     if (level == null) {
1078       return;
1079     }
1080     level = level.toUpperCase();
1081     if (GraphicsPicking.pickLevelHash.containsKey(level)) {
1082       GraphicsPicking.PickLevel pickLevel = GraphicsPicking.pickLevelHash.get(level);
1083       switch (pickLevel) {
1084         case PICKATOM:
1085         case PICKBOND:
1086         case PICKANGLE:
1087         case PICKDIHEDRAL:
1088         case PICKRESIDUE:
1089         case PICKPOLYMER:
1090         case PICKSYSTEM:
1091           rendererPicking.setPickLevel(level);
1092           break;
1093         case MEASUREDISTANCE:
1094         case MEASUREANGLE:
1095         case MEASUREDIHEDRAL:
1096           rendererPicking.setPickLevel(level);
1097           rendererPicking.setPicking(true);
1098           mainPanel.getMainMenu().setPickBehavior(true);
1099           rendererPicking.clear();
1100           rendererPicking.resetCount();
1101           break;
1102         default:
1103           logger.warning("Unexpected PickingLevel");
1104       }
1105     }
1106   }
1107 
1108   /**
1109    * setSelectionColor
1110    */
1111   private void setSelectionColor() {
1112     Color newcolor =
1113         JColorChooser.showDialog(
1114             this,
1115             "Choose Selection Color",
1116             new Color(selectionColor.x, selectionColor.y, selectionColor.z));
1117     if (newcolor != null) {
1118       selectionColor = new Color3f(newcolor.getRGBColorComponents(new float[3]));
1119     }
1120     if (RendererCache.highlightSelections) {
1121       this.updateScene(
1122           mainPanel.getDataRoot(), false, false, null, true, RendererCache.ColorModel.SELECT);
1123     }
1124   }
1125 
1126   /**
1127    * setUserColor
1128    */
1129   private void setUserColor() {
1130     Color newcolor =
1131         JColorChooser.showDialog(
1132             this, "Choose User Color", new Color(userColor.x, userColor.y, userColor.z));
1133     if (newcolor != null) {
1134       userColor = new Color3f(newcolor.getRGBColorComponents(new float[3]));
1135     }
1136   }
1137 
1138   /**
1139    * Operates on the active nodes.
1140    *
1141    * @param model String
1142    */
1143   private void setViewModel(String model) {
1144     if (!RendererCache.viewModelHash.containsKey(model.toUpperCase())) {
1145       return;
1146     }
1147     RendererCache.ViewModel viewModel = RendererCache.viewModelHash.get(model.toUpperCase());
1148     if (viewModel == RendererCache.ViewModel.RESTRICT) {
1149       renderer.arm(mainPanel.getDataRoot(), false, true, viewModel, false, null);
1150       return;
1151     }
1152     ArrayList<MSNode> active = mainPanel.getHierarchy().getActiveNodes();
1153     if (active == null) {
1154       return;
1155     }
1156     renderer.arm(active, false, true, viewModel, false, null);
1157   }
1158 
1159   /**
1160    * updateSceneWait
1161    *
1162    * @param n             a {@link java.util.ArrayList} object.
1163    * @param t             a boolean.
1164    * @param v             a boolean.
1165    * @param newViewModel  a {@link ffx.potential.bonded.RendererCache.ViewModel} object.
1166    * @param c             a boolean.
1167    * @param newColorModel a {@link ffx.potential.bonded.RendererCache.ColorModel} object.
1168    */
1169   private void updateSceneWait(
1170       ArrayList<MSNode> n,
1171       boolean t,
1172       boolean v,
1173       ViewModel newViewModel,
1174       boolean c,
1175       ColorModel newColorModel) {
1176     if (n != null) {
1177       renderer.arm(n, t, v, newViewModel, c, newColorModel);
1178     }
1179     while (isSceneRendering() || isCacheFull()) {
1180       synchronized (this) {
1181         try {
1182           wait(1);
1183         } catch (Exception e) {
1184           logger.warning(e.getMessage());
1185         }
1186       }
1187     }
1188   }
1189 
1190   /**
1191    * viewWait
1192    *
1193    * @param viewModel a {@link ffx.potential.bonded.RendererCache.ViewModel} object.
1194    */
1195   private void viewWait(ViewModel viewModel) {
1196     if (viewModel == null) {
1197       logger.info("Null view.");
1198       return;
1199     }
1200     ArrayList<MSNode> nodes = mainPanel.getHierarchy().getActiveNodes();
1201     if (nodes == null) {
1202       logger.info("No active nodes.");
1203       return;
1204     }
1205     updateSceneWait(nodes, false, true, viewModel, false, null);
1206   }
1207 
1208   /**
1209    * colorWait
1210    *
1211    * @param colorModel a {@link ffx.potential.bonded.RendererCache.ColorModel} object.
1212    */
1213   private void colorWait(ColorModel colorModel) {
1214     if (colorModel == null) {
1215       logger.info("Null color.");
1216       return;
1217     }
1218     ArrayList<MSNode> nodes = mainPanel.getHierarchy().getActiveNodes();
1219     if (nodes == null) {
1220       logger.info("No active nodes.");
1221       return;
1222     }
1223     updateSceneWait(nodes, false, false, null, true, colorModel);
1224   }
1225 
1226   /**
1227    * updateSceneWait
1228    *
1229    * @param n             a {@link ffx.potential.bonded.MSNode} object.
1230    * @param t             a boolean.
1231    * @param v             a boolean.
1232    * @param newViewModel  a {@link ffx.potential.bonded.RendererCache.ViewModel} object.
1233    * @param c             a boolean.
1234    * @param newColorModel a {@link ffx.potential.bonded.RendererCache.ColorModel} object.
1235    */
1236   void updateSceneWait(
1237       MSNode n, boolean t, boolean v, ViewModel newViewModel, boolean c, ColorModel newColorModel) {
1238     if (n != null) {
1239       renderer.arm(n, t, v, newViewModel, c, newColorModel);
1240     }
1241     while (isSceneRendering() || isCacheFull()) {
1242       synchronized (this) {
1243         try {
1244           wait(1);
1245         } catch (Exception e) {
1246           String message = "Exception waiting for a Graphics operation.";
1247           logger.log(Level.WARNING, message, e);
1248         }
1249       }
1250     }
1251   }
1252 
1253   /**
1254    * zoomIn
1255    */
1256   private void zoomIn() {
1257     baseTransformGroup.getTransform(baseTransform3D);
1258     double scale = baseTransform3D.getScale() + 0.01;
1259     baseTransform3D.setScale(scale);
1260     baseTransformGroup.setTransform(baseTransform3D);
1261   }
1262 
1263   /**
1264    * zoomOut
1265    */
1266   private void zoomOut() {
1267     baseTransformGroup.getTransform(baseTransform3D);
1268     double scale = baseTransform3D.getScale() - 0.01;
1269     if (scale > 0.0) {
1270       baseTransform3D.setScale(scale);
1271       baseTransformGroup.setTransform(baseTransform3D);
1272     }
1273   }
1274 
1275   /**
1276    * The ImageFormat enum lists supported image formats.
1277    */
1278   public enum ImageFormat {
1279     BMP,
1280     GIF,
1281     JPEG,
1282     PNG,
1283     WBMP
1284   }
1285 
1286   /**
1287    * The MouseMode enum describes what system is affected by mouse drags.
1288    */
1289   public enum MouseMode {
1290     SYSTEMBELOWMOUSE,
1291     ACTIVESYSTEM
1292   }
1293 
1294   /**
1295    * The LeftButtonMode enum describes what the left mouse button does.
1296    */
1297   public enum LeftButtonMode {
1298     ROTATE,
1299     TRANSLATE,
1300     ZOOM
1301   }
1302 }