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 for good alignment.
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 * Set the character width for protein to 3 times that for dna.
120 boolean scaleThreeToOne = true; // TODO a new Preference option?
123 final AlignViewport topViewport = ((AlignFrame) getTopFrame()).viewport;
124 final AlignViewport bottomViewport = ((AlignFrame) getBottomFrame()).viewport;
125 final AlignmentI topAlignment = topViewport.getAlignment();
126 final AlignmentI bottomAlignment = bottomViewport.getAlignment();
127 AlignmentViewport cdna = topAlignment.isNucleotide() ? topViewport
128 : (bottomAlignment.isNucleotide() ? bottomViewport : null);
129 AlignmentViewport protein = !topAlignment.isNucleotide() ? topViewport
130 : (!bottomAlignment.isNucleotide() ? bottomViewport : null);
131 if (protein != null && cdna != null)
133 ViewStyleI vs = cdna.getViewStyle();
134 ViewStyleI vs2 = protein.getViewStyle();
135 vs2.setCharWidth(3 * vs.getCharWidth());
136 protein.setViewStyle(vs2);
142 * Add a listener to tidy up when the frame is closed.
144 protected void addCloseFrameListener()
146 addInternalFrameListener(new InternalFrameAdapter()
149 public void internalFrameClosed(InternalFrameEvent evt)
151 if (getTopFrame() instanceof AlignFrame)
153 ((AlignFrame) getTopFrame())
154 .closeMenuItem_actionPerformed(true);
156 if (getBottomFrame() instanceof AlignFrame)
158 ((AlignFrame) getBottomFrame())
159 .closeMenuItem_actionPerformed(true);
166 * Add a key listener that delegates to whichever split component the mouse is
167 * in (or does nothing if neither).
169 protected void addKeyListener()
171 addKeyListener(new KeyAdapter() {
174 public void keyPressed(KeyEvent e)
176 AlignFrame af = (AlignFrame) getFrameAtMouse();
179 * Intercept and override any keys here if wanted.
181 if (!overrideKey(e, af))
185 for (KeyListener kl : af.getKeyListeners())
194 public void keyReleased(KeyEvent e)
196 Component c = getFrameAtMouse();
199 for (KeyListener kl : c.getKeyListeners())
210 * Returns true if the key event is overriden and actioned (or ignored) here,
211 * else returns false, indicating it should be delegated to the AlignFrame's
214 * We can't handle Cmd-Key combinations here, instead this is done by
215 * overriding key bindings.
217 * @see addKeyOverrides
222 protected boolean overrideKey(KeyEvent e, AlignFrame af)
224 boolean actioned = false;
225 int keyCode = e.getKeyCode();
228 case KeyEvent.VK_DOWN:
229 if (e.isAltDown() || !af.viewport.cursorMode)
232 * Key down (or Alt-key-down in cursor mode) - move selected sequences
234 ((AlignFrame) getTopFrame()).moveSelectedSequences(false);
235 ((AlignFrame) getBottomFrame()).moveSelectedSequences(false);
241 if (e.isAltDown() || !af.viewport.cursorMode)
244 * Key up (or Alt-key-up in cursor mode) - move selected sequences
246 ((AlignFrame) getTopFrame()).moveSelectedSequences(true);
247 ((AlignFrame) getBottomFrame()).moveSelectedSequences(true);
257 * Set key bindings (recommended for Swing over key accelerators).
259 private void addKeyBindings()
261 overrideDelegatedKeyBindings();
263 overrideImplementedKeyBindings();
267 * Override key bindings with alternative action methods implemented in this
270 protected void overrideImplementedKeyBindings()
275 overrideExpandViews();
276 overrideGatherViews();
280 * Replace Cmd-W close view action with our version.
282 protected void overrideCloseView()
284 AbstractAction action;
286 * Ctrl-W / Cmd-W - close view or window
288 KeyStroke key_cmdW = KeyStroke.getKeyStroke(KeyEvent.VK_W, Toolkit
289 .getDefaultToolkit().getMenuShortcutKeyMask(), false);
290 action = new AbstractAction()
293 public void actionPerformed(ActionEvent e)
295 closeView_actionPerformed();
298 overrideKeyBinding(key_cmdW, action);
302 * Replace Cmd-T new view action with our version.
304 protected void overrideNewView()
307 * Ctrl-T / Cmd-T open new view
309 KeyStroke key_cmdT = KeyStroke.getKeyStroke(KeyEvent.VK_T, Toolkit
310 .getDefaultToolkit().getMenuShortcutKeyMask(), false);
311 AbstractAction action = new AbstractAction()
314 public void actionPerformed(ActionEvent e)
316 newView_actionPerformed();
319 overrideKeyBinding(key_cmdT, action);
323 * For now, delegates key events to the corresponding key accelerator for the
324 * AlignFrame that the mouse is in. Hopefully can be simplified in future if
325 * AlignFrame is changed to use key bindings rather than accelerators.
327 protected void overrideDelegatedKeyBindings()
329 if (getTopFrame() instanceof AlignFrame)
332 * Get all accelerator keys in the top frame (the bottom should be
333 * identical) and override each one.
335 for (Entry<KeyStroke, JMenuItem> acc : ((AlignFrame) getTopFrame())
336 .getAccelerators().entrySet())
338 overrideKeyBinding(acc);
344 * Overrides an AlignFrame key accelerator with our version which delegates to
345 * the action listener in whichever frame has the mouse (and does nothing if
350 private void overrideKeyBinding(Entry<KeyStroke, JMenuItem> acc)
352 final KeyStroke ks = acc.getKey();
353 InputMap inputMap = this.getInputMap(JComponent.WHEN_FOCUSED);
354 inputMap.put(ks, ks);
355 this.getActionMap().put(ks, new AbstractAction()
358 public void actionPerformed(ActionEvent e)
360 Component c = getFrameAtMouse();
361 if (c != null && c instanceof AlignFrame)
363 for (ActionListener a : ((AlignFrame) c).getAccelerators()
364 .get(ks).getActionListeners())
366 a.actionPerformed(null);
374 * Replace an accelerator key's action with the specified action.
378 protected void overrideKeyBinding(KeyStroke ks, AbstractAction action)
380 this.getActionMap().put(ks, action);
381 overrideMenuItem(ks, action);
385 * Create and link new views (with matching names) in both panes.
387 * Note this is _not_ multiple tabs, each hosting a split pane view, rather it
388 * is a single split pane with each split holding multiple tabs which are
391 * TODO implement instead with a tabbed holder in the SplitView, each tab
392 * holding a single JSplitPane. Would avoid a duplicated tab, at the cost of
393 * some additional coding.
395 protected void newView_actionPerformed()
397 AlignFrame topFrame = (AlignFrame) getTopFrame();
398 AlignFrame bottomFrame = (AlignFrame) getBottomFrame();
400 AlignmentPanel newTopPanel = topFrame.newView(null, true);
401 AlignmentPanel newBottomPanel = bottomFrame.newView(null, true);
404 * This currently (for the first new view only) leaves the top pane on tab 0
405 * but the bottom on tab 1. This results from 'setInitialTabVisible' echoing
406 * from the bottom back to the first frame. Next line is a fudge to work
407 * around this. TODO find a better way.
409 if (topFrame.getTabIndex() != bottomFrame.getTabIndex())
411 topFrame.setDisplayedView(newTopPanel);
414 newBottomPanel.av.viewName = newTopPanel.av.viewName;
415 newTopPanel.av.setCodingComplement(newBottomPanel.av);
417 final StructureSelectionManager ssm = StructureSelectionManager
418 .getStructureSelectionManager(Desktop.instance);
419 ssm.addCommandListener(newTopPanel.av);
420 ssm.addCommandListener(newBottomPanel.av);
424 * Close the currently selected view in both panes. If there is only one view,
425 * close this split frame.
427 protected void closeView_actionPerformed()
429 int viewCount = ((AlignFrame) getTopFrame()).getAlignPanels().size();
436 AlignmentPanel topPanel = ((AlignFrame) getTopFrame()).alignPanel;
437 AlignmentPanel bottomPanel = ((AlignFrame) getBottomFrame()).alignPanel;
439 ((AlignFrame) getTopFrame()).closeView(topPanel);
440 ((AlignFrame) getBottomFrame()).closeView(bottomPanel);
445 * Close child frames and this split frame.
449 ((AlignFrame) getTopFrame()).closeMenuItem_actionPerformed(true);
450 ((AlignFrame) getBottomFrame()).closeMenuItem_actionPerformed(true);
453 this.setClosed(true);
454 } catch (PropertyVetoException e)
461 * Replace AlignFrame 'expand views' action with SplitFrame version.
463 protected void overrideExpandViews()
465 KeyStroke key_X = KeyStroke.getKeyStroke(KeyEvent.VK_X, 0, false);
466 AbstractAction action = new AbstractAction()
469 public void actionPerformed(ActionEvent e)
471 expandViews_actionPerformed();
474 overrideMenuItem(key_X, action);
478 * Replace AlignFrame 'gather views' action with SplitFrame version.
480 protected void overrideGatherViews()
482 KeyStroke key_G = KeyStroke.getKeyStroke(KeyEvent.VK_G, 0, false);
483 AbstractAction action = new AbstractAction()
486 public void actionPerformed(ActionEvent e)
488 gatherViews_actionPerformed();
491 overrideMenuItem(key_G, action);
495 * Override the menu action associated with the keystroke in the child frames,
496 * replacing it with the given action.
501 private void overrideMenuItem(KeyStroke ks, AbstractAction action)
503 overrideMenuItem(ks, action, getTopFrame());
504 overrideMenuItem(ks, action, getBottomFrame());
508 * Override the menu action associated with the keystroke in one child frame,
509 * replacing it with the given action. Mwahahahaha.
515 private void overrideMenuItem(KeyStroke key, final AbstractAction action,
518 if (comp instanceof AlignFrame)
520 JMenuItem mi = ((AlignFrame) comp).getAccelerators().get(key);
523 for (ActionListener al : mi.getActionListeners())
525 mi.removeActionListener(al);
527 mi.addActionListener(new ActionListener()
530 public void actionPerformed(ActionEvent e)
532 action.actionPerformed(e);
540 * Expand any multiple views (which are always in pairs) into separate split
543 protected void expandViews_actionPerformed()
545 Desktop.instance.explodeViews(this);
549 * Gather any other SplitFrame views of this alignment back in as multiple
550 * (pairs of) views in this SplitFrame.
552 protected void gatherViews_actionPerformed()
554 Desktop.instance.gatherViews(this);
558 * Returns the alignment in the complementary frame to the one given.
561 public AlignmentI getComplement(Object alignFrame)
563 if (alignFrame == this.getTopFrame())
565 return ((AlignFrame) getBottomFrame()).viewport.getAlignment();
567 else if (alignFrame == this.getBottomFrame())
569 return ((AlignFrame) getTopFrame()).viewport.getAlignment();
575 * Returns the title of the complementary frame to the one given.
578 public String getComplementTitle(Object alignFrame)
580 if (alignFrame == this.getTopFrame())
582 return ((AlignFrame) getBottomFrame()).getTitle();
584 else if (alignFrame == this.getBottomFrame())
586 return ((AlignFrame) getTopFrame()).getTitle();
592 * Set the 'other half' to hidden / revealed.
595 public void setComplementVisible(Object alignFrame, boolean show)
598 * Hiding the AlignPanel suppresses unnecessary repaints
600 if (alignFrame == getTopFrame())
602 ((AlignFrame) getBottomFrame()).alignPanel.setVisible(show);
604 else if (alignFrame == getBottomFrame())
606 ((AlignFrame) getTopFrame()).alignPanel.setVisible(show);
608 super.setComplementVisible(alignFrame, show);
612 * Replace Cmd-F Find action with our version. This is necessary because the
613 * 'default' Finder searches in the first AlignFrame it finds. We need it to
614 * search in the half of the SplitFrame that has the mouse.
616 protected void overrideFind()
619 * Ctrl-F / Cmd-F open Finder dialog, 'focused' on the right alignment
621 KeyStroke key_cmdF = KeyStroke.getKeyStroke(KeyEvent.VK_F, Toolkit
622 .getDefaultToolkit().getMenuShortcutKeyMask(), false);
623 AbstractAction action = new AbstractAction()
626 public void actionPerformed(ActionEvent e)
628 Component c = getFrameAtMouse();
629 if (c != null && c instanceof AlignFrame)
631 AlignFrame af = (AlignFrame) c;
632 new Finder(af.viewport, af.alignPanel);
636 overrideKeyBinding(key_cmdF, action);