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