3 import java.awt.Component;
4 import java.awt.Toolkit;
5 import java.awt.event.ActionEvent;
6 import java.awt.event.ActionListener;
7 import java.awt.event.KeyAdapter;
8 import java.awt.event.KeyEvent;
9 import java.awt.event.KeyListener;
10 import java.beans.PropertyVetoException;
11 import java.util.Map.Entry;
13 import javax.swing.AbstractAction;
14 import javax.swing.InputMap;
15 import javax.swing.JComponent;
16 import javax.swing.JMenuItem;
17 import javax.swing.KeyStroke;
18 import javax.swing.event.InternalFrameAdapter;
19 import javax.swing.event.InternalFrameEvent;
21 import jalview.api.SplitContainerI;
22 import jalview.api.ViewStyleI;
23 import jalview.datamodel.AlignmentI;
24 import jalview.jbgui.GAlignFrame;
25 import jalview.jbgui.GSplitFrame;
26 import jalview.structure.StructureSelectionManager;
27 import jalview.viewmodel.AlignmentViewport;
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 height = Math.min(height, Desktop.instance.getHeight() - 20);
69 // setSize(AlignFrame.DEFAULT_WIDTH, Desktop.instance.getHeight() - 20);
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)
147 if (getTopFrame() instanceof AlignFrame)
149 ((AlignFrame) getTopFrame())
150 .closeMenuItem_actionPerformed(true);
152 if (getBottomFrame() instanceof AlignFrame)
154 ((AlignFrame) getBottomFrame())
155 .closeMenuItem_actionPerformed(true);
162 * Add a key listener that delegates to whichever split component the mouse is
163 * in (or does nothing if neither).
165 protected void addKeyListener()
167 addKeyListener(new KeyAdapter() {
170 public void keyPressed(KeyEvent e)
172 AlignFrame af = (AlignFrame) getFrameAtMouse();
175 * Intercept and override any keys here if wanted.
177 if (!overrideKey(e, af))
181 for (KeyListener kl : af.getKeyListeners())
190 public void keyReleased(KeyEvent e)
192 Component c = getFrameAtMouse();
195 for (KeyListener kl : c.getKeyListeners())
206 * Returns true if the key event is overriden and actioned (or ignored) here,
207 * else returns false, indicating it should be delegated to the AlignFrame's
210 * We can't handle Cmd-Key combinations here, instead this is done by
211 * overriding key bindings.
213 * @see addKeyOverrides
218 protected boolean overrideKey(KeyEvent e, AlignFrame af)
220 boolean actioned = false;
221 int keyCode = e.getKeyCode();
224 case KeyEvent.VK_DOWN:
225 if (e.isAltDown() || !af.viewport.cursorMode)
228 * Key down (or Alt-key-down in cursor mode) - move selected sequences
230 ((AlignFrame) getTopFrame()).moveSelectedSequences(false);
231 ((AlignFrame) getBottomFrame()).moveSelectedSequences(false);
237 if (e.isAltDown() || !af.viewport.cursorMode)
240 * Key up (or Alt-key-up in cursor mode) - move selected sequences
242 ((AlignFrame) getTopFrame()).moveSelectedSequences(true);
243 ((AlignFrame) getBottomFrame()).moveSelectedSequences(true);
253 * Set key bindings (recommended for Swing over key accelerators).
255 private void addKeyBindings()
257 overrideDelegatedKeyBindings();
259 overrideImplementedKeyBindings();
263 * Override key bindings with alternative action methods implemented in this
266 protected void overrideImplementedKeyBindings()
271 overrideExpandViews();
272 overrideGatherViews();
276 * Replace Cmd-W close view action with our version.
278 protected void overrideCloseView()
280 AbstractAction action;
282 * Ctrl-W / Cmd-W - close view or window
284 KeyStroke key_cmdW = KeyStroke.getKeyStroke(KeyEvent.VK_W, Toolkit
285 .getDefaultToolkit().getMenuShortcutKeyMask(), false);
286 action = new AbstractAction()
289 public void actionPerformed(ActionEvent e)
291 closeView_actionPerformed();
294 overrideKeyBinding(key_cmdW, action);
298 * Replace Cmd-T new view action with our version.
300 protected void overrideNewView()
303 * Ctrl-T / Cmd-T open new view
305 KeyStroke key_cmdT = KeyStroke.getKeyStroke(KeyEvent.VK_T, Toolkit
306 .getDefaultToolkit().getMenuShortcutKeyMask(), false);
307 AbstractAction action = new AbstractAction()
310 public void actionPerformed(ActionEvent e)
312 newView_actionPerformed();
315 overrideKeyBinding(key_cmdT, action);
319 * For now, delegates key events to the corresponding key accelerator for the
320 * AlignFrame that the mouse is in. Hopefully can be simplified in future if
321 * AlignFrame is changed to use key bindings rather than accelerators.
323 protected void overrideDelegatedKeyBindings()
325 if (getTopFrame() instanceof AlignFrame)
328 * Get all accelerator keys in the top frame (the bottom should be
329 * identical) and override each one.
331 for (Entry<KeyStroke, JMenuItem> acc : ((AlignFrame) getTopFrame())
332 .getAccelerators().entrySet())
334 overrideKeyBinding(acc);
340 * Overrides an AlignFrame key accelerator with our version which delegates to
341 * the action listener in whichever frame has the mouse (and does nothing if
346 private void overrideKeyBinding(Entry<KeyStroke, JMenuItem> acc)
348 final KeyStroke ks = acc.getKey();
349 InputMap inputMap = this.getInputMap(JComponent.WHEN_FOCUSED);
350 inputMap.put(ks, ks);
351 this.getActionMap().put(ks, new AbstractAction()
354 public void actionPerformed(ActionEvent e)
356 Component c = getFrameAtMouse();
357 if (c != null && c instanceof AlignFrame)
359 for (ActionListener a : ((AlignFrame) c).getAccelerators()
360 .get(ks).getActionListeners())
362 a.actionPerformed(null);
370 * Replace an accelerator key's action with the specified action.
374 protected void overrideKeyBinding(KeyStroke ks, AbstractAction action)
376 this.getActionMap().put(ks, action);
377 overrideMenuItem(ks, action);
381 * Create and link new views (with matching names) in both panes.
383 * Note this is _not_ multiple tabs, each hosting a split pane view, rather it
384 * is a single split pane with each split holding multiple tabs which are
387 * TODO implement instead with a tabbed holder in the SplitView, each tab
388 * holding a single JSplitPane. Would avoid a duplicated tab, at the cost of
389 * some additional coding.
391 protected void newView_actionPerformed()
393 AlignFrame topFrame = (AlignFrame) getTopFrame();
394 AlignFrame bottomFrame = (AlignFrame) getBottomFrame();
396 AlignmentPanel newTopPanel = topFrame.newView(null, true);
397 AlignmentPanel newBottomPanel = bottomFrame.newView(null, true);
400 * This currently (for the first new view only) leaves the top pane on tab 0
401 * but the bottom on tab 1. This results from 'setInitialTabVisible' echoing
402 * from the bottom back to the first frame. Next line is a fudge to work
403 * around this. TODO find a better way.
405 if (topFrame.getTabIndex() != bottomFrame.getTabIndex())
407 topFrame.setDisplayedView(newTopPanel);
410 newBottomPanel.av.viewName = newTopPanel.av.viewName;
411 newTopPanel.av.setCodingComplement(newBottomPanel.av);
413 final StructureSelectionManager ssm = StructureSelectionManager
414 .getStructureSelectionManager(Desktop.instance);
415 ssm.addCommandListener(newTopPanel.av);
416 ssm.addCommandListener(newBottomPanel.av);
420 * Close the currently selected view in both panes. If there is only one view,
421 * close this split frame.
423 protected void closeView_actionPerformed()
425 int viewCount = ((AlignFrame) getTopFrame()).getAlignPanels().size();
432 AlignmentPanel topPanel = ((AlignFrame) getTopFrame()).alignPanel;
433 AlignmentPanel bottomPanel = ((AlignFrame) getBottomFrame()).alignPanel;
435 ((AlignFrame) getTopFrame()).closeView(topPanel);
436 ((AlignFrame) getBottomFrame()).closeView(bottomPanel);
441 * Close child frames and this split frame.
445 ((AlignFrame) getTopFrame()).closeMenuItem_actionPerformed(true);
446 ((AlignFrame) getBottomFrame()).closeMenuItem_actionPerformed(true);
449 this.setClosed(true);
450 } catch (PropertyVetoException e)
457 * Replace AlignFrame 'expand views' action with SplitFrame version.
459 protected void overrideExpandViews()
461 KeyStroke key_X = KeyStroke.getKeyStroke(KeyEvent.VK_X, 0, false);
462 AbstractAction action = new AbstractAction()
465 public void actionPerformed(ActionEvent e)
467 expandViews_actionPerformed();
470 overrideMenuItem(key_X, action);
474 * Replace AlignFrame 'gather views' action with SplitFrame version.
476 protected void overrideGatherViews()
478 KeyStroke key_G = KeyStroke.getKeyStroke(KeyEvent.VK_G, 0, false);
479 AbstractAction action = new AbstractAction()
482 public void actionPerformed(ActionEvent e)
484 gatherViews_actionPerformed();
487 overrideMenuItem(key_G, action);
491 * Override the menu action associated with the keystroke in the child frames,
492 * replacing it with the given action.
497 private void overrideMenuItem(KeyStroke ks, AbstractAction action)
499 overrideMenuItem(ks, action, getTopFrame());
500 overrideMenuItem(ks, action, getBottomFrame());
504 * Override the menu action associated with the keystroke in one child frame,
505 * replacing it with the given action. Mwahahahaha.
511 private void overrideMenuItem(KeyStroke key, final AbstractAction action,
514 if (comp instanceof AlignFrame)
516 JMenuItem mi = ((AlignFrame) comp).getAccelerators().get(key);
519 for (ActionListener al : mi.getActionListeners())
521 mi.removeActionListener(al);
523 mi.addActionListener(new ActionListener()
526 public void actionPerformed(ActionEvent e)
528 action.actionPerformed(e);
536 * Expand any multiple views (which are always in pairs) into separate split
539 protected void expandViews_actionPerformed()
541 Desktop.instance.explodeViews(this);
545 * Gather any other SplitFrame views of this alignment back in as multiple
546 * (pairs of) views in this SplitFrame.
548 protected void gatherViews_actionPerformed()
550 Desktop.instance.gatherViews(this);
554 * Returns the alignment in the complementary frame to the one given.
557 public AlignmentI getComplement(Object alignFrame)
559 if (alignFrame == this.getTopFrame())
561 return ((AlignFrame) getBottomFrame()).viewport.getAlignment();
563 else if (alignFrame == this.getBottomFrame())
565 return ((AlignFrame) getTopFrame()).viewport.getAlignment();
571 * Returns the title of the complementary frame to the one given.
574 public String getComplementTitle(Object alignFrame)
576 if (alignFrame == this.getTopFrame())
578 return ((AlignFrame) getBottomFrame()).getTitle();
580 else if (alignFrame == this.getBottomFrame())
582 return ((AlignFrame) getTopFrame()).getTitle();
588 * Set the 'other half' to hidden / revealed.
591 public void setComplementVisible(Object alignFrame, boolean show)
594 * Hiding the AlignPanel suppresses unnecessary repaints
596 if (alignFrame == getTopFrame())
598 ((AlignFrame) getBottomFrame()).alignPanel.setVisible(show);
600 else if (alignFrame == getBottomFrame())
602 ((AlignFrame) getTopFrame()).alignPanel.setVisible(show);
604 super.setComplementVisible(alignFrame, show);
608 * Replace Cmd-F Find action with our version. This is necessary because the
609 * 'default' Finder searches in the first AlignFrame it finds. We need it to
610 * search in the half of the SplitFrame that has the mouse.
612 protected void overrideFind()
615 * Ctrl-F / Cmd-F open Finder dialog, 'focused' on the right alignment
617 KeyStroke key_cmdF = KeyStroke.getKeyStroke(KeyEvent.VK_F, Toolkit
618 .getDefaultToolkit().getMenuShortcutKeyMask(), false);
619 AbstractAction action = new AbstractAction()
622 public void actionPerformed(ActionEvent e)
624 Component c = getFrameAtMouse();
625 if (c != null && c instanceof AlignFrame)
627 AlignFrame af = (AlignFrame) c;
628 new Finder(af.viewport, af.alignPanel);
632 overrideKeyBinding(key_cmdF, action);