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 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)
153 * Add a key listener that delegates to whichever split component the mouse is
154 * in (or does nothing if neither).
156 protected void addKeyListener()
158 addKeyListener(new KeyAdapter()
162 public void keyPressed(KeyEvent e)
164 AlignFrame af = (AlignFrame) getFrameAtMouse();
167 * Intercept and override any keys here if wanted.
169 if (!overrideKey(e, af))
173 for (KeyListener kl : af.getKeyListeners())
182 public void keyReleased(KeyEvent e)
184 Component c = getFrameAtMouse();
187 for (KeyListener kl : c.getKeyListeners())
198 * Returns true if the key event is overriden and actioned (or ignored) here,
199 * else returns false, indicating it should be delegated to the AlignFrame's
202 * We can't handle Cmd-Key combinations here, instead this is done by
203 * overriding key bindings.
205 * @see addKeyOverrides
210 protected boolean overrideKey(KeyEvent e, AlignFrame af)
212 boolean actioned = false;
213 int keyCode = e.getKeyCode();
216 case KeyEvent.VK_DOWN:
217 if (e.isAltDown() || !af.viewport.cursorMode)
220 * Key down (or Alt-key-down in cursor mode) - move selected sequences
222 ((AlignFrame) getTopFrame()).moveSelectedSequences(false);
223 ((AlignFrame) getBottomFrame()).moveSelectedSequences(false);
229 if (e.isAltDown() || !af.viewport.cursorMode)
232 * Key up (or Alt-key-up in cursor mode) - move selected sequences
234 ((AlignFrame) getTopFrame()).moveSelectedSequences(true);
235 ((AlignFrame) getBottomFrame()).moveSelectedSequences(true);
245 * Set key bindings (recommended for Swing over key accelerators).
247 private void addKeyBindings()
249 overrideDelegatedKeyBindings();
251 overrideImplementedKeyBindings();
255 * Override key bindings with alternative action methods implemented in this
258 protected void overrideImplementedKeyBindings()
263 overrideExpandViews();
264 overrideGatherViews();
268 * Replace Cmd-W close view action with our version.
270 protected void overrideCloseView()
272 AbstractAction action;
274 * Ctrl-W / Cmd-W - close view or window
276 KeyStroke key_cmdW = KeyStroke.getKeyStroke(KeyEvent.VK_W, Toolkit
277 .getDefaultToolkit().getMenuShortcutKeyMask(), false);
278 action = new AbstractAction()
281 public void actionPerformed(ActionEvent e)
283 closeView_actionPerformed();
286 overrideKeyBinding(key_cmdW, action);
290 * Replace Cmd-T new view action with our version.
292 protected void overrideNewView()
295 * Ctrl-T / Cmd-T open new view
297 KeyStroke key_cmdT = KeyStroke.getKeyStroke(KeyEvent.VK_T, Toolkit
298 .getDefaultToolkit().getMenuShortcutKeyMask(), false);
299 AbstractAction action = new AbstractAction()
302 public void actionPerformed(ActionEvent e)
304 newView_actionPerformed();
307 overrideKeyBinding(key_cmdT, action);
311 * For now, delegates key events to the corresponding key accelerator for the
312 * AlignFrame that the mouse is in. Hopefully can be simplified in future if
313 * AlignFrame is changed to use key bindings rather than accelerators.
315 protected void overrideDelegatedKeyBindings()
317 if (getTopFrame() instanceof AlignFrame)
320 * Get all accelerator keys in the top frame (the bottom should be
321 * identical) and override each one.
323 for (Entry<KeyStroke, JMenuItem> acc : ((AlignFrame) getTopFrame())
324 .getAccelerators().entrySet())
326 overrideKeyBinding(acc);
332 * Overrides an AlignFrame key accelerator with our version which delegates to
333 * the action listener in whichever frame has the mouse (and does nothing if
338 private void overrideKeyBinding(Entry<KeyStroke, JMenuItem> acc)
340 final KeyStroke ks = acc.getKey();
341 InputMap inputMap = this.getInputMap(JComponent.WHEN_FOCUSED);
342 inputMap.put(ks, ks);
343 this.getActionMap().put(ks, new AbstractAction()
346 public void actionPerformed(ActionEvent e)
348 Component c = getFrameAtMouse();
349 if (c != null && c instanceof AlignFrame)
351 for (ActionListener a : ((AlignFrame) c).getAccelerators()
352 .get(ks).getActionListeners())
354 a.actionPerformed(null);
362 * Replace an accelerator key's action with the specified action.
366 protected void overrideKeyBinding(KeyStroke ks, AbstractAction action)
368 this.getActionMap().put(ks, action);
369 overrideMenuItem(ks, action);
373 * Create and link new views (with matching names) in both panes.
375 * Note this is _not_ multiple tabs, each hosting a split pane view, rather it
376 * is a single split pane with each split holding multiple tabs which are
379 * TODO implement instead with a tabbed holder in the SplitView, each tab
380 * holding a single JSplitPane. Would avoid a duplicated tab, at the cost of
381 * some additional coding.
383 protected void newView_actionPerformed()
385 AlignFrame topFrame = (AlignFrame) getTopFrame();
386 AlignFrame bottomFrame = (AlignFrame) getBottomFrame();
387 final boolean scaleProteinAsCdna = topFrame.viewport
388 .isScaleProteinAsCdna();
390 AlignmentPanel newTopPanel = topFrame.newView(null, true);
391 AlignmentPanel newBottomPanel = bottomFrame.newView(null, true);
394 * This currently (for the first new view only) leaves the top pane on tab 0
395 * but the bottom on tab 1. This results from 'setInitialTabVisible' echoing
396 * from the bottom back to the first frame. Next line is a fudge to work
397 * around this. TODO find a better way.
399 if (topFrame.getTabIndex() != bottomFrame.getTabIndex())
401 topFrame.setDisplayedView(newTopPanel);
404 newBottomPanel.av.viewName = newTopPanel.av.viewName;
405 newTopPanel.av.setCodingComplement(newBottomPanel.av);
408 * These lines can be removed once scaleProteinAsCdna is added to element
409 * Viewport in jalview.xsd, as Jalview2XML.copyAlignPanel will then take
412 newTopPanel.av.setScaleProteinAsCdna(scaleProteinAsCdna);
413 newBottomPanel.av.setScaleProteinAsCdna(scaleProteinAsCdna);
416 * Line up id labels etc
420 final StructureSelectionManager ssm = StructureSelectionManager
421 .getStructureSelectionManager(Desktop.instance);
422 ssm.addCommandListener(newTopPanel.av);
423 ssm.addCommandListener(newBottomPanel.av);
427 * Close the currently selected view in both panes. If there is only one view,
428 * close this split frame.
430 protected void closeView_actionPerformed()
432 int viewCount = ((AlignFrame) getTopFrame()).getAlignPanels().size();
439 AlignmentPanel topPanel = ((AlignFrame) getTopFrame()).alignPanel;
440 AlignmentPanel bottomPanel = ((AlignFrame) getBottomFrame()).alignPanel;
442 ((AlignFrame) getTopFrame()).closeView(topPanel);
443 ((AlignFrame) getBottomFrame()).closeView(bottomPanel);
448 * Close child frames and this split frame.
452 ((AlignFrame) getTopFrame()).closeMenuItem_actionPerformed(true);
453 ((AlignFrame) getBottomFrame()).closeMenuItem_actionPerformed(true);
456 this.setClosed(true);
457 } catch (PropertyVetoException e)
464 * Replace AlignFrame 'expand views' action with SplitFrame version.
466 protected void overrideExpandViews()
468 KeyStroke key_X = KeyStroke.getKeyStroke(KeyEvent.VK_X, 0, false);
469 AbstractAction action = new AbstractAction()
472 public void actionPerformed(ActionEvent e)
474 expandViews_actionPerformed();
477 overrideMenuItem(key_X, action);
481 * Replace AlignFrame 'gather views' action with SplitFrame version.
483 protected void overrideGatherViews()
485 KeyStroke key_G = KeyStroke.getKeyStroke(KeyEvent.VK_G, 0, false);
486 AbstractAction action = new AbstractAction()
489 public void actionPerformed(ActionEvent e)
491 gatherViews_actionPerformed();
494 overrideMenuItem(key_G, action);
498 * Override the menu action associated with the keystroke in the child frames,
499 * replacing it with the given action.
504 private void overrideMenuItem(KeyStroke ks, AbstractAction action)
506 overrideMenuItem(ks, action, getTopFrame());
507 overrideMenuItem(ks, action, getBottomFrame());
511 * Override the menu action associated with the keystroke in one child frame,
512 * replacing it with the given action. Mwahahahaha.
518 private void overrideMenuItem(KeyStroke key, final AbstractAction action,
521 if (comp instanceof AlignFrame)
523 JMenuItem mi = ((AlignFrame) comp).getAccelerators().get(key);
526 for (ActionListener al : mi.getActionListeners())
528 mi.removeActionListener(al);
530 mi.addActionListener(new ActionListener()
533 public void actionPerformed(ActionEvent e)
535 action.actionPerformed(e);
543 * Expand any multiple views (which are always in pairs) into separate split
546 protected void expandViews_actionPerformed()
548 Desktop.instance.explodeViews(this);
552 * Gather any other SplitFrame views of this alignment back in as multiple
553 * (pairs of) views in this SplitFrame.
555 protected void gatherViews_actionPerformed()
557 Desktop.instance.gatherViews(this);
561 * Returns the alignment in the complementary frame to the one given.
564 public AlignmentI getComplement(Object alignFrame)
566 if (alignFrame == this.getTopFrame())
568 return ((AlignFrame) getBottomFrame()).viewport.getAlignment();
570 else if (alignFrame == this.getBottomFrame())
572 return ((AlignFrame) getTopFrame()).viewport.getAlignment();
578 * Returns the title of the complementary frame to the one given.
581 public String getComplementTitle(Object alignFrame)
583 if (alignFrame == this.getTopFrame())
585 return ((AlignFrame) getBottomFrame()).getTitle();
587 else if (alignFrame == this.getBottomFrame())
589 return ((AlignFrame) getTopFrame()).getTitle();
595 * Set the 'other half' to hidden / revealed.
598 public void setComplementVisible(Object alignFrame, boolean show)
601 * Hiding the AlignPanel suppresses unnecessary repaints
603 if (alignFrame == getTopFrame())
605 ((AlignFrame) getBottomFrame()).alignPanel.setVisible(show);
607 else if (alignFrame == getBottomFrame())
609 ((AlignFrame) getTopFrame()).alignPanel.setVisible(show);
611 super.setComplementVisible(alignFrame, show);
615 * Replace Cmd-F Find action with our version. This is necessary because the
616 * 'default' Finder searches in the first AlignFrame it finds. We need it to
617 * search in the half of the SplitFrame that has the mouse.
619 protected void overrideFind()
622 * Ctrl-F / Cmd-F open Finder dialog, 'focused' on the right alignment
624 KeyStroke key_cmdF = KeyStroke.getKeyStroke(KeyEvent.VK_F, Toolkit
625 .getDefaultToolkit().getMenuShortcutKeyMask(), false);
626 AbstractAction action = new AbstractAction()
629 public void actionPerformed(ActionEvent e)
631 Component c = getFrameAtMouse();
632 if (c != null && c instanceof AlignFrame)
634 AlignFrame af = (AlignFrame) c;
635 new Finder(af.viewport, af.alignPanel);
639 overrideKeyBinding(key_cmdF, action);