Class |
Line # |
Actions |
|||
---|---|---|---|---|---|
StructureViewer | 48 | 125 | 57 | ||
StructureViewer.ViewerType | 69 | 8 | 3 |
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 java.util.ArrayList; | |
24 | import java.util.EnumSet; | |
25 | import java.util.HashMap; | |
26 | import java.util.LinkedHashMap; | |
27 | import java.util.List; | |
28 | import java.util.Locale; | |
29 | import java.util.Map; | |
30 | import java.util.Map.Entry; | |
31 | ||
32 | import jalview.api.structures.JalviewStructureDisplayI; | |
33 | import jalview.bin.Cache; | |
34 | import jalview.bin.Console; | |
35 | import jalview.datamodel.PDBEntry; | |
36 | import jalview.datamodel.SequenceI; | |
37 | import jalview.datamodel.StructureViewerModel; | |
38 | import jalview.structure.StructureSelectionManager; | |
39 | ||
40 | /** | |
41 | * A proxy for handling structure viewers, that orchestrates adding selected | |
42 | * structures, associated with sequences in Jalview, to an existing viewer, or | |
43 | * opening a new one. Currently supports either Jmol or Chimera as the structure | |
44 | * viewer. | |
45 | * | |
46 | * @author jprocter | |
47 | */ | |
48 | public class StructureViewer | |
49 | { | |
50 | private static final String UNKNOWN_VIEWER_TYPE = "Unknown structure viewer type "; | |
51 | ||
52 | StructureSelectionManager ssm; | |
53 | ||
54 | /** | |
55 | * decide if new structures are aligned to existing ones | |
56 | */ | |
57 | private boolean superposeAdded = true; | |
58 | ||
59 | /** | |
60 | * whether to open structures in their own thread or not | |
61 | */ | |
62 | private boolean async = true; | |
63 | ||
64 | 19 | public void setAsync(boolean b) |
65 | { | |
66 | 19 | async = b; |
67 | } | |
68 | ||
69 | public enum ViewerType | |
70 | { | |
71 | JMOL, CHIMERA, CHIMERAX, PYMOL; | |
72 | ||
73 | 42 | public static ViewerType getFromString(String viewerString) |
74 | { | |
75 | 42 | ViewerType viewerType = null; |
76 | 42 | if (!"none".equals(viewerString)) |
77 | { | |
78 | 33 | for (ViewerType v : EnumSet.allOf(ViewerType.class)) |
79 | { | |
80 | 33 | String name = v.name().toLowerCase(Locale.ROOT).replaceAll(" ", |
81 | ""); | |
82 | 33 | if (viewerString.equals(name)) |
83 | { | |
84 | 33 | viewerType = v; |
85 | 33 | break; |
86 | } | |
87 | } | |
88 | } | |
89 | 42 | return viewerType; |
90 | } | |
91 | ||
92 | }; | |
93 | ||
94 | /** | |
95 | * Constructor | |
96 | * | |
97 | * @param structureSelectionManager | |
98 | */ | |
99 | 39 | public StructureViewer( |
100 | StructureSelectionManager structureSelectionManager) | |
101 | { | |
102 | 39 | ssm = structureSelectionManager; |
103 | } | |
104 | ||
105 | /** | |
106 | * Factory to create a proxy for modifying existing structure viewer | |
107 | * | |
108 | */ | |
109 | 0 | public static StructureViewer reconfigure( |
110 | JalviewStructureDisplayI display) | |
111 | { | |
112 | 0 | StructureViewer sv = new StructureViewer(display.getBinding().getSsm()); |
113 | 0 | sv.sview = display; |
114 | 0 | return sv; |
115 | } | |
116 | ||
117 | 0 | @Override |
118 | public String toString() | |
119 | { | |
120 | 0 | if (sview != null) |
121 | { | |
122 | 0 | return sview.toString(); |
123 | } | |
124 | 0 | return "New View"; |
125 | } | |
126 | ||
127 | /** | |
128 | * | |
129 | * @return ViewerType for currently configured structure viewer | |
130 | */ | |
131 | 57 | public static ViewerType getViewerType() |
132 | { | |
133 | 57 | String viewType = Cache.getDefault(Preferences.STRUCTURE_DISPLAY, |
134 | ViewerType.JMOL.name()); | |
135 | 57 | return ViewerType.valueOf(viewType); |
136 | } | |
137 | ||
138 | 2 | public void setViewerType(ViewerType type) |
139 | { | |
140 | 2 | Cache.setProperty(Preferences.STRUCTURE_DISPLAY, type.name()); |
141 | } | |
142 | ||
143 | /** | |
144 | * View multiple PDB entries, each with associated sequences | |
145 | * | |
146 | * @param pdbs | |
147 | * @param seqs | |
148 | * @param ap | |
149 | * @return | |
150 | */ | |
151 | 0 | public JalviewStructureDisplayI viewStructures(PDBEntry[] pdbs, |
152 | SequenceI[] seqs, AlignmentPanel ap) | |
153 | { | |
154 | 0 | return viewStructures(pdbs, seqs, ap, null); |
155 | } | |
156 | ||
157 | 0 | public JalviewStructureDisplayI viewStructures(PDBEntry[] pdbs, |
158 | SequenceI[] seqs, AlignmentPanel ap, ViewerType viewerType) | |
159 | { | |
160 | 0 | JalviewStructureDisplayI viewer = onlyOnePdb(pdbs, seqs, ap); |
161 | 0 | if (viewer != null) |
162 | { | |
163 | /* | |
164 | * user added structure to an existing viewer - all done | |
165 | */ | |
166 | 0 | return viewer; |
167 | } | |
168 | ||
169 | 0 | if (viewerType == null) |
170 | 0 | viewerType = getViewerType(); |
171 | ||
172 | 0 | Map<PDBEntry, SequenceI[]> seqsForPdbs = getSequencesForPdbs(pdbs, |
173 | seqs); | |
174 | 0 | PDBEntry[] pdbsForFile = seqsForPdbs.keySet() |
175 | .toArray(new PDBEntry[seqsForPdbs.size()]); | |
176 | 0 | SequenceI[][] theSeqs = seqsForPdbs.values() |
177 | .toArray(new SequenceI[seqsForPdbs.size()][]); | |
178 | 0 | if (sview != null) |
179 | { | |
180 | 0 | sview.setAlignAddedStructures(superposeAdded); |
181 | ||
182 | 0 | Runnable viewRunnable = new Runnable() |
183 | { | |
184 | 0 | @Override |
185 | public void run() | |
186 | { | |
187 | ||
188 | 0 | for (int pdbep = 0; pdbep < pdbsForFile.length; pdbep++) |
189 | { | |
190 | 0 | PDBEntry pdb = pdbsForFile[pdbep]; |
191 | 0 | if (!sview.addAlreadyLoadedFile(theSeqs[pdbep], null, ap, |
192 | pdb.getId())) | |
193 | { | |
194 | 0 | sview.addToExistingViewer(pdb, theSeqs[pdbep], null, ap, |
195 | pdb.getId()); | |
196 | } | |
197 | } | |
198 | ||
199 | 0 | sview.updateTitleAndMenus(); |
200 | } | |
201 | }; | |
202 | 0 | if (async) |
203 | { | |
204 | 0 | new Thread(viewRunnable).start(); |
205 | } | |
206 | else | |
207 | { | |
208 | 0 | viewRunnable.run(); |
209 | } | |
210 | 0 | return sview; |
211 | } | |
212 | ||
213 | 0 | if (viewerType.equals(ViewerType.JMOL)) |
214 | { | |
215 | 0 | sview = new AppJmol(ap, superposeAdded, pdbsForFile, theSeqs); |
216 | } | |
217 | 0 | else if (viewerType.equals(ViewerType.CHIMERA)) |
218 | { | |
219 | 0 | sview = new ChimeraViewFrame(pdbsForFile, superposeAdded, theSeqs, |
220 | ap); | |
221 | } | |
222 | 0 | else if (viewerType.equals(ViewerType.CHIMERAX)) |
223 | { | |
224 | 0 | sview = new ChimeraXViewFrame(pdbsForFile, superposeAdded, theSeqs, |
225 | ap); | |
226 | } | |
227 | 0 | else if (viewerType.equals(ViewerType.PYMOL)) |
228 | { | |
229 | 0 | sview = new PymolViewer(pdbsForFile, superposeAdded, theSeqs, ap); |
230 | } | |
231 | else | |
232 | { | |
233 | 0 | Console.error(UNKNOWN_VIEWER_TYPE + getViewerType().toString()); |
234 | } | |
235 | 0 | return sview; |
236 | } | |
237 | ||
238 | /** | |
239 | * Converts the list of selected PDB entries (possibly including duplicates | |
240 | * for multiple chains), and corresponding sequences, into a map of sequences | |
241 | * for each distinct PDB file. Returns null if either argument is null, or | |
242 | * their lengths do not match. | |
243 | * | |
244 | * @param pdbs | |
245 | * @param seqs | |
246 | * @return | |
247 | */ | |
248 | 2 | Map<PDBEntry, SequenceI[]> getSequencesForPdbs(PDBEntry[] pdbs, |
249 | SequenceI[] seqs) | |
250 | { | |
251 | 2 | if (pdbs == null || seqs == null || pdbs.length != seqs.length) |
252 | { | |
253 | 1 | return null; |
254 | } | |
255 | ||
256 | /* | |
257 | * we want only one 'representative' PDBEntry per distinct file name | |
258 | * (there may be entries for distinct chains) | |
259 | */ | |
260 | 1 | Map<String, PDBEntry> pdbsSeen = new HashMap<>(); |
261 | ||
262 | /* | |
263 | * LinkedHashMap preserves order of PDB entries (significant if they | |
264 | * will get superimposed to the first structure) | |
265 | */ | |
266 | 1 | Map<PDBEntry, List<SequenceI>> pdbSeqs = new LinkedHashMap<>(); |
267 | 8 | for (int i = 0; i < pdbs.length; i++) |
268 | { | |
269 | 7 | PDBEntry pdb = pdbs[i]; |
270 | 7 | SequenceI seq = seqs[i]; |
271 | 7 | String pdbFile = pdb.getFile(); |
272 | 7 | if (pdbFile == null || pdbFile.length() == 0) |
273 | { | |
274 | 3 | pdbFile = pdb.getId(); |
275 | } | |
276 | 7 | if (!pdbsSeen.containsKey(pdbFile)) |
277 | { | |
278 | 4 | pdbsSeen.put(pdbFile, pdb); |
279 | 4 | pdbSeqs.put(pdb, new ArrayList<SequenceI>()); |
280 | } | |
281 | else | |
282 | { | |
283 | 3 | pdb = pdbsSeen.get(pdbFile); |
284 | } | |
285 | 7 | List<SequenceI> seqsForPdb = pdbSeqs.get(pdb); |
286 | 7 | if (!seqsForPdb.contains(seq)) |
287 | { | |
288 | 7 | seqsForPdb.add(seq); |
289 | } | |
290 | } | |
291 | ||
292 | /* | |
293 | * convert to Map<PDBEntry, SequenceI[]> | |
294 | */ | |
295 | 1 | Map<PDBEntry, SequenceI[]> result = new LinkedHashMap<>(); |
296 | 1 | for (Entry<PDBEntry, List<SequenceI>> entry : pdbSeqs.entrySet()) |
297 | { | |
298 | 4 | List<SequenceI> theSeqs = entry.getValue(); |
299 | 4 | result.put(entry.getKey(), |
300 | theSeqs.toArray(new SequenceI[theSeqs.size()])); | |
301 | } | |
302 | ||
303 | 1 | return result; |
304 | } | |
305 | ||
306 | /** | |
307 | * A strictly temporary method pending JAL-1761 refactoring. Determines if all | |
308 | * the passed PDB entries are the same (this is the case if selected sequences | |
309 | * to view structure for are chains of the same structure). If so, calls the | |
310 | * single-pdb version of viewStructures and returns the viewer, else returns | |
311 | * null. | |
312 | * | |
313 | * @param pdbs | |
314 | * @param seqsForPdbs | |
315 | * @param ap | |
316 | * @return | |
317 | */ | |
318 | 0 | private JalviewStructureDisplayI onlyOnePdb(PDBEntry[] pdbs, |
319 | SequenceI[] seqsForPdbs, AlignmentPanel ap) | |
320 | { | |
321 | 0 | List<SequenceI> seqs = new ArrayList<>(); |
322 | 0 | if (pdbs == null || pdbs.length == 0) |
323 | { | |
324 | 0 | return null; |
325 | } | |
326 | 0 | int i = 0; |
327 | 0 | String firstFile = pdbs[0].getFile(); |
328 | 0 | for (PDBEntry pdb : pdbs) |
329 | { | |
330 | 0 | String pdbFile = pdb.getFile(); |
331 | 0 | if (pdbFile == null || !pdbFile.equals(firstFile)) |
332 | { | |
333 | 0 | return null; |
334 | } | |
335 | 0 | SequenceI pdbseq = seqsForPdbs[i++]; |
336 | 0 | if (pdbseq != null) |
337 | { | |
338 | 0 | seqs.add(pdbseq); |
339 | } | |
340 | } | |
341 | 0 | return viewStructures(pdbs[0], seqs.toArray(new SequenceI[seqs.size()]), |
342 | ap); | |
343 | } | |
344 | ||
345 | JalviewStructureDisplayI sview = null; | |
346 | ||
347 | 46 | public JalviewStructureDisplayI getJalviewStructureDisplay() |
348 | { | |
349 | 46 | return sview; |
350 | } | |
351 | ||
352 | 3 | public JalviewStructureDisplayI viewStructures(PDBEntry pdb, |
353 | SequenceI[] seqsForPdb, AlignmentPanel ap) | |
354 | { | |
355 | 3 | return viewStructures(pdb, seqsForPdb, ap, null); |
356 | } | |
357 | ||
358 | 39 | public JalviewStructureDisplayI viewStructures(PDBEntry pdb, |
359 | SequenceI[] seqsForPdb, AlignmentPanel ap, ViewerType viewerType) | |
360 | { | |
361 | 39 | if (sview != null) |
362 | { | |
363 | 1 | sview.setAlignAddedStructures(superposeAdded); |
364 | 1 | String pdbId = pdb.getId(); |
365 | 1 | if (!sview.addAlreadyLoadedFile(seqsForPdb, null, ap, pdbId)) |
366 | { | |
367 | 1 | sview.addToExistingViewer(pdb, seqsForPdb, null, ap, pdbId); |
368 | } | |
369 | 1 | sview.updateTitleAndMenus(); |
370 | 1 | sview.raiseViewer(); |
371 | 1 | return sview; |
372 | } | |
373 | 38 | if (viewerType == null) |
374 | 2 | viewerType = getViewerType(); |
375 | 38 | if (viewerType.equals(ViewerType.JMOL)) |
376 | { | |
377 | 38 | sview = new AppJmol(pdb, seqsForPdb, null, ap); |
378 | } | |
379 | 0 | else if (viewerType.equals(ViewerType.CHIMERA)) |
380 | { | |
381 | 0 | sview = new ChimeraViewFrame(pdb, seqsForPdb, null, ap); |
382 | } | |
383 | 0 | else if (viewerType.equals(ViewerType.CHIMERAX)) |
384 | { | |
385 | 0 | sview = new ChimeraXViewFrame(pdb, seqsForPdb, null, ap); |
386 | } | |
387 | 0 | else if (viewerType.equals(ViewerType.PYMOL)) |
388 | { | |
389 | 0 | sview = new PymolViewer(pdb, seqsForPdb, null, ap); |
390 | } | |
391 | else | |
392 | { | |
393 | 0 | Console.error(UNKNOWN_VIEWER_TYPE + getViewerType().toString()); |
394 | } | |
395 | 38 | return sview; |
396 | } | |
397 | ||
398 | /** | |
399 | * Creates a new panel controlling a structure viewer | |
400 | * | |
401 | * @param type | |
402 | * @param alignPanel | |
403 | * @param viewerData | |
404 | * @param sessionFile | |
405 | * @param vid | |
406 | * @return | |
407 | */ | |
408 | 7 | public static JalviewStructureDisplayI createView(ViewerType type, |
409 | AlignmentPanel alignPanel, StructureViewerModel viewerData, | |
410 | String sessionFile, String vid) | |
411 | { | |
412 | 7 | JalviewStructureDisplayI viewer = null; |
413 | 7 | switch (type) |
414 | { | |
415 | 7 | case JMOL: |
416 | 7 | viewer = new AppJmol(viewerData, alignPanel, sessionFile, vid); |
417 | // todo or construct and then openSession(sessionFile)? | |
418 | 7 | break; |
419 | 0 | case CHIMERA: |
420 | 0 | viewer = new ChimeraViewFrame(viewerData, alignPanel, sessionFile, |
421 | vid); | |
422 | 0 | break; |
423 | 0 | case CHIMERAX: |
424 | 0 | viewer = new ChimeraXViewFrame(viewerData, alignPanel, sessionFile, |
425 | vid); | |
426 | 0 | break; |
427 | 0 | case PYMOL: |
428 | 0 | viewer = new PymolViewer(viewerData, alignPanel, sessionFile, vid); |
429 | 0 | break; |
430 | 0 | default: |
431 | 0 | Console.error(UNKNOWN_VIEWER_TYPE + type.toString()); |
432 | } | |
433 | 7 | return viewer; |
434 | } | |
435 | ||
436 | 585 | public boolean isBusy() |
437 | { | |
438 | 585 | if (sview != null) |
439 | { | |
440 | 585 | if (!sview.hasMapping()) |
441 | { | |
442 | 518 | return true; |
443 | } | |
444 | } | |
445 | 67 | return false; |
446 | } | |
447 | ||
448 | /** | |
449 | * | |
450 | * @param pDBid | |
451 | * @return true if view is already showing PDBid | |
452 | */ | |
453 | 0 | public boolean hasPdbId(String pDBid) |
454 | { | |
455 | 0 | if (sview == null) |
456 | { | |
457 | 0 | return false; |
458 | } | |
459 | ||
460 | 0 | return sview.getBinding().hasPdbId(pDBid); |
461 | } | |
462 | ||
463 | 1 | public boolean isVisible() |
464 | { | |
465 | 1 | return sview != null && sview.isVisible(); |
466 | } | |
467 | ||
468 | 36 | public void setSuperpose(boolean alignAddedStructures) |
469 | { | |
470 | 36 | superposeAdded = alignAddedStructures; |
471 | } | |
472 | ||
473 | } |