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