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 // about 65 pixels for Desktop decorators on Windows
69 height = Math.min(height, Desktop.instance.getHeight() - 65);
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 so sequences line up
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 * Scale protein to either 1 or 3 times character width of dna
120 final AlignViewport topViewport = ((AlignFrame) getTopFrame()).viewport;
121 final AlignViewport bottomViewport = ((AlignFrame) getBottomFrame()).viewport;
122 final AlignmentI topAlignment = topViewport.getAlignment();
123 final AlignmentI bottomAlignment = bottomViewport.getAlignment();
124 AlignmentViewport cdna = topAlignment.isNucleotide() ? topViewport
125 : (bottomAlignment.isNucleotide() ? bottomViewport : null);
126 AlignmentViewport protein = !topAlignment.isNucleotide() ? topViewport
127 : (!bottomAlignment.isNucleotide() ? bottomViewport : null);
128 if (protein != null && cdna != null)
130 ViewStyleI vs = protein.getViewStyle();
131 int scale = vs.isScaleProteinAsCdna() ? 3 : 1;
132 vs.setCharWidth(scale * cdna.getViewStyle().getCharWidth());
133 protein.setViewStyle(vs);
138 * Add a listener to tidy up when the frame is closed.
140 protected void addCloseFrameListener()
142 addInternalFrameListener(new InternalFrameAdapter()
145 public void internalFrameClosed(InternalFrameEvent evt)
147 if (getTopFrame() instanceof AlignFrame)
149 ((AlignFrame) getTopFrame())
150 .closeMenuItem_actionPerformed(true);
152 if (getBottomFrame() instanceof AlignFrame)
154 ((AlignFrame) getBottomFrame())
155 .closeMenuItem_actionPerformed(true);
162 * Add a key listener that delegates to whichever split component the mouse is
163 * in (or does nothing if neither).
165 protected void addKeyListener()
167 addKeyListener(new KeyAdapter() {
170 public void keyPressed(KeyEvent e)
172 AlignFrame af = (AlignFrame) getFrameAtMouse();
175 * Intercept and override any keys here if wanted.
177 if (!overrideKey(e, af))
181 for (KeyListener kl : af.getKeyListeners())
190 public void keyReleased(KeyEvent e)
192 Component c = getFrameAtMouse();
195 for (KeyListener kl : c.getKeyListeners())
206 * Returns true if the key event is overriden and actioned (or ignored) here,
207 * else returns false, indicating it should be delegated to the AlignFrame's
210 * We can't handle Cmd-Key combinations here, instead this is done by
211 * overriding key bindings.
213 * @see addKeyOverrides
218 protected boolean overrideKey(KeyEvent e, AlignFrame af)
220 boolean actioned = false;
221 int keyCode = e.getKeyCode();
224 case KeyEvent.VK_DOWN:
225 if (e.isAltDown() || !af.viewport.cursorMode)
228 * Key down (or Alt-key-down in cursor mode) - move selected sequences
230 ((AlignFrame) getTopFrame()).moveSelectedSequences(false);
231 ((AlignFrame) getBottomFrame()).moveSelectedSequences(false);
237 if (e.isAltDown() || !af.viewport.cursorMode)
240 * Key up (or Alt-key-up in cursor mode) - move selected sequences
242 ((AlignFrame) getTopFrame()).moveSelectedSequences(true);
243 ((AlignFrame) getBottomFrame()).moveSelectedSequences(true);
253 * Set key bindings (recommended for Swing over key accelerators).
255 private void addKeyBindings()
257 overrideDelegatedKeyBindings();
259 overrideImplementedKeyBindings();
263 * Override key bindings with alternative action methods implemented in this
266 protected void overrideImplementedKeyBindings()
271 overrideExpandViews();
272 overrideGatherViews();
276 * Replace Cmd-W close view action with our version.
278 protected void overrideCloseView()
280 AbstractAction action;
282 * Ctrl-W / Cmd-W - close view or window
284 KeyStroke key_cmdW = KeyStroke.getKeyStroke(KeyEvent.VK_W, Toolkit
285 .getDefaultToolkit().getMenuShortcutKeyMask(), false);
286 action = new AbstractAction()
289 public void actionPerformed(ActionEvent e)
291 closeView_actionPerformed();
294 overrideKeyBinding(key_cmdW, action);
298 * Replace Cmd-T new view action with our version.
300 protected void overrideNewView()
303 * Ctrl-T / Cmd-T open new view
305 KeyStroke key_cmdT = KeyStroke.getKeyStroke(KeyEvent.VK_T, Toolkit
306 .getDefaultToolkit().getMenuShortcutKeyMask(), false);
307 AbstractAction action = new AbstractAction()
310 public void actionPerformed(ActionEvent e)
312 newView_actionPerformed();
315 overrideKeyBinding(key_cmdT, action);
319 * For now, delegates key events to the corresponding key accelerator for the
320 * AlignFrame that the mouse is in. Hopefully can be simplified in future if
321 * AlignFrame is changed to use key bindings rather than accelerators.
323 protected void overrideDelegatedKeyBindings()
325 if (getTopFrame() instanceof AlignFrame)
328 * Get all accelerator keys in the top frame (the bottom should be
329 * identical) and override each one.
331 for (Entry<KeyStroke, JMenuItem> acc : ((AlignFrame) getTopFrame())
332 .getAccelerators().entrySet())
334 overrideKeyBinding(acc);
340 * Overrides an AlignFrame key accelerator with our version which delegates to
341 * the action listener in whichever frame has the mouse (and does nothing if
346 private void overrideKeyBinding(Entry<KeyStroke, JMenuItem> acc)
348 final KeyStroke ks = acc.getKey();
349 InputMap inputMap = this.getInputMap(JComponent.WHEN_FOCUSED);
350 inputMap.put(ks, ks);
351 this.getActionMap().put(ks, new AbstractAction()
354 public void actionPerformed(ActionEvent e)
356 Component c = getFrameAtMouse();
357 if (c != null && c instanceof AlignFrame)
359 for (ActionListener a : ((AlignFrame) c).getAccelerators()
360 .get(ks).getActionListeners())
362 a.actionPerformed(null);
370 * Replace an accelerator key's action with the specified action.
374 protected void overrideKeyBinding(KeyStroke ks, AbstractAction action)
376 this.getActionMap().put(ks, action);
377 overrideMenuItem(ks, action);
381 * Create and link new views (with matching names) in both panes.
383 * Note this is _not_ multiple tabs, each hosting a split pane view, rather it
384 * is a single split pane with each split holding multiple tabs which are
387 * TODO implement instead with a tabbed holder in the SplitView, each tab
388 * holding a single JSplitPane. Would avoid a duplicated tab, at the cost of
389 * some additional coding.
391 protected void newView_actionPerformed()
393 AlignFrame topFrame = (AlignFrame) getTopFrame();
394 AlignFrame bottomFrame = (AlignFrame) getBottomFrame();
395 final boolean scaleProteinAsCdna = topFrame.viewport
396 .isScaleProteinAsCdna();
398 AlignmentPanel newTopPanel = topFrame.newView(null, true);
399 AlignmentPanel newBottomPanel = bottomFrame.newView(null, true);
402 * This currently (for the first new view only) leaves the top pane on tab 0
403 * but the bottom on tab 1. This results from 'setInitialTabVisible' echoing
404 * from the bottom back to the first frame. Next line is a fudge to work
405 * around this. TODO find a better way.
407 if (topFrame.getTabIndex() != bottomFrame.getTabIndex())
409 topFrame.setDisplayedView(newTopPanel);
412 newBottomPanel.av.viewName = newTopPanel.av.viewName;
413 newTopPanel.av.setCodingComplement(newBottomPanel.av);
416 * These lines can be removed once scaleProteinAsCdna is added to element
417 * Viewport in jalview.xsd, as Jalview2XML.copyAlignPanel will then take
420 newTopPanel.av.setScaleProteinAsCdna(scaleProteinAsCdna);
421 newBottomPanel.av.setScaleProteinAsCdna(scaleProteinAsCdna);
424 * Line up id labels etc
428 final StructureSelectionManager ssm = StructureSelectionManager
429 .getStructureSelectionManager(Desktop.instance);
430 ssm.addCommandListener(newTopPanel.av);
431 ssm.addCommandListener(newBottomPanel.av);
435 * Close the currently selected view in both panes. If there is only one view,
436 * close this split frame.
438 protected void closeView_actionPerformed()
440 int viewCount = ((AlignFrame) getTopFrame()).getAlignPanels().size();
447 AlignmentPanel topPanel = ((AlignFrame) getTopFrame()).alignPanel;
448 AlignmentPanel bottomPanel = ((AlignFrame) getBottomFrame()).alignPanel;
450 ((AlignFrame) getTopFrame()).closeView(topPanel);
451 ((AlignFrame) getBottomFrame()).closeView(bottomPanel);
456 * Close child frames and this split frame.
460 ((AlignFrame) getTopFrame()).closeMenuItem_actionPerformed(true);
461 ((AlignFrame) getBottomFrame()).closeMenuItem_actionPerformed(true);
464 this.setClosed(true);
465 } catch (PropertyVetoException e)
472 * Replace AlignFrame 'expand views' action with SplitFrame version.
474 protected void overrideExpandViews()
476 KeyStroke key_X = KeyStroke.getKeyStroke(KeyEvent.VK_X, 0, false);
477 AbstractAction action = new AbstractAction()
480 public void actionPerformed(ActionEvent e)
482 expandViews_actionPerformed();
485 overrideMenuItem(key_X, action);
489 * Replace AlignFrame 'gather views' action with SplitFrame version.
491 protected void overrideGatherViews()
493 KeyStroke key_G = KeyStroke.getKeyStroke(KeyEvent.VK_G, 0, false);
494 AbstractAction action = new AbstractAction()
497 public void actionPerformed(ActionEvent e)
499 gatherViews_actionPerformed();
502 overrideMenuItem(key_G, action);
506 * Override the menu action associated with the keystroke in the child frames,
507 * replacing it with the given action.
512 private void overrideMenuItem(KeyStroke ks, AbstractAction action)
514 overrideMenuItem(ks, action, getTopFrame());
515 overrideMenuItem(ks, action, getBottomFrame());
519 * Override the menu action associated with the keystroke in one child frame,
520 * replacing it with the given action. Mwahahahaha.
526 private void overrideMenuItem(KeyStroke key, final AbstractAction action,
529 if (comp instanceof AlignFrame)
531 JMenuItem mi = ((AlignFrame) comp).getAccelerators().get(key);
534 for (ActionListener al : mi.getActionListeners())
536 mi.removeActionListener(al);
538 mi.addActionListener(new ActionListener()
541 public void actionPerformed(ActionEvent e)
543 action.actionPerformed(e);
551 * Expand any multiple views (which are always in pairs) into separate split
554 protected void expandViews_actionPerformed()
556 Desktop.instance.explodeViews(this);
560 * Gather any other SplitFrame views of this alignment back in as multiple
561 * (pairs of) views in this SplitFrame.
563 protected void gatherViews_actionPerformed()
565 Desktop.instance.gatherViews(this);
569 * Returns the alignment in the complementary frame to the one given.
572 public AlignmentI getComplement(Object alignFrame)
574 if (alignFrame == this.getTopFrame())
576 return ((AlignFrame) getBottomFrame()).viewport.getAlignment();
578 else if (alignFrame == this.getBottomFrame())
580 return ((AlignFrame) getTopFrame()).viewport.getAlignment();
586 * Returns the title of the complementary frame to the one given.
589 public String getComplementTitle(Object alignFrame)
591 if (alignFrame == this.getTopFrame())
593 return ((AlignFrame) getBottomFrame()).getTitle();
595 else if (alignFrame == this.getBottomFrame())
597 return ((AlignFrame) getTopFrame()).getTitle();
603 * Set the 'other half' to hidden / revealed.
606 public void setComplementVisible(Object alignFrame, boolean show)
609 * Hiding the AlignPanel suppresses unnecessary repaints
611 if (alignFrame == getTopFrame())
613 ((AlignFrame) getBottomFrame()).alignPanel.setVisible(show);
615 else if (alignFrame == getBottomFrame())
617 ((AlignFrame) getTopFrame()).alignPanel.setVisible(show);
619 super.setComplementVisible(alignFrame, show);
623 * Replace Cmd-F Find action with our version. This is necessary because the
624 * 'default' Finder searches in the first AlignFrame it finds. We need it to
625 * search in the half of the SplitFrame that has the mouse.
627 protected void overrideFind()
630 * Ctrl-F / Cmd-F open Finder dialog, 'focused' on the right alignment
632 KeyStroke key_cmdF = KeyStroke.getKeyStroke(KeyEvent.VK_F, Toolkit
633 .getDefaultToolkit().getMenuShortcutKeyMask(), false);
634 AbstractAction action = new AbstractAction()
637 public void actionPerformed(ActionEvent e)
639 Component c = getFrameAtMouse();
640 if (c != null && c instanceof AlignFrame)
642 AlignFrame af = (AlignFrame) c;
643 new Finder(af.viewport, af.alignPanel);
647 overrideKeyBinding(key_cmdF, action);