JAL-3626 from JAL-3253-applet StructureViewer/Chooser
[jalview.git] / src / jalview / gui / StructureViewer.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
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
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.gui;
22
23 import jalview.api.structures.JalviewStructureDisplayI;
24 import jalview.bin.Cache;
25 import jalview.datamodel.PDBEntry;
26 import jalview.datamodel.SequenceI;
27 import jalview.datamodel.StructureViewerModel;
28 import jalview.structure.StructureMapping;
29 import jalview.structure.StructureSelectionManager;
30 import jalview.util.MessageManager;
31 import jalview.util.Platform;
32 import jalview.ws.DBRefFetcher;
33 import jalview.ws.sifts.SiftsSettings;
34
35 import java.awt.Rectangle;
36 import java.util.ArrayList;
37 import java.util.HashMap;
38 import java.util.LinkedHashMap;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Map.Entry;
42
43 /**
44  * A proxy for handling structure viewers, that orchestrates adding selected
45  * structures, associated with sequences in Jalview, to an existing viewer, or
46  * opening a new one. Currently supports either Jmol or Chimera as the structure
47  * viewer.
48  * 
49  * @author jprocter
50  */
51 public class StructureViewer
52 {
53   
54   static
55   {
56     Platform.loadStaticResource("core/core_jvjmol.z.js",
57             "org.jmol.viewer.Viewer");
58   }
59
60
61   
62   
63   private static final String UNKNOWN_VIEWER_TYPE = "Unknown structure viewer type ";
64
65   StructureSelectionManager ssm;
66
67   /**
68    * decide if new structures are aligned to existing ones
69    */
70   private boolean superposeAdded = true;
71
72   public enum ViewerType
73   {
74     JMOL, CHIMERA
75   };
76
77   /**
78    * Constructor
79    * 
80    * @param structureSelectionManager
81    */
82   public StructureViewer(StructureSelectionManager structureSelectionManager)
83   {
84     ssm = structureSelectionManager;
85   }
86
87   /**
88    * Factory to create a proxy for modifying existing structure viewer
89    * 
90    */
91   public static StructureViewer reconfigure(
92           JalviewStructureDisplayI display)
93   {
94     StructureViewer sv = new StructureViewer(display.getBinding().getSsm());
95     sv.sview = display;
96     return sv;
97   }
98
99   @Override
100   public String toString()
101   {
102     if (sview != null)
103     {
104       return sview.toString();
105     }
106     return "New View";
107   }
108   public ViewerType getViewerType()
109   {
110     String viewType = Cache.getDefault(Preferences.STRUCTURE_DISPLAY,
111             ViewerType.JMOL.name());
112     return ViewerType.valueOf(viewType);
113   }
114
115   public void setViewerType(ViewerType type)
116   {
117     Cache.setProperty(Preferences.STRUCTURE_DISPLAY, type.name());
118   }
119
120   /**
121    * View multiple PDB entries, each with associated sequences
122    * 
123    * @param pdbs
124    * @param seqs
125    * @param ap
126    * @return
127    */
128   public JalviewStructureDisplayI viewStructures(PDBEntry[] pdbs,
129           SequenceI[] seqs, AlignmentPanel ap)
130   {
131     JalviewStructureDisplayI viewer = onlyOnePdb(pdbs, seqs, ap);
132     if (viewer != null)
133     {
134       /*
135        * user added structure to an existing viewer - all done
136        */
137       return viewer;
138     }
139
140     ViewerType viewerType = getViewerType();
141
142     Map<PDBEntry, SequenceI[]> seqsForPdbs = getSequencesForPdbs(pdbs,
143             seqs);
144     PDBEntry[] pdbsForFile = seqsForPdbs.keySet().toArray(
145             new PDBEntry[seqsForPdbs.size()]);
146     SequenceI[][] theSeqs = seqsForPdbs.values().toArray(
147             new SequenceI[seqsForPdbs.size()][]);
148     if (sview != null)
149     {
150       sview.setAlignAddedStructures(superposeAdded);
151       new Thread(new Runnable()
152       {
153         @Override
154         public void run()
155         {
156
157           for (int pdbep = 0; pdbep < pdbsForFile.length; pdbep++)
158           {
159             PDBEntry pdb = pdbsForFile[pdbep];
160             if (!sview.addAlreadyLoadedFile(theSeqs[pdbep], null, ap,
161                     pdb.getId()))
162             {
163               sview.addToExistingViewer(pdb, theSeqs[pdbep], null, ap,
164                       pdb.getId());
165             }
166           }
167
168           sview.updateTitleAndMenus();
169         }
170       }).start();
171       return sview;
172     }
173
174     if (viewerType.equals(ViewerType.JMOL))
175     {
176       sview = new AppJmol(ap, superposeAdded, pdbsForFile, theSeqs);
177     }
178     else if (viewerType.equals(ViewerType.CHIMERA))
179     {
180       sview = new ChimeraViewFrame(pdbsForFile, superposeAdded, theSeqs,
181               ap);
182     }
183     else
184     {
185       Cache.log.error(UNKNOWN_VIEWER_TYPE + getViewerType().toString());
186     }
187     return sview;
188   }
189
190   /**
191    * Converts the list of selected PDB entries (possibly including duplicates
192    * for multiple chains), and corresponding sequences, into a map of sequences
193    * for each distinct PDB file. Returns null if either argument is null, or
194    * their lengths do not match.
195    * 
196    * @param pdbs
197    * @param seqs
198    * @return
199    */
200   Map<PDBEntry, SequenceI[]> getSequencesForPdbs(PDBEntry[] pdbs,
201           SequenceI[] seqs)
202   {
203     if (pdbs == null || seqs == null || pdbs.length != seqs.length)
204     {
205       return null;
206     }
207
208     /*
209      * we want only one 'representative' PDBEntry per distinct file name
210      * (there may be entries for distinct chains)
211      */
212     Map<String, PDBEntry> pdbsSeen = new HashMap<>();
213
214     /*
215      * LinkedHashMap preserves order of PDB entries (significant if they
216      * will get superimposed to the first structure)
217      */
218     Map<PDBEntry, List<SequenceI>> pdbSeqs = new LinkedHashMap<>();
219     for (int i = 0; i < pdbs.length; i++)
220     {
221       PDBEntry pdb = pdbs[i];
222       SequenceI seq = seqs[i];
223       String pdbFile = pdb.getFile();
224       if (pdbFile == null || pdbFile.length() == 0)
225       {
226         pdbFile = pdb.getId();
227       }
228       if (!pdbsSeen.containsKey(pdbFile))
229       {
230         pdbsSeen.put(pdbFile, pdb);
231         pdbSeqs.put(pdb, new ArrayList<SequenceI>());
232       }
233       else
234       {
235         pdb = pdbsSeen.get(pdbFile);
236       }
237       List<SequenceI> seqsForPdb = pdbSeqs.get(pdb);
238       if (!seqsForPdb.contains(seq))
239       {
240         seqsForPdb.add(seq);
241       }
242     }
243
244     /*
245      * convert to Map<PDBEntry, SequenceI[]>
246      */
247     Map<PDBEntry, SequenceI[]> result = new LinkedHashMap<>();
248     for (Entry<PDBEntry, List<SequenceI>> entry : pdbSeqs.entrySet())
249     {
250       List<SequenceI> theSeqs = entry.getValue();
251       result.put(entry.getKey(),
252               theSeqs.toArray(new SequenceI[theSeqs.size()]));
253     }
254
255     return result;
256   }
257
258   /**
259    * A strictly temporary method pending JAL-1761 refactoring. Determines if all
260    * the passed PDB entries are the same (this is the case if selected sequences
261    * to view structure for are chains of the same structure). If so, calls the
262    * single-pdb version of viewStructures and returns the viewer, else returns
263    * null.
264    * 
265    * @param pdbs
266    * @param seqsForPdbs
267    * @param ap
268    * @return
269    */
270   private JalviewStructureDisplayI onlyOnePdb(PDBEntry[] pdbs,
271           SequenceI[] seqsForPdbs, AlignmentPanel ap)
272   {
273     List<SequenceI> seqs = new ArrayList<>();
274     if (pdbs == null || pdbs.length == 0)
275     {
276       return null;
277     }
278     int i = 0;
279     String firstFile = pdbs[0].getFile();
280     for (PDBEntry pdb : pdbs)
281     {
282       String pdbFile = pdb.getFile();
283       if (pdbFile == null || !pdbFile.equals(firstFile))
284       {
285         return null;
286       }
287       SequenceI pdbseq = seqsForPdbs[i++];
288       if (pdbseq != null)
289       {
290         seqs.add(pdbseq);
291       }
292     }
293     return viewStructures(pdbs[0], seqs.toArray(new SequenceI[seqs.size()]),
294             ap);
295   }
296
297   JalviewStructureDisplayI sview = null;
298
299   public JalviewStructureDisplayI viewStructures(PDBEntry pdb,
300           SequenceI[] seqsForPdb, AlignmentPanel ap)
301   {
302     if (sview != null)
303     {
304       sview.setAlignAddedStructures(superposeAdded);
305       String pdbId = pdb.getId();
306       if (!sview.addAlreadyLoadedFile(seqsForPdb, null, ap, pdbId))
307       {
308         sview.addToExistingViewer(pdb, seqsForPdb, null, ap, pdbId);
309       }
310       sview.updateTitleAndMenus();
311       sview.raiseViewer();
312       return sview;
313     }
314     ViewerType viewerType = getViewerType();
315     if (viewerType.equals(ViewerType.JMOL))
316     {
317       sview = new AppJmol(pdb, seqsForPdb, null, ap);
318     }
319     else if (viewerType.equals(ViewerType.CHIMERA))
320     {
321       sview = new ChimeraViewFrame(pdb, seqsForPdb, null, ap);
322     }
323     else
324     {
325       Cache.log.error(UNKNOWN_VIEWER_TYPE + getViewerType().toString());
326     }
327     return sview;
328   }
329
330   /**
331    * Create a new panel controlling a structure viewer.
332    * 
333    * @param type
334    * @param pdbf
335    * @param id
336    * @param sq
337    * @param alignPanel
338    * @param viewerData
339    * @param fileloc
340    * @param rect
341    * @param vid
342    * @return
343    */
344   public JalviewStructureDisplayI createView(ViewerType type, String[] pdbf,
345           String[] id, SequenceI[][] sq, AlignmentPanel alignPanel,
346           StructureViewerModel viewerData, String fileloc, Rectangle rect,
347           String vid)
348   {
349     final boolean useinViewerSuperpos = viewerData.isAlignWithPanel();
350     final boolean usetoColourbyseq = viewerData.isColourWithAlignPanel();
351     final boolean viewerColouring = viewerData.isColourByViewer();
352
353     switch (type)
354     {
355     case JMOL:
356       sview = new AppJmol(pdbf, id, sq, alignPanel, usetoColourbyseq,
357               useinViewerSuperpos, viewerColouring, fileloc, rect, vid);
358       break;
359     case CHIMERA:
360       Cache.log.error(
361               "Unsupported structure viewer type " + type.toString());
362       break;
363     default:
364       Cache.log.error(UNKNOWN_VIEWER_TYPE + type.toString());
365     }
366     return sview;
367   }
368
369   public boolean isBusy()
370   {
371     if (sview != null)
372     {
373       if (!sview.hasMapping())
374       {
375         return true;
376       }
377     }
378     return false;
379   }
380
381   /**
382    * 
383    * @param pDBid
384    * @return true if view is already showing PDBid
385    */
386   public boolean hasPdbId(String pDBid)
387   {
388     if (sview == null)
389     {
390       return false;
391     }
392
393     return sview.getBinding().hasPdbId(pDBid);
394   }
395
396   public boolean isVisible()
397   {
398     return sview != null && sview.isVisible();
399   }
400
401   public void setSuperpose(boolean alignAddedStructures)
402   {
403     superposeAdded = alignAddedStructures;
404   }
405
406   /**
407    * Launch a minimal implementation of a StructureViewer.
408    * 
409    * @param alignPanel
410    * @param pdb
411    * @param seqs
412    * @return
413    */
414   public static StructureViewer launchStructureViewer(
415           AlignmentPanel alignPanel, PDBEntry pdb, SequenceI[] seqs)
416   {
417     return launchStructureViewer(alignPanel, new PDBEntry[] { pdb }, seqs,
418             false, null, null);
419   }
420
421   /**
422    * Adds PDB structures to a new or existing structure viewer
423    * 
424    * @param ssm
425    * @param pdbEntriesToView
426    * @param alignPanel
427    * @param sequences
428    * @return
429    */
430   protected static StructureViewer launchStructureViewer(
431           final AlignmentPanel ap, final PDBEntry[] pdbEntriesToView,
432           SequenceI[] sequences, boolean superimpose,
433           StructureViewer theViewer, IProgressIndicator pb)
434   {
435     final StructureSelectionManager ssm = ap.getStructureSelectionManager();
436     long progressId = sequences.hashCode();
437     if (pb != null)
438       pb.setProgressBar(MessageManager.getString(
439               "status.launching_3d_structure_viewer"), progressId);
440     theViewer.setSuperpose(superimpose);
441   
442     /*
443      * remember user's choice of superimpose or not
444      */
445     Cache.setProperty(StructureChooser.AUTOSUPERIMPOSE,
446             Boolean.valueOf(superimpose).toString());
447   
448     if (pb != null)
449       pb.setProgressBar(null, progressId);
450     if (SiftsSettings.isMapWithSifts())
451     {
452       List<SequenceI> seqsWithoutSourceDBRef = new ArrayList<>();
453       int p = 0;
454       // TODO: skip PDBEntry:Sequence pairs where PDBEntry doesn't look like a
455       // real PDB ID. For moment, we can also safely do this if there is already
456       // a known mapping between the PDBEntry and the sequence.
457       for (SequenceI seq : sequences)
458       {
459         PDBEntry pdbe = pdbEntriesToView[p++];
460         if (pdbe != null && pdbe.getFile() != null)
461         {
462           StructureMapping[] smm = ssm.getMapping(pdbe.getFile());
463           if (smm != null && smm.length > 0)
464           {
465             for (StructureMapping sm : smm)
466             {
467               if (sm.getSequence() == seq)
468               {
469                 continue;
470               }
471             }
472           }
473         }
474         if (seq.getPrimaryDBRefs().isEmpty())
475         {
476           seqsWithoutSourceDBRef.add(seq);
477           continue;
478         }
479       }
480       if (!seqsWithoutSourceDBRef.isEmpty())
481       {
482         int y = seqsWithoutSourceDBRef.size();
483         if (pb != null)
484           pb.setProgressBar(MessageManager.formatMessage(
485                   "status.fetching_dbrefs_for_sequences_without_valid_refs",
486                   y), progressId);
487         SequenceI[] seqWithoutSrcDBRef = seqsWithoutSourceDBRef
488                 .toArray(new SequenceI[y]);
489         DBRefFetcher dbRefFetcher = new DBRefFetcher(seqWithoutSrcDBRef);
490         dbRefFetcher.fetchDBRefs(true);
491   
492         if (pb != null)
493           pb.setProgressBar("Fetch complete.", progressId); // todo i18n
494       }
495     }
496     if (pdbEntriesToView.length > 1)
497     {
498       if (pb != null)
499         pb.setProgressBar(MessageManager.getString(
500                 "status.fetching_3d_structures_for_selected_entries"),
501                 progressId);
502       theViewer.viewStructures(pdbEntriesToView, sequences, ap);
503     }
504     else
505     {
506       if (pb != null)
507         pb.setProgressBar(MessageManager.formatMessage(
508                 "status.fetching_3d_structures_for",
509                 pdbEntriesToView[0].getId()), progressId);
510       theViewer.viewStructures(pdbEntriesToView[0], sequences, ap);
511     }
512     if (pb != null)
513       pb.setProgressBar(null, progressId);
514     // remember the last viewer we used...
515     StructureChooser.lastTargetedView = theViewer;
516     return theViewer;
517   }
518
519 }