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