streamlined app and applet jmol console + event handling via component listener
[jalview.git] / src / jalview / appletgui / AppletJmol.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.5)
3  * Copyright (C) 2010 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
10  * 
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.appletgui;
19
20 import java.util.*;
21 import java.awt.*;
22 import java.awt.event.*;
23
24 import jalview.api.SequenceStructureBinding;
25 import jalview.datamodel.*;
26 import jalview.structure.*;
27 import jalview.io.*;
28
29 import org.jmol.api.*;
30
31 import org.jmol.popup.*;
32 import org.jmol.viewer.JmolConstants;
33
34 import jalview.schemes.*;
35
36 public class AppletJmol extends EmbmenuFrame implements
37 // StructureListener,
38         KeyListener, ActionListener, ItemListener, SequenceStructureBinding
39
40 {
41   Menu fileMenu = new Menu("File");
42
43   Menu viewMenu = new Menu("View");
44
45   Menu coloursMenu = new Menu("Colours");
46
47   Menu chainMenu = new Menu("Show Chain");
48
49   Menu helpMenu = new Menu("Help");
50
51   MenuItem mappingMenuItem = new MenuItem("View Mapping");
52
53   CheckboxMenuItem seqColour = new CheckboxMenuItem("By Sequence", true);
54
55   MenuItem chain = new MenuItem("By Chain");
56
57   MenuItem charge = new MenuItem("Charge & Cysteine");
58
59   MenuItem zappo = new MenuItem("Zappo");
60
61   MenuItem taylor = new MenuItem("Taylor");
62
63   MenuItem hydro = new MenuItem("Hydrophobicity");
64
65   MenuItem helix = new MenuItem("Helix Propensity");
66
67   MenuItem strand = new MenuItem("Strand Propensity");
68
69   MenuItem turn = new MenuItem("Turn Propensity");
70
71   MenuItem buried = new MenuItem("Buried Index");
72
73   MenuItem user = new MenuItem("User Defined Colours");
74
75   MenuItem jmolHelp = new MenuItem("Jmol Help");
76
77   Panel scriptWindow;
78
79   TextField inputLine;
80
81   TextArea history;
82
83   RenderPanel renderPanel;
84
85   AlignmentPanel ap;
86
87   String fileLoadingError;
88
89   boolean loadedInline;
90
91   // boolean colourBySequence = true;
92
93   FeatureRenderer fr = null;
94
95   AppletJmolBinding jmb;
96
97   /**
98    * datasource protocol for access to PDBEntry
99    */
100   String protocol = null;
101   /**
102    * Load a bunch of pdb entries associated with sequences in the alignment and display them - aligning them if necessary.
103    * @param pdbentries each pdb file (at least one needed)
104    * @param boundseqs each set of sequences for each pdb file (must match number of pdb files)
105    * @param boundchains the target pdb chain corresponding with each sequence associated with each pdb file (may be null at any level)
106    * @param align true/false
107    * @param ap associated alignment
108    * @param protocol how to get pdb data
109    */
110   public AppletJmol(PDBEntry[] pdbentries, SequenceI[][] boundseqs, String[][] boundchains, boolean align, AlignmentPanel ap, String protocol)
111   {
112     throw new Error("Not yet implemented.");
113   }
114   public AppletJmol(PDBEntry pdbentry, SequenceI[] seq, String[] chains,
115           AlignmentPanel ap, String protocol)
116   {
117     this.ap = ap;
118     jmb = new AppletJmolBinding(this, new PDBEntry[]
119     { pdbentry }, new SequenceI[][]{seq}, new String[][]{ chains }, protocol);
120     jmb.setColourBySequence(true);
121     if (pdbentry.getId() == null || pdbentry.getId().length() < 1)
122     {
123       if (protocol.equals(AppletFormatAdapter.PASTE))
124       {
125         pdbentry.setId("PASTED PDB"
126                 + (chains == null ? "_" : chains.toString()));
127       }
128       else
129       {
130         pdbentry.setId(pdbentry.getFile());
131       }
132     }
133
134     if (jalview.bin.JalviewLite.debug)
135     {
136       System.err
137               .println("AppletJmol: PDB ID is '" + pdbentry.getId() + "'");
138     }
139
140     String alreadyMapped = StructureSelectionManager
141             .getStructureSelectionManager().alreadyMappedToFile(
142                     pdbentry.getId());
143     MCview.PDBfile reader = null;
144     if (alreadyMapped != null)
145     {
146       reader = StructureSelectionManager.getStructureSelectionManager()
147               .setMapping(seq, chains, pdbentry.getFile(), protocol);
148       // PROMPT USER HERE TO ADD TO NEW OR EXISTING VIEW?
149       // FOR NOW, LETS JUST OPEN A NEW WINDOW
150     }
151     MenuBar menuBar = new MenuBar();
152     menuBar.add(fileMenu);
153     fileMenu.add(mappingMenuItem);
154     menuBar.add(viewMenu);
155     mappingMenuItem.addActionListener(this);
156     viewMenu.add(chainMenu);
157     menuBar.add(coloursMenu);
158     menuBar.add(helpMenu);
159
160     charge.addActionListener(this);
161     hydro.addActionListener(this);
162     chain.addActionListener(this);
163     seqColour.addItemListener(this);
164     zappo.addActionListener(this);
165     taylor.addActionListener(this);
166     helix.addActionListener(this);
167     strand.addActionListener(this);
168     turn.addActionListener(this);
169     buried.addActionListener(this);
170     user.addActionListener(this);
171
172     jmolHelp.addActionListener(this);
173
174     coloursMenu.add(seqColour);
175     coloursMenu.add(chain);
176     coloursMenu.add(charge);
177     coloursMenu.add(zappo);
178     coloursMenu.add(taylor);
179     coloursMenu.add(hydro);
180     coloursMenu.add(helix);
181     coloursMenu.add(strand);
182     coloursMenu.add(turn);
183     coloursMenu.add(buried);
184     coloursMenu.add(user);
185
186     helpMenu.add(jmolHelp);
187     this.setLayout(new BorderLayout());
188
189     setMenuBar(menuBar);
190
191     renderPanel = new RenderPanel();
192     embedMenuIfNeeded(renderPanel);
193     this.add(renderPanel, BorderLayout.CENTER);
194     scriptWindow = new Panel();
195     scriptWindow.setVisible(false);
196     // this.add(scriptWindow, BorderLayout.SOUTH);
197     
198     try
199     {
200       jmb.allocateViewer(renderPanel, true, ap.av.applet.getName()+"_jmol_",
201               ap.av.applet.getDocumentBase(), ap.av.applet.getCodeBase(),
202               "-applet", scriptWindow, null);
203     } catch (Exception e)
204     {
205       System.err
206               .println("Couldn't create a jmol viewer. Args to allocate viewer were:\nDocumentBase="
207                       + ap.av.applet.getDocumentBase()
208                       + "\nCodebase="
209                       + ap.av.applet.getCodeBase());
210       e.printStackTrace();
211       dispose();
212       return;
213     }
214     jmb.newJmolPopup(true, "Jmol", true);
215
216     this.addWindowListener(new WindowAdapter()
217     {
218       public void windowClosing(WindowEvent evt)
219       {
220         closeViewer();
221       }
222     });
223     if (pdbentry.getProperty()==null)
224     {
225       pdbentry.setProperty(new Hashtable());
226       pdbentry.getProperty().put("protocol", protocol);
227     }
228     if (pdbentry.getFile() != null)
229     {
230       // import structure data from pdbentry.getFile based on given protocol
231       if (protocol.equals(AppletFormatAdapter.PASTE))
232       {
233         // TODO: JAL-623 : correctly record file contents for matching up later
234         // pdbentry.getProperty().put("pdbfilehash",""+pdbentry.getFile().hashCode());
235         loadInline(pdbentry.getFile());
236       }
237       else if (protocol.equals(AppletFormatAdapter.FILE)
238               || protocol.equals(AppletFormatAdapter.URL))
239       {
240         jmb.viewer.openFile(pdbentry.getFile());
241       }
242       else
243       {
244         // probably CLASSLOADER based datasource..
245         // Try and get a reader on the datasource, and pass that to Jmol
246         try
247         {
248           java.io.Reader freader = null;
249           if (reader != null)
250           {
251             if (jalview.bin.JalviewLite.debug)
252             {
253               System.err
254                       .println("AppletJmol:Trying to reuse existing PDBfile IO parser.");
255             }
256             // re-use the one we opened earlier
257             freader = reader.getReader();
258           }
259           if (freader == null)
260           {
261             if (jalview.bin.JalviewLite.debug)
262             {
263               System.err
264                       .println("AppletJmol:Creating new PDBfile IO parser.");
265             }
266             FileParse fp = new FileParse(pdbentry.getFile(), protocol);
267             fp.mark();
268             // reader = new MCview.PDBfile(fp);
269             // could set ID, etc.
270             // if (!reader.isValid())
271             // {
272             // throw new Exception("Invalid datasource.
273             // "+reader.getWarningMessage());
274             // }
275             // fp.reset();
276             freader = fp.getReader();
277           }
278           if (freader == null)
279           {
280             throw new Exception(
281                     "Invalid datasource. Could not obtain Reader.");
282           }
283           jmb.viewer.openReader(pdbentry.getFile(), pdbentry.getId(),
284                   freader);
285         } catch (Exception e)
286         {
287           // give up!
288           System.err.println("Couldn't access pdbentry id="
289                   + pdbentry.getId() + " and file=" + pdbentry.getFile()
290                   + " using protocol=" + protocol);
291           e.printStackTrace();
292         }
293       }
294     }
295
296     jalview.bin.JalviewLite.addFrame(this, jmb.getViewerTitle(), 400, 400);
297   }
298
299   public void loadInline(String string)
300   {
301     loadedInline = true;
302     jmb.loadInline(string);
303   }
304
305   void setChainMenuItems(Vector chains)
306   {
307     chainMenu.removeAll();
308
309     MenuItem menuItem = new MenuItem("All");
310     menuItem.addActionListener(this);
311
312     chainMenu.add(menuItem);
313
314     CheckboxMenuItem menuItemCB;
315     for (int c = 0; c < chains.size(); c++)
316     {
317       menuItemCB = new CheckboxMenuItem(chains.elementAt(c).toString(),
318               true);
319       menuItemCB.addItemListener(this);
320       chainMenu.add(menuItemCB);
321     }
322   }
323
324   boolean allChainsSelected = false;
325
326   void centerViewer()
327   {
328     Vector toshow = new Vector();
329     String lbl;
330     int mlength, p, mnum;
331     for (int i = 0; i < chainMenu.getItemCount(); i++)
332     {
333       if (chainMenu.getItem(i) instanceof CheckboxMenuItem)
334       {
335         CheckboxMenuItem item = (CheckboxMenuItem) chainMenu.getItem(i);
336         if (item.getState())
337         {
338           toshow.addElement(item.getLabel());
339         }
340       }
341     }
342     jmb.centerViewer(toshow);
343   }
344
345   void closeViewer()
346   {
347     jmb.closeViewer();
348     jmb = null;
349     this.setVisible(false);
350   }
351
352   public void actionPerformed(ActionEvent evt)
353   {
354     if (evt.getSource() == mappingMenuItem)
355     {
356       jalview.appletgui.CutAndPasteTransfer cap = new jalview.appletgui.CutAndPasteTransfer(
357               false, null);
358       Frame frame = new Frame();
359       frame.add(cap);
360
361       StringBuffer sb = new StringBuffer();
362       try {
363       for (int s = 0; s < jmb.pdbentry.length; s++)
364       {
365         sb.append(StructureSelectionManager.getStructureSelectionManager()
366                 .printMapping(jmb.pdbentry[s].getFile()));
367         sb.append("\n");
368       }
369       cap.setText(sb.toString());
370       }
371       catch (OutOfMemoryError ex)
372       {
373         frame.dispose();
374         System.err.println("Out of memory when trying to create dialog box with sequence-structure mapping.");
375         return;
376       }
377       jalview.bin.JalviewLite.addFrame(frame, "PDB - Sequence Mapping",
378               550, 600);
379     }
380     else if (evt.getSource() == charge)
381     {
382       setEnabled(charge);
383       jmb.colourByCharge();
384     }
385
386     else if (evt.getSource() == chain)
387     {
388       setEnabled(chain);
389       jmb.colourByChain();
390     }
391     else if (evt.getSource() == zappo)
392     {
393       setEnabled(zappo);
394       jmb.setJalviewColourScheme(new ZappoColourScheme());
395     }
396     else if (evt.getSource() == taylor)
397     {
398       setEnabled(taylor);
399       jmb.setJalviewColourScheme(new TaylorColourScheme());
400     }
401     else if (evt.getSource() == hydro)
402     {
403       setEnabled(hydro);
404       jmb.setJalviewColourScheme(new HydrophobicColourScheme());
405     }
406     else if (evt.getSource() == helix)
407     {
408       setEnabled(helix);
409       jmb.setJalviewColourScheme(new HelixColourScheme());
410     }
411     else if (evt.getSource() == strand)
412     {
413       setEnabled(strand);
414       jmb.setJalviewColourScheme(new StrandColourScheme());
415     }
416     else if (evt.getSource() == turn)
417     {
418       setEnabled(turn);
419       jmb.setJalviewColourScheme(new TurnColourScheme());
420     }
421     else if (evt.getSource() == buried)
422     {
423       setEnabled(buried);
424       jmb.setJalviewColourScheme(new BuriedColourScheme());
425     }
426     else if (evt.getSource() == user)
427     {
428       setEnabled(user);
429       new UserDefinedColours(this);
430     }
431     else if (evt.getSource() == jmolHelp)
432     {
433       try
434       {
435         ap.av.applet.getAppletContext().showDocument(
436                 new java.net.URL(
437                         "http://jmol.sourceforge.net/docs/JmolUserGuide/"),
438                 "jmolHelp");
439       } catch (java.net.MalformedURLException ex)
440       {
441       }
442     }
443     else
444     {
445       allChainsSelected = true;
446       for (int i = 0; i < chainMenu.getItemCount(); i++)
447       {
448         if (chainMenu.getItem(i) instanceof CheckboxMenuItem)
449           ((CheckboxMenuItem) chainMenu.getItem(i)).setState(true);
450       }
451
452       centerViewer();
453       allChainsSelected = false;
454     }
455   }
456
457   /**
458    * tick or untick the seqColour menu entry depending upon if it was selected
459    * or not.
460    * 
461    * @param itm
462    */
463   private void setEnabled(MenuItem itm)
464   {
465     seqColour.setState(itm == seqColour);
466     jmb.setColourBySequence(itm == seqColour);
467   }
468
469   public void itemStateChanged(ItemEvent evt)
470   {
471     if (evt.getSource() == seqColour)
472     {
473       setEnabled(seqColour);
474       jmb.colourBySequence(ap.av.getShowSequenceFeatures(), ap.av.alignment);
475     }
476     else if (!allChainsSelected)
477       centerViewer();
478   }
479
480   public void keyPressed(KeyEvent evt)
481   {
482     if (evt.getKeyCode() == KeyEvent.VK_ENTER && scriptWindow.isVisible())
483     {
484       jmb.eval(inputLine.getText());
485       history.append("\n$ " + inputLine.getText());
486       inputLine.setText("");
487     }
488
489   }
490
491   public void keyTyped(KeyEvent evt)
492   {
493   }
494
495   public void keyReleased(KeyEvent evt)
496   {
497   }
498
499   public void updateColours(Object source)
500   {
501     AlignmentPanel ap = (AlignmentPanel) source;
502     jmb.colourBySequence(ap.av.getShowSequenceFeatures(), ap.av.alignment);
503   }
504
505   public void updateTitleAndMenus()
506   {
507     if (jmb.fileLoadingError != null && jmb.fileLoadingError.length() > 0)
508     {
509       repaint();
510       return;
511     }
512     setChainMenuItems(jmb.chainNames);
513     jmb.colourBySequence(ap.av.getShowSequenceFeatures(), ap.av.alignment);
514
515     setTitle(jmb.getViewerTitle());
516   }
517
518   public void showUrl(String url)
519   {
520     try
521     {
522       ap.av.applet.getAppletContext().showDocument(new java.net.URL(url),
523               "jmolOutput");
524     } catch (java.net.MalformedURLException ex)
525     {
526     }
527   }
528   Panel splitPane=null;
529   public void showConsole(boolean showConsole)
530   {
531     if (showConsole)
532     {
533       remove(renderPanel);
534       splitPane = new Panel();
535       
536       splitPane.setLayout(new java.awt.GridLayout(2,1));
537       splitPane.add(renderPanel);
538       splitPane.add(scriptWindow);
539       scriptWindow.setVisible(true);
540       this.add(splitPane, BorderLayout.CENTER);
541       splitPane.setVisible(true);
542       splitPane.validate();
543     } else {
544       scriptWindow.setVisible(false);
545       remove(splitPane);
546       add(renderPanel, BorderLayout.CENTER);
547       splitPane=null;
548     }
549     validate();
550   }
551
552   public float[][] functionXY(String functionName, int x, int y)
553   {
554     return null;
555   }
556
557   // /End JmolStatusListener
558   // /////////////////////////////
559
560   class RenderPanel extends Panel
561   {
562     Dimension currentSize = new Dimension();
563
564     Rectangle rectClip = new Rectangle();
565
566     public void update(Graphics g)
567     {
568       paint(g);
569     }
570
571     public void paint(Graphics g)
572     {
573       currentSize = this.getSize();
574       rectClip = g.getClipBounds();
575
576       if (jmb.viewer == null)
577       {
578         g.setColor(Color.black);
579         g.fillRect(0, 0, currentSize.width, currentSize.height);
580         g.setColor(Color.white);
581         g.setFont(new Font("Verdana", Font.BOLD, 14));
582         g.drawString("Retrieving PDB data....", 20, currentSize.height / 2);
583       }
584       else
585       {
586         jmb.viewer.renderScreenImage(g, currentSize, rectClip);
587       }
588     }
589   }
590
591   /*
592    * @Override public Color getColour(int atomIndex, int pdbResNum, String
593    * chain, String pdbId) { return jmb.getColour(atomIndex, pdbResNum, chain,
594    * pdbId); }
595    * 
596    * @Override public String[] getPdbFile() { return jmb.getPdbFile(); }
597    * 
598    * @Override public void highlightAtom(int atomIndex, int pdbResNum, String
599    * chain, String pdbId) { jmb.highlightAtom(atomIndex, pdbResNum, chain,
600    * pdbId);
601    * 
602    * }
603    * 
604    * @Override public void mouseOverStructure(int atomIndex, String strInfo) {
605    * jmb.mouseOverStructure(atomIndex, strInfo);
606    * 
607    * }
608    */
609   public void setJalviewColourScheme(UserColourScheme ucs)
610   {
611     jmb.setJalviewColourScheme(ucs);
612   }
613 }