Clover icon

Coverage Report

  1. Project Clover database Mon Sep 2 2024 17:57:51 BST
  2. Package jalview.gui

File StructureViewerBase.java

 

Coverage histogram

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

Code metrics

170
426
78
2
1,526
1,122
192
0.45
5.46
39
2.46

Classes

Class Line # Actions
StructureViewerBase 81 426 192
0.486646948.7%
StructureViewerBase.ViewerColour 87 0 0
-1.0 -
 

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.awt.Color;
24    import java.awt.Component;
25    import java.awt.event.ActionEvent;
26    import java.awt.event.ActionListener;
27    import java.awt.event.ItemEvent;
28    import java.awt.event.ItemListener;
29    import java.beans.PropertyVetoException;
30    import java.io.BufferedReader;
31    import java.io.File;
32    import java.io.FileOutputStream;
33    import java.io.FileReader;
34    import java.io.IOException;
35    import java.io.PrintWriter;
36    import java.util.ArrayList;
37    import java.util.List;
38    import java.util.Map;
39    import java.util.Random;
40    import java.util.Vector;
41   
42    import javax.swing.ButtonGroup;
43    import javax.swing.JCheckBoxMenuItem;
44    import javax.swing.JMenu;
45    import javax.swing.JMenuItem;
46    import javax.swing.JRadioButtonMenuItem;
47    import javax.swing.event.MenuEvent;
48    import javax.swing.event.MenuListener;
49   
50    import jalview.api.AlignmentViewPanel;
51    import jalview.api.structures.JalviewStructureDisplayI;
52    import jalview.bin.Cache;
53    import jalview.bin.Console;
54    import jalview.datamodel.AlignmentI;
55    import jalview.datamodel.PDBEntry;
56    import jalview.datamodel.SequenceI;
57    import jalview.gui.JalviewColourChooser.ColourChooserListener;
58    import jalview.gui.StructureViewer.ViewerType;
59    import jalview.gui.ViewSelectionMenu.ViewSetProvider;
60    import jalview.io.DataSourceType;
61    import jalview.io.JalviewFileChooser;
62    import jalview.io.JalviewFileView;
63    import jalview.jbgui.GStructureViewer;
64    import jalview.schemes.ColourSchemeI;
65    import jalview.schemes.ColourSchemes;
66    import jalview.structure.StructureMapping;
67    import jalview.structures.models.AAStructureBindingModel;
68    import jalview.util.BrowserLauncher;
69    import jalview.util.MessageManager;
70    import jalview.ws.dbsources.EBIAlfaFold;
71    import jalview.ws.dbsources.Pdb;
72    import jalview.ws.utils.UrlDownloadClient;
73   
74    /**
75    * Base class with common functionality for JMol, Chimera or other structure
76    * viewers.
77    *
78    * @author gmcarstairs
79    *
80    */
 
81    public abstract class StructureViewerBase extends GStructureViewer
82    implements Runnable, ViewSetProvider
83    {
84    /*
85    * names for colour options (additional to Jalview colour schemes)
86    */
 
87    enum ViewerColour
88    {
89    BySequence, ByChain, ChargeCysteine, ByViewer
90    }
91   
92    /**
93    * Singleton list of all (open) instances of structureViewerBase TODO:
94    * JAL-3362 - review and adopt the swingJS-safe singleton pattern so each
95    * structure viewer base instance is kept to its own JalviewJS parent
96    */
97    private static List<JalviewStructureDisplayI> svbs = new ArrayList<>();
98   
99    /**
100    *
101    * @return list with all existing StructureViewers instance
102    */
 
103  0 toggle public static List<JalviewStructureDisplayI> getAllStructureViewerBases()
104    {
105  0 List<JalviewStructureDisplayI> goodSvbs = new ArrayList<>();
106  0 for (JalviewStructureDisplayI s : svbs)
107    {
108  0 if (s != null && !goodSvbs.contains(s))
109    {
110  0 goodSvbs.add(s);
111    }
112    }
113  0 return goodSvbs;
114    }
115   
116    /**
117    * list of sequenceSet ids associated with the view
118    */
119    protected List<String> _aps = new ArrayList<>();
120   
121    /**
122    * list of alignment panels to use for superposition
123    */
124    protected Vector<AlignmentViewPanel> _alignwith = new Vector<>();
125   
126    /**
127    * list of alignment panels that are used for colouring structures by aligned
128    * sequences
129    */
130    protected Vector<AlignmentViewPanel> _colourwith = new Vector<>();
131   
132    private String viewId = null;
133   
134    private AlignmentPanel ap;
135   
136    protected boolean alignAddedStructures = false;
137   
138    protected volatile boolean _started = false;
139   
140    protected volatile boolean addingStructures = false;
141   
142    protected Thread worker = null;
143   
144    protected boolean allChainsSelected = false;
145   
146    protected boolean allHetatmBeingSelected = false;
147   
148    protected JMenu viewSelectionMenu;
149   
150    /**
151    * set after sequence colouring has been applied for this structure viewer.
152    * used to determine if the final sequence/structure mapping has been
153    * determined
154    */
155    protected volatile boolean seqColoursApplied = false;
156   
157    private IProgressIndicator progressBar = null;
158   
159    private Random random = new Random();
160   
161    /**
162    * Default constructor
163    */
 
164  45 toggle public StructureViewerBase()
165    {
166  45 super();
167  45 setFrameIcon(null);
168  45 svbs.add(this);
169    }
170   
171    /**
172    * @return true if added structures should be aligned to existing one(s)
173    */
 
174  0 toggle @Override
175    public boolean isAlignAddedStructures()
176    {
177  0 return alignAddedStructures;
178    }
179   
180    /**
181    *
182    * @param true
183    * if added structures should be aligned to existing one(s)
184    */
 
185  1 toggle @Override
186    public void setAlignAddedStructures(boolean alignAdded)
187    {
188  1 alignAddedStructures = alignAdded;
189    }
190   
191    /**
192    * called by the binding model to indicate when adding structures is happening
193    * or has been completed
194    *
195    * @param addingStructures
196    */
 
197  2 toggle public synchronized void setAddingStructures(boolean addingStructures)
198    {
199  2 this.addingStructures = addingStructures;
200    }
201   
202    /**
203    *
204    * @param ap2
205    * @return true if this Jmol instance is linked with the given alignPanel
206    */
 
207  17 toggle public boolean isLinkedWith(AlignmentPanel ap2)
208    {
209  17 return _aps.contains(ap2.av.getSequenceSetId());
210    }
211   
 
212  20 toggle @Override
213    public boolean isUsedforaligment(AlignmentViewPanel ap2)
214    {
215   
216  20 return (_alignwith != null) && _alignwith.contains(ap2);
217    }
218   
 
219  1153 toggle @Override
220    public boolean isUsedForColourBy(AlignmentViewPanel ap2)
221    {
222  1153 return (_colourwith != null) && _colourwith.contains(ap2);
223    }
224   
225    /**
226    *
227    * @return TRUE if the view is NOT being coloured by the alignment colours.
228    */
 
229  20 toggle public boolean isColouredByViewer()
230    {
231  20 return !getBinding().isColourBySequence();
232    }
233   
 
234  101 toggle public String getViewId()
235    {
236  101 if (viewId == null)
237    {
238  0 viewId = System.currentTimeMillis() + "." + this.hashCode();
239    }
240  101 return viewId;
241    }
242   
 
243  7 toggle protected void setViewId(String viewId)
244    {
245  7 this.viewId = viewId;
246    }
247   
 
248  38 toggle protected void buildActionMenu()
249    {
250  38 if (_alignwith == null)
251    {
252  0 _alignwith = new Vector<>();
253    }
254  38 if (_alignwith.size() == 0 && ap != null)
255    {
256  6 _alignwith.add(ap);
257    }
258  38 ;
259    // TODO: refactor to allow concrete classes to register buttons for adding
260    // here
261    // currently have to override to add buttons back in after they are cleared
262    // in this loop
263  38 for (Component c : viewerActionMenu.getMenuComponents())
264    {
265  45 if (c != alignStructs)
266    {
267  7 viewerActionMenu.remove((JMenuItem) c);
268    }
269    }
270    }
271   
 
272  335 toggle @Override
273    public AlignmentPanel getAlignmentPanel()
274    {
275  335 return ap;
276    }
277   
 
278  69 toggle protected void setAlignmentPanel(AlignmentPanel alp)
279    {
280  69 this.ap = alp;
281    }
282   
 
283  1 toggle @Override
284    public AlignmentPanel[] getAllAlignmentPanels()
285    {
286  1 AlignmentPanel[] t, list = new AlignmentPanel[0];
287  1 for (String setid : _aps)
288    {
289  1 AlignmentPanel[] panels = PaintRefresher.getAssociatedPanels(setid);
290  1 if (panels != null)
291    {
292  1 t = new AlignmentPanel[list.length + panels.length];
293  1 System.arraycopy(list, 0, t, 0, list.length);
294  1 System.arraycopy(panels, 0, t, list.length, panels.length);
295  1 list = t;
296    }
297    }
298   
299  1 return list;
300    }
301   
302    /**
303    * set the primary alignmentPanel reference and add another alignPanel to the
304    * list of ones to use for colouring and aligning
305    *
306    * @param nap
307    */
 
308  125 toggle public void addAlignmentPanel(AlignmentPanel nap)
309    {
310  125 if (getAlignmentPanel() == null)
311    {
312  45 setAlignmentPanel(nap);
313    }
314  125 if (!_aps.contains(nap.av.getSequenceSetId()))
315    {
316  46 _aps.add(nap.av.getSequenceSetId());
317    }
318    }
319   
320    /**
321    * remove any references held to the given alignment panel
322    *
323    * @param nap
324    */
 
325  40 toggle @Override
326    public void removeAlignmentPanel(AlignmentViewPanel nap)
327    {
328  40 try
329    {
330  40 _alignwith.remove(nap);
331  40 _colourwith.remove(nap);
332  40 if (getAlignmentPanel() == nap)
333    {
334  1 setAlignmentPanel(null);
335  1 for (AlignmentPanel aps : getAllAlignmentPanels())
336    {
337  0 if (aps != nap)
338    {
339  0 setAlignmentPanel(aps);
340  0 break;
341    }
342    }
343    }
344    } catch (Exception ex)
345    {
346    }
347  40 if (getAlignmentPanel() != null)
348    {
349  38 buildActionMenu();
350    }
351    }
352   
 
353  1 toggle public void useAlignmentPanelForSuperposition(AlignmentPanel nap)
354    {
355  1 addAlignmentPanel(nap);
356  1 if (!_alignwith.contains(nap))
357    {
358  1 _alignwith.add(nap);
359    }
360    }
361   
 
362  33 toggle public void excludeAlignmentPanelForSuperposition(AlignmentPanel nap)
363    {
364  33 if (_alignwith.contains(nap))
365    {
366  0 _alignwith.remove(nap);
367    }
368    }
369   
 
370  8 toggle public void useAlignmentPanelForColourbyseq(AlignmentPanel nap,
371    boolean enableColourBySeq)
372    {
373  8 useAlignmentPanelForColourbyseq(nap);
374  8 getBinding().setColourBySequence(enableColourBySeq);
375  8 seqColour.setSelected(enableColourBySeq);
376  8 viewerColour.setSelected(!enableColourBySeq);
377    }
378   
 
379  46 toggle public void useAlignmentPanelForColourbyseq(AlignmentPanel nap)
380    {
381  46 addAlignmentPanel(nap);
382  46 if (!_colourwith.contains(nap))
383    {
384  46 _colourwith.add(nap);
385    }
386    }
387   
 
388  25 toggle public void excludeAlignmentPanelForColourbyseq(AlignmentPanel nap)
389    {
390  25 if (_colourwith.contains(nap))
391    {
392  0 _colourwith.remove(nap);
393    }
394    }
395   
396    public abstract ViewerType getViewerType();
397   
398    /**
399    * add a new structure (with associated sequences and chains) to this viewer,
400    * retrieving it if necessary first.
401    *
402    * @param pdbentry
403    * @param seqs
404    * @param chains
405    * @param align
406    * if true, new structure(s) will be aligned using associated
407    * alignment
408    * @param alignFrame
409    */
 
410  1 toggle protected void addStructure(final PDBEntry pdbentry,
411    final SequenceI[] seqs, final String[] chains,
412    final IProgressIndicator alignFrame)
413    {
414  1 if (pdbentry.getFile() == null)
415    {
416  0 if (worker != null && worker.isAlive())
417    {
418    // a retrieval is in progress, wait around and add ourselves to the
419    // queue.
420  0 new Thread(new Runnable()
421    {
 
422  0 toggle @Override
423    public void run()
424    {
425  0 while (worker != null && worker.isAlive() && _started)
426    {
427  0 try
428    {
429  0 Thread.sleep(100 + ((int) Math.random() * 100));
430   
431    } catch (Exception e)
432    {
433    }
434    }
435    // and call ourselves again.
436  0 addStructure(pdbentry, seqs, chains, alignFrame);
437    }
438    }).start();
439  0 return;
440    }
441    }
442    // otherwise, start adding the structure.
443  1 getBinding().addSequenceAndChain(new PDBEntry[] { pdbentry },
444    new SequenceI[][]
445    { seqs }, new String[][] { chains });
446  1 addingStructures = true;
447  1 _started = false;
448  1 worker = new Thread(this);
449  1 worker.start();
450  1 return;
451    }
452   
 
453  1 toggle protected boolean hasPdbId(String pdbId)
454    {
455  1 return getBinding().hasPdbId(pdbId);
456    }
457   
458    /**
459    * Returns a list of any viewer of the instantiated type. The list is
460    * restricted to those linked to the given alignment panel if it is not null.
461    */
 
462  0 toggle protected List<StructureViewerBase> getViewersFor(AlignmentPanel alp)
463    {
464  0 return Desktop.instance.getStructureViewers(alp, this.getClass());
465    }
466   
 
467  1 toggle @Override
468    public void addToExistingViewer(PDBEntry pdbentry, SequenceI[] seq,
469    String[] chains, final AlignmentViewPanel apanel, String pdbId)
470    {
471    /*
472    * JAL-1742 exclude view with this structure already mapped (don't offer
473    * to align chain B to chain A of the same structure); code may defend
474    * against this possibility before we reach here
475    */
476  1 if (hasPdbId(pdbId))
477    {
478  0 return;
479    }
480  1 AlignmentPanel alignPanel = (AlignmentPanel) apanel; // Implementation error
481    // if this
482    // cast fails
483  1 useAlignmentPanelForSuperposition(alignPanel);
484  1 addStructure(pdbentry, seq, chains, alignPanel.alignFrame);
485    }
486   
487    /**
488    * Adds mappings for the given sequences to an already opened PDB structure,
489    * and updates any viewers that have the PDB file
490    *
491    * @param seq
492    * @param chains
493    * @param apanel
494    * @param pdbFilename
495    */
 
496  0 toggle public void addSequenceMappingsToStructure(SequenceI[] seq,
497    String[] chains, final AlignmentViewPanel alpanel,
498    String pdbFilename)
499    {
500  0 AlignmentPanel apanel = (AlignmentPanel) alpanel;
501   
502    // TODO : Fix multiple seq to one chain issue here.
503    /*
504    * create the mappings
505    */
506  0 apanel.getStructureSelectionManager().setMapping(seq, chains,
507    pdbFilename, DataSourceType.FILE, getProgressIndicator());
508   
509    /*
510    * alert the FeatureRenderer to show new (PDB RESNUM) features
511    */
512  0 if (apanel.getSeqPanel().seqCanvas.fr != null)
513    {
514  0 apanel.getSeqPanel().seqCanvas.fr.featuresAdded();
515    // note - we don't do a refresh for structure here because we do it
516    // explicitly for all panels later on
517  0 apanel.paintAlignment(true, false);
518    }
519   
520    /*
521    * add the sequences to any other viewers (of the same type) for this pdb
522    * file
523    */
524    // JBPNOTE: this looks like a binding routine, rather than a gui routine
525  0 for (StructureViewerBase viewer : getViewersFor(null))
526    {
527  0 AAStructureBindingModel bindingModel = viewer.getBinding();
528  0 for (int pe = 0; pe < bindingModel.getPdbCount(); pe++)
529    {
530  0 if (bindingModel.getPdbEntry(pe).getFile().equals(pdbFilename))
531    {
532  0 bindingModel.addSequence(pe, seq);
533  0 viewer.addAlignmentPanel(apanel);
534    /*
535    * add it to the set of alignments used for colouring structure by
536    * sequence
537    */
538  0 viewer.useAlignmentPanelForColourbyseq(apanel);
539  0 viewer.buildActionMenu();
540  0 apanel.getStructureSelectionManager()
541    .sequenceColoursChanged(apanel);
542  0 break;
543    }
544    }
545    }
546    }
547   
 
548  1 toggle @Override
549    public boolean addAlreadyLoadedFile(SequenceI[] seq, String[] chains,
550    final AlignmentViewPanel apanel, String pdbId)
551    {
552  1 String alreadyMapped = apanel.getStructureSelectionManager()
553    .alreadyMappedToFile(pdbId);
554   
555  1 if (alreadyMapped == null)
556    {
557  1 return false;
558    }
559   
560  0 addSequenceMappingsToStructure(seq, chains, apanel, alreadyMapped);
561  0 return true;
562    }
563   
 
564  49 toggle void setChainMenuItems(List<String> chainNames)
565    {
566  49 chainMenu.removeAll();
567  49 if (chainNames == null || chainNames.isEmpty())
568    {
569  6 return;
570    }
571  43 JMenuItem menuItem = new JMenuItem(
572    MessageManager.getString("label.all"));
573  43 menuItem.addActionListener(new ActionListener()
574    {
 
575  0 toggle @Override
576    public void actionPerformed(ActionEvent evt)
577    {
578  0 allChainsSelected = true;
579  0 for (int i = 0; i < chainMenu.getItemCount(); i++)
580    {
581  0 if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
582    {
583  0 ((JCheckBoxMenuItem) chainMenu.getItem(i)).setSelected(true);
584    }
585    }
586  0 showSelectedChains();
587  0 allChainsSelected = false;
588    }
589    });
590   
591  43 chainMenu.add(menuItem);
592   
593  43 for (String chain : chainNames)
594    {
595  66 menuItem = new JCheckBoxMenuItem(chain, true);
596  66 menuItem.addItemListener(new ItemListener()
597    {
 
598  0 toggle @Override
599    public void itemStateChanged(ItemEvent evt)
600    {
601  0 if (!allChainsSelected)
602    {
603  0 showSelectedChains();
604    }
605    }
606    });
607   
608  66 chainMenu.add(menuItem);
609    }
610    }
611   
 
612  49 toggle void setHetatmMenuItems(Map<String, String> hetatmNames)
613    {
614  49 hetatmMenu.removeAll();
615  49 if (hetatmNames == null || hetatmNames.isEmpty())
616    {
617  39 hetatmMenu.setVisible(false);
618  39 return;
619    }
620  10 hetatmMenu.setVisible(true);
621  10 allHetatmBeingSelected = false;
622  10 JMenuItem allMenuItem = new JMenuItem(
623    MessageManager.getString("label.all"));
624  10 JMenuItem noneMenuItem = new JMenuItem(
625    MessageManager.getString("label.none"));
626  10 allMenuItem.addActionListener(new ActionListener()
627    {
 
628  0 toggle @Override
629    public void actionPerformed(ActionEvent e)
630    {
631    {
632  0 allHetatmBeingSelected = true;
633    // Toggle state of everything - on
634  0 for (int i = 0; i < hetatmMenu.getItemCount(); i++)
635    {
636  0 if (hetatmMenu.getItem(i) instanceof JCheckBoxMenuItem)
637    {
638  0 ((JCheckBoxMenuItem) hetatmMenu.getItem(i)).setSelected(true);
639    }
640    }
641  0 allHetatmBeingSelected = false;
642  0 showSelectedHetatms();
643    }
644    }
645    });
646   
647  10 noneMenuItem.addActionListener(new ActionListener()
648    {
 
649  0 toggle @Override
650    public void actionPerformed(ActionEvent e)
651    {
652    {
653  0 allHetatmBeingSelected = true;
654    // Toggle state of everything off
655  0 for (int i = 0; i < hetatmMenu.getItemCount(); i++)
656    {
657  0 if (hetatmMenu.getItem(i) instanceof JCheckBoxMenuItem)
658    {
659  0 ((JCheckBoxMenuItem) hetatmMenu.getItem(i))
660    .setSelected(false);
661    }
662    }
663  0 allHetatmBeingSelected = false;
664  0 showSelectedHetatms();
665    }
666    }
667    });
668  10 hetatmMenu.add(noneMenuItem);
669  10 hetatmMenu.add(allMenuItem);
670   
671  10 for (Map.Entry<String, String> chain : hetatmNames.entrySet())
672    {
673  13 JCheckBoxMenuItem menuItem = new JCheckBoxMenuItem(chain.getKey(),
674    false);
675  13 menuItem.setToolTipText(chain.getValue());
676  13 menuItem.addItemListener(new ItemListener()
677    {
 
678  0 toggle @Override
679    public void itemStateChanged(ItemEvent evt)
680    {
681  0 if (!allHetatmBeingSelected)
682    {
683    // update viewer only when we were clicked, not programmatically
684    // checked/unchecked
685  0 showSelectedHetatms();
686    }
687    }
688    });
689   
690  13 hetatmMenu.add(menuItem);
691    }
692    }
693   
694    /**
695    * Action on selecting one of Jalview's registered colour schemes
696    */
 
697  0 toggle @Override
698    public void changeColour_actionPerformed(String colourSchemeName)
699    {
700  0 AlignmentI al = getAlignmentPanel().av.getAlignment();
701  0 ColourSchemeI cs = ColourSchemes.getInstance().getColourScheme(
702    colourSchemeName, getAlignmentPanel().av, al, null);
703  0 getBinding().colourByJalviewColourScheme(cs);
704    }
705   
706    /**
707    * Builds the colour menu
708    */
 
709  45 toggle protected void buildColourMenu()
710    {
711  45 colourMenu.removeAll();
712  45 AlignmentI al = getAlignmentPanel().av.getAlignment();
713   
714    /*
715    * add colour by sequence, by chain, by charge and cysteine
716    */
717  45 colourMenu.add(seqColour);
718  45 colourMenu.add(chainColour);
719  45 colourMenu.add(chargeColour);
720  45 chargeColour.setEnabled(!al.isNucleotide());
721   
722    /*
723    * add all 'simple' (per-residue) colour schemes registered to Jalview
724    */
725  45 ButtonGroup itemGroup = ColourMenuHelper.addMenuItems(colourMenu, this,
726    al, true);
727   
728    /*
729    * add 'colour by viewer' (menu item text is set in subclasses)
730    */
731  45 viewerColour.setSelected(false);
732  45 viewerColour.addActionListener(new ActionListener()
733    {
 
734  0 toggle @Override
735    public void actionPerformed(ActionEvent actionEvent)
736    {
737  0 viewerColour_actionPerformed();
738    }
739    });
740  45 colourMenu.add(viewerColour);
741   
742    /*
743    * add 'set background colour'
744    */
745  45 JMenuItem backGround = new JMenuItem();
746  45 backGround
747    .setText(MessageManager.getString("action.background_colour"));
748  45 backGround.addActionListener(new ActionListener()
749    {
 
750  0 toggle @Override
751    public void actionPerformed(ActionEvent actionEvent)
752    {
753  0 background_actionPerformed();
754    }
755    });
756  45 colourMenu.add(backGround);
757   
758    /*
759    * add colour buttons to a group so their selection is
760    * mutually exclusive (background colour is a separate option)
761    */
762  45 itemGroup.add(seqColour);
763  45 itemGroup.add(chainColour);
764  45 itemGroup.add(chargeColour);
765  45 itemGroup.add(viewerColour);
766    }
767   
768    /**
769    * Construct menu items
770    */
 
771  45 toggle protected void initMenus()
772    {
773  45 AAStructureBindingModel binding = getBinding();
774   
775  45 seqColour = new JRadioButtonMenuItem();
776  45 seqColour.setText(MessageManager.getString("action.by_sequence"));
777  45 seqColour.setName(ViewerColour.BySequence.name());
778  45 seqColour.setSelected(binding.isColourBySequence());
779  45 seqColour.addActionListener(new ActionListener()
780    {
 
781  0 toggle @Override
782    public void actionPerformed(ActionEvent actionEvent)
783    {
784  0 seqColour_actionPerformed();
785    }
786    });
787   
788  45 chainColour = new JRadioButtonMenuItem();
789  45 chainColour.setText(MessageManager.getString("action.by_chain"));
790  45 chainColour.setName(ViewerColour.ByChain.name());
791  45 chainColour.addActionListener(new ActionListener()
792    {
 
793  0 toggle @Override
794    public void actionPerformed(ActionEvent actionEvent)
795    {
796  0 chainColour_actionPerformed();
797    }
798    });
799   
800  45 chargeColour = new JRadioButtonMenuItem();
801  45 chargeColour.setText(MessageManager.getString("label.charge_cysteine"));
802  45 chargeColour.setName(ViewerColour.ChargeCysteine.name());
803  45 chargeColour.addActionListener(new ActionListener()
804    {
 
805  0 toggle @Override
806    public void actionPerformed(ActionEvent actionEvent)
807    {
808  0 chargeColour_actionPerformed();
809    }
810    });
811   
812  45 viewerColour = new JRadioButtonMenuItem();
813  45 viewerColour
814    .setText(MessageManager.getString("label.colour_with_viewer"));
815  45 viewerColour.setToolTipText(MessageManager
816    .getString("label.let_viewer_manage_structure_colours"));
817  45 viewerColour.setName(ViewerColour.ByViewer.name());
818  45 viewerColour.setSelected(!binding.isColourBySequence());
819   
820  45 if (_colourwith == null)
821    {
822  0 _colourwith = new Vector<>();
823    }
824  45 if (_alignwith == null)
825    {
826  0 _alignwith = new Vector<>();
827    }
828   
829  45 ViewSelectionMenu seqColourBy = new ViewSelectionMenu(
830    MessageManager.getString("label.colour_by"), this, _colourwith,
831    new ItemListener()
832    {
 
833  0 toggle @Override
834    public void itemStateChanged(ItemEvent e)
835    {
836  0 if (!seqColour.isSelected())
837    {
838  0 seqColour.doClick();
839    }
840    else
841    {
842    // update the viewer display now.
843  0 seqColour_actionPerformed();
844    }
845    }
846    });
847  45 viewMenu.add(seqColourBy);
848   
849  45 final ItemListener handler = new ItemListener()
850    {
 
851  45 toggle @Override
852    public void itemStateChanged(ItemEvent e)
853    {
854  45 if (_alignwith.isEmpty())
855    {
856  45 alignStructs.setEnabled(false);
857  45 alignStructs.setToolTipText(null);
858    }
859    else
860    {
861  0 alignStructs.setEnabled(true);
862  0 alignStructs.setToolTipText(MessageManager.formatMessage(
863    "label.align_structures_using_linked_alignment_views",
864    _alignwith.size()));
865    }
866    }
867    };
868  45 viewSelectionMenu = new ViewSelectionMenu(
869    MessageManager.getString("label.superpose_with"), this,
870    _alignwith, handler);
871  45 handler.itemStateChanged(null);
872  45 viewerActionMenu.add(viewSelectionMenu, 0);
873  45 viewerActionMenu.addMenuListener(new MenuListener()
874    {
 
875  0 toggle @Override
876    public void menuSelected(MenuEvent e)
877    {
878  0 handler.itemStateChanged(null);
879    }
880   
 
881  0 toggle @Override
882    public void menuDeselected(MenuEvent e)
883    {
884    }
885   
 
886  0 toggle @Override
887    public void menuCanceled(MenuEvent e)
888    {
889    }
890    });
891   
892  45 viewerActionMenu.setText(getViewerName());
893  45 helpItem.setText(MessageManager.formatMessage("label.viewer_help",
894    getViewerName()));
895   
896  45 buildColourMenu();
897    }
898   
899    /**
900    * Sends commands to the structure viewer to superimpose structures based on
901    * currently associated alignments. May optionally return an error message for
902    * the operation.
903    */
 
904  1 toggle @Override
905    protected String alignStructsWithAllAlignPanels()
906    {
907  1 if (getAlignmentPanel() == null)
908    {
909  0 return null;
910    }
911   
912  1 if (_alignwith.size() == 0)
913    {
914  0 _alignwith.add(getAlignmentPanel());
915    }
916   
917  1 String reply = null;
918  1 try
919    {
920  1 reply = getBinding().superposeStructures(_alignwith);
921  1 if (reply != null && !reply.isEmpty())
922    {
923  0 String text = MessageManager
924    .formatMessage("error.superposition_failed", reply);
925  0 statusBar.setText(text);
926    }
927    } catch (Exception e)
928    {
929  0 StringBuffer sp = new StringBuffer();
930  0 for (AlignmentViewPanel alignPanel : _alignwith)
931    {
932  0 sp.append("'" + alignPanel.getViewName() + "' ");
933    }
934  0 Console.info("Couldn't align structures with the " + sp.toString()
935    + "associated alignment panels.", e);
936    }
937  1 return reply;
938    }
939   
940    /**
941    * Opens a colour chooser dialog, and applies the chosen colour to the
942    * background of the structure viewer
943    */
 
944  0 toggle @Override
945    public void background_actionPerformed()
946    {
947  0 String ttl = MessageManager.getString("label.select_background_colour");
948  0 ColourChooserListener listener = new ColourChooserListener()
949    {
 
950  0 toggle @Override
951    public void colourSelected(Color c)
952    {
953  0 getBinding().setBackgroundColour(c);
954    }
955    };
956  0 JalviewColourChooser.showColourChooser(this, ttl, null, listener);
957    }
958   
 
959  0 toggle @Override
960    public void viewerColour_actionPerformed()
961    {
962  0 if (viewerColour.isSelected())
963    {
964    // disable automatic sequence colouring.
965  0 getBinding().setColourBySequence(false);
966    }
967    }
968   
 
969  0 toggle @Override
970    public void chainColour_actionPerformed()
971    {
972  0 chainColour.setSelected(true);
973  0 getBinding().colourByChain();
974    }
975   
 
976  0 toggle @Override
977    public void chargeColour_actionPerformed()
978    {
979  0 chargeColour.setSelected(true);
980  0 getBinding().colourByCharge();
981    }
982   
 
983  49 toggle @Override
984    public void seqColour_actionPerformed()
985    {
986  49 AAStructureBindingModel binding = getBinding();
987  49 binding.setColourBySequence(seqColour.isSelected());
988  49 if (_colourwith == null)
989    {
990  0 _colourwith = new Vector<>();
991    }
992  49 if (binding.isColourBySequence())
993    {
994  42 if (!binding.isLoadingFromArchive())
995    {
996  42 if (_colourwith.size() == 0 && getAlignmentPanel() != null)
997    {
998    // Make the currently displayed alignment panel the associated view
999  0 _colourwith.add(getAlignmentPanel().alignFrame.alignPanel);
1000    }
1001    }
1002    // Set the colour using the current view for the associated alignframe
1003  42 for (AlignmentViewPanel alignPanel : _colourwith)
1004    {
1005  42 binding.colourBySequence(alignPanel);
1006    }
1007  36 seqColoursApplied = true;
1008    }
1009    }
1010   
 
1011  0 toggle @Override
1012    public void pdbFile_actionPerformed()
1013    {
1014    // TODO: JAL-3048 not needed for Jalview-JS - save PDB file
1015  0 JalviewFileChooser chooser = new JalviewFileChooser(
1016    Cache.getProperty("LAST_DIRECTORY"));
1017   
1018  0 chooser.setFileView(new JalviewFileView());
1019  0 chooser.setDialogTitle(MessageManager.getString("label.save_pdb_file"));
1020  0 chooser.setToolTipText(MessageManager.getString("action.save"));
1021   
1022  0 int value = chooser.showSaveDialog(this);
1023   
1024  0 if (value == JalviewFileChooser.APPROVE_OPTION)
1025    {
1026  0 BufferedReader in = null;
1027  0 try
1028    {
1029    // TODO: cope with multiple PDB files in view
1030  0 in = new BufferedReader(
1031    new FileReader(getBinding().getStructureFiles()[0]));
1032  0 File outFile = chooser.getSelectedFile();
1033   
1034  0 PrintWriter out = new PrintWriter(new FileOutputStream(outFile));
1035  0 String data;
1036  0 while ((data = in.readLine()) != null)
1037    {
1038  0 if (!(data.indexOf("<PRE>") > -1 || data.indexOf("</PRE>") > -1))
1039    {
1040  0 out.println(data);
1041    }
1042    }
1043  0 out.close();
1044    } catch (Exception ex)
1045    {
1046  0 ex.printStackTrace();
1047    } finally
1048    {
1049  0 if (in != null)
1050    {
1051  0 try
1052    {
1053  0 in.close();
1054    } catch (IOException e)
1055    {
1056    // ignore
1057    }
1058    }
1059    }
1060    }
1061    }
1062   
 
1063  0 toggle @Override
1064    public void viewMapping_actionPerformed()
1065    {
1066  0 CutAndPasteTransfer cap = new CutAndPasteTransfer();
1067  0 try
1068    {
1069  0 cap.appendText(getBinding().printMappings());
1070    } catch (OutOfMemoryError e)
1071    {
1072  0 new OOMWarning(
1073    "composing sequence-structure alignments for display in text box.",
1074    e);
1075  0 cap.dispose();
1076  0 return;
1077    }
1078  0 Desktop.addInternalFrame(cap,
1079    MessageManager.getString("label.pdb_sequence_mapping"), 550,
1080    600);
1081    }
1082   
1083    protected abstract String getViewerName();
1084   
1085    /**
1086    * Configures the title and menu items of the viewer panel.
1087    */
 
1088  49 toggle @Override
1089    public void updateTitleAndMenus()
1090    {
1091  49 AAStructureBindingModel binding = getBinding();
1092  49 if (binding.hasFileLoadingError())
1093    {
1094  0 repaint();
1095  0 return;
1096    }
1097  49 setChainMenuItems(binding.getChainNames());
1098  49 setHetatmMenuItems(binding.getHetatmNames());
1099   
1100  49 this.setTitle(binding.getViewerTitle(getViewerName(), true));
1101   
1102    /*
1103    * enable 'Superpose with' if more than one mapped structure
1104    */
1105  49 viewSelectionMenu.setEnabled(false);
1106  49 if (getBinding().getMappedStructureCount() > 1
1107    && getBinding().getSequence().length > 1)
1108    {
1109  1 viewSelectionMenu.setEnabled(true);
1110    }
1111   
1112    /*
1113    * Show action menu if it has any enabled items
1114    */
1115  49 viewerActionMenu.setVisible(false);
1116  145 for (int i = 0; i < viewerActionMenu.getItemCount(); i++)
1117    {
1118  97 if (viewerActionMenu.getItem(i).isEnabled())
1119    {
1120  1 viewerActionMenu.setVisible(true);
1121  1 break;
1122    }
1123    }
1124   
1125  49 if (!binding.isLoadingFromArchive())
1126    {
1127  49 seqColour_actionPerformed();
1128    }
1129    }
1130   
 
1131  0 toggle @Override
1132    public String toString()
1133    {
1134  0 return getTitle();
1135    }
1136   
 
1137  553 toggle @Override
1138    public boolean hasMapping()
1139    {
1140  553 if (worker != null && (addingStructures || _started))
1141    {
1142  204 return false;
1143    }
1144  349 if (getBinding() == null)
1145    {
1146  0 if (_aps == null || _aps.size() == 0)
1147    {
1148    // viewer has been closed, but we did at some point run.
1149  0 return true;
1150    }
1151  0 return false;
1152    }
1153  349 String[] pdbids = getBinding().getStructureFiles();
1154  349 if (pdbids == null)
1155    {
1156  0 return false;
1157    }
1158  349 int p = 0;
1159  349 for (String pdbid : pdbids)
1160    {
1161  340 StructureMapping sm[] = getBinding().getSsm().getMapping(pdbid);
1162  340 if (sm != null && sm.length > 0 && sm[0] != null)
1163    {
1164  118 p++;
1165    }
1166    }
1167    // only return true if there is a mapping for every structure file we have
1168    // loaded
1169  349 if (p == 0 || p != pdbids.length)
1170    {
1171  232 return false;
1172    }
1173    // and that coloring has been applied
1174  117 return seqColoursApplied;
1175    }
1176   
 
1177  37 toggle @Override
1178    public void raiseViewer()
1179    {
1180  37 toFront();
1181    }
1182   
 
1183  316 toggle @Override
1184    public long startProgressBar(String msg)
1185    {
1186    // TODO would rather have startProgress/stopProgress as the
1187    // IProgressIndicator interface
1188  316 long tm = random.nextLong();
1189  316 if (progressBar != null)
1190    {
1191  316 progressBar.setProgressBar(msg, tm);
1192    }
1193  316 return tm;
1194    }
1195   
 
1196  315 toggle @Override
1197    public void stopProgressBar(String msg, long handle)
1198    {
1199  315 if (progressBar != null)
1200    {
1201  315 progressBar.setProgressBar(msg, handle);
1202    }
1203    }
1204   
 
1205  10 toggle protected IProgressIndicator getProgressIndicator()
1206    {
1207  10 return progressBar;
1208    }
1209   
 
1210  76 toggle protected void setProgressIndicator(IProgressIndicator pi)
1211    {
1212  76 progressBar = pi;
1213    }
1214   
 
1215  0 toggle public void setProgressMessage(String message, long id)
1216    {
1217  0 if (progressBar != null)
1218    {
1219  0 progressBar.setProgressBar(message, id);
1220    }
1221    }
1222   
 
1223  0 toggle @Override
1224    public void showConsole(boolean show)
1225    {
1226    // default does nothing
1227    }
1228   
1229    /**
1230    * Show only the selected chain(s) in the viewer
1231    */
 
1232  0 toggle protected void showSelectedChains()
1233    {
1234  0 List<String> toshow = new ArrayList<>();
1235  0 for (int i = 0; i < chainMenu.getItemCount(); i++)
1236    {
1237  0 if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
1238    {
1239  0 JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i);
1240  0 if (item.isSelected())
1241    {
1242  0 toshow.add(item.getText());
1243    }
1244    }
1245    }
1246  0 getBinding().showChains(toshow);
1247    }
1248   
1249    /**
1250    * Display selected hetatms in viewer
1251    */
 
1252  0 toggle protected void showSelectedHetatms()
1253    {
1254  0 List<String> toshow = new ArrayList<>();
1255  0 for (int i = 0; i < hetatmMenu.getItemCount(); i++)
1256    {
1257  0 if (hetatmMenu.getItem(i) instanceof JCheckBoxMenuItem)
1258    {
1259  0 JCheckBoxMenuItem item = (JCheckBoxMenuItem) hetatmMenu.getItem(i);
1260  0 if (item.isSelected())
1261    {
1262  0 toshow.add(item.getText());
1263    }
1264    }
1265    }
1266  0 getBinding().showHetatms(toshow);
1267    }
1268   
1269    /**
1270    * Tries to fetch a PDB file and save to a temporary local file. Returns the
1271    * saved file path if successful, or null if not.
1272    *
1273    * @param processingEntry
1274    * @return
1275    */
 
1276  0 toggle protected String fetchPdbFile(PDBEntry processingEntry)
1277    {
1278  0 String filePath = null;
1279  0 Pdb pdbclient = new Pdb();
1280  0 EBIAlfaFold afclient = new EBIAlfaFold();
1281  0 AlignmentI pdbseq = null;
1282  0 String pdbid = processingEntry.getId();
1283  0 long handle = System.currentTimeMillis()
1284    + Thread.currentThread().hashCode();
1285   
1286    /*
1287    * Write 'fetching PDB' progress on AlignFrame as we are not yet visible
1288    */
1289  0 String msg = MessageManager.formatMessage("status.fetching_pdb",
1290    new Object[]
1291    { pdbid });
1292  0 getAlignmentPanel().alignFrame.setProgressBar(msg, handle);
1293    // long hdl = startProgressBar(MessageManager.formatMessage(
1294    // "status.fetching_pdb", new Object[]
1295    // { pdbid }));
1296  0 try
1297    {
1298  0 if (afclient.isValidReference(pdbid))
1299    {
1300  0 pdbseq = afclient.getSequenceRecords(pdbid,
1301    processingEntry.getRetrievalUrl());
1302    }
1303    else
1304    {
1305  0 if (processingEntry.hasRetrievalUrl())
1306    {
1307  0 String safePDBId = java.net.URLEncoder.encode(pdbid, "UTF-8")
1308    .replace("%", "__");
1309   
1310    // retrieve from URL to new local tmpfile
1311  0 File tmpFile = File.createTempFile(safePDBId,
1312  0 "." + (PDBEntry.Type.MMCIF.toString().equals(
1313    processingEntry.getType().toString()) ? "cif"
1314    : "pdb"));
1315  0 String fromUrl = processingEntry.getRetrievalUrl();
1316  0 UrlDownloadClient.download(fromUrl, tmpFile);
1317   
1318    // may not need this check ?
1319  0 String file = tmpFile.getAbsolutePath();
1320  0 if (file != null)
1321    {
1322  0 pdbseq = EBIAlfaFold.importDownloadedStructureFromUrl(fromUrl,
1323    tmpFile, pdbid, null, null, null);
1324    }
1325    }
1326    else
1327    {
1328  0 pdbseq = pdbclient.getSequenceRecords(pdbid);
1329    }
1330    }
1331    } catch (Exception e)
1332    {
1333  0 jalview.bin.Console.errPrintln(
1334    "Error retrieving PDB id " + pdbid + ": " + e.getMessage());
1335    } finally
1336    {
1337  0 msg = pdbid + " " + MessageManager.getString("label.state_completed");
1338  0 getAlignmentPanel().alignFrame.setProgressBar(msg, handle);
1339    // stopProgressBar(msg, hdl);
1340    }
1341    /*
1342    * If PDB data were saved and are not invalid (empty alignment), return the
1343    * file path.
1344    */
1345  0 if (pdbseq != null && pdbseq.getHeight() > 0)
1346    {
1347    // just use the file name from the first sequence's first PDBEntry
1348  0 filePath = new File(pdbseq.getSequenceAt(0).getAllPDBEntries()
1349    .elementAt(0).getFile()).getAbsolutePath();
1350  0 processingEntry.setFile(filePath);
1351    }
1352  0 return filePath;
1353    }
1354   
1355    /**
1356    * If supported, saves the state of the structure viewer to a temporary file
1357    * and returns the file, else returns null
1358    *
1359    * @return
1360    */
 
1361  2 toggle public File saveSession()
1362    {
1363  2 if (getBinding() == null)
1364    {
1365  0 return null;
1366    }
1367  2 File session = getBinding().saveSession();
1368  2 long l = session.length();
1369  2 int wait = 50;
1370  2 do
1371    {
1372  100 try
1373    {
1374  100 Thread.sleep(5);
1375    } catch (InterruptedException e)
1376    {
1377    }
1378  100 long nextl = session.length();
1379  100 if (nextl != l)
1380    {
1381  0 wait = 50;
1382  0 l = nextl;
1383    }
1384  100 } while (--wait > 0);
1385  2 return session;
1386    }
1387   
1388    private static boolean quitClose = false;
1389   
 
1390  109 toggle public static void setQuitClose(boolean b)
1391    {
1392  109 quitClose = b;
1393    }
1394   
 
1395  23 toggle @Override
1396    public boolean stillRunning()
1397    {
1398  23 AAStructureBindingModel binding = getBinding();
1399  23 return binding != null && binding.isViewerRunning();
1400    }
1401   
1402    /**
1403    * Close down this instance of Jalview's Chimera viewer, giving the user the
1404    * option to close the associated Chimera window (process). They may wish to
1405    * keep it open until they have had an opportunity to save any work.
1406    *
1407    * @param forceClose
1408    * if true, close any linked Chimera process; if false, prompt first
1409    */
 
1410  23 toggle @Override
1411    public void closeViewer(boolean forceClose)
1412    {
1413  23 AAStructureBindingModel binding = getBinding();
1414  23 if (stillRunning())
1415    {
1416  0 if (!forceClose)
1417    {
1418  0 String viewerName = getViewerName();
1419   
1420  0 int confirm = JvOptionPane.CANCEL_OPTION;
1421  0 if (QuitHandler.quitting())
1422    {
1423    // already asked about closing external windows
1424  0 confirm = quitClose ? JvOptionPane.YES_OPTION
1425    : JvOptionPane.NO_OPTION;
1426    }
1427    else
1428    {
1429  0 String prompt = MessageManager
1430    .formatMessage("label.confirm_close_viewer", new Object[]
1431    { binding.getViewerTitle(viewerName, false),
1432    viewerName });
1433  0 prompt = JvSwingUtils.wrapTooltip(true, prompt);
1434  0 String title = MessageManager.getString("label.close_viewer");
1435  0 confirm = showCloseDialog(title, prompt);
1436    }
1437   
1438    /*
1439    * abort closure if user hits escape or Cancel
1440    */
1441  0 if (confirm == JvOptionPane.CANCEL_OPTION
1442    || confirm == JvOptionPane.CLOSED_OPTION)
1443    {
1444    // abort possible quit handling if CANCEL chosen
1445  0 if (confirm == JvOptionPane.CANCEL_OPTION)
1446    {
1447  0 try
1448    {
1449    // this is a bit futile
1450  0 this.setClosed(false);
1451    } catch (PropertyVetoException e)
1452    {
1453    }
1454  0 QuitHandler.abortQuit();
1455    }
1456  0 return;
1457    }
1458  0 forceClose = confirm == JvOptionPane.YES_OPTION;
1459    }
1460    }
1461  23 if (binding != null)
1462    {
1463  23 binding.closeViewer(forceClose);
1464    }
1465  23 setAlignmentPanel(null);
1466  23 _aps.clear();
1467  23 _alignwith.clear();
1468  23 _colourwith.clear();
1469    // TODO: check for memory leaks where instance isn't finalised because jmb
1470    // holds a reference to the window
1471    // jmb = null;
1472   
1473  23 try
1474    {
1475  23 svbs.remove(this);
1476    } catch (Throwable t)
1477    {
1478  0 Console.info(
1479    "Unexpected exception when deregistering structure viewer",
1480    t);
1481    }
1482  23 dispose();
1483    }
1484   
 
1485  0 toggle private int showCloseDialog(final String title, final String prompt)
1486    {
1487  0 int confirmResponse = JvOptionPane.CANCEL_OPTION;
1488  0 confirmResponse = JvOptionPane.showConfirmDialog(this, prompt,
1489    MessageManager.getString("label.close_viewer"),
1490    JvOptionPane.YES_NO_CANCEL_OPTION,
1491    JvOptionPane.WARNING_MESSAGE);
1492  0 return confirmResponse;
1493    }
1494   
 
1495  0 toggle @Override
1496    public void showHelp_actionPerformed()
1497    {
1498    /*
1499    try
1500    {
1501    */
1502  0 String url = getBinding().getHelpURL();
1503  0 if (url != null)
1504    {
1505  0 BrowserLauncher.openURL(url);
1506    }
1507    /*
1508    }
1509    catch (IOException ex)
1510    {
1511    System.err
1512    .println("Show " + getViewerName() + " failed with: "
1513    + ex.getMessage());
1514    }
1515    */
1516    }
1517   
 
1518  0 toggle @Override
1519    public boolean hasViewerActionsMenu()
1520    {
1521  0 return viewerActionMenu != null && viewerActionMenu.isEnabled()
1522    && viewerActionMenu.getItemCount() > 0
1523    && viewerActionMenu.isVisible();
1524    }
1525   
1526    }