3 import jalview.api.ViewStyleI;
4 import jalview.datamodel.AlignmentI;
5 import jalview.jbgui.GAlignFrame;
6 import jalview.jbgui.GSplitFrame;
7 import jalview.structure.StructureSelectionManager;
8 import jalview.viewmodel.AlignmentViewport;
10 import java.awt.Component;
11 import java.awt.Toolkit;
12 import java.awt.event.ActionEvent;
13 import java.awt.event.ActionListener;
14 import java.awt.event.KeyAdapter;
15 import java.awt.event.KeyEvent;
16 import java.awt.event.KeyListener;
17 import java.beans.PropertyVetoException;
18 import java.util.Map.Entry;
20 import javax.swing.AbstractAction;
21 import javax.swing.InputMap;
22 import javax.swing.JComponent;
23 import javax.swing.JMenuItem;
24 import javax.swing.KeyStroke;
25 import javax.swing.event.InternalFrameAdapter;
26 import javax.swing.event.InternalFrameEvent;
29 * An internal frame on the desktop that hosts a horizontally split view of
30 * linked DNA and Protein alignments. Additional views can be created in linked
31 * pairs, expanded to separate split frames, or regathered into a single frame.
33 * (Some) operations on each alignment are automatically mirrored on the other.
34 * These include mouseover (highlighting), sequence and column selection,
35 * sequence ordering and sorting, and grouping, colouring and sorting by tree.
40 public class SplitFrame extends GSplitFrame
42 private static final long serialVersionUID = 1L;
44 public SplitFrame(GAlignFrame top, GAlignFrame bottom)
51 * Initialise this frame.
55 setSize(AlignFrame.DEFAULT_WIDTH, Desktop.instance.getHeight() - 20);
59 addCloseFrameListener();
67 * Set the character width for protein to 3 times that for dna.
69 private void setCharacterWidth()
71 final AlignViewport topViewport = ((AlignFrame) getTopFrame()).viewport;
72 final AlignViewport bottomViewport = ((AlignFrame) getBottomFrame()).viewport;
73 final AlignmentI topAlignment = topViewport.getAlignment();
74 final AlignmentI bottomAlignment = bottomViewport.getAlignment();
75 AlignmentViewport cdna = topAlignment.isNucleotide() ? topViewport
76 : (bottomAlignment.isNucleotide() ? bottomViewport : null);
77 AlignmentViewport protein = !topAlignment.isNucleotide() ? topViewport
78 : (!bottomAlignment.isNucleotide() ? bottomViewport : null);
79 if (protein != null && cdna != null)
81 ViewStyleI vs = cdna.getViewStyle();
82 vs.setCharWidth(3 * vs.getCharWidth());
83 protein.setViewStyle(vs);
88 * Add a listener to tidy up when the frame is closed.
90 protected void addCloseFrameListener()
92 addInternalFrameListener(new InternalFrameAdapter()
95 public void internalFrameClosed(InternalFrameEvent evt)
97 if (getTopFrame() instanceof AlignFrame)
99 ((AlignFrame) getTopFrame())
100 .closeMenuItem_actionPerformed(true);
102 if (getBottomFrame() instanceof AlignFrame)
104 ((AlignFrame) getBottomFrame())
105 .closeMenuItem_actionPerformed(true);
112 * Add a key listener that delegates to whichever split component the mouse is
113 * in (or does nothing if neither).
115 protected void addKeyListener()
117 addKeyListener(new KeyAdapter() {
120 public void keyPressed(KeyEvent e)
122 AlignFrame af = (AlignFrame) getFrameAtMouse();
125 * Intercept and override any keys here if wanted.
127 if (!overrideKey(e, af))
131 for (KeyListener kl : af.getKeyListeners())
140 public void keyReleased(KeyEvent e)
142 Component c = getFrameAtMouse();
145 for (KeyListener kl : c.getKeyListeners())
156 * Returns true if the key event is overriden and actioned (or ignored) here,
157 * else returns false, indicating it should be delegated to the AlignFrame's
160 * We can't handle Cmd-Key combinations here, instead this is done by
161 * overriding key bindings.
163 * @see addKeyOverrides
168 protected boolean overrideKey(KeyEvent e, AlignFrame af)
170 boolean actioned = false;
171 int keyCode = e.getKeyCode();
174 case KeyEvent.VK_DOWN:
175 if (e.isAltDown() || !af.viewport.cursorMode)
178 * Key down (or Alt-key-down in cursor mode) - move selected sequences
180 ((AlignFrame) getTopFrame()).moveSelectedSequences(false);
181 ((AlignFrame) getBottomFrame()).moveSelectedSequences(false);
187 if (e.isAltDown() || !af.viewport.cursorMode)
190 * Key up (or Alt-key-up in cursor mode) - move selected sequences
192 ((AlignFrame) getTopFrame()).moveSelectedSequences(true);
193 ((AlignFrame) getBottomFrame()).moveSelectedSequences(true);
203 * Set key bindings (recommended for Swing over key accelerators).
205 private void addKeyBindings()
207 overrideDelegatedKeyBindings();
209 overrideImplementedKeyBindings();
213 * Override key bindings with alternative action methods implemented in this
216 protected void overrideImplementedKeyBindings()
220 overrideExpandViews();
221 overrideGatherViews();
225 * Replace Cmd-W close view action with our version.
227 protected void overrideCloseView()
229 AbstractAction action;
231 * Ctrl-W / Cmd-W - close view or window
233 KeyStroke key_cmdW = KeyStroke.getKeyStroke(KeyEvent.VK_W, Toolkit
234 .getDefaultToolkit().getMenuShortcutKeyMask(), false);
235 action = new AbstractAction()
238 public void actionPerformed(ActionEvent e)
240 closeView_actionPerformed();
243 overrideKeyBinding(key_cmdW, action);
247 * Replace Cmd-T new view action with our version.
249 protected void overrideNewView()
252 * Ctrl-T / Cmd-T open new view
254 KeyStroke key_cmdT = KeyStroke.getKeyStroke(KeyEvent.VK_T, Toolkit
255 .getDefaultToolkit().getMenuShortcutKeyMask(), false);
256 AbstractAction action = new AbstractAction()
259 public void actionPerformed(ActionEvent e)
261 newView_actionPerformed();
264 overrideKeyBinding(key_cmdT, action);
268 * For now, delegates key events to the corresponding key accelerator for the
269 * AlignFrame that the mouse is in. Hopefully can be simplified in future if
270 * AlignFrame is changed to use key bindings rather than accelerators.
272 protected void overrideDelegatedKeyBindings()
274 if (getTopFrame() instanceof AlignFrame)
277 * Get all accelerator keys in the top frame (the bottom should be
278 * identical) and override each one.
280 for (Entry<KeyStroke, JMenuItem> acc : ((AlignFrame) getTopFrame())
281 .getAccelerators().entrySet())
283 overrideKeyBinding(acc);
289 * Overrides an AlignFrame key accelerator with our version which delegates to
290 * the action listener in whichever frame has the mouse (and does nothing if
295 private void overrideKeyBinding(Entry<KeyStroke, JMenuItem> acc)
297 final KeyStroke ks = acc.getKey();
298 InputMap inputMap = this.getInputMap(JComponent.WHEN_FOCUSED);
299 inputMap.put(ks, ks);
300 this.getActionMap().put(ks, new AbstractAction()
303 public void actionPerformed(ActionEvent e)
305 Component c = getFrameAtMouse();
306 if (c != null && c instanceof AlignFrame)
308 for (ActionListener a : ((AlignFrame) c).getAccelerators()
309 .get(ks).getActionListeners())
311 a.actionPerformed(null);
319 * Replace an accelerator key's action with the specified action.
323 protected void overrideKeyBinding(KeyStroke ks, AbstractAction action)
325 this.getActionMap().put(ks, action);
326 overrideMenuItem(ks, action);
330 * Create and link new views (with matching names) in both panes.
332 * Note this is _not_ multiple tabs, each hosting a split pane view, rather it
333 * is a single split pane with each split holding multiple tabs which are
336 * TODO implement instead with a tabbed holder in the SplitView, each tab
337 * holding a single JSplitPane. Would avoid a duplicated tab, at the cost of
338 * some additional coding.
340 protected void newView_actionPerformed()
342 AlignFrame topFrame = (AlignFrame) getTopFrame();
343 AlignFrame bottomFrame = (AlignFrame) getBottomFrame();
345 AlignmentPanel newTopPanel = topFrame.newView(null, true);
346 AlignmentPanel newBottomPanel = bottomFrame.newView(null, true);
349 * This currently (for the first new view only) leaves the top pane on tab 0
350 * but the bottom on tab 1. This results from 'setInitialTabVisible' echoing
351 * from the bottom back to the first frame. Next line is a fudge to work
352 * around this. TODO find a better way.
354 if (topFrame.getTabIndex() != bottomFrame.getTabIndex())
356 topFrame.setDisplayedView(newTopPanel);
359 newBottomPanel.av.viewName = newTopPanel.av.viewName;
360 newTopPanel.av.setCodingComplement(newBottomPanel.av);
362 final StructureSelectionManager ssm = StructureSelectionManager
363 .getStructureSelectionManager(Desktop.instance);
364 ssm.addCommandListener(newTopPanel.av);
365 ssm.addCommandListener(newBottomPanel.av);
369 * Close the currently selected view in both panes. If there is only one view,
370 * close this split frame.
372 protected void closeView_actionPerformed()
374 int viewCount = ((AlignFrame) getTopFrame()).getAlignPanels().size();
381 AlignmentPanel topPanel = ((AlignFrame) getTopFrame()).alignPanel;
382 AlignmentPanel bottomPanel = ((AlignFrame) getBottomFrame()).alignPanel;
384 ((AlignFrame) getTopFrame()).closeView(topPanel);
385 ((AlignFrame) getBottomFrame()).closeView(bottomPanel);
390 * Close child frames and this split frame.
394 ((AlignFrame) getTopFrame()).closeMenuItem_actionPerformed(true);
395 ((AlignFrame) getBottomFrame()).closeMenuItem_actionPerformed(true);
398 this.setClosed(true);
399 } catch (PropertyVetoException e)
406 * Replace AlignFrame 'expand views' action with SplitFrame version.
408 protected void overrideExpandViews()
410 KeyStroke key_X = KeyStroke.getKeyStroke(KeyEvent.VK_X, 0, false);
411 AbstractAction action = new AbstractAction()
414 public void actionPerformed(ActionEvent e)
416 expandViews_actionPerformed();
419 overrideMenuItem(key_X, action);
423 * Replace AlignFrame 'gather views' action with SplitFrame version.
425 protected void overrideGatherViews()
427 KeyStroke key_G = KeyStroke.getKeyStroke(KeyEvent.VK_G, 0, false);
428 AbstractAction action = new AbstractAction()
431 public void actionPerformed(ActionEvent e)
433 gatherViews_actionPerformed();
436 overrideMenuItem(key_G, action);
440 * Override the menu action associated with the keystroke in the child frames,
441 * replacing it with the given action.
446 private void overrideMenuItem(KeyStroke ks, AbstractAction action)
448 overrideMenuItem(ks, action, getTopFrame());
449 overrideMenuItem(ks, action, getBottomFrame());
453 * Override the menu action associated with the keystroke in one child frame,
454 * replacing it with the given action. Mwahahahaha.
460 private void overrideMenuItem(KeyStroke key, final AbstractAction action,
463 if (comp instanceof AlignFrame)
465 JMenuItem mi = ((AlignFrame) comp).getAccelerators().get(key);
468 for (ActionListener al : mi.getActionListeners())
470 mi.removeActionListener(al);
472 mi.addActionListener(new ActionListener()
475 public void actionPerformed(ActionEvent e)
477 action.actionPerformed(e);
485 * Expand any multiple views (which are always in pairs) into separate split
488 protected void expandViews_actionPerformed()
490 Desktop.instance.explodeViews(this);
494 * Gather any other SplitFrame views of this alignment back in as multiple
495 * (pairs of) views in this SplitFrame.
497 protected void gatherViews_actionPerformed()
499 Desktop.instance.gatherViews(this);