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