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