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