Clover icon

Coverage Report

  1. Project Clover database Thu Dec 4 2025 16:11:35 GMT
  2. Package jalview.gui

File StructureViewer.java

 

Coverage histogram

../../img/srcFileCovDistChart5.png
43% of files have more coverage

Code metrics

96
189
30
2
669
473
89
0.47
6.3
15
2.97

Classes

Class Line # Actions
StructureViewer 57 181 85
0.438538243.9%
StructureViewer.ViewerType 88 8 4
0.928571492.9%
 

Contributing tests

This file is covered by 43 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    import jalview.api.structures.JalviewStructureDisplayI;
32    import jalview.bin.Cache;
33    import jalview.bin.Console;
34    import jalview.datamodel.PDBEntry;
35    import jalview.datamodel.SequenceI;
36    import jalview.datamodel.StructureViewerModel;
37    import jalview.io.DataSourceType;
38    import jalview.structure.StructureImportSettings.TFType;
39    import jalview.structure.StructureMapping;
40    import jalview.structure.StructureSelectionManager;
41    import jalview.util.MessageManager;
42    import jalview.util.Platform;
43    import jalview.ws.DBRefFetcher;
44    import jalview.ws.seqfetcher.DbSourceProxy;
45    import jalview.ws.sifts.SiftsSettings;
46   
47   
48   
49    /**
50    * A proxy for handling structure viewers, that orchestrates adding selected
51    * structures, associated with sequences in Jalview, to an existing viewer, or
52    * opening a new one. Currently supports either Jmol or Chimera as the structure
53    * viewer.
54    *
55    * @author jprocter
56    */
 
57    public class StructureViewer
58    {
59   
 
60  5 toggle static
61    {
62  5 Platform.loadStaticResource("core/core_jvjmol.z.js",
63    "org.jmol.viewer.Viewer");
64    }
65   
66   
67   
68   
69    private static final String UNKNOWN_VIEWER_TYPE = "Unknown structure viewer type ";
70   
71    StructureSelectionManager ssm;
72   
73    /**
74    * decide if new structures are aligned to existing ones
75    */
76    private boolean superposeAdded = true;
77   
78    /**
79    * whether to open structures in their own thread or not
80    */
81    private boolean async = true;
82   
 
83  2 toggle public void setAsync(boolean b)
84    {
85  2 async = b;
86    }
87   
 
88    public enum ViewerType
89    {
90    JMOL, CHIMERA, CHIMERAX, PYMOL;
91   
 
92  47 toggle public static ViewerType getFromString(String viewerString)
93    {
94  47 ViewerType viewerType = null;
95  47 if (!"none".equals(viewerString))
96    {
97  38 for (ViewerType v : EnumSet.allOf(ViewerType.class))
98    {
99  38 if (viewerString.equals(v.shortName()))
100    {
101  38 viewerType = v;
102  38 break;
103    }
104    }
105    }
106  47 return viewerType;
107    }
108   
 
109  38 toggle public String shortName()
110    {
111  38 return name().toLowerCase(Locale.ROOT).replaceAll(" ", "");
112    }
113   
114    };
115   
116    /**
117    * Constructor
118    *
119    * @param structureSelectionManager
120    */
 
121  39 toggle public StructureViewer(
122    StructureSelectionManager structureSelectionManager)
123    {
124  39 ssm = structureSelectionManager;
125    }
126   
127    /**
128    * Factory to create a proxy for modifying existing structure viewer
129    *
130    */
 
131  0 toggle public static StructureViewer reconfigure(
132    JalviewStructureDisplayI display)
133    {
134  0 StructureViewer sv = new StructureViewer(display.getBinding().getSsm());
135  0 sv.sview = display;
136  0 return sv;
137    }
138   
 
139  0 toggle @Override
140    public String toString()
141    {
142  0 if (sview != null)
143    {
144  0 return sview.toString();
145    }
146  0 return "New View";
147    }
148   
149    /**
150    *
151    * @return ViewerType for currently configured structure viewer
152    */
 
153  57 toggle public static ViewerType getViewerType()
154    {
155  57 String viewType = Cache.getDefault(Preferences.STRUCTURE_DISPLAY,
156    ViewerType.JMOL.name());
157  57 return ViewerType.valueOf(viewType);
158    }
159   
 
160  2 toggle public void setViewerType(ViewerType type)
161    {
162  2 Cache.setProperty(Preferences.STRUCTURE_DISPLAY, type.name());
163    }
164   
165    /**
166    * View multiple PDB entries, each with associated sequences
167    *
168    * @param pdbs
169    * @param seqs
170    * @param ap
171    * @return
172    */
 
173  0 toggle public JalviewStructureDisplayI viewStructures(PDBEntry[] pdbs,
174    SequenceI[] seqs, AlignmentPanel ap)
175    {
176  0 return viewStructures(pdbs, seqs, ap, null);
177    }
178   
 
179  5 toggle public JalviewStructureDisplayI viewStructures(String structureLocation,
180    DataSourceType type, SequenceI seq, AlignmentPanel ap,
181    boolean prompt, TFType tft, String paeFileSource,
182    boolean forceHeadless, boolean showRefAnnotations,
183    boolean doXferSettings, boolean tempAlignStructures)
184    {
185  5 PDBEntry fileEntry = AssociatePdbFileWithSeq.associatePdbWithSeq(
186    structureLocation, type, seq, prompt, Desktop.getInstance(), tft,
187    paeFileSource, doXferSettings);
188   
189  5 return viewStructures(new PDBEntry[] { fileEntry },
190    new SequenceI[]
191    { seq }, ap, null, tempAlignStructures);
192    }
193   
 
194  5 toggle public JalviewStructureDisplayI viewStructures(PDBEntry[] pdbs,
195    SequenceI[] seqs, AlignmentPanel ap, ViewerType viewerType)
196    {
197  5 return viewStructures(pdbs, seqs, ap, viewerType, superposeAdded);
198    }
199   
 
200  10 toggle public JalviewStructureDisplayI viewStructures(PDBEntry[] pdbs,
201    SequenceI[] seqs, AlignmentPanel ap, ViewerType viewerType,
202    boolean thisSuperpose)
203    {
204  10 JalviewStructureDisplayI viewer = onlyOnePdb(pdbs, seqs, ap,
205    thisSuperpose);
206  10 if (viewer != null)
207    {
208    /*
209    * user added structure to an existing viewer - all done
210    */
211  5 return viewer;
212    }
213   
214  5 if (viewerType == null)
215  0 viewerType = getViewerType();
216   
217  5 Map<PDBEntry, SequenceI[]> seqsForPdbs = getSequencesForPdbs(pdbs,
218    seqs);
219  5 PDBEntry[] pdbsForFile = seqsForPdbs.keySet()
220    .toArray(new PDBEntry[seqsForPdbs.size()]);
221  5 SequenceI[][] theSeqs = seqsForPdbs.values()
222    .toArray(new SequenceI[seqsForPdbs.size()][]);
223  5 if (sview != null)
224    {
225  0 sview.setAlignAddedStructures(thisSuperpose);
226   
227  0 Runnable viewRunnable = new Runnable()
228    {
 
229  0 toggle @Override
230    public void run()
231    {
232   
233  0 for (int pdbep = 0; pdbep < pdbsForFile.length; pdbep++)
234    {
235  0 PDBEntry pdb = pdbsForFile[pdbep];
236  0 if (!sview.addAlreadyLoadedFile(theSeqs[pdbep], null, ap,
237    pdb.getId()))
238    {
239  0 sview.addToExistingViewer(pdb, theSeqs[pdbep], null, ap,
240    pdb.getId());
241    }
242    }
243   
244  0 sview.updateTitleAndMenus();
245    }
246    };
247  0 if (async)
248    {
249  0 new Thread(viewRunnable).start();
250    }
251    else
252    {
253  0 viewRunnable.run();
254    }
255  0 return sview;
256    }
257   
258  5 if (viewerType.equals(ViewerType.JMOL))
259    {
260  5 sview = new AppJmol(ap, superposeAdded, pdbsForFile, theSeqs);
261    }
262  0 else if (viewerType.equals(ViewerType.CHIMERA))
263    {
264  0 sview = new ChimeraViewFrame(pdbsForFile, superposeAdded, theSeqs,
265    ap);
266    }
267  0 else if (viewerType.equals(ViewerType.CHIMERAX))
268    {
269  0 sview = new ChimeraXViewFrame(pdbsForFile, superposeAdded, theSeqs,
270    ap);
271    }
272  0 else if (viewerType.equals(ViewerType.PYMOL))
273    {
274  0 sview = new PymolViewer(pdbsForFile, superposeAdded, theSeqs, ap);
275    }
276    else
277    {
278  0 Console.error(UNKNOWN_VIEWER_TYPE + getViewerType().toString());
279    }
280  5 return sview;
281    }
282   
283    /**
284    * Converts the list of selected PDB entries (possibly including duplicates
285    * for multiple chains), and corresponding sequences, into a map of sequences
286    * for each distinct PDB file. Returns null if either argument is null, or
287    * their lengths do not match.
288    *
289    * @param pdbs
290    * @param seqs
291    * @return
292    */
 
293  7 toggle Map<PDBEntry, SequenceI[]> getSequencesForPdbs(PDBEntry[] pdbs,
294    SequenceI[] seqs)
295    {
296  7 if (pdbs == null || seqs == null || pdbs.length != seqs.length)
297    {
298  1 return null;
299    }
300   
301    /*
302    * we want only one 'representative' PDBEntry per distinct file name
303    * (there may be entries for distinct chains)
304    */
305  6 Map<String, PDBEntry> pdbsSeen = new HashMap<>();
306   
307    /*
308    * LinkedHashMap preserves order of PDB entries (significant if they
309    * will get superimposed to the first structure)
310    */
311  6 Map<PDBEntry, List<SequenceI>> pdbSeqs = new LinkedHashMap<>();
312  28 for (int i = 0; i < pdbs.length; i++)
313    {
314  22 PDBEntry pdb = pdbs[i];
315  22 SequenceI seq = seqs[i];
316  22 String pdbFile = pdb.getFile();
317  22 if (pdbFile == null || pdbFile.length() == 0)
318    {
319  3 pdbFile = pdb.getId();
320    }
321  22 if (!pdbsSeen.containsKey(pdbFile))
322    {
323  19 pdbsSeen.put(pdbFile, pdb);
324  19 pdbSeqs.put(pdb, new ArrayList<SequenceI>());
325    }
326    else
327    {
328  3 pdb = pdbsSeen.get(pdbFile);
329    }
330  22 List<SequenceI> seqsForPdb = pdbSeqs.get(pdb);
331  22 if (!seqsForPdb.contains(seq))
332    {
333  22 seqsForPdb.add(seq);
334    }
335    }
336   
337    /*
338    * convert to Map<PDBEntry, SequenceI[]>
339    */
340  6 Map<PDBEntry, SequenceI[]> result = new LinkedHashMap<>();
341  6 for (Entry<PDBEntry, List<SequenceI>> entry : pdbSeqs.entrySet())
342    {
343  19 List<SequenceI> theSeqs = entry.getValue();
344  19 result.put(entry.getKey(),
345    theSeqs.toArray(new SequenceI[theSeqs.size()]));
346    }
347   
348  6 return result;
349    }
350   
351    /**
352    * A strictly temporary method pending JAL-1761 refactoring. Determines if all
353    * the passed PDB entries are the same (this is the case if selected sequences
354    * to view structure for are chains of the same structure). If so, calls the
355    * single-pdb version of viewStructures and returns the viewer, else returns
356    * null.
357    *
358    * @param pdbs
359    * @param seqsForPdbs
360    * @param ap
361    * @return
362    */
 
363  0 toggle private JalviewStructureDisplayI onlyOnePdb(PDBEntry[] pdbs,
364    SequenceI[] seqsForPdbs, AlignmentPanel ap)
365    {
366  0 return onlyOnePdb(pdbs, seqsForPdbs, ap, superposeAdded);
367    }
368   
 
369  10 toggle private JalviewStructureDisplayI onlyOnePdb(PDBEntry[] pdbs,
370    SequenceI[] seqsForPdbs, AlignmentPanel ap, boolean thisSuperpose)
371    {
372  10 List<SequenceI> seqs = new ArrayList<>();
373  10 if (pdbs == null || pdbs.length == 0)
374    {
375  0 return null;
376    }
377  10 int i = 0;
378  10 String firstFile = pdbs[0].getFile();
379  10 for (PDBEntry pdb : pdbs)
380    {
381  15 String pdbFile = pdb.getFile();
382  15 if (pdbFile == null || !pdbFile.equals(firstFile))
383    {
384    // ***** BS 2025-06-25: Do we not want an i++ here too?
385  5 return null;
386    }
387  10 SequenceI pdbseq = seqsForPdbs[i++];
388  10 if (pdbseq != null)
389    {
390  10 seqs.add(pdbseq);
391    }
392    }
393  5 return viewStructures(pdbs[0], seqs.toArray(new SequenceI[seqs.size()]),
394    ap, null, thisSuperpose);
395    }
396   
397    JalviewStructureDisplayI sview = null;
398   
 
399  31 toggle public JalviewStructureDisplayI getJalviewStructureDisplay()
400    {
401  31 return sview;
402    }
403   
 
404  3 toggle public JalviewStructureDisplayI viewStructures(PDBEntry pdb,
405    SequenceI[] seqsForPdb, AlignmentPanel ap)
406    {
407  3 return viewStructures(pdb, seqsForPdb, ap, null);
408    }
409   
 
410  34 toggle public JalviewStructureDisplayI viewStructures(PDBEntry pdb,
411    SequenceI[] seqsForPdb, AlignmentPanel ap, ViewerType viewerType)
412    {
413  34 return viewStructures(pdb, seqsForPdb, ap, viewerType, superposeAdded);
414    }
415   
 
416  39 toggle public JalviewStructureDisplayI viewStructures(PDBEntry pdb,
417    SequenceI[] seqsForPdb, AlignmentPanel ap, ViewerType viewerType,
418    boolean thisSuperpose)
419    {
420  39 if (sview != null)
421    {
422  6 sview.setAlignAddedStructures(thisSuperpose);
423  6 String pdbId = pdb.getId();
424  6 if (!sview.addAlreadyLoadedFile(seqsForPdb, null, ap, pdbId))
425    {
426  5 sview.addToExistingViewer(pdb, seqsForPdb, null, ap, pdbId);
427    }
428  6 sview.updateTitleAndMenus();
429  6 sview.raiseViewer();
430  6 return sview;
431    }
432  33 if (viewerType == null)
433  2 viewerType = getViewerType();
434  33 if (viewerType.equals(ViewerType.JMOL))
435    {
436  33 sview = new AppJmol(pdb, seqsForPdb, null, ap);
437    }
438  0 else if (viewerType.equals(ViewerType.CHIMERA))
439    {
440  0 sview = new ChimeraViewFrame(pdb, seqsForPdb, null, ap);
441    }
442  0 else if (viewerType.equals(ViewerType.CHIMERAX))
443    {
444  0 sview = new ChimeraXViewFrame(pdb, seqsForPdb, null, ap);
445    }
446  0 else if (viewerType.equals(ViewerType.PYMOL))
447    {
448  0 sview = new PymolViewer(pdb, seqsForPdb, null, ap);
449    }
450    else
451    {
452  0 Console.error(UNKNOWN_VIEWER_TYPE + getViewerType().toString());
453    }
454   
455  33 return sview;
456    }
457   
 
458  10 toggle public void showAllChains()
459    {
460  10 if (sview != null)
461    {
462  10 sview.showAllChains();
463    }
464    }
465   
 
466  0 toggle public void setPermanentTitle(String title)
467    {
468  0 if (sview != null)
469    {
470  0 sview.setPermanentTitle(title);
471  0 sview.updateTitleAndMenus();
472    }
473    }
474   
475    /**
476    * Creates a new panel controlling a structure viewer
477    *
478    * @param type
479    * @param pdbf
480    * @param id
481    * @param sq
482    * @param alignPanel
483    * @param viewerData
484    * @param sessionFile
485    * @param vid
486    * @return
487    */
 
488  14 toggle public static JalviewStructureDisplayI createView(ViewerType type,
489    AlignmentPanel alignPanel, StructureViewerModel viewerData,
490    String sessionFile, String vid)
491    {
492  14 JalviewStructureDisplayI viewer = null;
493   
494  14 switch (type)
495    {
496  14 case JMOL:
497  14 viewer = new AppJmol(viewerData, alignPanel, sessionFile, vid);
498    // todo or construct and then openSession(sessionFile)?
499  12 break;
500  0 case CHIMERA:
501  0 viewer = new ChimeraViewFrame(viewerData, alignPanel, sessionFile,
502    vid);
503  0 break;
504  0 case CHIMERAX:
505  0 viewer = new ChimeraXViewFrame(viewerData, alignPanel, sessionFile,
506    vid);
507  0 break;
508  0 case PYMOL:
509  0 viewer = new PymolViewer(viewerData, alignPanel, sessionFile, vid);
510  0 break;
511  0 default:
512  0 Console.error(UNKNOWN_VIEWER_TYPE + type.toString());
513    }
514  12 return viewer;
515    }
516   
 
517  704 toggle public boolean isBusy()
518    {
519  704 if (sview != null)
520    {
521  704 if (!sview.hasMapping())
522    {
523  638 return true;
524    }
525    }
526  66 return false;
527    }
528   
529    /**
530    *
531    * @param pDBid
532    * @return true if view is already showing PDBid
533    */
 
534  0 toggle public boolean hasPdbId(String pDBid)
535    {
536  0 if (sview == null)
537    {
538  0 return false;
539    }
540   
541  0 return sview.getBinding().hasPdbId(pDBid);
542    }
543   
 
544  1 toggle public boolean isVisible()
545    {
546  1 return sview != null && sview.isVisible();
547    }
548   
 
549  36 toggle public void setSuperpose(boolean alignAddedStructures)
550    {
551  36 superposeAdded = alignAddedStructures;
552    }
553   
554    /**
555    * Launch a minimal implementation of a StructureViewer.
556    *
557    * @param alignPanel
558    * @param pdb
559    * @param seqs
560    * @return
561    */
 
562  0 toggle public static StructureViewer launchStructureViewer(
563    AlignmentPanel alignPanel, PDBEntry pdb, SequenceI[] seqs)
564    {
565  0 return launchStructureViewer(alignPanel, new PDBEntry[] { pdb }, seqs,
566    false, null, null);
567    }
568   
569    /**
570    * Adds PDB structures to a new or existing structure viewer
571    *
572    * @param ssm
573    * @param pdbEntriesToView
574    * @param alignPanel
575    * @param sequences
576    * @return
577    */
 
578  0 toggle protected static StructureViewer launchStructureViewer(
579    final AlignmentPanel ap, final PDBEntry[] pdbEntriesToView,
580    SequenceI[] sequences, boolean superimpose,
581    StructureViewer theViewer, IProgressIndicator pb)
582    {
583  0 final StructureSelectionManager ssm = ap.getStructureSelectionManager();
584  0 if (theViewer == null)
585  0 theViewer = new StructureViewer(ssm);
586  0 long progressId = sequences.hashCode();
587  0 if (pb != null)
588  0 pb.setProgressBar(MessageManager.getString(
589    "status.launching_3d_structure_viewer"), progressId);
590  0 theViewer.setSuperpose(superimpose);
591   
592    /*
593    * remember user's choice of superimpose or not
594    */
595  0 Cache.setProperty(StructureChooser.AUTOSUPERIMPOSE,
596    Boolean.valueOf(superimpose).toString());
597   
598  0 if (pb != null)
599  0 pb.setProgressBar(null, progressId);
600  0 if (SiftsSettings.isMapWithSifts())
601    {
602  0 List<SequenceI> seqsWithoutSourceDBRef = new ArrayList<>();
603  0 int p = 0;
604    // TODO: skip PDBEntry:Sequence pairs where PDBEntry doesn't look like a
605    // real PDB ID. For moment, we can also safely do this if there is already
606    // a known mapping between the PDBEntry and the sequence.
607  0 for (SequenceI seq : sequences)
608    {
609  0 PDBEntry pdbe = pdbEntriesToView[p++];
610  0 if (pdbe != null && pdbe.getFile() != null)
611    {
612  0 StructureMapping[] smm = ssm.getMapping(pdbe.getFile());
613  0 if (smm != null && smm.length > 0)
614    {
615  0 for (StructureMapping sm : smm)
616    {
617  0 if (sm.getSequence() == seq)
618    {
619  0 continue;
620    }
621    }
622    }
623    }
624  0 if (seq.getPrimaryDBRefs().isEmpty())
625    {
626  0 seqsWithoutSourceDBRef.add(seq);
627  0 continue;
628    }
629    }
630  0 if (!seqsWithoutSourceDBRef.isEmpty())
631    {
632  0 int y = seqsWithoutSourceDBRef.size();
633  0 if (pb != null)
634  0 pb.setProgressBar(MessageManager.formatMessage(
635    "status.fetching_dbrefs_for_sequences_without_valid_refs",
636    y), progressId);
637  0 SequenceI[] seqWithoutSrcDBRef = seqsWithoutSourceDBRef
638    .toArray(new SequenceI[y]);
639  0 DBRefFetcher dbRefFetcher = new DBRefFetcher(seqWithoutSrcDBRef);
640  0 dbRefFetcher.fetchDBRefs(true);
641   
642  0 if (pb != null)
643  0 pb.setProgressBar("Fetch complete.", progressId); // todo i18n
644    }
645    }
646  0 if (pdbEntriesToView.length > 1)
647    {
648  0 if (pb != null)
649  0 pb.setProgressBar(MessageManager.getString(
650    "status.fetching_3d_structures_for_selected_entries"),
651    progressId);
652  0 theViewer.viewStructures(pdbEntriesToView, sequences, ap);
653    }
654    else
655    {
656  0 if (pb != null)
657  0 pb.setProgressBar(MessageManager.formatMessage(
658    "status.fetching_3d_structures_for",
659    pdbEntriesToView[0].getId()), progressId);
660  0 theViewer.viewStructures(pdbEntriesToView[0], sequences, ap);
661    }
662  0 if (pb != null)
663  0 pb.setProgressBar(null, progressId);
664    // remember the last viewer we used...
665  0 StructureChooser.lastTargetedView = theViewer;
666  0 return theViewer;
667    }
668   
669    }