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 vs.setCharWidth(3 * vs.getCharWidth());
129 protein.setViewStyle(vs);
135 * Add a listener to tidy up when the frame is closed.
137 protected void addCloseFrameListener()
139 addInternalFrameListener(new InternalFrameAdapter()
142 public void internalFrameClosed(InternalFrameEvent evt)
144 if (getTopFrame() instanceof AlignFrame)
146 ((AlignFrame) getTopFrame())
147 .closeMenuItem_actionPerformed(true);
149 if (getBottomFrame() instanceof AlignFrame)
151 ((AlignFrame) getBottomFrame())
152 .closeMenuItem_actionPerformed(true);
159 * Add a key listener that delegates to whichever split component the mouse is
160 * in (or does nothing if neither).
162 protected void addKeyListener()
164 addKeyListener(new KeyAdapter() {
167 public void keyPressed(KeyEvent e)
169 AlignFrame af = (AlignFrame) getFrameAtMouse();
172 * Intercept and override any keys here if wanted.
174 if (!overrideKey(e, af))
178 for (KeyListener kl : af.getKeyListeners())
187 public void keyReleased(KeyEvent e)
189 Component c = getFrameAtMouse();
192 for (KeyListener kl : c.getKeyListeners())
203 * Returns true if the key event is overriden and actioned (or ignored) here,
204 * else returns false, indicating it should be delegated to the AlignFrame's
207 * We can't handle Cmd-Key combinations here, instead this is done by
208 * overriding key bindings.
210 * @see addKeyOverrides
215 protected boolean overrideKey(KeyEvent e, AlignFrame af)
217 boolean actioned = false;
218 int keyCode = e.getKeyCode();
221 case KeyEvent.VK_DOWN:
222 if (e.isAltDown() || !af.viewport.cursorMode)
225 * Key down (or Alt-key-down in cursor mode) - move selected sequences
227 ((AlignFrame) getTopFrame()).moveSelectedSequences(false);
228 ((AlignFrame) getBottomFrame()).moveSelectedSequences(false);
234 if (e.isAltDown() || !af.viewport.cursorMode)
237 * Key up (or Alt-key-up in cursor mode) - move selected sequences
239 ((AlignFrame) getTopFrame()).moveSelectedSequences(true);
240 ((AlignFrame) getBottomFrame()).moveSelectedSequences(true);
250 * Set key bindings (recommended for Swing over key accelerators).
252 private void addKeyBindings()
254 overrideDelegatedKeyBindings();
256 overrideImplementedKeyBindings();
260 * Override key bindings with alternative action methods implemented in this
263 protected void overrideImplementedKeyBindings()
267 overrideExpandViews();
268 overrideGatherViews();
272 * Replace Cmd-W close view action with our version.
274 protected void overrideCloseView()
276 AbstractAction action;
278 * Ctrl-W / Cmd-W - close view or window
280 KeyStroke key_cmdW = KeyStroke.getKeyStroke(KeyEvent.VK_W, Toolkit
281 .getDefaultToolkit().getMenuShortcutKeyMask(), false);
282 action = new AbstractAction()
285 public void actionPerformed(ActionEvent e)
287 closeView_actionPerformed();
290 overrideKeyBinding(key_cmdW, action);
294 * Replace Cmd-T new view action with our version.
296 protected void overrideNewView()
299 * Ctrl-T / Cmd-T open new view
301 KeyStroke key_cmdT = KeyStroke.getKeyStroke(KeyEvent.VK_T, Toolkit
302 .getDefaultToolkit().getMenuShortcutKeyMask(), false);
303 AbstractAction action = new AbstractAction()
306 public void actionPerformed(ActionEvent e)
308 newView_actionPerformed();
311 overrideKeyBinding(key_cmdT, action);
315 * For now, delegates key events to the corresponding key accelerator for the
316 * AlignFrame that the mouse is in. Hopefully can be simplified in future if
317 * AlignFrame is changed to use key bindings rather than accelerators.
319 protected void overrideDelegatedKeyBindings()
321 if (getTopFrame() instanceof AlignFrame)
324 * Get all accelerator keys in the top frame (the bottom should be
325 * identical) and override each one.
327 for (Entry<KeyStroke, JMenuItem> acc : ((AlignFrame) getTopFrame())
328 .getAccelerators().entrySet())
330 overrideKeyBinding(acc);
336 * Overrides an AlignFrame key accelerator with our version which delegates to
337 * the action listener in whichever frame has the mouse (and does nothing if
342 private void overrideKeyBinding(Entry<KeyStroke, JMenuItem> acc)
344 final KeyStroke ks = acc.getKey();
345 InputMap inputMap = this.getInputMap(JComponent.WHEN_FOCUSED);
346 inputMap.put(ks, ks);
347 this.getActionMap().put(ks, new AbstractAction()
350 public void actionPerformed(ActionEvent e)
352 Component c = getFrameAtMouse();
353 if (c != null && c instanceof AlignFrame)
355 for (ActionListener a : ((AlignFrame) c).getAccelerators()
356 .get(ks).getActionListeners())
358 a.actionPerformed(null);
366 * Replace an accelerator key's action with the specified action.
370 protected void overrideKeyBinding(KeyStroke ks, AbstractAction action)
372 this.getActionMap().put(ks, action);
373 overrideMenuItem(ks, action);
377 * Create and link new views (with matching names) in both panes.
379 * Note this is _not_ multiple tabs, each hosting a split pane view, rather it
380 * is a single split pane with each split holding multiple tabs which are
383 * TODO implement instead with a tabbed holder in the SplitView, each tab
384 * holding a single JSplitPane. Would avoid a duplicated tab, at the cost of
385 * some additional coding.
387 protected void newView_actionPerformed()
389 AlignFrame topFrame = (AlignFrame) getTopFrame();
390 AlignFrame bottomFrame = (AlignFrame) getBottomFrame();
392 AlignmentPanel newTopPanel = topFrame.newView(null, true);
393 AlignmentPanel newBottomPanel = bottomFrame.newView(null, true);
396 * This currently (for the first new view only) leaves the top pane on tab 0
397 * but the bottom on tab 1. This results from 'setInitialTabVisible' echoing
398 * from the bottom back to the first frame. Next line is a fudge to work
399 * around this. TODO find a better way.
401 if (topFrame.getTabIndex() != bottomFrame.getTabIndex())
403 topFrame.setDisplayedView(newTopPanel);
406 newBottomPanel.av.viewName = newTopPanel.av.viewName;
407 newTopPanel.av.setCodingComplement(newBottomPanel.av);
409 final StructureSelectionManager ssm = StructureSelectionManager
410 .getStructureSelectionManager(Desktop.instance);
411 ssm.addCommandListener(newTopPanel.av);
412 ssm.addCommandListener(newBottomPanel.av);
416 * Close the currently selected view in both panes. If there is only one view,
417 * close this split frame.
419 protected void closeView_actionPerformed()
421 int viewCount = ((AlignFrame) getTopFrame()).getAlignPanels().size();
428 AlignmentPanel topPanel = ((AlignFrame) getTopFrame()).alignPanel;
429 AlignmentPanel bottomPanel = ((AlignFrame) getBottomFrame()).alignPanel;
431 ((AlignFrame) getTopFrame()).closeView(topPanel);
432 ((AlignFrame) getBottomFrame()).closeView(bottomPanel);
437 * Close child frames and this split frame.
441 ((AlignFrame) getTopFrame()).closeMenuItem_actionPerformed(true);
442 ((AlignFrame) getBottomFrame()).closeMenuItem_actionPerformed(true);
445 this.setClosed(true);
446 } catch (PropertyVetoException e)
453 * Replace AlignFrame 'expand views' action with SplitFrame version.
455 protected void overrideExpandViews()
457 KeyStroke key_X = KeyStroke.getKeyStroke(KeyEvent.VK_X, 0, false);
458 AbstractAction action = new AbstractAction()
461 public void actionPerformed(ActionEvent e)
463 expandViews_actionPerformed();
466 overrideMenuItem(key_X, action);
470 * Replace AlignFrame 'gather views' action with SplitFrame version.
472 protected void overrideGatherViews()
474 KeyStroke key_G = KeyStroke.getKeyStroke(KeyEvent.VK_G, 0, false);
475 AbstractAction action = new AbstractAction()
478 public void actionPerformed(ActionEvent e)
480 gatherViews_actionPerformed();
483 overrideMenuItem(key_G, action);
487 * Override the menu action associated with the keystroke in the child frames,
488 * replacing it with the given action.
493 private void overrideMenuItem(KeyStroke ks, AbstractAction action)
495 overrideMenuItem(ks, action, getTopFrame());
496 overrideMenuItem(ks, action, getBottomFrame());
500 * Override the menu action associated with the keystroke in one child frame,
501 * replacing it with the given action. Mwahahahaha.
507 private void overrideMenuItem(KeyStroke key, final AbstractAction action,
510 if (comp instanceof AlignFrame)
512 JMenuItem mi = ((AlignFrame) comp).getAccelerators().get(key);
515 for (ActionListener al : mi.getActionListeners())
517 mi.removeActionListener(al);
519 mi.addActionListener(new ActionListener()
522 public void actionPerformed(ActionEvent e)
524 action.actionPerformed(e);
532 * Expand any multiple views (which are always in pairs) into separate split
535 protected void expandViews_actionPerformed()
537 Desktop.instance.explodeViews(this);
541 * Gather any other SplitFrame views of this alignment back in as multiple
542 * (pairs of) views in this SplitFrame.
544 protected void gatherViews_actionPerformed()
546 Desktop.instance.gatherViews(this);
550 * Returns the alignment in the complementary frame to the one given.
553 public AlignmentI getComplement(Object alignFrame)
555 if (alignFrame == this.getTopFrame())
557 return ((AlignFrame) getBottomFrame()).viewport.getAlignment();
559 else if (alignFrame == this.getBottomFrame())
561 return ((AlignFrame) getTopFrame()).viewport.getAlignment();
567 * Returns the title of the complementary frame to the one given.
570 public String getComplementTitle(Object alignFrame)
572 if (alignFrame == this.getTopFrame())
574 return ((AlignFrame) getBottomFrame()).getTitle();
576 else if (alignFrame == this.getBottomFrame())
578 return ((AlignFrame) getTopFrame()).getTitle();
584 * Set the 'other half' to hidden / revealed.
587 public void setComplementVisible(Object alignFrame, boolean show)
590 * Hiding the AlignPanel suppresses unnecessary repaints
592 if (alignFrame == getTopFrame())
594 ((AlignFrame) getBottomFrame()).alignPanel.setVisible(show);
596 else if (alignFrame == getBottomFrame())
598 ((AlignFrame) getTopFrame()).alignPanel.setVisible(show);
600 super.setComplementVisible(alignFrame, show);