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