2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
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.
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.
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.
23 import jalview.api.SplitContainerI;
24 import jalview.api.ViewStyleI;
25 import jalview.datamodel.AlignmentI;
26 import jalview.jbgui.GAlignFrame;
27 import jalview.jbgui.GSplitFrame;
28 import jalview.structure.StructureSelectionManager;
29 import jalview.viewmodel.AlignmentViewport;
31 import java.awt.Component;
32 import java.awt.Toolkit;
33 import java.awt.event.ActionEvent;
34 import java.awt.event.ActionListener;
35 import java.awt.event.KeyAdapter;
36 import java.awt.event.KeyEvent;
37 import java.awt.event.KeyListener;
38 import java.beans.PropertyVetoException;
39 import java.util.Map.Entry;
41 import javax.swing.AbstractAction;
42 import javax.swing.InputMap;
43 import javax.swing.JComponent;
44 import javax.swing.JMenuItem;
45 import javax.swing.KeyStroke;
46 import javax.swing.event.InternalFrameAdapter;
47 import javax.swing.event.InternalFrameEvent;
50 * An internal frame on the desktop that hosts a horizontally split view of
51 * linked DNA and Protein alignments. Additional views can be created in linked
52 * pairs, expanded to separate split frames, or regathered into a single frame.
54 * (Some) operations on each alignment are automatically mirrored on the other.
55 * These include mouseover (highlighting), sequence and column selection,
56 * sequence ordering and sorting, and grouping, colouring and sorting by tree.
61 public class SplitFrame extends GSplitFrame implements SplitContainerI
63 private static final long serialVersionUID = 1L;
65 public SplitFrame(GAlignFrame top, GAlignFrame bottom)
72 * Initialise this frame.
76 getTopFrame().setSplitFrame(this);
77 getBottomFrame().setSplitFrame(this);
78 getTopFrame().setVisible(true);
79 getBottomFrame().setVisible(true);
81 ((AlignFrame) getTopFrame()).getViewport().setCodingComplement(
82 ((AlignFrame) getBottomFrame()).getViewport());
84 int width = ((AlignFrame) getTopFrame()).getWidth();
85 // about 50 pixels for the SplitFrame's title bar etc
86 int height = ((AlignFrame) getTopFrame()).getHeight()
87 + ((AlignFrame) getBottomFrame()).getHeight() + 50;
88 height = fitHeightToDesktop(height);
89 setSize(width, height);
93 addCloseFrameListener();
99 addCommandListeners();
103 * Reduce the height if too large to fit in the Desktop. Also adjust the
104 * divider location in proportion.
108 * @return original or reduced height
110 public int fitHeightToDesktop(int height)
112 // allow about 65 pixels for Desktop decorators on Windows
114 int newHeight = Math.min(height, Desktop.instance.getHeight() - 65);
115 if (newHeight != height)
117 int oldDividerLocation = getDividerLocation();
118 setDividerLocation(oldDividerLocation * newHeight / height);
124 * Set the top and bottom frames to listen to each others Commands (e.g. Edit,
127 protected void addCommandListeners()
129 // TODO if CommandListener is only ever 1:1 for complementary views,
130 // may change broadcast pattern to direct messaging (more efficient)
131 final StructureSelectionManager ssm = StructureSelectionManager
132 .getStructureSelectionManager(Desktop.instance);
133 ssm.addCommandListener(((AlignFrame) getTopFrame()).getViewport());
134 ssm.addCommandListener(((AlignFrame) getBottomFrame()).getViewport());
138 * Do any tweaking and twerking of the layout wanted.
140 public void adjustLayout()
143 * Ensure sequence ids are the same width so sequences line up
145 int w1 = ((AlignFrame) getTopFrame()).getViewport().getIdWidth();
146 int w2 = ((AlignFrame) getBottomFrame()).getViewport().getIdWidth();
147 int w3 = Math.max(w1, w2);
150 ((AlignFrame) getTopFrame()).getViewport().setIdWidth(w3);
154 ((AlignFrame) getBottomFrame()).getViewport().setIdWidth(w3);
158 * Scale protein to either 1 or 3 times character width of dna
160 final AlignViewport topViewport = ((AlignFrame) getTopFrame()).viewport;
161 final AlignViewport bottomViewport = ((AlignFrame) getBottomFrame()).viewport;
162 final AlignmentI topAlignment = topViewport.getAlignment();
163 final AlignmentI bottomAlignment = bottomViewport.getAlignment();
164 AlignmentViewport cdna = topAlignment.isNucleotide() ? topViewport
165 : (bottomAlignment.isNucleotide() ? bottomViewport : null);
166 AlignmentViewport protein = !topAlignment.isNucleotide() ? topViewport
167 : (!bottomAlignment.isNucleotide() ? bottomViewport : null);
168 if (protein != null && cdna != null)
170 ViewStyleI vs = protein.getViewStyle();
171 int scale = vs.isScaleProteinAsCdna() ? 3 : 1;
172 vs.setCharWidth(scale * cdna.getViewStyle().getCharWidth());
173 protein.setViewStyle(vs);
178 * Add a listener to tidy up when the frame is closed.
180 protected void addCloseFrameListener()
182 addInternalFrameListener(new InternalFrameAdapter()
185 public void internalFrameClosed(InternalFrameEvent evt)
193 * Add a key listener that delegates to whichever split component the mouse is
194 * in (or does nothing if neither).
196 protected void addKeyListener()
198 addKeyListener(new KeyAdapter()
202 public void keyPressed(KeyEvent e)
204 AlignFrame af = (AlignFrame) getFrameAtMouse();
207 * Intercept and override any keys here if wanted.
209 if (!overrideKey(e, af))
213 for (KeyListener kl : af.getKeyListeners())
222 public void keyReleased(KeyEvent e)
224 Component c = getFrameAtMouse();
227 for (KeyListener kl : c.getKeyListeners())
238 * Returns true if the key event is overriden and actioned (or ignored) here,
239 * else returns false, indicating it should be delegated to the AlignFrame's
242 * We can't handle Cmd-Key combinations here, instead this is done by
243 * overriding key bindings.
245 * @see addKeyOverrides
250 protected boolean overrideKey(KeyEvent e, AlignFrame af)
252 boolean actioned = false;
253 int keyCode = e.getKeyCode();
256 case KeyEvent.VK_DOWN:
257 if (e.isAltDown() || !af.viewport.cursorMode)
260 * Key down (or Alt-key-down in cursor mode) - move selected sequences
262 ((AlignFrame) getTopFrame()).moveSelectedSequences(false);
263 ((AlignFrame) getBottomFrame()).moveSelectedSequences(false);
269 if (e.isAltDown() || !af.viewport.cursorMode)
272 * Key up (or Alt-key-up in cursor mode) - move selected sequences
274 ((AlignFrame) getTopFrame()).moveSelectedSequences(true);
275 ((AlignFrame) getBottomFrame()).moveSelectedSequences(true);
285 * Set key bindings (recommended for Swing over key accelerators).
287 private void addKeyBindings()
289 overrideDelegatedKeyBindings();
291 overrideImplementedKeyBindings();
295 * Override key bindings with alternative action methods implemented in this
298 protected void overrideImplementedKeyBindings()
303 overrideExpandViews();
304 overrideGatherViews();
308 * Replace Cmd-W close view action with our version.
310 protected void overrideCloseView()
312 AbstractAction action;
314 * Ctrl-W / Cmd-W - close view or window
316 KeyStroke key_cmdW = KeyStroke.getKeyStroke(KeyEvent.VK_W, Toolkit
317 .getDefaultToolkit().getMenuShortcutKeyMask(), false);
318 action = new AbstractAction()
321 public void actionPerformed(ActionEvent e)
323 closeView_actionPerformed();
326 overrideKeyBinding(key_cmdW, action);
330 * Replace Cmd-T new view action with our version.
332 protected void overrideNewView()
335 * Ctrl-T / Cmd-T open new view
337 KeyStroke key_cmdT = KeyStroke.getKeyStroke(KeyEvent.VK_T, Toolkit
338 .getDefaultToolkit().getMenuShortcutKeyMask(), false);
339 AbstractAction action = new AbstractAction()
342 public void actionPerformed(ActionEvent e)
344 newView_actionPerformed();
347 overrideKeyBinding(key_cmdT, action);
351 * For now, delegates key events to the corresponding key accelerator for the
352 * AlignFrame that the mouse is in. Hopefully can be simplified in future if
353 * AlignFrame is changed to use key bindings rather than accelerators.
355 protected void overrideDelegatedKeyBindings()
357 if (getTopFrame() instanceof AlignFrame)
360 * Get all accelerator keys in the top frame (the bottom should be
361 * identical) and override each one.
363 for (Entry<KeyStroke, JMenuItem> acc : ((AlignFrame) getTopFrame())
364 .getAccelerators().entrySet())
366 overrideKeyBinding(acc);
372 * Overrides an AlignFrame key accelerator with our version which delegates to
373 * the action listener in whichever frame has the mouse (and does nothing if
378 private void overrideKeyBinding(Entry<KeyStroke, JMenuItem> acc)
380 final KeyStroke ks = acc.getKey();
381 InputMap inputMap = this.getInputMap(JComponent.WHEN_FOCUSED);
382 inputMap.put(ks, ks);
383 this.getActionMap().put(ks, new AbstractAction()
386 public void actionPerformed(ActionEvent e)
388 Component c = getFrameAtMouse();
389 if (c != null && c instanceof AlignFrame)
391 for (ActionListener a : ((AlignFrame) c).getAccelerators()
392 .get(ks).getActionListeners())
394 a.actionPerformed(null);
402 * Replace an accelerator key's action with the specified action.
406 protected void overrideKeyBinding(KeyStroke ks, AbstractAction action)
408 this.getActionMap().put(ks, action);
409 overrideMenuItem(ks, action);
413 * Create and link new views (with matching names) in both panes.
415 * Note this is _not_ multiple tabs, each hosting a split pane view, rather it
416 * is a single split pane with each split holding multiple tabs which are
419 * TODO implement instead with a tabbed holder in the SplitView, each tab
420 * holding a single JSplitPane. Would avoid a duplicated tab, at the cost of
421 * some additional coding.
423 protected void newView_actionPerformed()
425 AlignFrame topFrame = (AlignFrame) getTopFrame();
426 AlignFrame bottomFrame = (AlignFrame) getBottomFrame();
427 final boolean scaleProteinAsCdna = topFrame.viewport
428 .isScaleProteinAsCdna();
430 AlignmentPanel newTopPanel = topFrame.newView(null, true);
431 AlignmentPanel newBottomPanel = bottomFrame.newView(null, true);
434 * This currently (for the first new view only) leaves the top pane on tab 0
435 * but the bottom on tab 1. This results from 'setInitialTabVisible' echoing
436 * from the bottom back to the first frame. Next line is a fudge to work
437 * around this. TODO find a better way.
439 if (topFrame.getTabIndex() != bottomFrame.getTabIndex())
441 topFrame.setDisplayedView(newTopPanel);
444 newBottomPanel.av.viewName = newTopPanel.av.viewName;
445 newTopPanel.av.setCodingComplement(newBottomPanel.av);
448 * These lines can be removed once scaleProteinAsCdna is added to element
449 * Viewport in jalview.xsd, as Jalview2XML.copyAlignPanel will then take
452 newTopPanel.av.setScaleProteinAsCdna(scaleProteinAsCdna);
453 newBottomPanel.av.setScaleProteinAsCdna(scaleProteinAsCdna);
456 * Line up id labels etc
460 final StructureSelectionManager ssm = StructureSelectionManager
461 .getStructureSelectionManager(Desktop.instance);
462 ssm.addCommandListener(newTopPanel.av);
463 ssm.addCommandListener(newBottomPanel.av);
467 * Close the currently selected view in both panes. If there is only one view,
468 * close this split frame.
470 protected void closeView_actionPerformed()
472 int viewCount = ((AlignFrame) getTopFrame()).getAlignPanels().size();
479 AlignmentPanel topPanel = ((AlignFrame) getTopFrame()).alignPanel;
480 AlignmentPanel bottomPanel = ((AlignFrame) getBottomFrame()).alignPanel;
482 ((AlignFrame) getTopFrame()).closeView(topPanel);
483 ((AlignFrame) getBottomFrame()).closeView(bottomPanel);
488 * Close child frames and this split frame.
492 ((AlignFrame) getTopFrame()).closeMenuItem_actionPerformed(true);
493 ((AlignFrame) getBottomFrame()).closeMenuItem_actionPerformed(true);
496 this.setClosed(true);
497 } catch (PropertyVetoException e)
504 * Replace AlignFrame 'expand views' action with SplitFrame version.
506 protected void overrideExpandViews()
508 KeyStroke key_X = KeyStroke.getKeyStroke(KeyEvent.VK_X, 0, false);
509 AbstractAction action = new AbstractAction()
512 public void actionPerformed(ActionEvent e)
514 expandViews_actionPerformed();
517 overrideMenuItem(key_X, action);
521 * Replace AlignFrame 'gather views' action with SplitFrame version.
523 protected void overrideGatherViews()
525 KeyStroke key_G = KeyStroke.getKeyStroke(KeyEvent.VK_G, 0, false);
526 AbstractAction action = new AbstractAction()
529 public void actionPerformed(ActionEvent e)
531 gatherViews_actionPerformed();
534 overrideMenuItem(key_G, action);
538 * Override the menu action associated with the keystroke in the child frames,
539 * replacing it with the given action.
544 private void overrideMenuItem(KeyStroke ks, AbstractAction action)
546 overrideMenuItem(ks, action, getTopFrame());
547 overrideMenuItem(ks, action, getBottomFrame());
551 * Override the menu action associated with the keystroke in one child frame,
552 * replacing it with the given action. Mwahahahaha.
558 private void overrideMenuItem(KeyStroke key, final AbstractAction action,
561 if (comp instanceof AlignFrame)
563 JMenuItem mi = ((AlignFrame) comp).getAccelerators().get(key);
566 for (ActionListener al : mi.getActionListeners())
568 mi.removeActionListener(al);
570 mi.addActionListener(new ActionListener()
573 public void actionPerformed(ActionEvent e)
575 action.actionPerformed(e);
583 * Expand any multiple views (which are always in pairs) into separate split
586 protected void expandViews_actionPerformed()
588 Desktop.instance.explodeViews(this);
592 * Gather any other SplitFrame views of this alignment back in as multiple
593 * (pairs of) views in this SplitFrame.
595 protected void gatherViews_actionPerformed()
597 Desktop.instance.gatherViews(this);
601 * Returns the alignment in the complementary frame to the one given.
604 public AlignmentI getComplement(Object alignFrame)
606 if (alignFrame == this.getTopFrame())
608 return ((AlignFrame) getBottomFrame()).viewport.getAlignment();
610 else if (alignFrame == this.getBottomFrame())
612 return ((AlignFrame) getTopFrame()).viewport.getAlignment();
618 * Returns the title of the complementary frame to the one given.
621 public String getComplementTitle(Object alignFrame)
623 if (alignFrame == this.getTopFrame())
625 return ((AlignFrame) getBottomFrame()).getTitle();
627 else if (alignFrame == this.getBottomFrame())
629 return ((AlignFrame) getTopFrame()).getTitle();
635 * Set the 'other half' to hidden / revealed.
638 public void setComplementVisible(Object alignFrame, boolean show)
641 * Hiding the AlignPanel suppresses unnecessary repaints
643 if (alignFrame == getTopFrame())
645 ((AlignFrame) getBottomFrame()).alignPanel.setVisible(show);
647 else if (alignFrame == getBottomFrame())
649 ((AlignFrame) getTopFrame()).alignPanel.setVisible(show);
651 super.setComplementVisible(alignFrame, show);
655 * Replace Cmd-F Find action with our version. This is necessary because the
656 * 'default' Finder searches in the first AlignFrame it finds. We need it to
657 * search in the half of the SplitFrame that has the mouse.
659 protected void overrideFind()
662 * Ctrl-F / Cmd-F open Finder dialog, 'focused' on the right alignment
664 KeyStroke key_cmdF = KeyStroke.getKeyStroke(KeyEvent.VK_F, Toolkit
665 .getDefaultToolkit().getMenuShortcutKeyMask(), false);
666 AbstractAction action = new AbstractAction()
669 public void actionPerformed(ActionEvent e)
671 Component c = getFrameAtMouse();
672 if (c != null && c instanceof AlignFrame)
674 AlignFrame af = (AlignFrame) c;
675 new Finder(af.viewport, af.alignPanel);
679 overrideKeyBinding(key_cmdF, action);