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 public 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 boolean scaleThreeToOne = true; // TODO a new Preference option?
117 final AlignViewport topViewport = ((AlignFrame) getTopFrame()).viewport;
118 final AlignViewport bottomViewport = ((AlignFrame) getBottomFrame()).viewport;
119 final AlignmentI topAlignment = topViewport.getAlignment();
120 final AlignmentI bottomAlignment = bottomViewport.getAlignment();
121 AlignmentViewport cdna = topAlignment.isNucleotide() ? topViewport
122 : (bottomAlignment.isNucleotide() ? bottomViewport : null);
123 AlignmentViewport protein = !topAlignment.isNucleotide() ? topViewport
124 : (!bottomAlignment.isNucleotide() ? bottomViewport : null);
125 if (protein != null && cdna != null)
127 ViewStyleI vs = cdna.getViewStyle();
128 ViewStyleI vs2 = protein.getViewStyle();
129 vs2.setCharWidth(3 * vs.getCharWidth());
130 protein.setViewStyle(vs2);
136 * Add a listener to tidy up when the frame is closed.
138 protected void addCloseFrameListener()
140 addInternalFrameListener(new InternalFrameAdapter()
143 public void internalFrameClosed(InternalFrameEvent evt)
145 if (getTopFrame() instanceof AlignFrame)
147 ((AlignFrame) getTopFrame())
148 .closeMenuItem_actionPerformed(true);
150 if (getBottomFrame() instanceof AlignFrame)
152 ((AlignFrame) getBottomFrame())
153 .closeMenuItem_actionPerformed(true);
160 * Add a key listener that delegates to whichever split component the mouse is
161 * in (or does nothing if neither).
163 protected void addKeyListener()
165 addKeyListener(new KeyAdapter() {
168 public void keyPressed(KeyEvent e)
170 AlignFrame af = (AlignFrame) getFrameAtMouse();
173 * Intercept and override any keys here if wanted.
175 if (!overrideKey(e, af))
179 for (KeyListener kl : af.getKeyListeners())
188 public void keyReleased(KeyEvent e)
190 Component c = getFrameAtMouse();
193 for (KeyListener kl : c.getKeyListeners())
204 * Returns true if the key event is overriden and actioned (or ignored) here,
205 * else returns false, indicating it should be delegated to the AlignFrame's
208 * We can't handle Cmd-Key combinations here, instead this is done by
209 * overriding key bindings.
211 * @see addKeyOverrides
216 protected boolean overrideKey(KeyEvent e, AlignFrame af)
218 boolean actioned = false;
219 int keyCode = e.getKeyCode();
222 case KeyEvent.VK_DOWN:
223 if (e.isAltDown() || !af.viewport.cursorMode)
226 * Key down (or Alt-key-down in cursor mode) - move selected sequences
228 ((AlignFrame) getTopFrame()).moveSelectedSequences(false);
229 ((AlignFrame) getBottomFrame()).moveSelectedSequences(false);
235 if (e.isAltDown() || !af.viewport.cursorMode)
238 * Key up (or Alt-key-up in cursor mode) - move selected sequences
240 ((AlignFrame) getTopFrame()).moveSelectedSequences(true);
241 ((AlignFrame) getBottomFrame()).moveSelectedSequences(true);
251 * Set key bindings (recommended for Swing over key accelerators).
253 private void addKeyBindings()
255 overrideDelegatedKeyBindings();
257 overrideImplementedKeyBindings();
261 * Override key bindings with alternative action methods implemented in this
264 protected void overrideImplementedKeyBindings()
268 overrideExpandViews();
269 overrideGatherViews();
273 * Replace Cmd-W close view action with our version.
275 protected void overrideCloseView()
277 AbstractAction action;
279 * Ctrl-W / Cmd-W - close view or window
281 KeyStroke key_cmdW = KeyStroke.getKeyStroke(KeyEvent.VK_W, Toolkit
282 .getDefaultToolkit().getMenuShortcutKeyMask(), false);
283 action = new AbstractAction()
286 public void actionPerformed(ActionEvent e)
288 closeView_actionPerformed();
291 overrideKeyBinding(key_cmdW, action);
295 * Replace Cmd-T new view action with our version.
297 protected void overrideNewView()
300 * Ctrl-T / Cmd-T open new view
302 KeyStroke key_cmdT = KeyStroke.getKeyStroke(KeyEvent.VK_T, Toolkit
303 .getDefaultToolkit().getMenuShortcutKeyMask(), false);
304 AbstractAction action = new AbstractAction()
307 public void actionPerformed(ActionEvent e)
309 newView_actionPerformed();
312 overrideKeyBinding(key_cmdT, action);
316 * For now, delegates key events to the corresponding key accelerator for the
317 * AlignFrame that the mouse is in. Hopefully can be simplified in future if
318 * AlignFrame is changed to use key bindings rather than accelerators.
320 protected void overrideDelegatedKeyBindings()
322 if (getTopFrame() instanceof AlignFrame)
325 * Get all accelerator keys in the top frame (the bottom should be
326 * identical) and override each one.
328 for (Entry<KeyStroke, JMenuItem> acc : ((AlignFrame) getTopFrame())
329 .getAccelerators().entrySet())
331 overrideKeyBinding(acc);
337 * Overrides an AlignFrame key accelerator with our version which delegates to
338 * the action listener in whichever frame has the mouse (and does nothing if
343 private void overrideKeyBinding(Entry<KeyStroke, JMenuItem> acc)
345 final KeyStroke ks = acc.getKey();
346 InputMap inputMap = this.getInputMap(JComponent.WHEN_FOCUSED);
347 inputMap.put(ks, ks);
348 this.getActionMap().put(ks, new AbstractAction()
351 public void actionPerformed(ActionEvent e)
353 Component c = getFrameAtMouse();
354 if (c != null && c instanceof AlignFrame)
356 for (ActionListener a : ((AlignFrame) c).getAccelerators()
357 .get(ks).getActionListeners())
359 a.actionPerformed(null);
367 * Replace an accelerator key's action with the specified action.
371 protected void overrideKeyBinding(KeyStroke ks, AbstractAction action)
373 this.getActionMap().put(ks, action);
374 overrideMenuItem(ks, action);
378 * Create and link new views (with matching names) in both panes.
380 * Note this is _not_ multiple tabs, each hosting a split pane view, rather it
381 * is a single split pane with each split holding multiple tabs which are
384 * TODO implement instead with a tabbed holder in the SplitView, each tab
385 * holding a single JSplitPane. Would avoid a duplicated tab, at the cost of
386 * some additional coding.
388 protected void newView_actionPerformed()
390 AlignFrame topFrame = (AlignFrame) getTopFrame();
391 AlignFrame bottomFrame = (AlignFrame) getBottomFrame();
393 AlignmentPanel newTopPanel = topFrame.newView(null, true);
394 AlignmentPanel newBottomPanel = bottomFrame.newView(null, true);
397 * This currently (for the first new view only) leaves the top pane on tab 0
398 * but the bottom on tab 1. This results from 'setInitialTabVisible' echoing
399 * from the bottom back to the first frame. Next line is a fudge to work
400 * around this. TODO find a better way.
402 if (topFrame.getTabIndex() != bottomFrame.getTabIndex())
404 topFrame.setDisplayedView(newTopPanel);
407 newBottomPanel.av.viewName = newTopPanel.av.viewName;
408 newTopPanel.av.setCodingComplement(newBottomPanel.av);
410 final StructureSelectionManager ssm = StructureSelectionManager
411 .getStructureSelectionManager(Desktop.instance);
412 ssm.addCommandListener(newTopPanel.av);
413 ssm.addCommandListener(newBottomPanel.av);
417 * Close the currently selected view in both panes. If there is only one view,
418 * close this split frame.
420 protected void closeView_actionPerformed()
422 int viewCount = ((AlignFrame) getTopFrame()).getAlignPanels().size();
429 AlignmentPanel topPanel = ((AlignFrame) getTopFrame()).alignPanel;
430 AlignmentPanel bottomPanel = ((AlignFrame) getBottomFrame()).alignPanel;
432 ((AlignFrame) getTopFrame()).closeView(topPanel);
433 ((AlignFrame) getBottomFrame()).closeView(bottomPanel);
438 * Close child frames and this split frame.
442 ((AlignFrame) getTopFrame()).closeMenuItem_actionPerformed(true);
443 ((AlignFrame) getBottomFrame()).closeMenuItem_actionPerformed(true);
446 this.setClosed(true);
447 } catch (PropertyVetoException e)
454 * Replace AlignFrame 'expand views' action with SplitFrame version.
456 protected void overrideExpandViews()
458 KeyStroke key_X = KeyStroke.getKeyStroke(KeyEvent.VK_X, 0, false);
459 AbstractAction action = new AbstractAction()
462 public void actionPerformed(ActionEvent e)
464 expandViews_actionPerformed();
467 overrideMenuItem(key_X, action);
471 * Replace AlignFrame 'gather views' action with SplitFrame version.
473 protected void overrideGatherViews()
475 KeyStroke key_G = KeyStroke.getKeyStroke(KeyEvent.VK_G, 0, false);
476 AbstractAction action = new AbstractAction()
479 public void actionPerformed(ActionEvent e)
481 gatherViews_actionPerformed();
484 overrideMenuItem(key_G, action);
488 * Override the menu action associated with the keystroke in the child frames,
489 * replacing it with the given action.
494 private void overrideMenuItem(KeyStroke ks, AbstractAction action)
496 overrideMenuItem(ks, action, getTopFrame());
497 overrideMenuItem(ks, action, getBottomFrame());
501 * Override the menu action associated with the keystroke in one child frame,
502 * replacing it with the given action. Mwahahahaha.
508 private void overrideMenuItem(KeyStroke key, final AbstractAction action,
511 if (comp instanceof AlignFrame)
513 JMenuItem mi = ((AlignFrame) comp).getAccelerators().get(key);
516 for (ActionListener al : mi.getActionListeners())
518 mi.removeActionListener(al);
520 mi.addActionListener(new ActionListener()
523 public void actionPerformed(ActionEvent e)
525 action.actionPerformed(e);
533 * Expand any multiple views (which are always in pairs) into separate split
536 protected void expandViews_actionPerformed()
538 Desktop.instance.explodeViews(this);
542 * Gather any other SplitFrame views of this alignment back in as multiple
543 * (pairs of) views in this SplitFrame.
545 protected void gatherViews_actionPerformed()
547 Desktop.instance.gatherViews(this);
551 * Returns the alignment in the complementary frame to the one given.
554 public AlignmentI getComplement(Object alignFrame)
556 if (alignFrame == this.getTopFrame())
558 return ((AlignFrame) getBottomFrame()).viewport.getAlignment();
560 else if (alignFrame == this.getBottomFrame())
562 return ((AlignFrame) getTopFrame()).viewport.getAlignment();
568 * Returns the title of the complementary frame to the one given.
571 public String getComplementTitle(Object alignFrame)
573 if (alignFrame == this.getTopFrame())
575 return ((AlignFrame) getBottomFrame()).getTitle();
577 else if (alignFrame == this.getBottomFrame())
579 return ((AlignFrame) getTopFrame()).getTitle();
585 * Set the 'other half' to hidden / revealed.
588 public void setComplementVisible(Object alignFrame, boolean show)
591 * Hiding the AlignPanel suppresses unnecessary repaints
593 if (alignFrame == getTopFrame())
595 ((AlignFrame) getBottomFrame()).alignPanel.setVisible(show);
597 else if (alignFrame == getBottomFrame())
599 ((AlignFrame) getTopFrame()).alignPanel.setVisible(show);
601 super.setComplementVisible(alignFrame, show);