JAL-3438 spotless for 2.11.2.0
[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     JalviewStructureDisplayI viewer = onlyOnePdb(pdbs, seqs, ap);
123     if (viewer != null)
124     {
125       /*
126        * user added structure to an existing viewer - all done
127        */
128       return viewer;
129     }
130
131     ViewerType viewerType = getViewerType();
132
133     Map<PDBEntry, SequenceI[]> seqsForPdbs = getSequencesForPdbs(pdbs,
134             seqs);
135     PDBEntry[] pdbsForFile = seqsForPdbs.keySet()
136             .toArray(new PDBEntry[seqsForPdbs.size()]);
137     SequenceI[][] theSeqs = seqsForPdbs.values()
138             .toArray(new SequenceI[seqsForPdbs.size()][]);
139     if (sview != null)
140     {
141       sview.setAlignAddedStructures(superposeAdded);
142       new Thread(new Runnable()
143       {
144         @Override
145         public void run()
146         {
147
148           for (int pdbep = 0; pdbep < pdbsForFile.length; pdbep++)
149           {
150             PDBEntry pdb = pdbsForFile[pdbep];
151             if (!sview.addAlreadyLoadedFile(theSeqs[pdbep], null, ap,
152                     pdb.getId()))
153             {
154               sview.addToExistingViewer(pdb, theSeqs[pdbep], null, ap,
155                       pdb.getId());
156             }
157           }
158
159           sview.updateTitleAndMenus();
160         }
161       }).start();
162       return sview;
163     }
164
165     if (viewerType.equals(ViewerType.JMOL))
166     {
167       sview = new AppJmol(ap, superposeAdded, pdbsForFile, theSeqs);
168     }
169     else if (viewerType.equals(ViewerType.CHIMERA))
170     {
171       sview = new ChimeraViewFrame(pdbsForFile, superposeAdded, theSeqs,
172               ap);
173     }
174     else if (viewerType.equals(ViewerType.CHIMERAX))
175     {
176       sview = new ChimeraXViewFrame(pdbsForFile, superposeAdded, theSeqs,
177               ap);
178     }
179     else if (viewerType.equals(ViewerType.PYMOL))
180     {
181       sview = new PymolViewer(pdbsForFile, superposeAdded, theSeqs, ap);
182     }
183     else
184     {
185       Console.error(UNKNOWN_VIEWER_TYPE + getViewerType().toString());
186     }
187     return sview;
188   }
189
190   /**
191    * Converts the list of selected PDB entries (possibly including duplicates
192    * for multiple chains), and corresponding sequences, into a map of sequences
193    * for each distinct PDB file. Returns null if either argument is null, or
194    * their lengths do not match.
195    * 
196    * @param pdbs
197    * @param seqs
198    * @return
199    */
200   Map<PDBEntry, SequenceI[]> getSequencesForPdbs(PDBEntry[] pdbs,
201           SequenceI[] seqs)
202   {
203     if (pdbs == null || seqs == null || pdbs.length != seqs.length)
204     {
205       return null;
206     }
207
208     /*
209      * we want only one 'representative' PDBEntry per distinct file name
210      * (there may be entries for distinct chains)
211      */
212     Map<String, PDBEntry> pdbsSeen = new HashMap<>();
213
214     /*
215      * LinkedHashMap preserves order of PDB entries (significant if they
216      * will get superimposed to the first structure)
217      */
218     Map<PDBEntry, List<SequenceI>> pdbSeqs = new LinkedHashMap<>();
219     for (int i = 0; i < pdbs.length; i++)
220     {
221       PDBEntry pdb = pdbs[i];
222       SequenceI seq = seqs[i];
223       String pdbFile = pdb.getFile();
224       if (pdbFile == null || pdbFile.length() == 0)
225       {
226         pdbFile = pdb.getId();
227       }
228       if (!pdbsSeen.containsKey(pdbFile))
229       {
230         pdbsSeen.put(pdbFile, pdb);
231         pdbSeqs.put(pdb, new ArrayList<SequenceI>());
232       }
233       else
234       {
235         pdb = pdbsSeen.get(pdbFile);
236       }
237       List<SequenceI> seqsForPdb = pdbSeqs.get(pdb);
238       if (!seqsForPdb.contains(seq))
239       {
240         seqsForPdb.add(seq);
241       }
242     }
243
244     /*
245      * convert to Map<PDBEntry, SequenceI[]>
246      */
247     Map<PDBEntry, SequenceI[]> result = new LinkedHashMap<>();
248     for (Entry<PDBEntry, List<SequenceI>> entry : pdbSeqs.entrySet())
249     {
250       List<SequenceI> theSeqs = entry.getValue();
251       result.put(entry.getKey(),
252               theSeqs.toArray(new SequenceI[theSeqs.size()]));
253     }
254
255     return result;
256   }
257
258   /**
259    * A strictly temporary method pending JAL-1761 refactoring. Determines if all
260    * the passed PDB entries are the same (this is the case if selected sequences
261    * to view structure for are chains of the same structure). If so, calls the
262    * single-pdb version of viewStructures and returns the viewer, else returns
263    * null.
264    * 
265    * @param pdbs
266    * @param seqsForPdbs
267    * @param ap
268    * @return
269    */
270   private JalviewStructureDisplayI onlyOnePdb(PDBEntry[] pdbs,
271           SequenceI[] seqsForPdbs, AlignmentPanel ap)
272   {
273     List<SequenceI> seqs = new ArrayList<>();
274     if (pdbs == null || pdbs.length == 0)
275     {
276       return null;
277     }
278     int i = 0;
279     String firstFile = pdbs[0].getFile();
280     for (PDBEntry pdb : pdbs)
281     {
282       String pdbFile = pdb.getFile();
283       if (pdbFile == null || !pdbFile.equals(firstFile))
284       {
285         return null;
286       }
287       SequenceI pdbseq = seqsForPdbs[i++];
288       if (pdbseq != null)
289       {
290         seqs.add(pdbseq);
291       }
292     }
293     return viewStructures(pdbs[0], seqs.toArray(new SequenceI[seqs.size()]),
294             ap);
295   }
296
297   JalviewStructureDisplayI sview = null;
298
299   public JalviewStructureDisplayI viewStructures(PDBEntry pdb,
300           SequenceI[] seqsForPdb, AlignmentPanel ap)
301   {
302     if (sview != null)
303     {
304       sview.setAlignAddedStructures(superposeAdded);
305       String pdbId = pdb.getId();
306       if (!sview.addAlreadyLoadedFile(seqsForPdb, null, ap, pdbId))
307       {
308         sview.addToExistingViewer(pdb, seqsForPdb, null, ap, pdbId);
309       }
310       sview.updateTitleAndMenus();
311       sview.raiseViewer();
312       return sview;
313     }
314     ViewerType viewerType = getViewerType();
315     if (viewerType.equals(ViewerType.JMOL))
316     {
317       sview = new AppJmol(pdb, seqsForPdb, null, ap);
318     }
319     else if (viewerType.equals(ViewerType.CHIMERA))
320     {
321       sview = new ChimeraViewFrame(pdb, seqsForPdb, null, ap);
322     }
323     else if (viewerType.equals(ViewerType.CHIMERAX))
324     {
325       sview = new ChimeraXViewFrame(pdb, seqsForPdb, null, ap);
326     }
327     else if (viewerType.equals(ViewerType.PYMOL))
328     {
329       sview = new PymolViewer(pdb, seqsForPdb, null, ap);
330     }
331     else
332     {
333       Console.error(UNKNOWN_VIEWER_TYPE + getViewerType().toString());
334     }
335     return sview;
336   }
337
338   /**
339    * Creates a new panel controlling a structure viewer
340    * 
341    * @param type
342    * @param alignPanel
343    * @param viewerData
344    * @param sessionFile
345    * @param vid
346    * @return
347    */
348   public static JalviewStructureDisplayI createView(ViewerType type,
349           AlignmentPanel alignPanel, StructureViewerModel viewerData,
350           String sessionFile, String vid)
351   {
352     JalviewStructureDisplayI viewer = null;
353     switch (type)
354     {
355     case JMOL:
356       viewer = new AppJmol(viewerData, alignPanel, sessionFile, vid);
357       // todo or construct and then openSession(sessionFile)?
358       break;
359     case CHIMERA:
360       viewer = new ChimeraViewFrame(viewerData, alignPanel, sessionFile,
361               vid);
362       break;
363     case CHIMERAX:
364       viewer = new ChimeraXViewFrame(viewerData, alignPanel, sessionFile,
365               vid);
366       break;
367     case PYMOL:
368       viewer = new PymolViewer(viewerData, alignPanel, sessionFile, vid);
369       break;
370     default:
371       Console.error(UNKNOWN_VIEWER_TYPE + type.toString());
372     }
373     return viewer;
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 }