Clover icon

Coverage Report

  1. Project Clover database Wed Nov 5 2025 13:15:40 GMT
  2. Package jalview.gui

File StructureViewer.java

 

Coverage histogram

../../img/srcFileCovDistChart6.png
37% of files have more coverage

Code metrics

66
143
27
2
533
376
69
0.48
5.3
13.5
2.56

Classes

Class Line # Actions
StructureViewer 50 135 65
0.585585658.6%
StructureViewer.ViewerType 71 8 4
0.928571492.9%
 

Contributing tests

This file is covered by 53 tests. .

Source view

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