JAL-629 Test and fix --annotation --ssannotation args. Added a viewerType arg/subval...
[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 java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.LinkedHashMap;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Map.Entry;
29
30 import jalview.api.structures.JalviewStructureDisplayI;
31 import jalview.bin.Cache;
32 import jalview.bin.Console;
33 import jalview.datamodel.PDBEntry;
34 import jalview.datamodel.SequenceI;
35 import jalview.datamodel.StructureViewerModel;
36 import jalview.structure.StructureSelectionManager;
37
38 /**
39  * A proxy for handling structure viewers, that orchestrates adding selected
40  * structures, associated with sequences in Jalview, to an existing viewer, or
41  * opening a new one. Currently supports either Jmol or Chimera as the structure
42  * viewer.
43  * 
44  * @author jprocter
45  */
46 public class StructureViewer
47 {
48   private static final String UNKNOWN_VIEWER_TYPE = "Unknown structure viewer type ";
49
50   StructureSelectionManager ssm;
51
52   /**
53    * decide if new structures are aligned to existing ones
54    */
55   private boolean superposeAdded = true;
56
57   public enum ViewerType
58   {
59     JMOL, CHIMERA, CHIMERAX, PYMOL
60   };
61
62   /**
63    * Constructor
64    * 
65    * @param structureSelectionManager
66    */
67   public StructureViewer(
68           StructureSelectionManager structureSelectionManager)
69   {
70     ssm = structureSelectionManager;
71   }
72
73   /**
74    * Factory to create a proxy for modifying existing structure viewer
75    * 
76    */
77   public static StructureViewer reconfigure(
78           JalviewStructureDisplayI display)
79   {
80     StructureViewer sv = new StructureViewer(display.getBinding().getSsm());
81     sv.sview = display;
82     return sv;
83   }
84
85   @Override
86   public String toString()
87   {
88     if (sview != null)
89     {
90       return sview.toString();
91     }
92     return "New View";
93   }
94
95   /**
96    * 
97    * @return ViewerType for currently configured structure viewer
98    */
99   public static ViewerType getViewerType()
100   {
101     String viewType = Cache.getDefault(Preferences.STRUCTURE_DISPLAY,
102             ViewerType.JMOL.name());
103     return ViewerType.valueOf(viewType);
104   }
105
106   public void setViewerType(ViewerType type)
107   {
108     Cache.setProperty(Preferences.STRUCTURE_DISPLAY, type.name());
109   }
110
111   /**
112    * View multiple PDB entries, each with associated sequences
113    * 
114    * @param pdbs
115    * @param seqs
116    * @param ap
117    * @return
118    */
119   public JalviewStructureDisplayI viewStructures(PDBEntry[] pdbs,
120           SequenceI[] seqs, AlignmentPanel ap)
121   {
122     return viewStructures(pdbs, seqs, ap, null);
123   }
124
125   public JalviewStructureDisplayI viewStructures(PDBEntry[] pdbs,
126           SequenceI[] seqs, AlignmentPanel ap, ViewerType viewerType)
127   {
128     JalviewStructureDisplayI viewer = onlyOnePdb(pdbs, seqs, ap);
129     if (viewer != null)
130     {
131       /*
132        * user added structure to an existing viewer - all done
133        */
134       return viewer;
135     }
136
137     if (viewerType == null)
138       viewerType = getViewerType();
139
140     Map<PDBEntry, SequenceI[]> seqsForPdbs = getSequencesForPdbs(pdbs,
141             seqs);
142     PDBEntry[] pdbsForFile = seqsForPdbs.keySet()
143             .toArray(new PDBEntry[seqsForPdbs.size()]);
144     SequenceI[][] theSeqs = seqsForPdbs.values()
145             .toArray(new SequenceI[seqsForPdbs.size()][]);
146     if (sview != null)
147     {
148       sview.setAlignAddedStructures(superposeAdded);
149       new Thread(new Runnable()
150       {
151         @Override
152         public void run()
153         {
154
155           for (int pdbep = 0; pdbep < pdbsForFile.length; pdbep++)
156           {
157             PDBEntry pdb = pdbsForFile[pdbep];
158             if (!sview.addAlreadyLoadedFile(theSeqs[pdbep], null, ap,
159                     pdb.getId()))
160             {
161               sview.addToExistingViewer(pdb, theSeqs[pdbep], null, ap,
162                       pdb.getId());
163             }
164           }
165
166           sview.updateTitleAndMenus();
167         }
168       }).start();
169       return sview;
170     }
171
172     if (viewerType.equals(ViewerType.JMOL))
173     {
174       sview = new AppJmol(ap, superposeAdded, pdbsForFile, theSeqs);
175     }
176     else if (viewerType.equals(ViewerType.CHIMERA))
177     {
178       sview = new ChimeraViewFrame(pdbsForFile, superposeAdded, theSeqs,
179               ap);
180     }
181     else if (viewerType.equals(ViewerType.CHIMERAX))
182     {
183       sview = new ChimeraXViewFrame(pdbsForFile, superposeAdded, theSeqs,
184               ap);
185     }
186     else if (viewerType.equals(ViewerType.PYMOL))
187     {
188       sview = new PymolViewer(pdbsForFile, superposeAdded, theSeqs, ap);
189     }
190     else
191     {
192       Console.error(UNKNOWN_VIEWER_TYPE + getViewerType().toString());
193     }
194     return sview;
195   }
196
197   /**
198    * Converts the list of selected PDB entries (possibly including duplicates
199    * for multiple chains), and corresponding sequences, into a map of sequences
200    * for each distinct PDB file. Returns null if either argument is null, or
201    * their lengths do not match.
202    * 
203    * @param pdbs
204    * @param seqs
205    * @return
206    */
207   Map<PDBEntry, SequenceI[]> getSequencesForPdbs(PDBEntry[] pdbs,
208           SequenceI[] seqs)
209   {
210     if (pdbs == null || seqs == null || pdbs.length != seqs.length)
211     {
212       return null;
213     }
214
215     /*
216      * we want only one 'representative' PDBEntry per distinct file name
217      * (there may be entries for distinct chains)
218      */
219     Map<String, PDBEntry> pdbsSeen = new HashMap<>();
220
221     /*
222      * LinkedHashMap preserves order of PDB entries (significant if they
223      * will get superimposed to the first structure)
224      */
225     Map<PDBEntry, List<SequenceI>> pdbSeqs = new LinkedHashMap<>();
226     for (int i = 0; i < pdbs.length; i++)
227     {
228       PDBEntry pdb = pdbs[i];
229       SequenceI seq = seqs[i];
230       String pdbFile = pdb.getFile();
231       if (pdbFile == null || pdbFile.length() == 0)
232       {
233         pdbFile = pdb.getId();
234       }
235       if (!pdbsSeen.containsKey(pdbFile))
236       {
237         pdbsSeen.put(pdbFile, pdb);
238         pdbSeqs.put(pdb, new ArrayList<SequenceI>());
239       }
240       else
241       {
242         pdb = pdbsSeen.get(pdbFile);
243       }
244       List<SequenceI> seqsForPdb = pdbSeqs.get(pdb);
245       if (!seqsForPdb.contains(seq))
246       {
247         seqsForPdb.add(seq);
248       }
249     }
250
251     /*
252      * convert to Map<PDBEntry, SequenceI[]>
253      */
254     Map<PDBEntry, SequenceI[]> result = new LinkedHashMap<>();
255     for (Entry<PDBEntry, List<SequenceI>> entry : pdbSeqs.entrySet())
256     {
257       List<SequenceI> theSeqs = entry.getValue();
258       result.put(entry.getKey(),
259               theSeqs.toArray(new SequenceI[theSeqs.size()]));
260     }
261
262     return result;
263   }
264
265   /**
266    * A strictly temporary method pending JAL-1761 refactoring. Determines if all
267    * the passed PDB entries are the same (this is the case if selected sequences
268    * to view structure for are chains of the same structure). If so, calls the
269    * single-pdb version of viewStructures and returns the viewer, else returns
270    * null.
271    * 
272    * @param pdbs
273    * @param seqsForPdbs
274    * @param ap
275    * @return
276    */
277   private JalviewStructureDisplayI onlyOnePdb(PDBEntry[] pdbs,
278           SequenceI[] seqsForPdbs, AlignmentPanel ap)
279   {
280     List<SequenceI> seqs = new ArrayList<>();
281     if (pdbs == null || pdbs.length == 0)
282     {
283       return null;
284     }
285     int i = 0;
286     String firstFile = pdbs[0].getFile();
287     for (PDBEntry pdb : pdbs)
288     {
289       String pdbFile = pdb.getFile();
290       if (pdbFile == null || !pdbFile.equals(firstFile))
291       {
292         return null;
293       }
294       SequenceI pdbseq = seqsForPdbs[i++];
295       if (pdbseq != null)
296       {
297         seqs.add(pdbseq);
298       }
299     }
300     return viewStructures(pdbs[0], seqs.toArray(new SequenceI[seqs.size()]),
301             ap);
302   }
303
304   JalviewStructureDisplayI sview = null;
305
306   public JalviewStructureDisplayI viewStructures(PDBEntry pdb,
307           SequenceI[] seqsForPdb, AlignmentPanel ap)
308   {
309     return viewStructures(pdb, seqsForPdb, ap, null);
310   }
311
312   public JalviewStructureDisplayI viewStructures(PDBEntry pdb,
313           SequenceI[] seqsForPdb, AlignmentPanel ap, ViewerType viewerType)
314   {
315     if (sview != null)
316     {
317       sview.setAlignAddedStructures(superposeAdded);
318       String pdbId = pdb.getId();
319       if (!sview.addAlreadyLoadedFile(seqsForPdb, null, ap, pdbId))
320       {
321         sview.addToExistingViewer(pdb, seqsForPdb, null, ap, pdbId);
322       }
323       sview.updateTitleAndMenus();
324       sview.raiseViewer();
325       return sview;
326     }
327     if (viewerType == null)
328       viewerType = getViewerType();
329     if (viewerType.equals(ViewerType.JMOL))
330     {
331       sview = new AppJmol(pdb, seqsForPdb, null, ap);
332     }
333     else if (viewerType.equals(ViewerType.CHIMERA))
334     {
335       sview = new ChimeraViewFrame(pdb, seqsForPdb, null, ap);
336     }
337     else if (viewerType.equals(ViewerType.CHIMERAX))
338     {
339       sview = new ChimeraXViewFrame(pdb, seqsForPdb, null, ap);
340     }
341     else if (viewerType.equals(ViewerType.PYMOL))
342     {
343       sview = new PymolViewer(pdb, seqsForPdb, null, ap);
344     }
345     else
346     {
347       Console.error(UNKNOWN_VIEWER_TYPE + getViewerType().toString());
348     }
349     return sview;
350   }
351
352   /**
353    * Creates a new panel controlling a structure viewer
354    * 
355    * @param type
356    * @param alignPanel
357    * @param viewerData
358    * @param sessionFile
359    * @param vid
360    * @return
361    */
362   public static JalviewStructureDisplayI createView(ViewerType type,
363           AlignmentPanel alignPanel, StructureViewerModel viewerData,
364           String sessionFile, String vid)
365   {
366     JalviewStructureDisplayI viewer = null;
367     switch (type)
368     {
369     case JMOL:
370       viewer = new AppJmol(viewerData, alignPanel, sessionFile, vid);
371       // todo or construct and then openSession(sessionFile)?
372       break;
373     case CHIMERA:
374       viewer = new ChimeraViewFrame(viewerData, alignPanel, sessionFile,
375               vid);
376       break;
377     case CHIMERAX:
378       viewer = new ChimeraXViewFrame(viewerData, alignPanel, sessionFile,
379               vid);
380       break;
381     case PYMOL:
382       viewer = new PymolViewer(viewerData, alignPanel, sessionFile, vid);
383       break;
384     default:
385       Console.error(UNKNOWN_VIEWER_TYPE + type.toString());
386     }
387     return viewer;
388   }
389
390   public boolean isBusy()
391   {
392     if (sview != null)
393     {
394       if (!sview.hasMapping())
395       {
396         return true;
397       }
398     }
399     return false;
400   }
401
402   /**
403    * 
404    * @param pDBid
405    * @return true if view is already showing PDBid
406    */
407   public boolean hasPdbId(String pDBid)
408   {
409     if (sview == null)
410     {
411       return false;
412     }
413
414     return sview.getBinding().hasPdbId(pDBid);
415   }
416
417   public boolean isVisible()
418   {
419     return sview != null && sview.isVisible();
420   }
421
422   public void setSuperpose(boolean alignAddedStructures)
423   {
424     superposeAdded = alignAddedStructures;
425   }
426
427 }