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 * Do any tweaking and twerking of the layout wanted.
69 private void adjustLayout()
72 * Ensure sequence ids are the same width for good alignment.
74 int w1 = ((AlignFrame) getTopFrame()).getViewport().getIdWidth();
75 int w2 = ((AlignFrame) getBottomFrame()).getViewport().getIdWidth();
76 int w3 = Math.max(w1, w2);
77 ((AlignFrame) getTopFrame()).getViewport().setIdWidth(w3);
78 ((AlignFrame) getBottomFrame()).getViewport().setIdWidth(w3);
81 * Set the character width for protein to 3 times that for dna.
83 final AlignViewport topViewport = ((AlignFrame) getTopFrame()).viewport;
84 final AlignViewport bottomViewport = ((AlignFrame) getBottomFrame()).viewport;
85 final AlignmentI topAlignment = topViewport.getAlignment();
86 final AlignmentI bottomAlignment = bottomViewport.getAlignment();
87 AlignmentViewport cdna = topAlignment.isNucleotide() ? topViewport
88 : (bottomAlignment.isNucleotide() ? bottomViewport : null);
89 AlignmentViewport protein = !topAlignment.isNucleotide() ? topViewport
90 : (!bottomAlignment.isNucleotide() ? bottomViewport : null);
91 if (protein != null && cdna != null)
93 ViewStyleI vs = cdna.getViewStyle();
94 vs.setCharWidth(3 * vs.getCharWidth());
95 protein.setViewStyle(vs);
100 * Add a listener to tidy up when the frame is closed.
102 protected void addCloseFrameListener()
104 addInternalFrameListener(new InternalFrameAdapter()
107 public void internalFrameClosed(InternalFrameEvent evt)
109 if (getTopFrame() instanceof AlignFrame)
111 ((AlignFrame) getTopFrame())
112 .closeMenuItem_actionPerformed(true);
114 if (getBottomFrame() instanceof AlignFrame)
116 ((AlignFrame) getBottomFrame())
117 .closeMenuItem_actionPerformed(true);
124 * Add a key listener that delegates to whichever split component the mouse is
125 * in (or does nothing if neither).
127 protected void addKeyListener()
129 addKeyListener(new KeyAdapter() {
132 public void keyPressed(KeyEvent e)
134 AlignFrame af = (AlignFrame) getFrameAtMouse();
137 * Intercept and override any keys here if wanted.
139 if (!overrideKey(e, af))
143 for (KeyListener kl : af.getKeyListeners())
152 public void keyReleased(KeyEvent e)
154 Component c = getFrameAtMouse();
157 for (KeyListener kl : c.getKeyListeners())
168 * Returns true if the key event is overriden and actioned (or ignored) here,
169 * else returns false, indicating it should be delegated to the AlignFrame's
172 * We can't handle Cmd-Key combinations here, instead this is done by
173 * overriding key bindings.
175 * @see addKeyOverrides
180 protected boolean overrideKey(KeyEvent e, AlignFrame af)
182 boolean actioned = false;
183 int keyCode = e.getKeyCode();
186 case KeyEvent.VK_DOWN:
187 if (e.isAltDown() || !af.viewport.cursorMode)
190 * Key down (or Alt-key-down in cursor mode) - move selected sequences
192 ((AlignFrame) getTopFrame()).moveSelectedSequences(false);
193 ((AlignFrame) getBottomFrame()).moveSelectedSequences(false);
199 if (e.isAltDown() || !af.viewport.cursorMode)
202 * Key up (or Alt-key-up in cursor mode) - move selected sequences
204 ((AlignFrame) getTopFrame()).moveSelectedSequences(true);
205 ((AlignFrame) getBottomFrame()).moveSelectedSequences(true);
215 * Set key bindings (recommended for Swing over key accelerators).
217 private void addKeyBindings()
219 overrideDelegatedKeyBindings();
221 overrideImplementedKeyBindings();
225 * Override key bindings with alternative action methods implemented in this
228 protected void overrideImplementedKeyBindings()
232 overrideExpandViews();
233 overrideGatherViews();
237 * Replace Cmd-W close view action with our version.
239 protected void overrideCloseView()
241 AbstractAction action;
243 * Ctrl-W / Cmd-W - close view or window
245 KeyStroke key_cmdW = KeyStroke.getKeyStroke(KeyEvent.VK_W, Toolkit
246 .getDefaultToolkit().getMenuShortcutKeyMask(), false);
247 action = new AbstractAction()
250 public void actionPerformed(ActionEvent e)
252 closeView_actionPerformed();
255 overrideKeyBinding(key_cmdW, action);
259 * Replace Cmd-T new view action with our version.
261 protected void overrideNewView()
264 * Ctrl-T / Cmd-T open new view
266 KeyStroke key_cmdT = KeyStroke.getKeyStroke(KeyEvent.VK_T, Toolkit
267 .getDefaultToolkit().getMenuShortcutKeyMask(), false);
268 AbstractAction action = new AbstractAction()
271 public void actionPerformed(ActionEvent e)
273 newView_actionPerformed();
276 overrideKeyBinding(key_cmdT, action);
280 * For now, delegates key events to the corresponding key accelerator for the
281 * AlignFrame that the mouse is in. Hopefully can be simplified in future if
282 * AlignFrame is changed to use key bindings rather than accelerators.
284 protected void overrideDelegatedKeyBindings()
286 if (getTopFrame() instanceof AlignFrame)
289 * Get all accelerator keys in the top frame (the bottom should be
290 * identical) and override each one.
292 for (Entry<KeyStroke, JMenuItem> acc : ((AlignFrame) getTopFrame())
293 .getAccelerators().entrySet())
295 overrideKeyBinding(acc);
301 * Overrides an AlignFrame key accelerator with our version which delegates to
302 * the action listener in whichever frame has the mouse (and does nothing if
307 private void overrideKeyBinding(Entry<KeyStroke, JMenuItem> acc)
309 final KeyStroke ks = acc.getKey();
310 InputMap inputMap = this.getInputMap(JComponent.WHEN_FOCUSED);
311 inputMap.put(ks, ks);
312 this.getActionMap().put(ks, new AbstractAction()
315 public void actionPerformed(ActionEvent e)
317 Component c = getFrameAtMouse();
318 if (c != null && c instanceof AlignFrame)
320 for (ActionListener a : ((AlignFrame) c).getAccelerators()
321 .get(ks).getActionListeners())
323 a.actionPerformed(null);
331 * Replace an accelerator key's action with the specified action.
335 protected void overrideKeyBinding(KeyStroke ks, AbstractAction action)
337 this.getActionMap().put(ks, action);
338 overrideMenuItem(ks, action);
342 * Create and link new views (with matching names) in both panes.
344 * Note this is _not_ multiple tabs, each hosting a split pane view, rather it
345 * is a single split pane with each split holding multiple tabs which are
348 * TODO implement instead with a tabbed holder in the SplitView, each tab
349 * holding a single JSplitPane. Would avoid a duplicated tab, at the cost of
350 * some additional coding.
352 protected void newView_actionPerformed()
354 AlignFrame topFrame = (AlignFrame) getTopFrame();
355 AlignFrame bottomFrame = (AlignFrame) getBottomFrame();
357 AlignmentPanel newTopPanel = topFrame.newView(null, true);
358 AlignmentPanel newBottomPanel = bottomFrame.newView(null, true);
361 * This currently (for the first new view only) leaves the top pane on tab 0
362 * but the bottom on tab 1. This results from 'setInitialTabVisible' echoing
363 * from the bottom back to the first frame. Next line is a fudge to work
364 * around this. TODO find a better way.
366 if (topFrame.getTabIndex() != bottomFrame.getTabIndex())
368 topFrame.setDisplayedView(newTopPanel);
371 newBottomPanel.av.viewName = newTopPanel.av.viewName;
372 newTopPanel.av.setCodingComplement(newBottomPanel.av);
374 final StructureSelectionManager ssm = StructureSelectionManager
375 .getStructureSelectionManager(Desktop.instance);
376 ssm.addCommandListener(newTopPanel.av);
377 ssm.addCommandListener(newBottomPanel.av);
381 * Close the currently selected view in both panes. If there is only one view,
382 * close this split frame.
384 protected void closeView_actionPerformed()
386 int viewCount = ((AlignFrame) getTopFrame()).getAlignPanels().size();
393 AlignmentPanel topPanel = ((AlignFrame) getTopFrame()).alignPanel;
394 AlignmentPanel bottomPanel = ((AlignFrame) getBottomFrame()).alignPanel;
396 ((AlignFrame) getTopFrame()).closeView(topPanel);
397 ((AlignFrame) getBottomFrame()).closeView(bottomPanel);
402 * Close child frames and this split frame.
406 ((AlignFrame) getTopFrame()).closeMenuItem_actionPerformed(true);
407 ((AlignFrame) getBottomFrame()).closeMenuItem_actionPerformed(true);
410 this.setClosed(true);
411 } catch (PropertyVetoException e)
418 * Replace AlignFrame 'expand views' action with SplitFrame version.
420 protected void overrideExpandViews()
422 KeyStroke key_X = KeyStroke.getKeyStroke(KeyEvent.VK_X, 0, false);
423 AbstractAction action = new AbstractAction()
426 public void actionPerformed(ActionEvent e)
428 expandViews_actionPerformed();
431 overrideMenuItem(key_X, action);
435 * Replace AlignFrame 'gather views' action with SplitFrame version.
437 protected void overrideGatherViews()
439 KeyStroke key_G = KeyStroke.getKeyStroke(KeyEvent.VK_G, 0, false);
440 AbstractAction action = new AbstractAction()
443 public void actionPerformed(ActionEvent e)
445 gatherViews_actionPerformed();
448 overrideMenuItem(key_G, action);
452 * Override the menu action associated with the keystroke in the child frames,
453 * replacing it with the given action.
458 private void overrideMenuItem(KeyStroke ks, AbstractAction action)
460 overrideMenuItem(ks, action, getTopFrame());
461 overrideMenuItem(ks, action, getBottomFrame());
465 * Override the menu action associated with the keystroke in one child frame,
466 * replacing it with the given action. Mwahahahaha.
472 private void overrideMenuItem(KeyStroke key, final AbstractAction action,
475 if (comp instanceof AlignFrame)
477 JMenuItem mi = ((AlignFrame) comp).getAccelerators().get(key);
480 for (ActionListener al : mi.getActionListeners())
482 mi.removeActionListener(al);
484 mi.addActionListener(new ActionListener()
487 public void actionPerformed(ActionEvent e)
489 action.actionPerformed(e);
497 * Expand any multiple views (which are always in pairs) into separate split
500 protected void expandViews_actionPerformed()
502 Desktop.instance.explodeViews(this);
506 * Gather any other SplitFrame views of this alignment back in as multiple
507 * (pairs of) views in this SplitFrame.
509 protected void gatherViews_actionPerformed()
511 Desktop.instance.gatherViews(this);