refactor to abstract jmol/jalview binding and applet and application specific sequenc...
[jalview.git] / src / jalview / gui / AppJmol.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.gui;
19
20 import java.util.regex.*;
21 import java.util.*;
22 import java.awt.*;
23 import javax.swing.*;
24 import javax.swing.event.*;
25 import java.awt.event.*;
26 import java.io.*;
27
28 import jalview.jbgui.GStructureViewer;
29 import jalview.api.SequenceStructureBinding;
30 import jalview.bin.Cache;
31 import jalview.datamodel.*;
32 import jalview.gui.*;
33 import jalview.structure.*;
34 import jalview.datamodel.PDBEntry;
35 import jalview.io.*;
36 import jalview.schemes.*;
37 import jalview.ws.ebi.EBIFetchClient;
38
39 import org.jmol.api.*;
40 import org.jmol.adapter.smarter.SmarterJmolAdapter;
41 import org.jmol.popup.*;
42 import org.jmol.viewer.JmolConstants;
43
44 public class AppJmol extends GStructureViewer implements Runnable,
45         SequenceStructureBinding
46
47 {
48   AppJmolBinding jmb;
49
50   ScriptWindow scriptWindow;
51
52   JSplitPane splitPane;
53
54   RenderPanel renderPanel;
55
56   AlignmentPanel ap;
57
58   Vector atomsPicked = new Vector();
59
60   public AppJmol(String file, String id, SequenceI[] seq,
61           AlignmentPanel ap, String loadStatus, Rectangle bounds)
62   {
63     this(file, id, seq, ap, loadStatus, bounds, null);
64   }
65
66   public AppJmol(String file, String id, SequenceI[] seq,
67           AlignmentPanel ap, String loadStatus, Rectangle bounds,
68           String viewid)
69   {
70     PDBEntry pdbentry = new PDBEntry();
71     pdbentry.setFile(file);
72     pdbentry.setId(id);
73     // / TODO: check if protocol is needed to be set, and if chains are
74     // autodiscovered.
75     jmb = new AppJmolBinding(this, new PDBEntry[]
76     { pdbentry }, seq, null, null);
77
78     jmb.setLoadingFromArchive(true);
79     this.ap = ap;
80     this.setBounds(bounds);
81     jmb.setColourBySequence(false);
82     seqColour.setSelected(false);
83     viewId = viewid;
84     // jalview.gui.Desktop.addInternalFrame(this, "Loading File",
85     // bounds.width,bounds.height);
86
87     this.addInternalFrameListener(new InternalFrameAdapter()
88     {
89       public void internalFrameClosing(InternalFrameEvent internalFrameEvent)
90       {
91         closeViewer();
92       }
93     });
94     initJmol(loadStatus); // pdbentry, seq, JBPCHECK!
95
96   }
97
98   public AppJmol(PDBEntry pdbentry, SequenceI[] seq, String[] chains,
99           AlignmentPanel ap)
100   {
101     // ////////////////////////////////
102     // Is the pdb file already loaded?
103     String alreadyMapped = StructureSelectionManager
104             .getStructureSelectionManager().alreadyMappedToFile(
105                     pdbentry.getId());
106
107     if (alreadyMapped != null)
108     {
109       int option = JOptionPane
110               .showInternalConfirmDialog(
111                       Desktop.desktop,
112                       pdbentry.getId()
113                               + " is already displayed."
114                               + "\nDo you want to re-use this viewer ?",
115                       "Map Sequences to Visible Window: "
116                               + pdbentry.getId(), JOptionPane.YES_NO_OPTION);
117
118       if (option == JOptionPane.YES_OPTION)
119       {
120         StructureSelectionManager.getStructureSelectionManager()
121                 .setMapping(seq, chains, alreadyMapped,
122                         AppletFormatAdapter.FILE);
123         if (ap.seqPanel.seqCanvas.fr != null)
124         {
125           ap.seqPanel.seqCanvas.fr.featuresAdded();
126           ap.paintAlignment(true);
127         }
128
129         // Now this AppJmol is mapped to new sequences. We must add them to
130         // the exisiting array
131         JInternalFrame[] frames = Desktop.instance.getAllFrames();
132
133         for (int i = 0; i < frames.length; i++)
134         {
135           if (frames[i] instanceof AppJmol)
136           {
137             AppJmol topJmol = ((AppJmol) frames[i]);
138             // JBPNOTE: this looks like a binding routine, rather than a gui
139             // routine
140             for (int pe = 0; pe < topJmol.jmb.pdbentry.length; pe++)
141             {
142               if (topJmol.jmb.pdbentry[pe].getFile().equals(alreadyMapped))
143               {
144                 topJmol.jmb.addSequence(seq);
145                 break;
146               }
147             }
148           }
149         }
150
151         return;
152       }
153     }
154     // /////////////////////////////////
155
156     jmb = new AppJmolBinding(this, new PDBEntry[]
157     { pdbentry }, seq, null, null);
158     this.ap = ap;
159     setSize(400, 400); // probably should be a configurable/dynamic default here
160
161     if (pdbentry.getFile() != null)
162     {
163       initJmol("load \"" + pdbentry.getFile() + "\"");
164     }
165     else
166     {
167       Thread worker = new Thread(this);
168       worker.start();
169     }
170
171     this.addInternalFrameListener(new InternalFrameAdapter()
172     {
173       public void internalFrameClosing(InternalFrameEvent internalFrameEvent)
174       {
175         closeViewer();
176       }
177     });
178
179   }
180
181   void initJmol(String command)
182   {
183     jmb.setFinishedInit(false);
184     renderPanel = new RenderPanel();
185     // TODO: consider waiting until the structure/view is fully loaded before
186     // displaying
187     this.getContentPane().add(renderPanel, java.awt.BorderLayout.CENTER);
188     jalview.gui.Desktop.addInternalFrame(this, jmb.getViewerTitle(),
189             getBounds().width, getBounds().height);
190     jmb.allocateViewer(renderPanel, "", null, null, "");
191     jmb.newJmolPopup(true, "Jmol", true);
192     jmb.evalStateCommand(command);
193     jmb.setFinishedInit(true);
194   }
195
196   void setChainMenuItems(Vector chains)
197   {
198     chainMenu.removeAll();
199     if (chains==null)
200     {
201       return;
202     }
203     JMenuItem menuItem = new JMenuItem("All");
204     menuItem.addActionListener(new ActionListener()
205     {
206       public void actionPerformed(ActionEvent evt)
207       {
208         allChainsSelected = true;
209         for (int i = 0; i < chainMenu.getItemCount(); i++)
210         {
211           if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
212             ((JCheckBoxMenuItem) chainMenu.getItem(i)).setSelected(true);
213         }
214         centerViewer();
215         allChainsSelected = false;
216       }
217     });
218
219     chainMenu.add(menuItem);
220
221     for (int c = 0; c < chains.size(); c++)
222     {
223       menuItem = new JCheckBoxMenuItem(chains.elementAt(c).toString(), true);
224       menuItem.addItemListener(new ItemListener()
225       {
226         public void itemStateChanged(ItemEvent evt)
227         {
228           if (!allChainsSelected)
229             centerViewer();
230         }
231       });
232
233       chainMenu.add(menuItem);
234     }
235   }
236
237   boolean allChainsSelected = false;
238
239   void centerViewer()
240   {
241     Vector toshow = new Vector();
242     String lbl;
243     int mlength, p, mnum;
244     for (int i = 0; i < chainMenu.getItemCount(); i++)
245     {
246       if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
247       {
248         JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i);
249         if (item.isSelected())
250         {
251           toshow.addElement(item.getText());
252         }
253       }
254     }
255     jmb.centerViewer(toshow);
256   }
257
258   void closeViewer()
259   {
260     jmb.closeViewer();
261     // TODO: check for memory leaks where instance isn't finalised because jmb
262     // holds a reference to the window
263     jmb = null;
264   }
265
266   public void run()
267   {
268     String pdbid = "";
269     // todo - record which pdbids were successfuly imported.
270     StringBuffer errormsgs = new StringBuffer(), files = new StringBuffer();
271     try
272     {
273       // TODO: replace with reference fetching/transfer code (validate PDBentry
274       // as a DBRef?)
275       jalview.ws.dbsources.Pdb pdbclient = new jalview.ws.dbsources.Pdb();
276       for (int pi = 0; pi < jmb.pdbentry.length; pi++)
277       {
278         AlignmentI pdbseq;
279         if ((pdbseq = pdbclient.getSequenceRecords(pdbid = jmb.pdbentry[pi]
280                 .getId())) != null)
281         {
282           String file;
283           // just transfer the file name from the first sequence's first
284           // PDBEntry
285           jmb.pdbentry[pi].setFile(file = ((PDBEntry) pdbseq.getSequenceAt(
286                   0).getPDBId().elementAt(0)).getFile());
287           files.append("\"" + file + "\"");
288         }
289         else
290         {
291           errormsgs.append("'" + pdbid + "' ");
292         }
293       }
294     } catch (OutOfMemoryError oomerror)
295     {
296       new OOMWarning("Retrieving PDB id " + pdbid, oomerror);
297     } catch (Exception ex)
298     {
299       ex.printStackTrace();
300       errormsgs.append("'" + pdbid + "'");
301     }
302     if (errormsgs.length() > 0)
303     {
304
305       JOptionPane.showInternalMessageDialog(Desktop.desktop,
306               "The following pdb entries could not be retrieved from the PDB:\n"
307                       + errormsgs.toString()
308                       + "\nPlease try downloading them manually.",
309               "Couldn't load file", JOptionPane.ERROR_MESSAGE);
310
311     }
312     if (files.length() > 0)
313     {
314       try
315       {
316         initJmol("load FILES " + files.toString());
317       } catch (OutOfMemoryError oomerror)
318       {
319         new OOMWarning("When trying to open the Jmol viewer!", oomerror);
320         Cache.log.debug("File locations are " + files);
321       } catch (Exception ex)
322       {
323         Cache.log.error("Couldn't open Jmol viewer!", ex);
324       }
325     }
326   }
327
328   public void pdbFile_actionPerformed(ActionEvent actionEvent)
329   {
330     JalviewFileChooser chooser = new JalviewFileChooser(jalview.bin.Cache
331             .getProperty("LAST_DIRECTORY"));
332
333     chooser.setFileView(new JalviewFileView());
334     chooser.setDialogTitle("Save PDB File");
335     chooser.setToolTipText("Save");
336
337     int value = chooser.showSaveDialog(this);
338
339     if (value == JalviewFileChooser.APPROVE_OPTION)
340     {
341       try
342       {
343         // TODO: cope with multiple PDB files in view 
344         BufferedReader in = new BufferedReader(new FileReader(jmb.getPdbFile()[0]));
345         File outFile = chooser.getSelectedFile();
346
347         PrintWriter out = new PrintWriter(new FileOutputStream(outFile));
348         String data;
349         while ((data = in.readLine()) != null)
350         {
351           if (!(data.indexOf("<PRE>") > -1 || data.indexOf("</PRE>") > -1))
352           {
353             out.println(data);
354           }
355         }
356         out.close();
357       } catch (Exception ex)
358       {
359         ex.printStackTrace();
360       }
361     }
362   }
363
364   public void viewMapping_actionPerformed(ActionEvent actionEvent)
365   {
366     jalview.gui.CutAndPasteTransfer cap = new jalview.gui.CutAndPasteTransfer();
367     jalview.gui.Desktop.addInternalFrame(cap, "PDB - Sequence Mapping",
368             550, 600);
369     for (int pdbe = 0; pdbe<jmb.pdbentry.length; pdbe++) {
370       cap.appendText(StructureSelectionManager.getStructureSelectionManager()
371             .printMapping(jmb.pdbentry[pdbe].getFile()));
372       cap.appendText("\n");
373     }
374   }
375
376   /**
377    * DOCUMENT ME!
378    * 
379    * @param e
380    *          DOCUMENT ME!
381    */
382   public void eps_actionPerformed(ActionEvent e)
383   {
384     makePDBImage(jalview.util.ImageMaker.EPS);
385   }
386
387   /**
388    * DOCUMENT ME!
389    * 
390    * @param e
391    *          DOCUMENT ME!
392    */
393   public void png_actionPerformed(ActionEvent e)
394   {
395     makePDBImage(jalview.util.ImageMaker.PNG);
396   }
397
398   void makePDBImage(int type)
399   {
400     int width = getWidth();
401     int height = getHeight();
402
403     jalview.util.ImageMaker im;
404
405     if (type == jalview.util.ImageMaker.PNG)
406     {
407       im = new jalview.util.ImageMaker(this, jalview.util.ImageMaker.PNG,
408               "Make PNG image from view", width, height, null, null);
409     }
410     else
411     {
412       im = new jalview.util.ImageMaker(this, jalview.util.ImageMaker.EPS,
413               "Make EPS file from view", width, height, null, this
414                       .getTitle());
415     }
416
417     if (im.getGraphics() != null)
418     {
419       Rectangle rect = new Rectangle(width, height);
420       jmb.viewer.renderScreenImage(im.getGraphics(), rect.getSize(), rect);
421       im.writeImage();
422     }
423   }
424
425   public void seqColour_actionPerformed(ActionEvent actionEvent)
426   {
427     jmb.setColourBySequence(seqColour.isSelected());
428     // Set the colour using the current view for the associated alignframe
429     jmb.colourBySequence(ap.alignFrame.viewport.showSequenceFeatures, ap.alignFrame.viewport.alignment);
430   }
431
432   public void chainColour_actionPerformed(ActionEvent actionEvent)
433   {
434     chainColour.setSelected(true);
435     jmb.colourByChain();
436   }
437
438   public void chargeColour_actionPerformed(ActionEvent actionEvent)
439   {
440     chargeColour.setSelected(true);
441     jmb.colourByCharge();
442   }
443
444   public void zappoColour_actionPerformed(ActionEvent actionEvent)
445   {
446     zappoColour.setSelected(true);
447     jmb.setJalviewColourScheme(new ZappoColourScheme());
448   }
449
450   public void taylorColour_actionPerformed(ActionEvent actionEvent)
451   {
452     taylorColour.setSelected(true);
453     jmb.setJalviewColourScheme(new TaylorColourScheme());
454   }
455
456   public void hydroColour_actionPerformed(ActionEvent actionEvent)
457   {
458     hydroColour.setSelected(true);
459     jmb.setJalviewColourScheme(new HydrophobicColourScheme());
460   }
461
462   public void helixColour_actionPerformed(ActionEvent actionEvent)
463   {
464     helixColour.setSelected(true);
465     jmb.setJalviewColourScheme(new HelixColourScheme());
466   }
467
468   public void strandColour_actionPerformed(ActionEvent actionEvent)
469   {
470     strandColour.setSelected(true);
471     jmb.setJalviewColourScheme(new StrandColourScheme());
472   }
473
474   public void turnColour_actionPerformed(ActionEvent actionEvent)
475   {
476     turnColour.setSelected(true);
477     jmb.setJalviewColourScheme(new TurnColourScheme());
478   }
479
480   public void buriedColour_actionPerformed(ActionEvent actionEvent)
481   {
482     buriedColour.setSelected(true);
483     jmb.setJalviewColourScheme(new BuriedColourScheme());
484   }
485
486
487   public void userColour_actionPerformed(ActionEvent actionEvent)
488   {
489     userColour.setSelected(true);
490     new UserDefinedColours(this, null);
491   }
492
493   public void backGround_actionPerformed(ActionEvent actionEvent)
494   {
495     java.awt.Color col = JColorChooser.showDialog(this,
496             "Select Background Colour", null);
497     if (col != null)
498     {
499       jmb.setBackgroundColour(col);
500     }
501   }
502
503   public void jmolHelp_actionPerformed(ActionEvent actionEvent)
504   {
505     try
506     {
507       jalview.util.BrowserLauncher
508               .openURL("http://jmol.sourceforge.net/docs/JmolUserGuide/");
509     } catch (Exception ex)
510     {
511     }
512   }
513
514   public void showConsole(boolean showConsole)
515   {
516     if (scriptWindow == null)
517       scriptWindow = new ScriptWindow(this);
518
519     if (showConsole)
520     {
521       if (splitPane == null)
522       {
523         splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
524         splitPane.setTopComponent(renderPanel);
525         splitPane.setBottomComponent(scriptWindow);
526         this.getContentPane().add(splitPane, BorderLayout.CENTER);
527       }
528
529       splitPane.setDividerLocation(getHeight() - 200);
530       splitPane.validate();
531     }
532     else
533     {
534       if (splitPane != null)
535         splitPane.setVisible(false);
536
537       splitPane = null;
538
539       this.getContentPane().add(renderPanel, BorderLayout.CENTER);
540     }
541
542     validate();
543   }
544
545   class RenderPanel extends JPanel
546   {
547     final Dimension currentSize = new Dimension();
548
549     final Rectangle rectClip = new Rectangle();
550
551     public void paintComponent(Graphics g)
552     {
553       getSize(currentSize);
554       g.getClipBounds(rectClip);
555
556       if (jmb.fileLoadingError != null)
557       {
558         g.setColor(Color.black);
559         g.fillRect(0, 0, currentSize.width, currentSize.height);
560         g.setColor(Color.white);
561         g.setFont(new Font("Verdana", Font.BOLD, 14));
562         g.drawString("Error loading file...", 20,
563                 currentSize.height / 2);
564         StringBuffer sb = new StringBuffer();
565         int lines=0;
566         for (int e=0;e<jmb.pdbentry.length; e++)
567         {
568           sb.append(jmb.pdbentry[e].getId());
569           if (e<jmb.pdbentry.length-1) {
570             sb.append(",");
571           }
572
573           if (e==jmb.pdbentry.length-1 || sb.length()>20)
574           {
575             lines++;
576             g.drawString(sb.toString(), 20, currentSize.height/2 - lines*g.getFontMetrics().getHeight());
577           }
578         }
579       }
580       else
581         if (jmb == null || jmb.viewer == null || !jmb.isFinishedInit())
582         {
583           g.setColor(Color.black);
584           g.fillRect(0, 0, currentSize.width, currentSize.height);
585           g.setColor(Color.white);
586           g.setFont(new Font("Verdana", Font.BOLD, 14));
587           g.drawString("Retrieving PDB data....", 20, currentSize.height / 2);
588         }
589         else 
590       {
591         jmb.viewer.renderScreenImage(g, currentSize, rectClip);
592       }
593     }
594   }
595
596   String viewId = null;
597
598   public String getViewId()
599   {
600     if (viewId == null)
601     {
602       viewId = System.currentTimeMillis() + "." + this.hashCode();
603     }
604     return viewId;
605   }
606   public void updateTitleAndMenus()
607   {
608     if (jmb.fileLoadingError != null && jmb.fileLoadingError.length() > 0)
609     {
610       repaint();
611       return;
612     }
613     setChainMenuItems(jmb.chainNames);
614     jmb.colourBySequence(ap.av.getShowSequenceFeatures(), ap.av.alignment);
615
616     this.setTitle(jmb.getViewerTitle());
617   }
618
619   public void setJalviewColourScheme(ColourSchemeI ucs)
620   {
621     jmb.setJalviewColourScheme(ucs);
622     
623   }
624
625 }