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