Clover icon

Coverage Report

  1. Project Clover database Mon Nov 18 2024 09:38:20 GMT
  2. Package jalview.gui

File SplitFrame.java

 

Coverage histogram

../../img/srcFileCovDistChart3.png
45% of files have more coverage

Code metrics

106
298
51
1
1,120
747
122
0.41
5.84
51
2.39

Classes

Class Line # Actions
SplitFrame 76 298 122
0.285714328.6%
 

Contributing tests

This file is covered by 1 test. .

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.BorderLayout;
24    import java.awt.Component;
25    import java.awt.Dimension;
26    import java.awt.event.ActionEvent;
27    import java.awt.event.ActionListener;
28    import java.awt.event.KeyAdapter;
29    import java.awt.event.KeyEvent;
30    import java.awt.event.KeyListener;
31    import java.beans.PropertyVetoException;
32    import java.util.Arrays;
33    import java.util.List;
34    import java.util.Map.Entry;
35   
36    import javax.swing.AbstractAction;
37    import javax.swing.InputMap;
38    import javax.swing.JButton;
39    import javax.swing.JComponent;
40    import javax.swing.JDesktopPane;
41    import javax.swing.JInternalFrame;
42    import javax.swing.JLayeredPane;
43    import javax.swing.JMenuItem;
44    import javax.swing.JPanel;
45    import javax.swing.JTabbedPane;
46    import javax.swing.KeyStroke;
47    import javax.swing.event.ChangeEvent;
48    import javax.swing.event.ChangeListener;
49    import javax.swing.event.InternalFrameAdapter;
50    import javax.swing.event.InternalFrameEvent;
51   
52    import jalview.api.AlignViewControllerGuiI;
53    import jalview.api.FeatureSettingsControllerI;
54    import jalview.api.SplitContainerI;
55    import jalview.controller.FeatureSettingsControllerGuiI;
56    import jalview.datamodel.AlignmentI;
57    import jalview.jbgui.GAlignFrame;
58    import jalview.jbgui.GSplitFrame;
59    import jalview.structure.StructureSelectionManager;
60    import jalview.util.MessageManager;
61    import jalview.util.Platform;
62    import jalview.viewmodel.AlignmentViewport;
63   
64    /**
65    * An internal frame on the desktop that hosts a horizontally split view of
66    * linked DNA and Protein alignments. Additional views can be created in linked
67    * pairs, expanded to separate split frames, or regathered into a single frame.
68    * <p>
69    * (Some) operations on each alignment are automatically mirrored on the other.
70    * These include mouseover (highlighting), sequence and column selection,
71    * sequence ordering and sorting, and grouping, colouring and sorting by tree.
72    *
73    * @author gmcarstairs
74    *
75    */
 
76    public class SplitFrame extends GSplitFrame implements SplitContainerI
77    {
78    private static final int WINDOWS_INSETS_WIDTH = 28; // tbc
79   
80    private static final int MAC_INSETS_WIDTH = 28;
81   
82    private static final int WINDOWS_INSETS_HEIGHT = 50; // tbc
83   
84    private static final int MAC_INSETS_HEIGHT = 50;
85   
86    private static final int DESKTOP_DECORATORS_HEIGHT = 65;
87   
88    private static final long serialVersionUID = 1L;
89   
90    /**
91    * geometry for Feature Settings Holder
92    */
93    private static final int FS_MIN_WIDTH = 400;
94   
95    private static final int FS_MIN_HEIGHT = 400;
96   
 
97  1 toggle public SplitFrame(GAlignFrame top, GAlignFrame bottom)
98    {
99  1 super(top, bottom);
100  1 init();
101    }
102   
103    /**
104    * Initialise this frame.
105    */
 
106  1 toggle protected void init()
107    {
108  1 setFrameIcon(null);
109  1 getTopFrame().setSplitFrame(this);
110  1 getBottomFrame().setSplitFrame(this);
111  1 getTopFrame().setVisible(true);
112  1 getBottomFrame().setVisible(true);
113   
114  1 ((AlignFrame) getTopFrame()).getViewport().setCodingComplement(
115    ((AlignFrame) getBottomFrame()).getViewport());
116   
117    /*
118    * estimate width and height of SplitFrame; this.getInsets() doesn't seem to
119    * give the full additional size (a few pixels short)
120    */
121  1 int widthFudge = Platform.isAMacAndNotJS() ? MAC_INSETS_WIDTH
122    : WINDOWS_INSETS_WIDTH;
123  1 int heightFudge = Platform.isAMacAndNotJS() ? MAC_INSETS_HEIGHT
124    : WINDOWS_INSETS_HEIGHT;
125  1 int width = ((AlignFrame) getTopFrame()).getWidth() + widthFudge;
126  1 int height = ((AlignFrame) getTopFrame()).getHeight()
127    + ((AlignFrame) getBottomFrame()).getHeight() + DIVIDER_SIZE
128    + heightFudge;
129  1 height = fitHeightToDesktop(height);
130  1 setSize(width, height);
131   
132  1 adjustLayout();
133   
134  1 addCloseFrameListener();
135   
136  1 addKeyListener();
137   
138  1 addKeyBindings();
139   
140  1 addCommandListeners();
141   
142    // ensure all workers are added
143  1 ((AlignFrame) getTopFrame()).getViewport().updateConsensus(((AlignFrame) getTopFrame()).alignPanel);
144  1 ((AlignFrame) getBottomFrame()).getViewport().updateConsensus(((AlignFrame) getBottomFrame()).alignPanel);
145    }
146   
147    /**
148    * Reduce the height if too large to fit in the Desktop. Also adjust the
149    * divider location in proportion.
150    *
151    * @param height
152    * in pixels
153    * @return original or reduced height
154    */
 
155  1 toggle public int fitHeightToDesktop(int height)
156    {
157    // allow about 65 pixels for Desktop decorators on Windows
158   
159  1 int newHeight = Math.min(height,
160    Desktop.instance.getHeight() - DESKTOP_DECORATORS_HEIGHT);
161  1 if (newHeight != height)
162    {
163  1 int oldDividerLocation = getDividerLocation();
164  1 setDividerLocation(oldDividerLocation * newHeight / height);
165    }
166  1 return newHeight;
167    }
168   
169    /**
170    * Set the top and bottom frames to listen to each others Commands (e.g. Edit,
171    * Order).
172    */
 
173  1 toggle protected void addCommandListeners()
174    {
175    // TODO if CommandListener is only ever 1:1 for complementary views,
176    // may change broadcast pattern to direct messaging (more efficient)
177  1 final StructureSelectionManager ssm = StructureSelectionManager
178    .getStructureSelectionManager(Desktop.instance);
179  1 ssm.addCommandListener(((AlignFrame) getTopFrame()).getViewport());
180  1 ssm.addCommandListener(((AlignFrame) getBottomFrame()).getViewport());
181    }
182   
183    /**
184    * Do any tweaking and twerking of the layout wanted.
185    */
 
186  1 toggle public void adjustLayout()
187    {
188  1 final AlignViewport topViewport = ((AlignFrame) getTopFrame()).viewport;
189  1 final AlignViewport bottomViewport = ((AlignFrame) getBottomFrame()).viewport;
190   
191    /*
192    * Ensure sequence ids are the same width so sequences line up
193    */
194  1 int w1 = topViewport.getIdWidth();
195  1 int w2 = bottomViewport.getIdWidth();
196  1 int w3 = Math.max(w1, w2);
197  1 topViewport.setIdWidth(w3);
198  1 bottomViewport.setIdWidth(w3);
199   
200    /*
201    * Scale protein to either 1 or 3 times character width of dna
202    */
203  1 final AlignmentI topAlignment = topViewport.getAlignment();
204  1 final AlignmentI bottomAlignment = bottomViewport.getAlignment();
205  1 AlignmentViewport cdna = topAlignment.isNucleotide() ? topViewport
206  0 : (bottomAlignment.isNucleotide() ? bottomViewport : null);
207  1 AlignmentViewport protein = !topAlignment.isNucleotide() ? topViewport
208  1 : (!bottomAlignment.isNucleotide() ? bottomViewport : null);
209  1 if (protein != null && cdna != null)
210    {
211  1 int scale = protein.isScaleProteinAsCdna() ? 3 : 1;
212  1 protein.setCharWidth(scale * cdna.getViewStyle().getCharWidth());
213    }
214    }
215   
216    /**
217    * Adjusts the divider for a sensible split of the real estate (for example,
218    * when many transcripts are shown with a single protein). This should only be
219    * called after the split pane has been laid out (made visible) so it has a
220    * height. The aim is to avoid unnecessary vertical scroll bars, while
221    * ensuring that at least 2 sequences are visible in each panel.
222    * <p>
223    * Once laid out, the user may choose to customise as they wish, so this
224    * method is not called again after the initial layout.
225    */
 
226  0 toggle protected void adjustInitialLayout()
227    {
228  0 AlignFrame topFrame = (AlignFrame) getTopFrame();
229  0 AlignFrame bottomFrame = (AlignFrame) getBottomFrame();
230   
231    /*
232    * recompute layout of top and bottom panels to reflect their
233    * actual (rather than requested) height
234    */
235  0 topFrame.alignPanel.adjustAnnotationHeight();
236  0 bottomFrame.alignPanel.adjustAnnotationHeight();
237   
238  0 final AlignViewport topViewport = topFrame.viewport;
239  0 final AlignViewport bottomViewport = bottomFrame.viewport;
240  0 final AlignmentI topAlignment = topViewport.getAlignment();
241  0 final AlignmentI bottomAlignment = bottomViewport.getAlignment();
242  0 boolean topAnnotations = topViewport.isShowAnnotation();
243  0 boolean bottomAnnotations = bottomViewport.isShowAnnotation();
244    // TODO need number of visible sequences here, not #sequences - how?
245  0 int topCount = topAlignment.getHeight();
246  0 int bottomCount = bottomAlignment.getHeight();
247  0 int topCharHeight = topViewport.getViewStyle().getCharHeight();
248  0 int bottomCharHeight = bottomViewport.getViewStyle().getCharHeight();
249   
250    /*
251    * calculate the minimum ratio that leaves at least the height
252    * of two sequences (after rounding) visible in the top panel
253    */
254  0 int topPanelHeight = topFrame.getHeight();
255  0 int bottomPanelHeight = bottomFrame.getHeight();
256  0 int topSequencesHeight = topFrame.alignPanel.getSeqPanel().seqCanvas
257    .getHeight();
258  0 int topPanelMinHeight = topPanelHeight
259    - Math.max(0, topSequencesHeight - 3 * topCharHeight);
260  0 double totalHeight = (double) topPanelHeight + bottomPanelHeight;
261  0 double minRatio = topPanelMinHeight / totalHeight;
262   
263    /*
264    * calculate the maximum ratio that leaves at least the height
265    * of two sequences (after rounding) visible in the bottom panel
266    */
267  0 int bottomSequencesHeight = bottomFrame.alignPanel
268    .getSeqPanel().seqCanvas.getHeight();
269  0 int bottomPanelMinHeight = bottomPanelHeight
270    - Math.max(0, bottomSequencesHeight - 3 * bottomCharHeight);
271  0 double maxRatio = (totalHeight - bottomPanelMinHeight) / totalHeight;
272   
273    /*
274    * estimate ratio of (topFrameContent / bottomFrameContent)
275    */
276  0 int insets = Platform.isAMacAndNotJS() ? MAC_INSETS_HEIGHT
277    : WINDOWS_INSETS_HEIGHT;
278    // allow 3 'rows' for scale, scrollbar, status bar
279  0 int topHeight = insets + (3 + topCount) * topCharHeight
280  0 + (topAnnotations ? topViewport.calcPanelHeight() : 0);
281  0 int bottomHeight = insets + (3 + bottomCount) * bottomCharHeight
282  0 + (bottomAnnotations ? bottomViewport.calcPanelHeight() : 0);
283  0 double ratio = ((double) topHeight)
284    / (double) (topHeight + bottomHeight);
285   
286    /*
287    * limit ratio to avoid concealing all sequences
288    */
289  0 ratio = Math.min(ratio, maxRatio);
290  0 ratio = Math.max(ratio, minRatio);
291  0 setRelativeDividerLocation(ratio);
292    }
293   
294    /**
295    * Add a listener to tidy up when the frame is closed.
296    */
 
297  1 toggle protected void addCloseFrameListener()
298    {
299  1 addInternalFrameListener(new InternalFrameAdapter()
300    {
 
301  1 toggle @Override
302    public void internalFrameClosed(InternalFrameEvent evt)
303    {
304  1 close();
305    };
306    });
307    }
308   
309    /**
310    * Add a key listener that delegates to whichever split component the mouse is
311    * in (or does nothing if neither).
312    */
 
313  1 toggle protected void addKeyListener()
314    {
315  1 addKeyListener(new KeyAdapter()
316    {
317   
 
318  0 toggle @Override
319    public void keyPressed(KeyEvent e)
320    {
321  0 AlignFrame af = (AlignFrame) getFrameAtMouse();
322   
323    /*
324    * Intercept and override any keys here if wanted.
325    */
326  0 if (!overrideKey(e, af))
327    {
328  0 if (af != null)
329    {
330  0 for (KeyListener kl : af.getKeyListeners())
331    {
332  0 kl.keyPressed(e);
333    }
334    }
335    }
336    }
337   
 
338  0 toggle @Override
339    public void keyReleased(KeyEvent e)
340    {
341  0 Component c = getFrameAtMouse();
342  0 if (c != null)
343    {
344  0 for (KeyListener kl : c.getKeyListeners())
345    {
346  0 kl.keyReleased(e);
347    }
348    }
349    }
350   
351    });
352    }
353   
354    /**
355    * Returns true if the key event is overriden and actioned (or ignored) here,
356    * else returns false, indicating it should be delegated to the AlignFrame's
357    * usual handler.
358    * <p>
359    * We can't handle Cmd-Key combinations here, instead this is done by
360    * overriding key bindings.
361    *
362    * @see addKeyOverrides
363    * @param e
364    * @param af
365    * @return
366    */
 
367  0 toggle protected boolean overrideKey(KeyEvent e, AlignFrame af)
368    {
369  0 boolean actioned = false;
370  0 int keyCode = e.getKeyCode();
371  0 switch (keyCode)
372    {
373  0 case KeyEvent.VK_DOWN:
374  0 if (e.isAltDown() || !af.viewport.cursorMode)
375    {
376    /*
377    * Key down (or Alt-key-down in cursor mode) - move selected sequences
378    */
379  0 ((AlignFrame) getTopFrame()).moveSelectedSequences(false);
380  0 ((AlignFrame) getBottomFrame()).moveSelectedSequences(false);
381  0 actioned = true;
382  0 e.consume();
383    }
384  0 break;
385  0 case KeyEvent.VK_UP:
386  0 if (e.isAltDown() || !af.viewport.cursorMode)
387    {
388    /*
389    * Key up (or Alt-key-up in cursor mode) - move selected sequences
390    */
391  0 ((AlignFrame) getTopFrame()).moveSelectedSequences(true);
392  0 ((AlignFrame) getBottomFrame()).moveSelectedSequences(true);
393  0 actioned = true;
394  0 e.consume();
395    }
396  0 break;
397  0 default:
398    }
399  0 return actioned;
400    }
401   
402    /**
403    * Set key bindings (recommended for Swing over key accelerators).
404    */
 
405  1 toggle private void addKeyBindings()
406    {
407  1 overrideDelegatedKeyBindings();
408   
409  1 overrideImplementedKeyBindings();
410    }
411   
412    /**
413    * Override key bindings with alternative action methods implemented in this
414    * class.
415    */
 
416  1 toggle protected void overrideImplementedKeyBindings()
417    {
418  1 overrideFind();
419  1 overrideNewView();
420  1 overrideCloseView();
421  1 overrideExpandViews();
422  1 overrideGatherViews();
423    }
424   
425    /**
426    * Replace Cmd-W close view action with our version.
427    */
 
428  1 toggle protected void overrideCloseView()
429    {
430  1 AbstractAction action;
431    /*
432    * Ctrl-W / Cmd-W - close view or window
433    */
434  1 KeyStroke key_cmdW = KeyStroke.getKeyStroke(KeyEvent.VK_W,
435    jalview.util.ShortcutKeyMaskExWrapper
436    .getMenuShortcutKeyMaskEx(),
437    false);
438  1 action = new AbstractAction()
439    {
 
440  0 toggle @Override
441    public void actionPerformed(ActionEvent e)
442    {
443  0 closeView_actionPerformed();
444    }
445    };
446  1 overrideKeyBinding(key_cmdW, action);
447    }
448   
449    /**
450    * Replace Cmd-T new view action with our version.
451    */
 
452  1 toggle protected void overrideNewView()
453    {
454    /*
455    * Ctrl-T / Cmd-T open new view
456    */
457  1 KeyStroke key_cmdT = KeyStroke.getKeyStroke(KeyEvent.VK_T,
458    jalview.util.ShortcutKeyMaskExWrapper
459    .getMenuShortcutKeyMaskEx(),
460    false);
461  1 AbstractAction action = new AbstractAction()
462    {
 
463  0 toggle @Override
464    public void actionPerformed(ActionEvent e)
465    {
466  0 newView_actionPerformed();
467    }
468    };
469  1 overrideKeyBinding(key_cmdT, action);
470    }
471   
472    /**
473    * For now, delegates key events to the corresponding key accelerator for the
474    * AlignFrame that the mouse is in. Hopefully can be simplified in future if
475    * AlignFrame is changed to use key bindings rather than accelerators.
476    */
 
477  1 toggle protected void overrideDelegatedKeyBindings()
478    {
479  1 if (getTopFrame() instanceof AlignFrame)
480    {
481    /*
482    * Get all accelerator keys in the top frame (the bottom should be
483    * identical) and override each one.
484    */
485  1 for (Entry<KeyStroke, JMenuItem> acc : ((AlignFrame) getTopFrame())
486    .getAccelerators().entrySet())
487    {
488  27 overrideKeyBinding(acc);
489    }
490    }
491    }
492   
493    /**
494    * Overrides an AlignFrame key accelerator with our version which delegates to
495    * the action listener in whichever frame has the mouse (and does nothing if
496    * neither has).
497    *
498    * @param acc
499    */
 
500  27 toggle private void overrideKeyBinding(Entry<KeyStroke, JMenuItem> acc)
501    {
502  27 final KeyStroke ks = acc.getKey();
503  27 InputMap inputMap = this.getInputMap(JComponent.WHEN_FOCUSED);
504  27 inputMap.put(ks, ks);
505  27 this.getActionMap().put(ks, new AbstractAction()
506    {
 
507  0 toggle @Override
508    public void actionPerformed(ActionEvent e)
509    {
510  0 Component c = getFrameAtMouse();
511  0 if (c != null && c instanceof AlignFrame)
512    {
513  0 for (ActionListener a : ((AlignFrame) c).getAccelerators().get(ks)
514    .getActionListeners())
515    {
516  0 a.actionPerformed(null);
517    }
518    }
519    }
520    });
521    }
522   
523    /**
524    * Replace an accelerator key's action with the specified action.
525    *
526    * @param ks
527    */
 
528  3 toggle protected void overrideKeyBinding(KeyStroke ks, AbstractAction action)
529    {
530  3 this.getActionMap().put(ks, action);
531  3 overrideMenuItem(ks, action);
532    }
533   
534    /**
535    * Create and link new views (with matching names) in both panes.
536    * <p>
537    * Note this is _not_ multiple tabs, each hosting a split pane view, rather it
538    * is a single split pane with each split holding multiple tabs which are
539    * linked in pairs.
540    * <p>
541    * TODO implement instead with a tabbed holder in the SplitView, each tab
542    * holding a single JSplitPane. Would avoid a duplicated tab, at the cost of
543    * some additional coding.
544    */
 
545  0 toggle protected void newView_actionPerformed()
546    {
547  0 AlignFrame topFrame = (AlignFrame) getTopFrame();
548  0 AlignFrame bottomFrame = (AlignFrame) getBottomFrame();
549  0 final boolean scaleProteinAsCdna = topFrame.viewport
550    .isScaleProteinAsCdna();
551   
552  0 AlignmentPanel newTopPanel = topFrame.newView(null, true);
553  0 AlignmentPanel newBottomPanel = bottomFrame.newView(null, true);
554   
555    /*
556    * This currently (for the first new view only) leaves the top pane on tab 0
557    * but the bottom on tab 1. This results from 'setInitialTabVisible' echoing
558    * from the bottom back to the first frame. Next line is a fudge to work
559    * around this. TODO find a better way.
560    */
561  0 if (topFrame.getTabIndex() != bottomFrame.getTabIndex())
562    {
563  0 topFrame.setDisplayedView(newTopPanel);
564    }
565   
566  0 newBottomPanel.av.setViewName(newTopPanel.av.getViewName());
567  0 newTopPanel.av.setCodingComplement(newBottomPanel.av);
568   
569    /*
570    * These lines can be removed once scaleProteinAsCdna is added to element
571    * Viewport in jalview.xsd, as Jalview2XML.copyAlignPanel will then take
572    * care of it
573    */
574  0 newTopPanel.av.setScaleProteinAsCdna(scaleProteinAsCdna);
575  0 newBottomPanel.av.setScaleProteinAsCdna(scaleProteinAsCdna);
576   
577    /*
578    * Line up id labels etc
579    */
580  0 adjustLayout();
581   
582  0 final StructureSelectionManager ssm = StructureSelectionManager
583    .getStructureSelectionManager(Desktop.instance);
584  0 ssm.addCommandListener(newTopPanel.av);
585  0 ssm.addCommandListener(newBottomPanel.av);
586    }
587   
588    /**
589    * Close the currently selected view in both panes. If there is only one view,
590    * close this split frame.
591    */
 
592  0 toggle protected void closeView_actionPerformed()
593    {
594  0 int viewCount = ((AlignFrame) getTopFrame()).getAlignPanels().size();
595  0 if (viewCount < 2)
596    {
597  0 close();
598  0 return;
599    }
600   
601  0 AlignmentPanel topPanel = ((AlignFrame) getTopFrame()).alignPanel;
602  0 AlignmentPanel bottomPanel = ((AlignFrame) getBottomFrame()).alignPanel;
603   
604  0 ((AlignFrame) getTopFrame()).closeView(topPanel);
605  0 ((AlignFrame) getBottomFrame()).closeView(bottomPanel);
606   
607    }
608   
609    /**
610    * Close child frames and this split frame.
611    */
 
612  1 toggle public void close()
613    {
614  1 ((AlignFrame) getTopFrame()).closeMenuItem_actionPerformed(true);
615  1 ((AlignFrame) getBottomFrame()).closeMenuItem_actionPerformed(true);
616  1 try
617    {
618  1 this.setClosed(true);
619    } catch (PropertyVetoException e)
620    {
621    // ignore
622    }
623    }
624   
625    /**
626    * Replace AlignFrame 'expand views' action with SplitFrame version.
627    */
 
628  1 toggle protected void overrideExpandViews()
629    {
630  1 KeyStroke key_X = KeyStroke.getKeyStroke(KeyEvent.VK_X, 0, false);
631  1 AbstractAction action = new AbstractAction()
632    {
 
633  0 toggle @Override
634    public void actionPerformed(ActionEvent e)
635    {
636  0 expandViews_actionPerformed();
637    }
638    };
639  1 overrideMenuItem(key_X, action);
640    }
641   
642    /**
643    * Replace AlignFrame 'gather views' action with SplitFrame version.
644    */
 
645  1 toggle protected void overrideGatherViews()
646    {
647  1 KeyStroke key_G = KeyStroke.getKeyStroke(KeyEvent.VK_G, 0, false);
648  1 AbstractAction action = new AbstractAction()
649    {
 
650  0 toggle @Override
651    public void actionPerformed(ActionEvent e)
652    {
653  0 gatherViews_actionPerformed();
654    }
655    };
656  1 overrideMenuItem(key_G, action);
657    }
658   
659    /**
660    * Override the menu action associated with the keystroke in the child frames,
661    * replacing it with the given action.
662    *
663    * @param ks
664    * @param action
665    */
 
666  5 toggle private void overrideMenuItem(KeyStroke ks, AbstractAction action)
667    {
668  5 overrideMenuItem(ks, action, getTopFrame());
669  5 overrideMenuItem(ks, action, getBottomFrame());
670    }
671   
672    /**
673    * Override the menu action associated with the keystroke in one child frame,
674    * replacing it with the given action. Mwahahahaha.
675    *
676    * @param key
677    * @param action
678    * @param comp
679    */
 
680  10 toggle private void overrideMenuItem(KeyStroke key, final AbstractAction action,
681    JComponent comp)
682    {
683  10 if (comp instanceof AlignFrame)
684    {
685  10 JMenuItem mi = ((AlignFrame) comp).getAccelerators().get(key);
686  10 if (mi != null)
687    {
688  10 for (ActionListener al : mi.getActionListeners())
689    {
690  10 mi.removeActionListener(al);
691    }
692  10 mi.addActionListener(new ActionListener()
693    {
 
694  0 toggle @Override
695    public void actionPerformed(ActionEvent e)
696    {
697  0 action.actionPerformed(e);
698    }
699    });
700    }
701    }
702    }
703   
704    /**
705    * Expand any multiple views (which are always in pairs) into separate split
706    * frames.
707    */
 
708  0 toggle protected void expandViews_actionPerformed()
709    {
710  0 Desktop.instance.explodeViews(this);
711    }
712   
713    /**
714    * Gather any other SplitFrame views of this alignment back in as multiple
715    * (pairs of) views in this SplitFrame.
716    */
 
717  0 toggle protected void gatherViews_actionPerformed()
718    {
719  0 Desktop.instance.gatherViews(this);
720    }
721   
722    /**
723    * Returns the alignment in the complementary frame to the one given.
724    */
 
725  0 toggle @Override
726    public AlignmentI getComplement(Object alignFrame)
727    {
728  0 if (alignFrame == this.getTopFrame())
729    {
730  0 return ((AlignFrame) getBottomFrame()).viewport.getAlignment();
731    }
732  0 else if (alignFrame == this.getBottomFrame())
733    {
734  0 return ((AlignFrame) getTopFrame()).viewport.getAlignment();
735    }
736  0 return null;
737    }
738   
739    /**
740    * Returns the title of the complementary frame to the one given.
741    */
 
742  0 toggle @Override
743    public String getComplementTitle(Object alignFrame)
744    {
745  0 if (alignFrame == this.getTopFrame())
746    {
747  0 return ((AlignFrame) getBottomFrame()).getTitle();
748    }
749  0 else if (alignFrame == this.getBottomFrame())
750    {
751  0 return ((AlignFrame) getTopFrame()).getTitle();
752    }
753  0 return null;
754    }
755   
756    /**
757    * Set the 'other half' to hidden / revealed.
758    */
 
759  0 toggle @Override
760    public void setComplementVisible(Object alignFrame, boolean show)
761    {
762    /*
763    * Hiding the AlignPanel suppresses unnecessary repaints
764    */
765  0 if (alignFrame == getTopFrame())
766    {
767  0 ((AlignFrame) getBottomFrame()).alignPanel.setVisible(show);
768    }
769  0 else if (alignFrame == getBottomFrame())
770    {
771  0 ((AlignFrame) getTopFrame()).alignPanel.setVisible(show);
772    }
773  0 super.setComplementVisible(alignFrame, show);
774    }
775   
776    /**
777    * return the AlignFrames held by this container
778    *
779    * @return { Top alignFrame (Usually CDS), Bottom AlignFrame (Usually
780    * Protein)}
781    */
 
782  0 toggle public List<AlignFrame> getAlignFrames()
783    {
784  0 return Arrays
785    .asList(new AlignFrame[]
786    { (AlignFrame) getTopFrame(), (AlignFrame) getBottomFrame() });
787    }
788   
 
789  0 toggle @Override
790    public AlignFrame getComplementAlignFrame(
791    AlignViewControllerGuiI alignFrame)
792    {
793  0 if (getTopFrame() == alignFrame)
794    {
795  0 return (AlignFrame) getBottomFrame();
796    }
797  0 if (getBottomFrame() == alignFrame)
798    {
799  0 return (AlignFrame) getTopFrame();
800    }
801    // we didn't know anything about this frame...
802  0 return null;
803    }
804   
805    /**
806    * Replace Cmd-F Find action with our version. This is necessary because the
807    * 'default' Finder searches in the first AlignFrame it finds. We need it to
808    * search in the half of the SplitFrame that has the mouse.
809    */
 
810  1 toggle protected void overrideFind()
811    {
812    /*
813    * Ctrl-F / Cmd-F open Finder dialog, 'focused' on the right alignment
814    */
815  1 KeyStroke key_cmdF = KeyStroke.getKeyStroke(KeyEvent.VK_F,
816    jalview.util.ShortcutKeyMaskExWrapper
817    .getMenuShortcutKeyMaskEx(),
818    false);
819  1 AbstractAction action = new AbstractAction()
820    {
 
821  0 toggle @Override
822    public void actionPerformed(ActionEvent e)
823    {
824  0 Component c = getFrameAtMouse();
825  0 if (c != null && c instanceof AlignFrame)
826    {
827  0 AlignFrame af = (AlignFrame) c;
828  0 boolean dna = af.getViewport().getAlignment().isNucleotide();
829  0 String scope = MessageManager.getString("label.in") + " "
830  0 + (dna ? MessageManager.getString("label.nucleotide")
831    : MessageManager.getString("label.protein"));
832  0 new Finder(af.alignPanel, true, scope);
833    }
834    }
835    };
836  1 overrideKeyBinding(key_cmdF, action);
837    }
838   
839    /**
840    * Override to do nothing if triggered from one of the child frames
841    */
 
842  3 toggle @Override
843    public void setSelected(boolean selected) throws PropertyVetoException
844    {
845  3 JDesktopPane desktopPane = getDesktopPane();
846  3 JInternalFrame fr = desktopPane == null ? null
847    : desktopPane.getSelectedFrame();
848  3 if (fr == getTopFrame() || fr == getBottomFrame())
849    {
850    /*
851    * patch for JAL-3288 (deselecting top/bottom frame closes popup menu);
852    * it may be possible to remove this method in future
853    * if the underlying Java behaviour changes
854    */
855  0 if (selected)
856    {
857  0 moveToFront();
858    }
859  0 return;
860    }
861  3 super.setSelected(selected);
862    }
863   
864    /**
865    * holds the frame for feature settings, so Protein and DNA tabs can be
866    * managed
867    */
868    JInternalFrame featureSettingsUI;
869   
870    JTabbedPane featureSettingsPanels;
871   
 
872  0 toggle @Override
873    public void addFeatureSettingsUI(
874    FeatureSettingsControllerGuiI featureSettings)
875    {
876  0 boolean showInternalFrame = false;
877  0 if (featureSettingsUI == null || featureSettingsPanels == null)
878    {
879  0 showInternalFrame = true;
880  0 featureSettingsPanels = new JTabbedPane();
881  0 featureSettingsPanels.addChangeListener(new ChangeListener()
882    {
883   
 
884  0 toggle @Override
885    public void stateChanged(ChangeEvent e)
886    {
887  0 if (e.getSource() != featureSettingsPanels
888    || featureSettingsUI == null
889    || featureSettingsUI.isClosed()
890    || !featureSettingsUI.isVisible())
891    {
892    // not our tabbed pane
893  0 return;
894    }
895  0 int tab = featureSettingsPanels.getSelectedIndex();
896  0 if (tab < 0 || featureSettingsPanels
897    .getSelectedComponent() instanceof FeatureSettingsControllerGuiI)
898    {
899    // no tab selected or already showing a feature settings GUI
900  0 return;
901    }
902  0 getAlignFrames().get(tab).showFeatureSettingsUI();
903    }
904    });
905  0 featureSettingsUI = new JInternalFrame(MessageManager.getString(
906    "label.sequence_feature_settings_for_CDS_and_Protein"));
907  0 featureSettingsUI.setFrameIcon(null);
908  0 featureSettingsPanels.setOpaque(true);
909   
910  0 JPanel dialog = new JPanel();
911  0 dialog.setOpaque(true);
912  0 dialog.setLayout(new BorderLayout());
913  0 dialog.add(featureSettingsPanels, BorderLayout.CENTER);
914  0 JPanel buttons = new JPanel();
915  0 JButton ok = new JButton(MessageManager.getString("action.ok"));
916  0 ok.addActionListener(new ActionListener()
917    {
918   
 
919  0 toggle @Override
920    public void actionPerformed(ActionEvent e)
921    {
922  0 try
923    {
924  0 featureSettingsUI.setClosed(true);
925    } catch (PropertyVetoException pv)
926    {
927  0 pv.printStackTrace();
928    }
929    }
930    });
931  0 JButton cancel = new JButton(
932    MessageManager.getString("action.cancel"));
933  0 cancel.addActionListener(new ActionListener()
934    {
935   
 
936  0 toggle @Override
937    public void actionPerformed(ActionEvent e)
938    {
939  0 try
940    {
941  0 for (Component fspanel : featureSettingsPanels.getComponents())
942    {
943  0 if (fspanel instanceof FeatureSettingsControllerGuiI)
944    {
945  0 ((FeatureSettingsControllerGuiI) fspanel).revert();
946    }
947    }
948  0 featureSettingsUI.setClosed(true);
949    } catch (Exception pv)
950    {
951  0 pv.printStackTrace();
952    }
953    }
954    });
955  0 buttons.add(ok);
956  0 buttons.add(cancel);
957  0 dialog.add(buttons, BorderLayout.SOUTH);
958  0 featureSettingsUI.setContentPane(dialog);
959  0 createDummyTabs();
960    }
961  0 if (featureSettingsPanels
962    .indexOfTabComponent((Component) featureSettings) > -1)
963    {
964    // just show the feature settings !
965  0 featureSettingsPanels
966    .setSelectedComponent((Component) featureSettings);
967  0 return;
968    }
969    // otherwise replace the dummy tab with the given feature settings
970  0 int pos = getAlignFrames().indexOf(featureSettings.getAlignframe());
971    // if pos==-1 then alignFrame isn't managed by this splitframe
972  0 if (pos == 0)
973    {
974  0 featureSettingsPanels.removeTabAt(0);
975  0 featureSettingsPanels.insertTab(tabName[0], null,
976    (Component) featureSettings,
977    MessageManager.formatMessage(
978    "label.sequence_feature_settings_for", tabName[0]),
979    0);
980    }
981  0 if (pos == 1)
982    {
983  0 featureSettingsPanels.removeTabAt(1);
984  0 featureSettingsPanels.insertTab(tabName[1], null,
985    (Component) featureSettings,
986    MessageManager.formatMessage(
987    "label.sequence_feature_settings_for", tabName[1]),
988    1);
989    }
990  0 featureSettingsPanels.setSelectedComponent((Component) featureSettings);
991   
992    // TODO: JAL-3535 - construct a feature settings title including names of
993    // currently selected CDS and Protein names
994   
995  0 if (showInternalFrame)
996    {
997  0 if (Platform.isAMacAndNotJS())
998    {
999  0 Desktop.addInternalFrame(featureSettingsUI,
1000    MessageManager.getString(
1001    "label.sequence_feature_settings_for_CDS_and_Protein"),
1002    600, 480);
1003    }
1004    else
1005    {
1006  0 Desktop.addInternalFrame(featureSettingsUI,
1007    MessageManager.getString(
1008    "label.sequence_feature_settings_for_CDS_and_Protein"),
1009    600, 450);
1010    }
1011  0 featureSettingsUI
1012    .setMinimumSize(new Dimension(FS_MIN_WIDTH, FS_MIN_HEIGHT));
1013   
1014  0 featureSettingsUI.addInternalFrameListener(
1015    new javax.swing.event.InternalFrameAdapter()
1016    {
 
1017  0 toggle @Override
1018    public void internalFrameClosed(
1019    javax.swing.event.InternalFrameEvent evt)
1020    {
1021  0 for (int tab = 0; tab < featureSettingsPanels
1022    .getTabCount();)
1023    {
1024  0 FeatureSettingsControllerGuiI fsettings = (FeatureSettingsControllerGuiI) featureSettingsPanels
1025    .getTabComponentAt(tab);
1026  0 if (fsettings != null)
1027    {
1028  0 featureSettingsPanels.removeTabAt(tab);
1029  0 fsettings.featureSettings_isClosed();
1030    }
1031    else
1032    {
1033  0 tab++;
1034    }
1035    }
1036  0 featureSettingsPanels = null;
1037  0 featureSettingsUI = null;
1038    };
1039    });
1040  0 featureSettingsUI.setLayer(JLayeredPane.PALETTE_LAYER);
1041    }
1042    }
1043   
1044    /**
1045    * tab names for feature settings
1046    */
1047    private String[] tabName = new String[] {
1048    MessageManager.getString("label.CDS"),
1049    MessageManager.getString("label.protein") };
1050   
1051    /**
1052    * create placeholder tabs which materialise the feature settings for a given
1053    * view. Also reinitialises any tabs containing stale feature settings
1054    */
 
1055  0 toggle private void createDummyTabs()
1056    {
1057  0 for (int tabIndex = 0; tabIndex < 2; tabIndex++)
1058    {
1059  0 JPanel dummyTab = new JPanel();
1060  0 featureSettingsPanels.addTab(tabName[tabIndex], dummyTab);
1061    }
1062    }
1063   
 
1064  0 toggle private void replaceWithDummyTab(FeatureSettingsControllerI toClose)
1065    {
1066  0 Component dummyTab = null;
1067  0 for (int tabIndex = 0; tabIndex < 2; tabIndex++)
1068    {
1069  0 if (featureSettingsPanels.getTabCount() > tabIndex)
1070    {
1071  0 dummyTab = featureSettingsPanels.getTabComponentAt(tabIndex);
1072  0 if (dummyTab instanceof FeatureSettingsControllerGuiI
1073    && !dummyTab.isVisible())
1074    {
1075  0 featureSettingsPanels.removeTabAt(tabIndex);
1076    // close the feature Settings tab
1077  0 ((FeatureSettingsControllerGuiI) dummyTab)
1078    .featureSettings_isClosed();
1079    // create a dummy tab in its place
1080  0 dummyTab = new JPanel();
1081  0 featureSettingsPanels.insertTab(tabName[tabIndex], null, dummyTab,
1082    MessageManager.formatMessage(
1083    "label.sequence_feature_settings_for",
1084    tabName[tabIndex]),
1085    tabIndex);
1086    }
1087    }
1088    }
1089    }
1090   
 
1091  0 toggle @Override
1092    public void closeFeatureSettings(
1093    FeatureSettingsControllerI featureSettings,
1094    boolean closeContainingFrame)
1095    {
1096  0 if (featureSettingsUI != null)
1097    {
1098  0 if (closeContainingFrame)
1099    {
1100  0 try
1101    {
1102  0 featureSettingsUI.setClosed(true);
1103    } catch (Exception x)
1104    {
1105    }
1106  0 featureSettingsUI = null;
1107    }
1108    else
1109    {
1110  0 replaceWithDummyTab(featureSettings);
1111    }
1112    }
1113    }
1114   
 
1115  0 toggle @Override
1116    public boolean isFeatureSettingsOpen()
1117    {
1118  0 return featureSettingsUI != null && !featureSettingsUI.isClosed();
1119    }
1120    }