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