3 import jalview.api.SplitContainerI;
4 import jalview.api.ViewStyleI;
5 import jalview.datamodel.AlignmentI;
6 import jalview.jbgui.GAlignFrame;
7 import jalview.jbgui.GSplitFrame;
8 import jalview.structure.StructureSelectionManager;
9 import jalview.viewmodel.AlignmentViewport;
11 import java.awt.Component;
12 import java.awt.Toolkit;
13 import java.awt.event.ActionEvent;
14 import java.awt.event.ActionListener;
15 import java.awt.event.KeyAdapter;
16 import java.awt.event.KeyEvent;
17 import java.awt.event.KeyListener;
18 import java.beans.PropertyVetoException;
19 import java.util.Map.Entry;
21 import javax.swing.AbstractAction;
22 import javax.swing.InputMap;
23 import javax.swing.JComponent;
24 import javax.swing.JMenuItem;
25 import javax.swing.KeyStroke;
26 import javax.swing.event.InternalFrameAdapter;
27 import javax.swing.event.InternalFrameEvent;
30 * An internal frame on the desktop that hosts a horizontally split view of
31 * linked DNA and Protein alignments. Additional views can be created in linked
32 * pairs, expanded to separate split frames, or regathered into a single frame.
34 * (Some) operations on each alignment are automatically mirrored on the other.
35 * These include mouseover (highlighting), sequence and column selection,
36 * sequence ordering and sorting, and grouping, colouring and sorting by tree.
41 public class SplitFrame extends GSplitFrame implements SplitContainerI
43 private static final long serialVersionUID = 1L;
45 public SplitFrame(GAlignFrame top, GAlignFrame bottom)
52 * Initialise this frame.
56 getTopFrame().setSplitFrame(this);
57 getBottomFrame().setSplitFrame(this);
58 getTopFrame().setVisible(true);
59 getBottomFrame().setVisible(true);
61 ((AlignFrame) getTopFrame()).getViewport().setCodingComplement(
62 ((AlignFrame) getBottomFrame()).getViewport());
64 int width = ((AlignFrame) getTopFrame()).getWidth();
65 // about 50 pixels for the SplitFrame's title bar etc
66 int height = ((AlignFrame) getTopFrame()).getHeight()
67 + ((AlignFrame) getBottomFrame()).getHeight() + 50;
68 // about 65 pixels for Desktop decorators on Windows
69 height = Math.min(height, Desktop.instance.getHeight() - 65);
70 setSize(width, height);
74 addCloseFrameListener();
80 addCommandListeners();
84 * Set the top and bottom frames to listen to each others Commands (e.g. Edit,
87 protected void addCommandListeners()
89 // TODO if CommandListener is only ever 1:1 for complementary views,
90 // may change broadcast pattern to direct messaging (more efficient)
91 final StructureSelectionManager ssm = StructureSelectionManager
92 .getStructureSelectionManager(Desktop.instance);
93 ssm.addCommandListener(((AlignFrame) getTopFrame()).getViewport());
94 ssm.addCommandListener(((AlignFrame) getBottomFrame()).getViewport());
98 * Do any tweaking and twerking of the layout wanted.
100 public void adjustLayout()
103 * Ensure sequence ids are the same width so sequences line up
105 int w1 = ((AlignFrame) getTopFrame()).getViewport().getIdWidth();
106 int w2 = ((AlignFrame) getBottomFrame()).getViewport().getIdWidth();
107 int w3 = Math.max(w1, w2);
110 ((AlignFrame) getTopFrame()).getViewport().setIdWidth(w3);
114 ((AlignFrame) getBottomFrame()).getViewport().setIdWidth(w3);
118 * Scale protein to either 1 or 3 times character width of dna
120 final AlignViewport topViewport = ((AlignFrame) getTopFrame()).viewport;
121 final AlignViewport bottomViewport = ((AlignFrame) getBottomFrame()).viewport;
122 final AlignmentI topAlignment = topViewport.getAlignment();
123 final AlignmentI bottomAlignment = bottomViewport.getAlignment();
124 AlignmentViewport cdna = topAlignment.isNucleotide() ? topViewport
125 : (bottomAlignment.isNucleotide() ? bottomViewport : null);
126 AlignmentViewport protein = !topAlignment.isNucleotide() ? topViewport
127 : (!bottomAlignment.isNucleotide() ? bottomViewport : null);
128 if (protein != null && cdna != null)
130 ViewStyleI vs = protein.getViewStyle();
131 int scale = vs.isScaleProteinAsCdna() ? 3 : 1;
132 vs.setCharWidth(scale * cdna.getViewStyle().getCharWidth());
133 protein.setViewStyle(vs);
138 * Add a listener to tidy up when the frame is closed.
140 protected void addCloseFrameListener()
142 addInternalFrameListener(new InternalFrameAdapter()
145 public void internalFrameClosed(InternalFrameEvent evt)
153 * Add a key listener that delegates to whichever split component the mouse is
154 * in (or does nothing if neither).
156 protected void addKeyListener()
158 addKeyListener(new KeyAdapter() {
161 public void keyPressed(KeyEvent e)
163 AlignFrame af = (AlignFrame) getFrameAtMouse();
166 * Intercept and override any keys here if wanted.
168 if (!overrideKey(e, af))
172 for (KeyListener kl : af.getKeyListeners())
181 public void keyReleased(KeyEvent e)
183 Component c = getFrameAtMouse();
186 for (KeyListener kl : c.getKeyListeners())
197 * Returns true if the key event is overriden and actioned (or ignored) here,
198 * else returns false, indicating it should be delegated to the AlignFrame's
201 * We can't handle Cmd-Key combinations here, instead this is done by
202 * overriding key bindings.
204 * @see addKeyOverrides
209 protected boolean overrideKey(KeyEvent e, AlignFrame af)
211 boolean actioned = false;
212 int keyCode = e.getKeyCode();
215 case KeyEvent.VK_DOWN:
216 if (e.isAltDown() || !af.viewport.cursorMode)
219 * Key down (or Alt-key-down in cursor mode) - move selected sequences
221 ((AlignFrame) getTopFrame()).moveSelectedSequences(false);
222 ((AlignFrame) getBottomFrame()).moveSelectedSequences(false);
228 if (e.isAltDown() || !af.viewport.cursorMode)
231 * Key up (or Alt-key-up in cursor mode) - move selected sequences
233 ((AlignFrame) getTopFrame()).moveSelectedSequences(true);
234 ((AlignFrame) getBottomFrame()).moveSelectedSequences(true);
244 * Set key bindings (recommended for Swing over key accelerators).
246 private void addKeyBindings()
248 overrideDelegatedKeyBindings();
250 overrideImplementedKeyBindings();
254 * Override key bindings with alternative action methods implemented in this
257 protected void overrideImplementedKeyBindings()
262 overrideExpandViews();
263 overrideGatherViews();
267 * Replace Cmd-W close view action with our version.
269 protected void overrideCloseView()
271 AbstractAction action;
273 * Ctrl-W / Cmd-W - close view or window
275 KeyStroke key_cmdW = KeyStroke.getKeyStroke(KeyEvent.VK_W, Toolkit
276 .getDefaultToolkit().getMenuShortcutKeyMask(), false);
277 action = new AbstractAction()
280 public void actionPerformed(ActionEvent e)
282 closeView_actionPerformed();
285 overrideKeyBinding(key_cmdW, action);
289 * Replace Cmd-T new view action with our version.
291 protected void overrideNewView()
294 * Ctrl-T / Cmd-T open new view
296 KeyStroke key_cmdT = KeyStroke.getKeyStroke(KeyEvent.VK_T, Toolkit
297 .getDefaultToolkit().getMenuShortcutKeyMask(), false);
298 AbstractAction action = new AbstractAction()
301 public void actionPerformed(ActionEvent e)
303 newView_actionPerformed();
306 overrideKeyBinding(key_cmdT, action);
310 * For now, delegates key events to the corresponding key accelerator for the
311 * AlignFrame that the mouse is in. Hopefully can be simplified in future if
312 * AlignFrame is changed to use key bindings rather than accelerators.
314 protected void overrideDelegatedKeyBindings()
316 if (getTopFrame() instanceof AlignFrame)
319 * Get all accelerator keys in the top frame (the bottom should be
320 * identical) and override each one.
322 for (Entry<KeyStroke, JMenuItem> acc : ((AlignFrame) getTopFrame())
323 .getAccelerators().entrySet())
325 overrideKeyBinding(acc);
331 * Overrides an AlignFrame key accelerator with our version which delegates to
332 * the action listener in whichever frame has the mouse (and does nothing if
337 private void overrideKeyBinding(Entry<KeyStroke, JMenuItem> acc)
339 final KeyStroke ks = acc.getKey();
340 InputMap inputMap = this.getInputMap(JComponent.WHEN_FOCUSED);
341 inputMap.put(ks, ks);
342 this.getActionMap().put(ks, new AbstractAction()
345 public void actionPerformed(ActionEvent e)
347 Component c = getFrameAtMouse();
348 if (c != null && c instanceof AlignFrame)
350 for (ActionListener a : ((AlignFrame) c).getAccelerators()
351 .get(ks).getActionListeners())
353 a.actionPerformed(null);
361 * Replace an accelerator key's action with the specified action.
365 protected void overrideKeyBinding(KeyStroke ks, AbstractAction action)
367 this.getActionMap().put(ks, action);
368 overrideMenuItem(ks, action);
372 * Create and link new views (with matching names) in both panes.
374 * Note this is _not_ multiple tabs, each hosting a split pane view, rather it
375 * is a single split pane with each split holding multiple tabs which are
378 * TODO implement instead with a tabbed holder in the SplitView, each tab
379 * holding a single JSplitPane. Would avoid a duplicated tab, at the cost of
380 * some additional coding.
382 protected void newView_actionPerformed()
384 AlignFrame topFrame = (AlignFrame) getTopFrame();
385 AlignFrame bottomFrame = (AlignFrame) getBottomFrame();
386 final boolean scaleProteinAsCdna = topFrame.viewport
387 .isScaleProteinAsCdna();
389 AlignmentPanel newTopPanel = topFrame.newView(null, true);
390 AlignmentPanel newBottomPanel = bottomFrame.newView(null, true);
393 * This currently (for the first new view only) leaves the top pane on tab 0
394 * but the bottom on tab 1. This results from 'setInitialTabVisible' echoing
395 * from the bottom back to the first frame. Next line is a fudge to work
396 * around this. TODO find a better way.
398 if (topFrame.getTabIndex() != bottomFrame.getTabIndex())
400 topFrame.setDisplayedView(newTopPanel);
403 newBottomPanel.av.viewName = newTopPanel.av.viewName;
404 newTopPanel.av.setCodingComplement(newBottomPanel.av);
407 * These lines can be removed once scaleProteinAsCdna is added to element
408 * Viewport in jalview.xsd, as Jalview2XML.copyAlignPanel will then take
411 newTopPanel.av.setScaleProteinAsCdna(scaleProteinAsCdna);
412 newBottomPanel.av.setScaleProteinAsCdna(scaleProteinAsCdna);
415 * Line up id labels etc
419 final StructureSelectionManager ssm = StructureSelectionManager
420 .getStructureSelectionManager(Desktop.instance);
421 ssm.addCommandListener(newTopPanel.av);
422 ssm.addCommandListener(newBottomPanel.av);
426 * Close the currently selected view in both panes. If there is only one view,
427 * close this split frame.
429 protected void closeView_actionPerformed()
431 int viewCount = ((AlignFrame) getTopFrame()).getAlignPanels().size();
438 AlignmentPanel topPanel = ((AlignFrame) getTopFrame()).alignPanel;
439 AlignmentPanel bottomPanel = ((AlignFrame) getBottomFrame()).alignPanel;
441 ((AlignFrame) getTopFrame()).closeView(topPanel);
442 ((AlignFrame) getBottomFrame()).closeView(bottomPanel);
447 * Close child frames and this split frame.
451 ((AlignFrame) getTopFrame()).closeMenuItem_actionPerformed(true);
452 ((AlignFrame) getBottomFrame()).closeMenuItem_actionPerformed(true);
455 this.setClosed(true);
456 } catch (PropertyVetoException e)
463 * Replace AlignFrame 'expand views' action with SplitFrame version.
465 protected void overrideExpandViews()
467 KeyStroke key_X = KeyStroke.getKeyStroke(KeyEvent.VK_X, 0, false);
468 AbstractAction action = new AbstractAction()
471 public void actionPerformed(ActionEvent e)
473 expandViews_actionPerformed();
476 overrideMenuItem(key_X, action);
480 * Replace AlignFrame 'gather views' action with SplitFrame version.
482 protected void overrideGatherViews()
484 KeyStroke key_G = KeyStroke.getKeyStroke(KeyEvent.VK_G, 0, false);
485 AbstractAction action = new AbstractAction()
488 public void actionPerformed(ActionEvent e)
490 gatherViews_actionPerformed();
493 overrideMenuItem(key_G, action);
497 * Override the menu action associated with the keystroke in the child frames,
498 * replacing it with the given action.
503 private void overrideMenuItem(KeyStroke ks, AbstractAction action)
505 overrideMenuItem(ks, action, getTopFrame());
506 overrideMenuItem(ks, action, getBottomFrame());
510 * Override the menu action associated with the keystroke in one child frame,
511 * replacing it with the given action. Mwahahahaha.
517 private void overrideMenuItem(KeyStroke key, final AbstractAction action,
520 if (comp instanceof AlignFrame)
522 JMenuItem mi = ((AlignFrame) comp).getAccelerators().get(key);
525 for (ActionListener al : mi.getActionListeners())
527 mi.removeActionListener(al);
529 mi.addActionListener(new ActionListener()
532 public void actionPerformed(ActionEvent e)
534 action.actionPerformed(e);
542 * Expand any multiple views (which are always in pairs) into separate split
545 protected void expandViews_actionPerformed()
547 Desktop.instance.explodeViews(this);
551 * Gather any other SplitFrame views of this alignment back in as multiple
552 * (pairs of) views in this SplitFrame.
554 protected void gatherViews_actionPerformed()
556 Desktop.instance.gatherViews(this);
560 * Returns the alignment in the complementary frame to the one given.
563 public AlignmentI getComplement(Object alignFrame)
565 if (alignFrame == this.getTopFrame())
567 return ((AlignFrame) getBottomFrame()).viewport.getAlignment();
569 else if (alignFrame == this.getBottomFrame())
571 return ((AlignFrame) getTopFrame()).viewport.getAlignment();
577 * Returns the title of the complementary frame to the one given.
580 public String getComplementTitle(Object alignFrame)
582 if (alignFrame == this.getTopFrame())
584 return ((AlignFrame) getBottomFrame()).getTitle();
586 else if (alignFrame == this.getBottomFrame())
588 return ((AlignFrame) getTopFrame()).getTitle();
594 * Set the 'other half' to hidden / revealed.
597 public void setComplementVisible(Object alignFrame, boolean show)
600 * Hiding the AlignPanel suppresses unnecessary repaints
602 if (alignFrame == getTopFrame())
604 ((AlignFrame) getBottomFrame()).alignPanel.setVisible(show);
606 else if (alignFrame == getBottomFrame())
608 ((AlignFrame) getTopFrame()).alignPanel.setVisible(show);
610 super.setComplementVisible(alignFrame, show);
614 * Replace Cmd-F Find action with our version. This is necessary because the
615 * 'default' Finder searches in the first AlignFrame it finds. We need it to
616 * search in the half of the SplitFrame that has the mouse.
618 protected void overrideFind()
621 * Ctrl-F / Cmd-F open Finder dialog, 'focused' on the right alignment
623 KeyStroke key_cmdF = KeyStroke.getKeyStroke(KeyEvent.VK_F, Toolkit
624 .getDefaultToolkit().getMenuShortcutKeyMask(), false);
625 AbstractAction action = new AbstractAction()
628 public void actionPerformed(ActionEvent e)
630 Component c = getFrameAtMouse();
631 if (c != null && c instanceof AlignFrame)
633 AlignFrame af = (AlignFrame) c;
634 new Finder(af.viewport, af.alignPanel);
638 overrideKeyBinding(key_cmdF, action);