JAL-2944 allow StructureViewer proxy to be created for an existing view in order...
[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   public enum ViewerType
53   {
54     JMOL, CHIMERA
55   };
56
57   /**
58    * Constructor
59    * 
60    * @param structureSelectionManager
61    */
62   public StructureViewer(StructureSelectionManager structureSelectionManager)
63   {
64     ssm = structureSelectionManager;
65   }
66
67   /**
68    * Factory to create a proxy for modifying existing structure viewer
69    * 
70    */
71   public static StructureViewer reconfigure(
72           JalviewStructureDisplayI display)
73   {
74     StructureViewer sv = new StructureViewer(display.getBinding().getSsm());
75     sv.sview = display;
76     return sv;
77   }
78   public ViewerType getViewerType()
79   {
80     String viewType = Cache.getDefault(Preferences.STRUCTURE_DISPLAY,
81             ViewerType.JMOL.name());
82     return ViewerType.valueOf(viewType);
83   }
84
85   public void setViewerType(ViewerType type)
86   {
87     Cache.setProperty(Preferences.STRUCTURE_DISPLAY, type.name());
88   }
89
90   /**
91    * View multiple PDB entries, each with associated sequences
92    * 
93    * @param pdbs
94    * @param seqs
95    * @param ap
96    * @return
97    */
98   public JalviewStructureDisplayI viewStructures(PDBEntry[] pdbs,
99           SequenceI[] seqs, AlignmentPanel ap)
100   {
101     JalviewStructureDisplayI viewer = onlyOnePdb(pdbs, seqs, ap);
102     if (viewer != null)
103     {
104       /*
105        * user added structure to an existing viewer - all done
106        */
107       return viewer;
108     }
109
110     ViewerType viewerType = getViewerType();
111
112     Map<PDBEntry, SequenceI[]> seqsForPdbs = getSequencesForPdbs(pdbs,
113             seqs);
114     PDBEntry[] pdbsForFile = seqsForPdbs.keySet().toArray(
115             new PDBEntry[seqsForPdbs.size()]);
116     SequenceI[][] theSeqs = seqsForPdbs.values().toArray(
117             new SequenceI[seqsForPdbs.size()][]);
118     if (sview != null)
119     {
120       new Thread(new Runnable()
121       {
122         @Override
123         public void run()
124         {
125
126           for (int pdbep = 0; pdbep < pdbsForFile.length; pdbep++)
127           {
128             PDBEntry pdb = pdbsForFile[pdbep];
129             if (!sview.addAlreadyLoadedFile(theSeqs[pdbep], null, ap,
130                     pdb.getId()))
131             {
132               sview.addToExistingViewer(pdb, theSeqs[pdbep], null, ap,
133                       pdb.getId());
134             }
135           }
136
137           sview.updateTitleAndMenus();
138         }
139       }).start();
140       return sview;
141     }
142
143     if (viewerType.equals(ViewerType.JMOL))
144     {
145       sview = new AppJmol(ap, pdbsForFile, theSeqs);
146     }
147     else if (viewerType.equals(ViewerType.CHIMERA))
148     {
149       sview = new ChimeraViewFrame(pdbsForFile, theSeqs, ap);
150     }
151     else
152     {
153       Cache.log.error(UNKNOWN_VIEWER_TYPE + getViewerType().toString());
154     }
155     return sview;
156   }
157
158   /**
159    * Converts the list of selected PDB entries (possibly including duplicates
160    * for multiple chains), and corresponding sequences, into a map of sequences
161    * for each distinct PDB file. Returns null if either argument is null, or
162    * their lengths do not match.
163    * 
164    * @param pdbs
165    * @param seqs
166    * @return
167    */
168   Map<PDBEntry, SequenceI[]> getSequencesForPdbs(PDBEntry[] pdbs,
169           SequenceI[] seqs)
170   {
171     if (pdbs == null || seqs == null || pdbs.length != seqs.length)
172     {
173       return null;
174     }
175
176     /*
177      * we want only one 'representative' PDBEntry per distinct file name
178      * (there may be entries for distinct chains)
179      */
180     Map<String, PDBEntry> pdbsSeen = new HashMap<>();
181
182     /*
183      * LinkedHashMap preserves order of PDB entries (significant if they
184      * will get superimposed to the first structure)
185      */
186     Map<PDBEntry, List<SequenceI>> pdbSeqs = new LinkedHashMap<>();
187     for (int i = 0; i < pdbs.length; i++)
188     {
189       PDBEntry pdb = pdbs[i];
190       SequenceI seq = seqs[i];
191       String pdbFile = pdb.getFile();
192       if (pdbFile == null || pdbFile.length() == 0)
193       {
194         pdbFile = pdb.getId();
195       }
196       if (!pdbsSeen.containsKey(pdbFile))
197       {
198         pdbsSeen.put(pdbFile, pdb);
199         pdbSeqs.put(pdb, new ArrayList<SequenceI>());
200       }
201       else
202       {
203         pdb = pdbsSeen.get(pdbFile);
204       }
205       List<SequenceI> seqsForPdb = pdbSeqs.get(pdb);
206       if (!seqsForPdb.contains(seq))
207       {
208         seqsForPdb.add(seq);
209       }
210     }
211
212     /*
213      * convert to Map<PDBEntry, SequenceI[]>
214      */
215     Map<PDBEntry, SequenceI[]> result = new LinkedHashMap<>();
216     for (Entry<PDBEntry, List<SequenceI>> entry : pdbSeqs.entrySet())
217     {
218       List<SequenceI> theSeqs = entry.getValue();
219       result.put(entry.getKey(),
220               theSeqs.toArray(new SequenceI[theSeqs.size()]));
221     }
222
223     return result;
224   }
225
226   /**
227    * A strictly temporary method pending JAL-1761 refactoring. Determines if all
228    * the passed PDB entries are the same (this is the case if selected sequences
229    * to view structure for are chains of the same structure). If so, calls the
230    * single-pdb version of viewStructures and returns the viewer, else returns
231    * null.
232    * 
233    * @param pdbs
234    * @param seqsForPdbs
235    * @param ap
236    * @return
237    */
238   private JalviewStructureDisplayI onlyOnePdb(PDBEntry[] pdbs,
239           SequenceI[] seqsForPdbs, AlignmentPanel ap)
240   {
241     List<SequenceI> seqs = new ArrayList<>();
242     if (pdbs == null || pdbs.length == 0)
243     {
244       return null;
245     }
246     int i = 0;
247     String firstFile = pdbs[0].getFile();
248     for (PDBEntry pdb : pdbs)
249     {
250       String pdbFile = pdb.getFile();
251       if (pdbFile == null || !pdbFile.equals(firstFile))
252       {
253         return null;
254       }
255       SequenceI pdbseq = seqsForPdbs[i++];
256       if (pdbseq != null)
257       {
258         seqs.add(pdbseq);
259       }
260     }
261     return viewStructures(pdbs[0], seqs.toArray(new SequenceI[seqs.size()]),
262             ap);
263   }
264
265   JalviewStructureDisplayI sview = null;
266
267   public JalviewStructureDisplayI viewStructures(PDBEntry pdb,
268           SequenceI[] seqsForPdb, AlignmentPanel ap)
269   {
270     if (sview != null)
271     {
272       if (!sview.addAlreadyLoadedFile(seqsForPdb, null, ap, pdb.getId()))
273       {
274         sview.addToExistingViewer(pdb, seqsForPdb, null, ap, pdb.getId());
275       }
276       sview.updateTitleAndMenus();
277       return sview;
278     }
279     ViewerType viewerType = getViewerType();
280     if (viewerType.equals(ViewerType.JMOL))
281     {
282       sview = new AppJmol(pdb, seqsForPdb, null, ap);
283     }
284     else if (viewerType.equals(ViewerType.CHIMERA))
285     {
286       sview = new ChimeraViewFrame(pdb, seqsForPdb, null, ap);
287     }
288     else
289     {
290       Cache.log.error(UNKNOWN_VIEWER_TYPE + getViewerType().toString());
291     }
292     return sview;
293   }
294
295   /**
296    * Create a new panel controlling a structure viewer.
297    * 
298    * @param type
299    * @param pdbf
300    * @param id
301    * @param sq
302    * @param alignPanel
303    * @param viewerData
304    * @param fileloc
305    * @param rect
306    * @param vid
307    * @return
308    */
309   public JalviewStructureDisplayI createView(ViewerType type, String[] pdbf,
310           String[] id, SequenceI[][] sq, AlignmentPanel alignPanel,
311           StructureViewerModel viewerData, String fileloc, Rectangle rect,
312           String vid)
313   {
314     final boolean useinViewerSuperpos = viewerData.isAlignWithPanel();
315     final boolean usetoColourbyseq = viewerData.isColourWithAlignPanel();
316     final boolean viewerColouring = viewerData.isColourByViewer();
317
318     switch (type)
319     {
320     case JMOL:
321       sview = new AppJmol(pdbf, id, sq, alignPanel, usetoColourbyseq,
322               useinViewerSuperpos, viewerColouring, fileloc, rect, vid);
323       break;
324     case CHIMERA:
325       Cache.log.error(
326               "Unsupported structure viewer type " + type.toString());
327       break;
328     default:
329       Cache.log.error(UNKNOWN_VIEWER_TYPE + type.toString());
330     }
331     return sview;
332   }
333
334   public boolean isBusy()
335   {
336     if (sview != null)
337     {
338       if (!sview.hasMapping())
339       {
340         return true;
341       }
342     }
343     return false;
344   }
345
346 }