X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fgui%2FSplitFrame.java;h=5c4e4d24085a2652912ad8ee0732504e3ffe7669;hb=466c5639624028f92e214121672dc409ad10514b;hp=2288d72bd03d70cc090ea4bca560d9cfa35b03d8;hpb=10ff37d2cb03f342ddbed679951d3e2fef0a404b;p=jalview.git diff --git a/src/jalview/gui/SplitFrame.java b/src/jalview/gui/SplitFrame.java index 2288d72..5c4e4d2 100644 --- a/src/jalview/gui/SplitFrame.java +++ b/src/jalview/gui/SplitFrame.java @@ -1,30 +1,48 @@ package jalview.gui; -import jalview.jbgui.GSplitFrame; - import java.awt.Component; -import java.awt.MouseInfo; -import java.awt.Point; -import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; +import java.beans.PropertyVetoException; import java.util.Map.Entry; import javax.swing.AbstractAction; +import javax.swing.InputMap; import javax.swing.JComponent; import javax.swing.JMenuItem; import javax.swing.KeyStroke; import javax.swing.event.InternalFrameAdapter; import javax.swing.event.InternalFrameEvent; -public class SplitFrame extends GSplitFrame +import jalview.api.SplitContainerI; +import jalview.api.ViewStyleI; +import jalview.datamodel.AlignmentI; +import jalview.jbgui.GAlignFrame; +import jalview.jbgui.GSplitFrame; +import jalview.structure.StructureSelectionManager; +import jalview.viewmodel.AlignmentViewport; + +/** + * An internal frame on the desktop that hosts a horizontally split view of + * linked DNA and Protein alignments. Additional views can be created in linked + * pairs, expanded to separate split frames, or regathered into a single frame. + *
+ * (Some) operations on each alignment are automatically mirrored on the other. + * These include mouseover (highlighting), sequence and column selection, + * sequence ordering and sorting, and grouping, colouring and sorting by tree. + * + * @author gmcarstairs + * + */ +public class SplitFrame extends GSplitFrame implements SplitContainerI { private static final long serialVersionUID = 1L; - public SplitFrame(JComponent top, JComponent bottom) + public SplitFrame(GAlignFrame top, GAlignFrame bottom) { super(top, bottom); init(); @@ -35,7 +53,23 @@ public class SplitFrame extends GSplitFrame */ protected void init() { - setSize(AlignFrame.DEFAULT_WIDTH, Desktop.instance.getHeight() - 20); + getTopFrame().setSplitFrame(this); + getBottomFrame().setSplitFrame(this); + getTopFrame().setVisible(true); + getBottomFrame().setVisible(true); + + ((AlignFrame) getTopFrame()).getViewport().setCodingComplement( + ((AlignFrame) getBottomFrame()).getViewport()); + + int width = ((AlignFrame) getTopFrame()).getWidth(); + // about 50 pixels for the SplitFrame's title bar etc + int height = ((AlignFrame) getTopFrame()).getHeight() + + ((AlignFrame) getBottomFrame()).getHeight() + 50; + height = Math.min(height, Desktop.instance.getHeight() - 20); + // setSize(AlignFrame.DEFAULT_WIDTH, Desktop.instance.getHeight() - 20); + setSize(width, height); + + adjustLayout(); addCloseFrameListener(); @@ -43,6 +77,65 @@ public class SplitFrame extends GSplitFrame addKeyBindings(); + addCommandListeners(); + } + + /** + * Set the top and bottom frames to listen to each others Commands (e.g. Edit, + * Order). + */ + protected void addCommandListeners() + { + // TODO if CommandListener is only ever 1:1 for complementary views, + // may change broadcast pattern to direct messaging (more efficient) + final StructureSelectionManager ssm = StructureSelectionManager + .getStructureSelectionManager(Desktop.instance); + ssm.addCommandListener(((AlignFrame) getTopFrame()).getViewport()); + ssm.addCommandListener(((AlignFrame) getBottomFrame()).getViewport()); + } + + /** + * Do any tweaking and twerking of the layout wanted. + */ + public void adjustLayout() + { + /* + * Ensure sequence ids are the same width for good alignment. + */ + int w1 = ((AlignFrame) getTopFrame()).getViewport().getIdWidth(); + int w2 = ((AlignFrame) getBottomFrame()).getViewport().getIdWidth(); + int w3 = Math.max(w1, w2); + if (w1 != w3) + { + ((AlignFrame) getTopFrame()).getViewport().setIdWidth(w3); + } + if (w2 != w3) + { + ((AlignFrame) getBottomFrame()).getViewport().setIdWidth(w3); + } + + /* + * Set the character width for protein to 3 times that for dna. + */ + boolean scaleThreeToOne = true; // TODO a new Preference option? + if (scaleThreeToOne) + { + final AlignViewport topViewport = ((AlignFrame) getTopFrame()).viewport; + final AlignViewport bottomViewport = ((AlignFrame) getBottomFrame()).viewport; + final AlignmentI topAlignment = topViewport.getAlignment(); + final AlignmentI bottomAlignment = bottomViewport.getAlignment(); + AlignmentViewport cdna = topAlignment.isNucleotide() ? topViewport + : (bottomAlignment.isNucleotide() ? bottomViewport : null); + AlignmentViewport protein = !topAlignment.isNucleotide() ? topViewport + : (!bottomAlignment.isNucleotide() ? bottomViewport : null); + if (protein != null && cdna != null) + { + ViewStyleI vs = cdna.getViewStyle(); + ViewStyleI vs2 = protein.getViewStyle(); + vs2.setCharWidth(3 * vs.getCharWidth()); + protein.setViewStyle(vs2); + } + } } /** @@ -55,14 +148,14 @@ public class SplitFrame extends GSplitFrame @Override public void internalFrameClosed(InternalFrameEvent evt) { - if (getTopComponent() instanceof AlignFrame) + if (getTopFrame() instanceof AlignFrame) { - ((AlignFrame) getTopComponent()) + ((AlignFrame) getTopFrame()) .closeMenuItem_actionPerformed(true); } - if (getBottomComponent() instanceof AlignFrame) + if (getBottomFrame() instanceof AlignFrame) { - ((AlignFrame) getBottomComponent()) + ((AlignFrame) getBottomFrame()) .closeMenuItem_actionPerformed(true); } }; @@ -75,18 +168,24 @@ public class SplitFrame extends GSplitFrame */ protected void addKeyListener() { - // TODO Key Bindings rather than KeyListener are recommended for Swing addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { - Component c = getComponentAtMouse(); - if (c != null) + AlignFrame af = (AlignFrame) getFrameAtMouse(); + + /* + * Intercept and override any keys here if wanted. + */ + if (!overrideKey(e, af)) { - for (KeyListener kl : c.getKeyListeners()) + if (af != null) { - kl.keyPressed(e); + for (KeyListener kl : af.getKeyListeners()) + { + kl.keyPressed(e); + } } } } @@ -94,7 +193,7 @@ public class SplitFrame extends GSplitFrame @Override public void keyReleased(KeyEvent e) { - Component c = getComponentAtMouse(); + Component c = getFrameAtMouse(); if (c != null) { for (KeyListener kl : c.getKeyListeners()) @@ -108,80 +207,433 @@ public class SplitFrame extends GSplitFrame } /** - * Returns the split pane component the mouse is in, or null if neither. + * Returns true if the key event is overriden and actioned (or ignored) here, + * else returns false, indicating it should be delegated to the AlignFrame's + * usual handler. + *
+ * We can't handle Cmd-Key combinations here, instead this is done by
+ * overriding key bindings.
*
+ * @see addKeyOverrides
+ * @param e
+ * @param af
* @return
*/
- protected Component getComponentAtMouse()
+ protected boolean overrideKey(KeyEvent e, AlignFrame af)
{
- Point loc = MouseInfo.getPointerInfo().getLocation();
-
- if (isIn(loc, getTopComponent())) {
- return getTopComponent();
- }
- else if (isIn(loc, getBottomComponent()))
+ boolean actioned = false;
+ int keyCode = e.getKeyCode();
+ switch (keyCode)
{
- return getBottomComponent();
+ case KeyEvent.VK_DOWN:
+ if (e.isAltDown() || !af.viewport.cursorMode)
+ {
+ /*
+ * Key down (or Alt-key-down in cursor mode) - move selected sequences
+ */
+ ((AlignFrame) getTopFrame()).moveSelectedSequences(false);
+ ((AlignFrame) getBottomFrame()).moveSelectedSequences(false);
+ actioned = true;
+ e.consume();
+ }
+ break;
+ case KeyEvent.VK_UP:
+ if (e.isAltDown() || !af.viewport.cursorMode)
+ {
+ /*
+ * Key up (or Alt-key-up in cursor mode) - move selected sequences
+ */
+ ((AlignFrame) getTopFrame()).moveSelectedSequences(true);
+ ((AlignFrame) getBottomFrame()).moveSelectedSequences(true);
+ actioned = true;
+ e.consume();
+ }
+ default:
}
- return null;
+ return actioned;
}
- private boolean isIn(Point loc, JComponent comp)
+ /**
+ * Set key bindings (recommended for Swing over key accelerators).
+ */
+ private void addKeyBindings()
{
- Point p = comp.getLocationOnScreen();
- Rectangle r = new Rectangle(p.x, p.y, comp.getWidth(), comp.getHeight());
- return r.contains(loc);
+ overrideDelegatedKeyBindings();
+
+ overrideImplementedKeyBindings();
}
/**
- * Set key bindings (recommended for Swing over key accelerators).
+ * Override key bindings with alternative action methods implemented in this
+ * class.
*/
- private void addKeyBindings()
+ protected void overrideImplementedKeyBindings()
{
+ overrideFind();
+ overrideNewView();
+ overrideCloseView();
+ overrideExpandViews();
+ overrideGatherViews();
+ }
+
+ /**
+ * Replace Cmd-W close view action with our version.
+ */
+ protected void overrideCloseView()
+ {
+ AbstractAction action;
/*
- * Bind Cmd-Alt/I (invert column selection)
+ * Ctrl-W / Cmd-W - close view or window
*/
- // If AlignFrame exposed a Map of Action objects by key,
- // we could delegate all key bindings with a single lookup
- this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
- KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_I, Toolkit
- .getDefaultToolkit().getMenuShortcutKeyMask()
- | java.awt.event.KeyEvent.ALT_MASK, false),
- "INV_SEQ_SEL");
- this.getActionMap().put("INV_SEQ_SEL", new AbstractAction()
+ KeyStroke key_cmdW = KeyStroke.getKeyStroke(KeyEvent.VK_W, Toolkit
+ .getDefaultToolkit().getMenuShortcutKeyMask(), false);
+ action = new AbstractAction()
{
@Override
public void actionPerformed(ActionEvent e)
{
- Component c = getComponentAtMouse();
- if (c instanceof AlignFrame)
+ closeView_actionPerformed();
+ }
+ };
+ overrideKeyBinding(key_cmdW, action);
+ }
+
+ /**
+ * Replace Cmd-T new view action with our version.
+ */
+ protected void overrideNewView()
+ {
+ /*
+ * Ctrl-T / Cmd-T open new view
+ */
+ KeyStroke key_cmdT = KeyStroke.getKeyStroke(KeyEvent.VK_T, Toolkit
+ .getDefaultToolkit().getMenuShortcutKeyMask(), false);
+ AbstractAction action = new AbstractAction()
+ {
+ @Override
+ public void actionPerformed(ActionEvent e)
+ {
+ newView_actionPerformed();
+ }
+ };
+ overrideKeyBinding(key_cmdT, action);
+ }
+
+ /**
+ * For now, delegates key events to the corresponding key accelerator for the
+ * AlignFrame that the mouse is in. Hopefully can be simplified in future if
+ * AlignFrame is changed to use key bindings rather than accelerators.
+ */
+ protected void overrideDelegatedKeyBindings()
+ {
+ if (getTopFrame() instanceof AlignFrame)
+ {
+ /*
+ * Get all accelerator keys in the top frame (the bottom should be
+ * identical) and override each one.
+ */
+ for (Entry
+ * Note this is _not_ multiple tabs, each hosting a split pane view, rather it
+ * is a single split pane with each split holding multiple tabs which are
+ * linked in pairs.
+ *
+ * TODO implement instead with a tabbed holder in the SplitView, each tab
+ * holding a single JSplitPane. Would avoid a duplicated tab, at the cost of
+ * some additional coding.
+ */
+ protected void newView_actionPerformed()
+ {
+ AlignFrame topFrame = (AlignFrame) getTopFrame();
+ AlignFrame bottomFrame = (AlignFrame) getBottomFrame();
+
+ AlignmentPanel newTopPanel = topFrame.newView(null, true);
+ AlignmentPanel newBottomPanel = bottomFrame.newView(null, true);
+
+ /*
+ * This currently (for the first new view only) leaves the top pane on tab 0
+ * but the bottom on tab 1. This results from 'setInitialTabVisible' echoing
+ * from the bottom back to the first frame. Next line is a fudge to work
+ * around this. TODO find a better way.
+ */
+ if (topFrame.getTabIndex() != bottomFrame.getTabIndex())
{
- for (Entry