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