Clover icon

Coverage Report

  1. Project Clover database Thu Nov 27 2025 16:51:35 GMT
  2. Package jalview.gui

File StructureViewerBase.java

 

Coverage histogram

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

Code metrics

174
436
83
2
1,567
1,157
199
0.46
5.25
41.5
2.4

Classes

Class Line # Actions
StructureViewerBase 81 436 199
0.5295815553%
StructureViewerBase.ViewerColour 87 0 0
-1.0 -
 

Contributing tests

This file is covered by 54 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  62 toggle public StructureViewerBase()
165    {
166  62 super();
167  62 setFrameIcon(null);
168  62 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  6 toggle @Override
186    public void setAlignAddedStructures(boolean alignAdded)
187    {
188  6 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  11 toggle public synchronized void setAddingStructures(boolean addingStructures)
198    {
199  11 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  34 toggle public boolean isLinkedWith(AlignmentPanel ap2)
208    {
209  34 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  1094 toggle @Override
220    public boolean isUsedForColourBy(AlignmentViewPanel ap2)
221    {
222  1094 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  42 toggle protected void buildActionMenu()
249    {
250  42 if (_alignwith == null)
251    {
252  0 _alignwith = new Vector<>();
253    }
254  42 if (_alignwith.size() == 0 && ap != null)
255    {
256  6 _alignwith.add(ap);
257    }
258  42 ;
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  42 for (Component c : viewerActionMenu.getMenuComponents())
264    {
265  51 if (c != alignStructs)
266    {
267  9 viewerActionMenu.remove((JMenuItem) c);
268    }
269    }
270    }
271   
 
272  449 toggle @Override
273    public AlignmentPanel getAlignmentPanel()
274    {
275  449 return ap;
276    }
277   
 
278  104 toggle protected void setAlignmentPanel(AlignmentPanel alp)
279    {
280  104 this.ap = alp;
281    }
282   
 
283  2 toggle @Override
284    public AlignmentPanel[] getAllAlignmentPanels()
285    {
286  2 AlignmentPanel[] t, list = new AlignmentPanel[0];
287  2 for (String setid : _aps)
288    {
289  2 AlignmentPanel[] panels = PaintRefresher.getAssociatedPanels(setid);
290  2 if (panels != null)
291    {
292  2 t = new AlignmentPanel[list.length + panels.length];
293  2 System.arraycopy(list, 0, t, 0, list.length);
294  2 System.arraycopy(panels, 0, t, list.length, panels.length);
295  2 list = t;
296    }
297    }
298   
299  2 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  172 toggle public void addAlignmentPanel(AlignmentPanel nap)
309    {
310  172 if (getAlignmentPanel() == null)
311    {
312  62 setAlignmentPanel(nap);
313    }
314  172 if (!_aps.contains(nap.av.getSequenceSetId()))
315    {
316  63 _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  44 toggle @Override
326    public void removeAlignmentPanel(AlignmentViewPanel nap)
327    {
328  44 try
329    {
330  44 _alignwith.remove(nap);
331  44 _colourwith.remove(nap);
332  44 if (getAlignmentPanel() == nap)
333    {
334  2 setAlignmentPanel(null);
335  2 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  44 if (getAlignmentPanel() != null)
348    {
349  40 buildActionMenu();
350    }
351    }
352   
 
353  10 toggle public void useAlignmentPanelForSuperposition(AlignmentPanel nap)
354    {
355  10 addAlignmentPanel(nap);
356  10 if (!_alignwith.contains(nap))
357    {
358  9 _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  65 toggle public void useAlignmentPanelForColourbyseq(AlignmentPanel nap)
380    {
381  65 addAlignmentPanel(nap);
382  65 if (!_colourwith.contains(nap))
383    {
384  63 _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  5 toggle protected void addStructure(final PDBEntry pdbentry,
411    final SequenceI[] seqs, final String[] chains,
412    final IProgressIndicator alignFrame)
413    {
414  5 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  5 getBinding().addSequenceAndChain(new PDBEntry[] { pdbentry },
444    new SequenceI[][]
445    { seqs }, new String[][] { chains });
446  5 addingStructures = true;
447  5 _started = false;
448  5 worker = new Thread(this);
449  5 worker.start();
450  5 return;
451    }
452   
 
453  5 toggle protected boolean hasPdbId(String pdbId)
454    {
455  5 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  1 toggle protected List<StructureViewerBase> getViewersFor(AlignmentPanel alp)
463    {
464  1 return Desktop.instance.getStructureViewers(alp, this.getClass());
465    }
466   
 
467  5 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  5 if (hasPdbId(pdbId))
477    {
478  0 return;
479    }
480  5 AlignmentPanel alignPanel = (AlignmentPanel) apanel; // Implementation error
481    // if this
482    // cast fails
483  5 useAlignmentPanelForSuperposition(alignPanel);
484  5 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  1 toggle public void addSequenceMappingsToStructure(SequenceI[] seq,
497    String[] chains, final AlignmentViewPanel alpanel,
498    String pdbFilename)
499    {
500  1 AlignmentPanel apanel = (AlignmentPanel) alpanel;
501   
502    // TODO : Fix multiple seq to one chain issue here.
503    /*
504    * create the mappings
505    */
506  1 apanel.getStructureSelectionManager().setMapping(seq, chains,
507    pdbFilename, DataSourceType.FILE, getProgressIndicator());
508   
509    /*
510    * alert the FeatureRenderer to show new (PDB RESNUM) features
511    */
512  1 if (apanel.getSeqPanel().seqCanvas.fr != null)
513    {
514  1 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  1 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  1 for (StructureViewerBase viewer : getViewersFor(null))
526    {
527  2 AAStructureBindingModel bindingModel = viewer.getBinding();
528  4 for (int pe = 0; pe < bindingModel.getPdbCount(); pe++)
529    {
530  4 if (bindingModel.getPdbEntry(pe).getFile().equals(pdbFilename))
531    {
532  2 bindingModel.addSequence(pe, seq);
533  2 viewer.addAlignmentPanel(apanel);
534    /*
535    * add it to the set of alignments used for colouring structure by
536    * sequence
537    */
538  2 viewer.useAlignmentPanelForColourbyseq(apanel);
539  2 viewer.buildActionMenu();
540  2 apanel.getStructureSelectionManager()
541    .sequenceColoursChanged(apanel);
542  2 break;
543    }
544    }
545    }
546    }
547   
 
548  6 toggle @Override
549    public boolean addAlreadyLoadedFile(SequenceI[] seq, String[] chains,
550    final AlignmentViewPanel apanel, String pdbId)
551    {
552  6 String alreadyMapped = apanel.getStructureSelectionManager()
553    .alreadyMappedToFile(pdbId);
554   
555  6 if (alreadyMapped == null)
556    {
557  5 return false;
558    }
559   
560  1 addSequenceMappingsToStructure(seq, chains, apanel, alreadyMapped);
561  1 return true;
562    }
563   
 
564  80 toggle void setChainMenuItems(List<String> chainNames)
565    {
566  80 chainMenu.removeAll();
567  80 if (chainNames == null || chainNames.isEmpty())
568    {
569  6 return;
570    }
571  74 JMenuItem menuItem = new JMenuItem(
572    MessageManager.getString("label.all"));
573  74 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  74 chainMenu.add(menuItem);
592   
593  74 for (String chain : chainNames)
594    {
595  124 menuItem = new JCheckBoxMenuItem(chain, true);
596  124 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  124 chainMenu.add(menuItem);
609    }
610    }
611   
 
612  80 toggle void setHetatmMenuItems(Map<String, String> hetatmNames)
613    {
614  80 hetatmMenu.removeAll();
615  80 if (hetatmNames == null || hetatmNames.isEmpty())
616    {
617  70 hetatmMenu.setVisible(false);
618  70 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  62 toggle protected void buildColourMenu()
710    {
711  62 colourMenu.removeAll();
712  62 AlignmentI al = getAlignmentPanel().av.getAlignment();
713   
714    /*
715    * add colour by sequence, by chain, by charge and cysteine
716    */
717  62 colourMenu.add(seqColour);
718  62 colourMenu.add(chainColour);
719  62 colourMenu.add(chargeColour);
720  62 chargeColour.setEnabled(!al.isNucleotide());
721   
722    /*
723    * add all 'simple' (per-residue) colour schemes registered to Jalview
724    */
725  62 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  62 viewerColour.setSelected(false);
732  62 viewerColour.addActionListener(new ActionListener()
733    {
 
734  0 toggle @Override
735    public void actionPerformed(ActionEvent actionEvent)
736    {
737  0 viewerColour_actionPerformed();
738    }
739    });
740  62 colourMenu.add(viewerColour);
741   
742    /*
743    * add 'set background colour'
744    */
745  62 JMenuItem backGround = new JMenuItem();
746  62 backGround
747    .setText(MessageManager.getString("action.background_colour"));
748  62 backGround.addActionListener(new ActionListener()
749    {
 
750  0 toggle @Override
751    public void actionPerformed(ActionEvent actionEvent)
752    {
753  0 background_actionPerformed();
754    }
755    });
756  62 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  62 itemGroup.add(seqColour);
763  62 itemGroup.add(chainColour);
764  62 itemGroup.add(chargeColour);
765  62 itemGroup.add(viewerColour);
766    }
767   
768    /**
769    * Construct menu items
770    */
 
771  62 toggle protected void initMenus()
772    {
773  62 AAStructureBindingModel binding = getBinding();
774   
775  62 seqColour = new JRadioButtonMenuItem();
776  62 seqColour.setText(MessageManager.getString("action.by_sequence"));
777  62 seqColour.setName(ViewerColour.BySequence.name());
778  62 seqColour.setSelected(binding.isColourBySequence());
779  62 seqColour.addActionListener(new ActionListener()
780    {
 
781  0 toggle @Override
782    public void actionPerformed(ActionEvent actionEvent)
783    {
784  0 seqColour_actionPerformed();
785    }
786    });
787   
788  62 chainColour = new JRadioButtonMenuItem();
789  62 chainColour.setText(MessageManager.getString("action.by_chain"));
790  62 chainColour.setName(ViewerColour.ByChain.name());
791  62 chainColour.addActionListener(new ActionListener()
792    {
 
793  0 toggle @Override
794    public void actionPerformed(ActionEvent actionEvent)
795    {
796  0 chainColour_actionPerformed();
797    }
798    });
799   
800  62 chargeColour = new JRadioButtonMenuItem();
801  62 chargeColour.setText(MessageManager.getString("label.charge_cysteine"));
802  62 chargeColour.setName(ViewerColour.ChargeCysteine.name());
803  62 chargeColour.addActionListener(new ActionListener()
804    {
 
805  0 toggle @Override
806    public void actionPerformed(ActionEvent actionEvent)
807    {
808  0 chargeColour_actionPerformed();
809    }
810    });
811   
812  62 viewerColour = new JRadioButtonMenuItem();
813  62 viewerColour
814    .setText(MessageManager.getString("label.colour_with_viewer"));
815  62 viewerColour.setToolTipText(MessageManager
816    .getString("label.let_viewer_manage_structure_colours"));
817  62 viewerColour.setName(ViewerColour.ByViewer.name());
818  62 viewerColour.setSelected(!binding.isColourBySequence());
819   
820  62 if (_colourwith == null)
821    {
822  0 _colourwith = new Vector<>();
823    }
824  62 if (_alignwith == null)
825    {
826  0 _alignwith = new Vector<>();
827    }
828   
829  62 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  62 viewMenu.add(seqColourBy);
848   
849  62 final ItemListener handler = new ItemListener()
850    {
 
851  62 toggle @Override
852    public void itemStateChanged(ItemEvent e)
853    {
854  62 if (_alignwith.isEmpty())
855    {
856  57 alignStructs.setEnabled(false);
857  57 alignStructs.setToolTipText(null);
858    }
859    else
860    {
861  5 alignStructs.setEnabled(true);
862  5 alignStructs.setToolTipText(MessageManager.formatMessage(
863    "label.align_structures_using_linked_alignment_views",
864    _alignwith.size()));
865    }
866    }
867    };
868  62 viewSelectionMenu = new ViewSelectionMenu(
869    MessageManager.getString("label.superpose_with"), this,
870    _alignwith, handler);
871  62 handler.itemStateChanged(null);
872  62 viewerActionMenu.add(viewSelectionMenu, 0);
873  62 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  62 viewerActionMenu.setText(getViewerName());
893  62 helpItem.setText(MessageManager.formatMessage("label.viewer_help",
894    getViewerName()));
895   
896  62 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  3 toggle @Override
905    protected String alignStructsWithAllAlignPanels()
906    {
907  3 if (getAlignmentPanel() == null)
908    {
909  0 return null;
910    }
911   
912  3 if (_alignwith.size() == 0)
913    {
914  0 _alignwith.add(getAlignmentPanel());
915    }
916   
917  3 String reply = null;
918  3 try
919    {
920  3 reply = getBinding().superposeStructures(_alignwith);
921  3 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  3 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  80 toggle @Override
984    public void seqColour_actionPerformed()
985    {
986  80 AAStructureBindingModel binding = getBinding();
987  80 binding.setColourBySequence(seqColour.isSelected());
988  80 if (_colourwith == null)
989    {
990  0 _colourwith = new Vector<>();
991    }
992  80 if (binding.isColourBySequence())
993    {
994  73 if (!binding.isLoadingFromArchive())
995    {
996  73 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  73 for (AlignmentViewPanel alignPanel : _colourwith)
1004    {
1005  73 binding.colourBySequence(alignPanel);
1006    }
1007  67 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  80 toggle @Override
1089    public void updateTitleAndMenus()
1090    {
1091  80 AAStructureBindingModel binding = getBinding();
1092  80 if (binding.hasFileLoadingError())
1093    {
1094  0 repaint();
1095  0 return;
1096    }
1097  80 setChainMenuItems(binding.getChainNames());
1098  80 setHetatmMenuItems(binding.getHetatmNames());
1099   
1100  80 this.setTitle(this.getViewerTitle());
1101   
1102    /*
1103    * enable 'Superpose with' if more than one mapped structure
1104    */
1105  80 viewSelectionMenu.setEnabled(false);
1106  80 if (getBinding().getMappedStructureCount() > 1
1107    && getBinding().getSequence().length > 1)
1108    {
1109  13 viewSelectionMenu.setEnabled(true);
1110    }
1111   
1112    /*
1113    * Show action menu if it has any enabled items
1114    */
1115  80 viewerActionMenu.setVisible(false);
1116  210 for (int i = 0; i < viewerActionMenu.getItemCount(); i++)
1117    {
1118  143 if (viewerActionMenu.getItem(i).isEnabled())
1119    {
1120  13 viewerActionMenu.setVisible(true);
1121  13 break;
1122    }
1123    }
1124   
1125  80 if (!binding.isLoadingFromArchive())
1126    {
1127  80 seqColour_actionPerformed();
1128    }
1129    }
1130   
 
1131  0 toggle @Override
1132    public String toString()
1133    {
1134  0 return getTitle();
1135    }
1136   
 
1137  923 toggle @Override
1138    public boolean hasMapping()
1139    {
1140  923 if (worker != null && (addingStructures || _started))
1141    {
1142  390 return false;
1143    }
1144  533 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  533 String[] pdbids = getBinding().getStructureFiles();
1154  533 if (pdbids == null)
1155    {
1156  0 return false;
1157    }
1158  533 int p = 0;
1159  533 for (String pdbid : pdbids)
1160    {
1161  527 StructureMapping sm[] = getBinding().getSsm().getMapping(pdbid);
1162  527 if (sm != null && sm.length > 0 && sm[0] != null)
1163    {
1164  215 p++;
1165    }
1166    }
1167    // only return true if there is a mapping for every structure file we have
1168    // loaded
1169  533 if (p == 0 || p != pdbids.length)
1170    {
1171  331 return false;
1172    }
1173    // and that coloring has been applied
1174  202 return seqColoursApplied;
1175    }
1176   
 
1177  54 toggle @Override
1178    public void raiseViewer()
1179    {
1180  54 toFront();
1181    }
1182   
 
1183  432 toggle @Override
1184    public long startProgressBar(String msg)
1185    {
1186    // TODO would rather have startProgress/stopProgress as the
1187    // IProgressIndicator interface
1188  432 long tm = random.nextLong();
1189  432 if (progressBar != null)
1190    {
1191  432 progressBar.setProgressBar(msg, tm);
1192    }
1193  432 return tm;
1194    }
1195   
 
1196  431 toggle @Override
1197    public void stopProgressBar(String msg, long handle)
1198    {
1199  431 if (progressBar != null)
1200    {
1201  431 progressBar.setProgressBar(msg, handle);
1202    }
1203    }
1204   
 
1205  11 toggle protected IProgressIndicator getProgressIndicator()
1206    {
1207  11 return progressBar;
1208    }
1209   
 
1210  105 toggle protected void setProgressIndicator(IProgressIndicator pi)
1211    {
1212  105 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  10 toggle @Override
1250    public void showAllChains()
1251    {
1252  10 getBinding().showAllChains();
1253    }
1254   
1255    /**
1256    * Display selected hetatms in viewer
1257    */
 
1258  0 toggle protected void showSelectedHetatms()
1259    {
1260  0 List<String> toshow = new ArrayList<>();
1261  0 for (int i = 0; i < hetatmMenu.getItemCount(); i++)
1262    {
1263  0 if (hetatmMenu.getItem(i) instanceof JCheckBoxMenuItem)
1264    {
1265  0 JCheckBoxMenuItem item = (JCheckBoxMenuItem) hetatmMenu.getItem(i);
1266  0 if (item.isSelected())
1267    {
1268  0 toshow.add(item.getText());
1269    }
1270    }
1271    }
1272  0 getBinding().showHetatms(toshow);
1273    }
1274   
1275    /**
1276    * Tries to fetch a PDB file and save to a temporary local file. Returns the
1277    * saved file path if successful, or null if not.
1278    *
1279    * @param processingEntry
1280    * @return
1281    */
 
1282  0 toggle protected String fetchPdbFile(PDBEntry processingEntry)
1283    {
1284  0 String filePath = null;
1285  0 Pdb pdbclient = new Pdb();
1286  0 EBIAlfaFold afclient = new EBIAlfaFold();
1287  0 AlignmentI pdbseq = null;
1288  0 String pdbid = processingEntry.getId();
1289  0 long handle = System.currentTimeMillis()
1290    + Thread.currentThread().hashCode();
1291   
1292    /*
1293    * Write 'fetching PDB' progress on AlignFrame as we are not yet visible
1294    */
1295  0 String msg = MessageManager.formatMessage("status.fetching_pdb",
1296    new Object[]
1297    { pdbid });
1298  0 getAlignmentPanel().alignFrame.setProgressBar(msg, handle);
1299    // long hdl = startProgressBar(MessageManager.formatMessage(
1300    // "status.fetching_pdb", new Object[]
1301    // { pdbid }));
1302  0 try
1303    {
1304  0 if (afclient.isValidReference(pdbid))
1305    {
1306  0 pdbseq = afclient.getSequenceRecords(pdbid,
1307    processingEntry.getRetrievalUrl());
1308    }
1309    else
1310    {
1311  0 if (processingEntry.hasRetrievalUrl())
1312    {
1313  0 String safePDBId = java.net.URLEncoder.encode(pdbid, "UTF-8")
1314    .replace("%", "__");
1315   
1316    // retrieve from URL to new local tmpfile
1317  0 File tmpFile = File.createTempFile(safePDBId,
1318  0 "." + (PDBEntry.Type.MMCIF.toString().equals(
1319    processingEntry.getType().toString()) ? "cif"
1320    : "pdb"));
1321  0 String fromUrl = processingEntry.getRetrievalUrl();
1322  0 UrlDownloadClient.download(fromUrl, tmpFile);
1323   
1324    // may not need this check ?
1325  0 String file = tmpFile.getAbsolutePath();
1326  0 if (file != null)
1327    {
1328  0 pdbseq = EBIAlfaFold.importDownloadedStructureFromUrl(fromUrl,
1329    tmpFile, pdbid, null, null, null);
1330    }
1331    }
1332    else
1333    {
1334  0 pdbseq = pdbclient.getSequenceRecords(pdbid);
1335    }
1336    }
1337    } catch (Exception e)
1338    {
1339  0 jalview.bin.Console.errPrintln(
1340    "Error retrieving PDB id " + pdbid + ": " + e.getMessage());
1341    } finally
1342    {
1343  0 msg = pdbid + " " + MessageManager.getString("label.state_completed");
1344  0 getAlignmentPanel().alignFrame.setProgressBar(msg, handle);
1345    // stopProgressBar(msg, hdl);
1346    }
1347    /*
1348    * If PDB data were saved and are not invalid (empty alignment), return the
1349    * file path.
1350    */
1351  0 if (pdbseq != null && pdbseq.getHeight() > 0)
1352    {
1353    // just use the file name from the first sequence's first PDBEntry
1354  0 filePath = new File(pdbseq.getSequenceAt(0).getAllPDBEntries()
1355    .elementAt(0).getFile()).getAbsolutePath();
1356  0 processingEntry.setFile(filePath);
1357    }
1358  0 return filePath;
1359    }
1360   
1361    /**
1362    * If supported, saves the state of the structure viewer to a temporary file
1363    * and returns the file, else returns null
1364    *
1365    * @return
1366    */
 
1367  2 toggle public File saveSession()
1368    {
1369  2 if (getBinding() == null)
1370    {
1371  0 return null;
1372    }
1373  2 File session = getBinding().saveSession();
1374  2 long l = session.length();
1375  2 int wait = 50;
1376  2 do
1377    {
1378  100 try
1379    {
1380  100 Thread.sleep(5 + 2 * (51 - wait));
1381    } catch (InterruptedException e)
1382    {
1383    }
1384  100 long nextl = session.length();
1385  100 Console.trace("Finished waiting around for session save... (" + nextl
1386    + " length - was " + l + " and waits " + wait + ")");
1387  100 if (nextl != l)
1388    {
1389  0 wait = 50;
1390  0 l = nextl;
1391    }
1392  100 } while (--wait > 0);
1393  2 if (l == 0)
1394    {
1395  0 Console.error(
1396    "Structure viewer session save resulted in zero length file. This is a bug, please report it.");
1397    }
1398  2 return session;
1399    }
1400   
1401    private static boolean quitClose = false;
1402   
 
1403  92 toggle public static void setQuitClose(boolean b)
1404    {
1405  92 quitClose = b;
1406    }
1407   
 
1408  40 toggle @Override
1409    public boolean stillRunning()
1410    {
1411  40 AAStructureBindingModel binding = getBinding();
1412  40 return binding != null && binding.isViewerRunning();
1413    }
1414   
1415    /**
1416    * Close down this instance of Jalview's Chimera viewer, giving the user the
1417    * option to close the associated Chimera window (process). They may wish to
1418    * keep it open until they have had an opportunity to save any work.
1419    *
1420    * @param forceClose
1421    * if true, close any linked Chimera process; if false, prompt first
1422    */
 
1423  40 toggle @Override
1424    public void closeViewer(boolean forceClose)
1425    {
1426  40 AAStructureBindingModel binding = getBinding();
1427  40 if (stillRunning())
1428    {
1429  0 if (!forceClose)
1430    {
1431  0 String viewerName = getViewerName();
1432   
1433  0 int confirm = JvOptionPane.CANCEL_OPTION;
1434  0 if (QuitHandler.quitting())
1435    {
1436    // already asked about closing external windows
1437  0 confirm = quitClose ? JvOptionPane.YES_OPTION
1438    : JvOptionPane.NO_OPTION;
1439    }
1440    else
1441    {
1442  0 String prompt = MessageManager
1443    .formatMessage("label.confirm_close_viewer", new Object[]
1444    { getActualTitle(), viewerName });
1445  0 prompt = JvSwingUtils.wrapTooltip(true, prompt);
1446  0 String title = MessageManager.getString("label.close_viewer");
1447  0 confirm = showCloseDialog(title, prompt);
1448    }
1449   
1450    /*
1451    * abort closure if user hits escape or Cancel
1452    */
1453  0 if (confirm == JvOptionPane.CANCEL_OPTION
1454    || confirm == JvOptionPane.CLOSED_OPTION)
1455    {
1456    // abort possible quit handling if CANCEL chosen
1457  0 if (confirm == JvOptionPane.CANCEL_OPTION)
1458    {
1459  0 try
1460    {
1461    // this is a bit futile
1462  0 this.setClosed(false);
1463    } catch (PropertyVetoException e)
1464    {
1465    }
1466  0 QuitHandler.abortQuit();
1467    }
1468  0 return;
1469    }
1470  0 forceClose = confirm == JvOptionPane.YES_OPTION;
1471    }
1472    }
1473  40 if (binding != null)
1474    {
1475  40 binding.closeViewer(forceClose);
1476    }
1477  40 setAlignmentPanel(null);
1478  40 _aps.clear();
1479  40 _alignwith.clear();
1480  40 _colourwith.clear();
1481    // TODO: check for memory leaks where instance isn't finalised because jmb
1482    // holds a reference to the window
1483    // jmb = null;
1484   
1485  40 try
1486    {
1487  40 svbs.remove(this);
1488    } catch (Throwable t)
1489    {
1490  0 Console.info(
1491    "Unexpected exception when deregistering structure viewer",
1492    t);
1493    }
1494  40 dispose();
1495    }
1496   
 
1497  0 toggle private int showCloseDialog(final String title, final String prompt)
1498    {
1499  0 int confirmResponse = JvOptionPane.CANCEL_OPTION;
1500  0 confirmResponse = JvOptionPane.showConfirmDialog(this, prompt,
1501    MessageManager.getString("label.close_viewer"),
1502    JvOptionPane.YES_NO_CANCEL_OPTION,
1503    JvOptionPane.WARNING_MESSAGE);
1504  0 return confirmResponse;
1505    }
1506   
 
1507  0 toggle @Override
1508    public void showHelp_actionPerformed()
1509    {
1510    /*
1511    try
1512    {
1513    */
1514  0 String url = getBinding().getHelpURL();
1515  0 if (url != null)
1516    {
1517  0 BrowserLauncher.openURL(url);
1518    }
1519    /*
1520    }
1521    catch (IOException ex)
1522    {
1523    System.err
1524    .println("Show " + getViewerName() + " failed with: "
1525    + ex.getMessage());
1526    }
1527    */
1528    }
1529   
 
1530  0 toggle @Override
1531    public boolean hasViewerActionsMenu()
1532    {
1533  0 return viewerActionMenu != null && viewerActionMenu.isEnabled()
1534    && viewerActionMenu.getItemCount() > 0
1535    && viewerActionMenu.isVisible();
1536    }
1537   
1538    private String permanentTitle = null;
1539   
 
1540  0 toggle protected String getPermanentTitle()
1541    {
1542  0 return permanentTitle;
1543    }
1544   
 
1545  0 toggle @Override
1546    public void setPermanentTitle(String title)
1547    {
1548  0 permanentTitle = title;
1549    }
1550   
 
1551  0 toggle @Override
1552    public String getActualTitle()
1553    {
1554  0 return this.getTitle();
1555    }
1556   
 
1557  80 toggle @Override
1558    public String getViewerTitle()
1559    {
1560  80 if (permanentTitle != null)
1561    {
1562  0 return permanentTitle;
1563    }
1564  80 return getBinding().getDynamicViewerTitle(getViewerName(), true);
1565    }
1566   
1567    }