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 setSize(AlignFrame.DEFAULT_WIDTH, Desktop.instance.getHeight() - 20);
68 addCloseFrameListener();
74 addCommandListeners();
78 * Set the top and bottom frames to listen to each others Commands (e.g. Edit,
81 protected void addCommandListeners()
83 // TODO if CommandListener is only ever 1:1 for complementary views,
84 // may change broadcast pattern to direct messaging (more efficient)
85 final StructureSelectionManager ssm = StructureSelectionManager
86 .getStructureSelectionManager(Desktop.instance);
87 ssm.addCommandListener(((AlignFrame) getTopFrame()).getViewport());
88 ssm.addCommandListener(((AlignFrame) getBottomFrame()).getViewport());
92 * Do any tweaking and twerking of the layout wanted.
94 private void adjustLayout()
97 * Ensure sequence ids are the same width for good alignment.
99 int w1 = ((AlignFrame) getTopFrame()).getViewport().getIdWidth();
100 int w2 = ((AlignFrame) getBottomFrame()).getViewport().getIdWidth();
101 int w3 = Math.max(w1, w2);
104 ((AlignFrame) getTopFrame()).getViewport().setIdWidth(w3);
108 ((AlignFrame) getBottomFrame()).getViewport().setIdWidth(w3);
112 * Set the character width for protein to 3 times that for dna.
114 final AlignViewport topViewport = ((AlignFrame) getTopFrame()).viewport;
115 final AlignViewport bottomViewport = ((AlignFrame) getBottomFrame()).viewport;
116 final AlignmentI topAlignment = topViewport.getAlignment();
117 final AlignmentI bottomAlignment = bottomViewport.getAlignment();
118 AlignmentViewport cdna = topAlignment.isNucleotide() ? topViewport
119 : (bottomAlignment.isNucleotide() ? bottomViewport : null);
120 AlignmentViewport protein = !topAlignment.isNucleotide() ? topViewport
121 : (!bottomAlignment.isNucleotide() ? bottomViewport : null);
122 if (protein != null && cdna != null)
124 ViewStyleI vs = cdna.getViewStyle();
125 vs.setCharWidth(3 * vs.getCharWidth());
126 protein.setViewStyle(vs);
131 * Add a listener to tidy up when the frame is closed.
133 protected void addCloseFrameListener()
135 addInternalFrameListener(new InternalFrameAdapter()
138 public void internalFrameClosed(InternalFrameEvent evt)
140 if (getTopFrame() instanceof AlignFrame)
142 ((AlignFrame) getTopFrame())
143 .closeMenuItem_actionPerformed(true);
145 if (getBottomFrame() instanceof AlignFrame)
147 ((AlignFrame) getBottomFrame())
148 .closeMenuItem_actionPerformed(true);
155 * Add a key listener that delegates to whichever split component the mouse is
156 * in (or does nothing if neither).
158 protected void addKeyListener()
160 addKeyListener(new KeyAdapter() {
163 public void keyPressed(KeyEvent e)
165 AlignFrame af = (AlignFrame) getFrameAtMouse();
168 * Intercept and override any keys here if wanted.
170 if (!overrideKey(e, af))
174 for (KeyListener kl : af.getKeyListeners())
183 public void keyReleased(KeyEvent e)
185 Component c = getFrameAtMouse();
188 for (KeyListener kl : c.getKeyListeners())
199 * Returns true if the key event is overriden and actioned (or ignored) here,
200 * else returns false, indicating it should be delegated to the AlignFrame's
203 * We can't handle Cmd-Key combinations here, instead this is done by
204 * overriding key bindings.
206 * @see addKeyOverrides
211 protected boolean overrideKey(KeyEvent e, AlignFrame af)
213 boolean actioned = false;
214 int keyCode = e.getKeyCode();
217 case KeyEvent.VK_DOWN:
218 if (e.isAltDown() || !af.viewport.cursorMode)
221 * Key down (or Alt-key-down in cursor mode) - move selected sequences
223 ((AlignFrame) getTopFrame()).moveSelectedSequences(false);
224 ((AlignFrame) getBottomFrame()).moveSelectedSequences(false);
230 if (e.isAltDown() || !af.viewport.cursorMode)
233 * Key up (or Alt-key-up in cursor mode) - move selected sequences
235 ((AlignFrame) getTopFrame()).moveSelectedSequences(true);
236 ((AlignFrame) getBottomFrame()).moveSelectedSequences(true);
246 * Set key bindings (recommended for Swing over key accelerators).
248 private void addKeyBindings()
250 overrideDelegatedKeyBindings();
252 overrideImplementedKeyBindings();
256 * Override key bindings with alternative action methods implemented in this
259 protected void overrideImplementedKeyBindings()
263 overrideExpandViews();
264 overrideGatherViews();
268 * Replace Cmd-W close view action with our version.
270 protected void overrideCloseView()
272 AbstractAction action;
274 * Ctrl-W / Cmd-W - close view or window
276 KeyStroke key_cmdW = KeyStroke.getKeyStroke(KeyEvent.VK_W, Toolkit
277 .getDefaultToolkit().getMenuShortcutKeyMask(), false);
278 action = new AbstractAction()
281 public void actionPerformed(ActionEvent e)
283 closeView_actionPerformed();
286 overrideKeyBinding(key_cmdW, action);
290 * Replace Cmd-T new view action with our version.
292 protected void overrideNewView()
295 * Ctrl-T / Cmd-T open new view
297 KeyStroke key_cmdT = KeyStroke.getKeyStroke(KeyEvent.VK_T, Toolkit
298 .getDefaultToolkit().getMenuShortcutKeyMask(), false);
299 AbstractAction action = new AbstractAction()
302 public void actionPerformed(ActionEvent e)
304 newView_actionPerformed();
307 overrideKeyBinding(key_cmdT, action);
311 * For now, delegates key events to the corresponding key accelerator for the
312 * AlignFrame that the mouse is in. Hopefully can be simplified in future if
313 * AlignFrame is changed to use key bindings rather than accelerators.
315 protected void overrideDelegatedKeyBindings()
317 if (getTopFrame() instanceof AlignFrame)
320 * Get all accelerator keys in the top frame (the bottom should be
321 * identical) and override each one.
323 for (Entry<KeyStroke, JMenuItem> acc : ((AlignFrame) getTopFrame())
324 .getAccelerators().entrySet())
326 overrideKeyBinding(acc);
332 * Overrides an AlignFrame key accelerator with our version which delegates to
333 * the action listener in whichever frame has the mouse (and does nothing if
338 private void overrideKeyBinding(Entry<KeyStroke, JMenuItem> acc)
340 final KeyStroke ks = acc.getKey();
341 InputMap inputMap = this.getInputMap(JComponent.WHEN_FOCUSED);
342 inputMap.put(ks, ks);
343 this.getActionMap().put(ks, new AbstractAction()
346 public void actionPerformed(ActionEvent e)
348 Component c = getFrameAtMouse();
349 if (c != null && c instanceof AlignFrame)
351 for (ActionListener a : ((AlignFrame) c).getAccelerators()
352 .get(ks).getActionListeners())
354 a.actionPerformed(null);
362 * Replace an accelerator key's action with the specified action.
366 protected void overrideKeyBinding(KeyStroke ks, AbstractAction action)
368 this.getActionMap().put(ks, action);
369 overrideMenuItem(ks, action);
373 * Create and link new views (with matching names) in both panes.
375 * Note this is _not_ multiple tabs, each hosting a split pane view, rather it
376 * is a single split pane with each split holding multiple tabs which are
379 * TODO implement instead with a tabbed holder in the SplitView, each tab
380 * holding a single JSplitPane. Would avoid a duplicated tab, at the cost of
381 * some additional coding.
383 protected void newView_actionPerformed()
385 AlignFrame topFrame = (AlignFrame) getTopFrame();
386 AlignFrame bottomFrame = (AlignFrame) getBottomFrame();
388 AlignmentPanel newTopPanel = topFrame.newView(null, true);
389 AlignmentPanel newBottomPanel = bottomFrame.newView(null, true);
392 * This currently (for the first new view only) leaves the top pane on tab 0
393 * but the bottom on tab 1. This results from 'setInitialTabVisible' echoing
394 * from the bottom back to the first frame. Next line is a fudge to work
395 * around this. TODO find a better way.
397 if (topFrame.getTabIndex() != bottomFrame.getTabIndex())
399 topFrame.setDisplayedView(newTopPanel);
402 newBottomPanel.av.viewName = newTopPanel.av.viewName;
403 newTopPanel.av.setCodingComplement(newBottomPanel.av);
405 final StructureSelectionManager ssm = StructureSelectionManager
406 .getStructureSelectionManager(Desktop.instance);
407 ssm.addCommandListener(newTopPanel.av);
408 ssm.addCommandListener(newBottomPanel.av);
412 * Close the currently selected view in both panes. If there is only one view,
413 * close this split frame.
415 protected void closeView_actionPerformed()
417 int viewCount = ((AlignFrame) getTopFrame()).getAlignPanels().size();
424 AlignmentPanel topPanel = ((AlignFrame) getTopFrame()).alignPanel;
425 AlignmentPanel bottomPanel = ((AlignFrame) getBottomFrame()).alignPanel;
427 ((AlignFrame) getTopFrame()).closeView(topPanel);
428 ((AlignFrame) getBottomFrame()).closeView(bottomPanel);
433 * Close child frames and this split frame.
437 ((AlignFrame) getTopFrame()).closeMenuItem_actionPerformed(true);
438 ((AlignFrame) getBottomFrame()).closeMenuItem_actionPerformed(true);
441 this.setClosed(true);
442 } catch (PropertyVetoException e)
449 * Replace AlignFrame 'expand views' action with SplitFrame version.
451 protected void overrideExpandViews()
453 KeyStroke key_X = KeyStroke.getKeyStroke(KeyEvent.VK_X, 0, false);
454 AbstractAction action = new AbstractAction()
457 public void actionPerformed(ActionEvent e)
459 expandViews_actionPerformed();
462 overrideMenuItem(key_X, action);
466 * Replace AlignFrame 'gather views' action with SplitFrame version.
468 protected void overrideGatherViews()
470 KeyStroke key_G = KeyStroke.getKeyStroke(KeyEvent.VK_G, 0, false);
471 AbstractAction action = new AbstractAction()
474 public void actionPerformed(ActionEvent e)
476 gatherViews_actionPerformed();
479 overrideMenuItem(key_G, action);
483 * Override the menu action associated with the keystroke in the child frames,
484 * replacing it with the given action.
489 private void overrideMenuItem(KeyStroke ks, AbstractAction action)
491 overrideMenuItem(ks, action, getTopFrame());
492 overrideMenuItem(ks, action, getBottomFrame());
496 * Override the menu action associated with the keystroke in one child frame,
497 * replacing it with the given action. Mwahahahaha.
503 private void overrideMenuItem(KeyStroke key, final AbstractAction action,
506 if (comp instanceof AlignFrame)
508 JMenuItem mi = ((AlignFrame) comp).getAccelerators().get(key);
511 for (ActionListener al : mi.getActionListeners())
513 mi.removeActionListener(al);
515 mi.addActionListener(new ActionListener()
518 public void actionPerformed(ActionEvent e)
520 action.actionPerformed(e);
528 * Expand any multiple views (which are always in pairs) into separate split
531 protected void expandViews_actionPerformed()
533 Desktop.instance.explodeViews(this);
537 * Gather any other SplitFrame views of this alignment back in as multiple
538 * (pairs of) views in this SplitFrame.
540 protected void gatherViews_actionPerformed()
542 Desktop.instance.gatherViews(this);
546 * Returns the alignment in the complementary frame to the one given.
549 public AlignmentI getComplement(Object alignFrame)
551 if (alignFrame == this.getTopFrame())
553 return ((AlignFrame) getBottomFrame()).viewport.getAlignment();
555 else if (alignFrame == this.getBottomFrame())
557 return ((AlignFrame) getTopFrame()).viewport.getAlignment();
563 * Returns the title of the complementary frame to the one given.
566 public String getComplementTitle(Object alignFrame)
568 if (alignFrame == this.getTopFrame())
570 return ((AlignFrame) getBottomFrame()).getTitle();
572 else if (alignFrame == this.getBottomFrame())
574 return ((AlignFrame) getTopFrame()).getTitle();
580 * Set the 'other half' to hidden / revealed.
583 public void setComplementVisible(Object alignFrame, boolean show)
586 * Hiding the AlignPanel suppresses unnecessary repaints
588 if (alignFrame == getTopFrame())
590 ((AlignFrame) getBottomFrame()).alignPanel.setVisible(show);
592 else if (alignFrame == getBottomFrame())
594 ((AlignFrame) getTopFrame()).alignPanel.setVisible(show);
596 super.setComplementVisible(alignFrame, show);