From: gmungoc Date: Wed, 11 Feb 2015 12:26:31 +0000 (+0000) Subject: JAL-845 handling eXpand/Gather split frame views X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=cc6d26d2738c5854ef81ce6787d66564c2281220;p=jalview.git JAL-845 handling eXpand/Gather split frame views --- diff --git a/src/jalview/gui/AlignFrame.java b/src/jalview/gui/AlignFrame.java index 5ef3da3..9f79999 100644 --- a/src/jalview/gui/AlignFrame.java +++ b/src/jalview/gui/AlignFrame.java @@ -1480,22 +1480,17 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, } /** - * close alignPanel2 and shuffle tabs appropriately. + * Close the specified panel and close up tabs appropriately. * - * @param alignPanel2 + * @param panelToClose */ - public void closeView(AlignmentPanel alignPanel2) + public void closeView(AlignmentPanel panelToClose) { int index = tabbedPane.getSelectedIndex(); - int closedindex = tabbedPane.indexOfComponent(alignPanel2); - alignPanels.remove(alignPanel2); - // Unnecessary - // if (viewport == alignPanel2.av) - // { - // viewport = null; - // } - alignPanel2.closePanel(); - alignPanel2 = null; + int closedindex = tabbedPane.indexOfComponent(panelToClose); + alignPanels.remove(panelToClose); + panelToClose.closePanel(); + panelToClose = null; tabbedPane.removeTabAt(closedindex); tabbedPane.validate(); @@ -1721,18 +1716,6 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, viewport.getAlignment().moveSelectedSequencesByOne(sg, viewport.getHiddenRepSequences(), up); alignPanel.paintAlignment(true); - - final AlignViewportI peer = viewport.getCodingComplement(); - if (peer != null) - { - final SequenceGroup selectionGroup = peer.getSelectionGroup(); - if (selectionGroup != null) - { - peer.getAlignment().moveSelectedSequencesByOne( - peer.getSelectionGroup(), peer.getHiddenRepSequences(), up); - ((AlignViewport) peer).getAlignPanel().paintAlignment(true); - } - } } synchronized void slideSequences(boolean right, int size) @@ -2710,32 +2693,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, @Override public void newView_actionPerformed(ActionEvent e) { - /* - * Note if the current view has a protein/cdna complementary view - */ - AlignViewportI linkedView = this.viewport.getCodingComplement(); - - AlignmentPanel newPanel = newView(null, true); - - /* - * If the original view has a protein/cdna linked view, make and link a new - * view there also. - */ - // TODO refactor the hell out of this - move to a controller, lose the casts - // and direct member access, etc - if (linkedView != null) - { - AlignFrame linkedAlignFrame = ((AlignViewport) linkedView) - .getAlignPanel().alignFrame; - AlignmentPanel newLinkedPanel = linkedAlignFrame.newView(null, true); - newLinkedPanel.av.viewName = newPanel.av.viewName; - newPanel.av.setCodingComplement(newLinkedPanel.av); - final StructureSelectionManager ssm = StructureSelectionManager - .getStructureSelectionManager(Desktop.instance); - ssm.addCommandListener(newPanel.av); - ssm.addCommandListener(newLinkedPanel.av); - - } + newView(null, true); } /** @@ -5386,6 +5344,23 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, avc.setViewportAndAlignmentPanel(viewport, alignPanel); setMenusFromViewport(viewport); } + + /* + * If there is a frame linked to this one in a SplitPane, switch it to the + * same view tab index. No infinite recursion of calls should happen, since + * tabSelectionChanged() should not get invoked on setting the selected + * index to an unchanged value. Guard against setting an invalid index + * before the new view peer tab has been created. + */ + final AlignViewportI peer = viewport.getCodingComplement(); + if (peer != null) + { + AlignFrame linkedAlignFrame = ((AlignViewport) peer).getAlignPanel().alignFrame; + if (linkedAlignFrame.tabbedPane.getTabCount() > index) + { + linkedAlignFrame.tabbedPane.setSelectedIndex(index); + } + } } /** @@ -5464,7 +5439,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, * * @param av */ - public boolean closeView(AlignmentViewport av) + public boolean closeView(AlignViewportI av) { if (viewport == av) { diff --git a/src/jalview/gui/AlignViewport.java b/src/jalview/gui/AlignViewport.java index 5698e0f..deba021 100644 --- a/src/jalview/gui/AlignViewport.java +++ b/src/jalview/gui/AlignViewport.java @@ -1485,9 +1485,7 @@ public class AlignViewport extends AlignmentViewport implements String linkedTitle = MessageManager.formatMessage( "label.linked_view_title", dnaShortName, proteinShortName); JInternalFrame splitFrame = new SplitFrame(cdnaFrame, proteinFrame); - Desktop.addInternalFrame(splitFrame, linkedTitle, - AlignFrame.DEFAULT_WIDTH, - AlignFrame.DEFAULT_HEIGHT); + Desktop.addInternalFrame(splitFrame, linkedTitle, -1, -1); /* * Set the frames to listen for each other's edit and sort commands. diff --git a/src/jalview/gui/Desktop.java b/src/jalview/gui/Desktop.java index 4bb828c..cb6bc6e 100644 --- a/src/jalview/gui/Desktop.java +++ b/src/jalview/gui/Desktop.java @@ -20,6 +20,7 @@ */ package jalview.gui; +import jalview.api.AlignmentViewPanel; import jalview.bin.Cache; import jalview.io.FileLoader; import jalview.io.FormatAdapter; @@ -334,8 +335,7 @@ public class Desktop extends jalview.jbgui.GDesktop implements + jalview.bin.Cache.getProperty("VERSION") + "\n" + "Jalview Installation: " + jalview.bin.Cache.getDefault("INSTALLATION", "unknown") - + "\n" - + "Build Date: " + + "\n" + "Build Date: " + jalview.bin.Cache.getDefault("BUILD_DATE", "unknown") + "\n" + "Java version: " + System.getProperty("java.version") + "\n" + System.getProperty("os.arch") + " " @@ -436,9 +436,10 @@ public class Desktop extends jalview.jbgui.GDesktop implements { ssm.setAddTempFacAnnot(jalview.bin.Cache.getDefault( Preferences.ADD_TEMPFACT_ANN, true)); - ssm.setProcessSecondaryStructure(jalview.bin.Cache.getDefault(Preferences.STRUCT_FROM_PDB, true)); - ssm.setSecStructServices(jalview.bin.Cache.getDefault(Preferences.USE_RNAVIEW, - true)); + ssm.setProcessSecondaryStructure(jalview.bin.Cache.getDefault( + Preferences.STRUCT_FROM_PDB, true)); + ssm.setSecStructServices(jalview.bin.Cache.getDefault( + Preferences.USE_RNAVIEW, true)); } else { @@ -484,7 +485,9 @@ public class Desktop extends jalview.jbgui.GDesktop implements public void run() { long instance = System.currentTimeMillis(); - Desktop.instance.setProgressBar(MessageManager.getString("status.refreshing_news"), instance); + Desktop.instance.setProgressBar( + MessageManager.getString("status.refreshing_news"), + instance); jvnews.refreshNews(); Desktop.instance.setProgressBar(null, instance); jvnews.showNews(); @@ -652,7 +655,6 @@ public class Desktop extends jalview.jbgui.GDesktop implements addInternalFrame(frame, title, true, w, h, true); } - /** * Add an internal frame to the Jalview desktop * @@ -1197,8 +1199,7 @@ public class Desktop extends jalview.jbgui.GDesktop implements if (shortv) { message.append("

Version: " - + jalview.bin.Cache.getProperty("VERSION") - + "

"); + + jalview.bin.Cache.getProperty("VERSION") + ""); message.append("Last Updated: " + jalview.bin.Cache.getDefault("BUILD_DATE", "unknown") + ""); @@ -1244,8 +1245,7 @@ public class Desktop extends jalview.jbgui.GDesktop implements } message.append("
Authors: " + jalview.bin.Cache - .getDefault( - "AUTHORFNAMES", + .getDefault("AUTHORFNAMES", "The Jalview Authors (See AUTHORS file for current list)") + "

Development managed by The Barton Group, University of Dundee, Scotland, UK.
" + "

For help, see the FAQ at www.jalview.org/faq and/or join the jalview-discuss@jalview.org mailing list" @@ -1482,8 +1482,9 @@ public class Desktop extends jalview.jbgui.GDesktop implements public void run() { - setProgressBar(MessageManager.formatMessage("label.saving_jalview_project", new Object[]{choice.getName()}), - choice.hashCode()); + setProgressBar(MessageManager.formatMessage( + "label.saving_jalview_project", new Object[] + { choice.getName() }), choice.hashCode()); jalview.bin.Cache.setProperty("LAST_DIRECTORY", choice.getParent()); // TODO catch and handle errors for savestate @@ -1500,10 +1501,11 @@ public class Desktop extends jalview.jbgui.GDesktop implements Cache.log.error( "Problems whilst trying to save to " + choice.getName(), ex); - JOptionPane.showMessageDialog( - me, - MessageManager.formatMessage("label.error_whilst_saving_current_state_to", new Object[]{ choice.getName()}), - MessageManager.getString("label.couldnt_save_project"), + JOptionPane.showMessageDialog(me, MessageManager.formatMessage( + "label.error_whilst_saving_current_state_to", + new Object[] + { choice.getName() }), MessageManager + .getString("label.couldnt_save_project"), JOptionPane.WARNING_MESSAGE); } setProgressBar(null, choice.hashCode()); @@ -1571,15 +1573,15 @@ public class Desktop extends jalview.jbgui.GDesktop implements final File selectedFile = chooser.getSelectedFile(); setProjectFile(selectedFile); final String choice = selectedFile.getAbsolutePath(); - jalview.bin.Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent()); + jalview.bin.Cache.setProperty("LAST_DIRECTORY", + selectedFile.getParent()); new Thread(new Runnable() { public void run() { setProgressBar(MessageManager.formatMessage( "label.loading_jalview_project", new Object[] - { choice }), - choice.hashCode()); + { choice }), choice.hashCode()); try { new Jalview2XML().loadJalviewAlign(choice); @@ -1590,13 +1592,13 @@ public class Desktop extends jalview.jbgui.GDesktop implements { Cache.log.error("Problems whilst loading project from " + choice, ex); - JOptionPane.showMessageDialog(Desktop.desktop, - MessageManager + JOptionPane.showMessageDialog(Desktop.desktop, MessageManager .formatMessage( "label.error_whilst_loading_project_from", new Object[] - { choice }), - MessageManager.getString("label.couldnt_load_project"), JOptionPane.WARNING_MESSAGE); + { choice }), MessageManager + .getString("label.couldnt_load_project"), + JOptionPane.WARNING_MESSAGE); } setProgressBar(null, choice.hashCode()); } @@ -1776,6 +1778,11 @@ public class Desktop extends jalview.jbgui.GDesktop implements return null; } + /** + * Explode the views in the given frame into separate AlignFrame + * + * @param af + */ public void explodeViews(AlignFrame af) { int size = af.alignPanels.size(); @@ -1805,6 +1812,14 @@ public class Desktop extends jalview.jbgui.GDesktop implements } + /** + * Gather expanded views (separate AlignFrame's) with the same sequence set + * identifier back in to this frame as additional views, and close the + * expanded views. Note the expanded frames may themselves have multiple + * views. We take the lot. + * + * @param source + */ public void gatherViews(AlignFrame source) { source.viewport.gatherViewsHere = true; @@ -1851,7 +1866,8 @@ public class Desktop extends jalview.jbgui.GDesktop implements jalview.bin.Cache.getProperty("LAST_DIRECTORY")); chooser.setFileView(new JalviewFileView()); - chooser.setDialogTitle(MessageManager.getString("label.open_saved_vamsas_session")); + chooser.setDialogTitle(MessageManager + .getString("label.open_saved_vamsas_session")); chooser.setToolTipText(MessageManager .getString("label.select_vamsas_session_opened_as_new_vamsas_session")); @@ -1944,23 +1960,26 @@ public class Desktop extends jalview.jbgui.GDesktop implements return false; } - setProgressBar(MessageManager.formatMessage("status.importing_vamsas_session_from", new Object[]{file.getName()}), - file.hashCode()); + setProgressBar(MessageManager.formatMessage( + "status.importing_vamsas_session_from", new Object[] + { file.getName() }), file.hashCode()); try { v_client = new jalview.gui.VamsasApplication(this, file, null); } catch (Exception ex) { - setProgressBar(MessageManager.formatMessage("status.importing_vamsas_session_from", new Object[]{file.getName()}), - file.hashCode()); + setProgressBar(MessageManager.formatMessage( + "status.importing_vamsas_session_from", new Object[] + { file.getName() }), file.hashCode()); jalview.bin.Cache.log.error( "New vamsas session from existing session file failed:", ex); return false; } setupVamsasConnectedGui(); v_client.initial_update(); // TODO: thread ? - setProgressBar(MessageManager.formatMessage("status.importing_vamsas_session_from", new Object[]{file.getName()}), - file.hashCode()); + setProgressBar(MessageManager.formatMessage( + "status.importing_vamsas_session_from", new Object[] + { file.getName() }), file.hashCode()); return v_client.inSession(); } @@ -1968,11 +1987,14 @@ public class Desktop extends jalview.jbgui.GDesktop implements { if (v_client != null) { - throw new Error(MessageManager.getString("error.try_join_vamsas_session_another")); + throw new Error( + MessageManager + .getString("error.try_join_vamsas_session_another")); } if (mysesid == null) { - throw new Error(MessageManager.getString("error.invalid_vamsas_session_id")); + throw new Error( + MessageManager.getString("error.invalid_vamsas_session_id")); } v_client = new VamsasApplication(this, mysesid); setupVamsasConnectedGui(); @@ -2119,14 +2141,17 @@ public class Desktop extends jalview.jbgui.GDesktop implements { "Vamsas Document" }, "Vamsas Document"); chooser.setFileView(new JalviewFileView()); - chooser.setDialogTitle(MessageManager.getString("label.save_vamsas_document_archive")); + chooser.setDialogTitle(MessageManager + .getString("label.save_vamsas_document_archive")); int value = chooser.showSaveDialog(this); if (value == JalviewFileChooser.APPROVE_OPTION) { java.io.File choice = chooser.getSelectedFile(); - JPanel progpanel = addProgressPanel(MessageManager.formatMessage("label.saving_vamsas_doc", new Object[]{choice.getName()})); + JPanel progpanel = addProgressPanel(MessageManager.formatMessage( + "label.saving_vamsas_doc", new Object[] + { choice.getName() })); jalview.bin.Cache.setProperty("LAST_DIRECTORY", choice.getParent()); String warnmsg = null; String warnttl = null; @@ -2178,7 +2203,8 @@ public class Desktop extends jalview.jbgui.GDesktop implements } if (b) { - vamUpdate = this.addProgressPanel(MessageManager.getString("label.updating_vamsas_session")); + vamUpdate = this.addProgressPanel(MessageManager + .getString("label.updating_vamsas_session")); } vamsasStart.setVisible(!b); vamsasStop.setVisible(!b); @@ -2355,13 +2381,13 @@ public class Desktop extends jalview.jbgui.GDesktop implements * Also check for a split frame containing an AlignFrame */ SplitFrame sf = (SplitFrame) frames[i]; - if (sf.getTopComponent() instanceof AlignFrame) + if (sf.getTopFrame() instanceof AlignFrame) { - avp.add((AlignFrame) sf.getTopComponent()); + avp.add((AlignFrame) sf.getTopFrame()); } - if (sf.getBottomComponent() instanceof AlignFrame) + if (sf.getBottomFrame() instanceof AlignFrame) { - avp.add((AlignFrame) sf.getBottomComponent()); + avp.add((AlignFrame) sf.getBottomFrame()); } } } @@ -2412,7 +2438,9 @@ public class Desktop extends jalview.jbgui.GDesktop implements // use reflection to avoid creating compilation dependency. if (!jalview.bin.Cache.groovyJarsPresent()) { - throw new Error(MessageManager.getString("error.implementation_error_cannot_create_groovyshell")); + throw new Error( + MessageManager + .getString("error.implementation_error_cannot_create_groovyshell")); } try { @@ -2430,13 +2458,11 @@ public class Desktop extends jalview.jbgui.GDesktop implements } catch (Exception ex) { jalview.bin.Cache.log.error("Groovy Shell Creation failed.", ex); - JOptionPane - .showInternalMessageDialog( - Desktop.desktop, + JOptionPane.showInternalMessageDialog(Desktop.desktop, - MessageManager.getString("label.couldnt_create_groovy_shell"), - MessageManager.getString("label.groovy_support_failed"), - JOptionPane.ERROR_MESSAGE); + MessageManager.getString("label.couldnt_create_groovy_shell"), + MessageManager.getString("label.groovy_support_failed"), + JOptionPane.ERROR_MESSAGE); } } @@ -2486,7 +2512,9 @@ public class Desktop extends jalview.jbgui.GDesktop implements { if (progressBarHandlers == null || !progressBars.contains(new Long(id))) { - throw new Error(MessageManager.getString("error.call_setprogressbar_before_registering_handler")); + throw new Error( + MessageManager + .getString("error.call_setprogressbar_before_registering_handler")); } progressBarHandlers.put(new Long(id), handler); final JPanel progressPanel = progressBars.get(new Long(id)); @@ -2501,7 +2529,10 @@ public class Desktop extends jalview.jbgui.GDesktop implements public void actionPerformed(ActionEvent e) { handler.cancelActivity(id); - us.setProgressBar(MessageManager.formatMessage("label.cancelled_params", new Object[]{((JLabel) progressPanel.getComponent(0)).getText()}), id); + us.setProgressBar(MessageManager.formatMessage( + "label.cancelled_params", new Object[] + { ((JLabel) progressPanel.getComponent(0)).getText() }), + id); } }); progressPanel.add(cancel, BorderLayout.EAST); @@ -2763,17 +2794,18 @@ public class Desktop extends jalview.jbgui.GDesktop implements { if (progress != null) { - progress.setProgressBar(MessageManager.formatMessage("status.opening_params", new Object[]{url}), this.hashCode()); + progress.setProgressBar(MessageManager.formatMessage( + "status.opening_params", new Object[] + { url }), this.hashCode()); } jalview.util.BrowserLauncher.openURL(url); } catch (Exception ex) { - JOptionPane - .showInternalMessageDialog( - Desktop.desktop, - MessageManager.getString("label.web_browser_not_found_unix"), - MessageManager.getString("label.web_browser_not_found"), - JOptionPane.WARNING_MESSAGE); + JOptionPane.showInternalMessageDialog(Desktop.desktop, + MessageManager + .getString("label.web_browser_not_found_unix"), + MessageManager.getString("label.web_browser_not_found"), + JOptionPane.WARNING_MESSAGE); ex.printStackTrace(); } @@ -2888,6 +2920,7 @@ public class Desktop extends jalview.jbgui.GDesktop implements dialogPause = false; block.release(); } + @Override protected void snapShotWindow_actionPerformed(ActionEvent e) { @@ -2897,14 +2930,133 @@ public class Desktop extends jalview.jbgui.GDesktop implements "View of Desktop", getWidth(), getHeight(), of = new File( "Jalview_snapshot" + System.currentTimeMillis() + ".eps"), "View of desktop"); - try { + try + { paintAll(im.getGraphics()); im.writeImage(); } catch (Exception q) { - Cache.log.error("Couldn't write snapshot to "+of.getAbsolutePath(),q); + Cache.log.error("Couldn't write snapshot to " + of.getAbsolutePath(), + q); + return; + } + Cache.log.info("Successfully written snapshot to file " + + of.getAbsolutePath()); + } + + /** + * Explode the views in the given frame into separate AlignFrame + * + * @param sf + */ + public void explodeViews(SplitFrame sf) + { + AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame(); + AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame(); + List topPanels = oldTopFrame + .getAlignPanels(); + List bottomPanels = oldBottomFrame + .getAlignPanels(); + int viewCount = topPanels.size(); + if (viewCount < 2) + { return; } - Cache.log.info("Successfully written snapshot to file "+of.getAbsolutePath()); + + /* + * Processing in reverse order works, forwards order leaves the first panels + * not visible. I don't know why! + */ + for (int i = viewCount - 1; i >= 0; i--) + { + /* + * Make new top and bottom frames. These take over the respective + * AlignmentPanel objects, including their AlignmentViewports, so the + * cdna/protein relationships between the viewports is carried over to the + * new split frames. + */ + AlignFrame newTopFrame = new AlignFrame( + (AlignmentPanel) topPanels.get(i)); + newTopFrame.setVisible(true); + AlignFrame newBottomFrame = new AlignFrame( + (AlignmentPanel) bottomPanels.get(i)); + newBottomFrame.setVisible(true); + JInternalFrame splitFrame = new SplitFrame(newTopFrame, + newBottomFrame); + Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1); + + // if (ap.av.explodedPosition != null + // && !ap.av.explodedPosition.equals(af.getBounds())) + // { + // newaf.setBounds(ap.av.explodedPosition); + // } + // + // ap.av.gatherViewsHere = false; + } + + /* + * Clear references to the panels (now relocated in the new SplitFrames) + * before closing the old SplitFrame. + */ + topPanels.clear(); + bottomPanels.clear(); + sf.close(); + } + + /** + * Gather expanded split frames, sharing the same pairs of sequence set ids, + * back into the given SplitFrame as additional views. Note that the gathered + * frames may themselves have multiple views. + * + * @param source + */ + public void gatherViews(SplitFrame source) + { + // source.viewport.gatherViewsHere = true; + // source.viewport.explodedPosition = source.getBounds(); + AlignFrame myTopFrame = (AlignFrame) source.getTopFrame(); + AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame(); + String topViewId = myTopFrame.viewport.getSequenceSetId(); + String bottomViewId = myBottomFrame.viewport.getSequenceSetId(); + + JInternalFrame[] frames = desktop.getAllFrames(); + for (JInternalFrame frame : frames) + { + if (frame instanceof SplitFrame && frame != source) + { + SplitFrame sf = (SplitFrame) frame; + AlignFrame topFrame = (AlignFrame) sf.getTopFrame(); + AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame(); + boolean gatherThis = false; + for (int a = 0; a < topFrame.alignPanels.size(); a++) + { + AlignmentPanel topPanel = topFrame.alignPanels.get(a); + AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a); + if (topViewId.equals(topPanel.av.getSequenceSetId()) + && bottomViewId.equals(bottomPanel.av.getSequenceSetId())) + { + gatherThis = true; + topPanel.av.gatherViewsHere = false; + bottomPanel.av.gatherViewsHere = false; + // topPanel.av.explodedPosition = af.getBounds(); + myTopFrame.addAlignmentPanel(topPanel, false); + myBottomFrame.addAlignmentPanel(bottomPanel, false); + } + } + + if (gatherThis) + { + topFrame.getAlignPanels().clear(); + bottomFrame.getAlignPanels().clear(); + sf.close(); + } + } + } + + /* + * The dust settles...give focus to the tab we did this from. + */ + myTopFrame.setDisplayedView(myTopFrame.alignPanel); + } } diff --git a/src/jalview/gui/SplitFrame.java b/src/jalview/gui/SplitFrame.java index f98eea7..ab4596e 100644 --- a/src/jalview/gui/SplitFrame.java +++ b/src/jalview/gui/SplitFrame.java @@ -1,19 +1,24 @@ package jalview.gui; +import jalview.jbgui.GAlignFrame; import jalview.jbgui.GSplitFrame; +import jalview.structure.StructureSelectionManager; 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; @@ -24,7 +29,7 @@ public class SplitFrame extends GSplitFrame { private static final long serialVersionUID = 1L; - public SplitFrame(JComponent top, JComponent bottom) + public SplitFrame(GAlignFrame top, GAlignFrame bottom) { super(top, bottom); init(); @@ -42,7 +47,6 @@ public class SplitFrame extends GSplitFrame addKeyListener(); addKeyBindings(); - } /** @@ -55,14 +59,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 +79,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 +104,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,20 +118,68 @@ public class SplitFrame extends GSplitFrame } /** + * 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 boolean overrideKey(KeyEvent e, AlignFrame af) + { + boolean actioned = false; + int keyCode = e.getKeyCode(); + switch (keyCode) + { + 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 actioned; + } + + /** * Returns the split pane component the mouse is in, or null if neither. * * @return */ - protected Component getComponentAtMouse() + protected GAlignFrame getFrameAtMouse() { Point loc = MouseInfo.getPointerInfo().getLocation(); - if (isIn(loc, getTopComponent())) { - return getTopComponent(); + if (isIn(loc, getTopFrame())) + { + return getTopFrame(); } - else if (isIn(loc, getBottomComponent())) + else if (isIn(loc, getBottomFrame())) { - return getBottomComponent(); + return getBottomFrame(); } return null; } @@ -134,86 +192,299 @@ public class SplitFrame extends GSplitFrame } /** - * Set key bindings (recommended for Swing over key accelerators). For now, - * delegate 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. + * Set key bindings (recommended for Swing over key accelerators). */ private void addKeyBindings() { - if (getTopComponent() instanceof AlignFrame) + overrideDelegatedKeyBindings(); + + overrideImplementedKeyBindings(); + } + + /** + * Override key bindings with alternative action methods implemented in this + * class. + */ + protected void overrideImplementedKeyBindings() + { + overrideNewView(); + overrideCloseView(); + overrideExpandViews(); + overrideGatherViews(); + } + + /** + * Replace Cmd-W close view action with our version. + */ + protected void overrideCloseView() + { + AbstractAction action; + /* + * Ctrl-W / Cmd-W - close view or window + */ + KeyStroke key_cmdW = KeyStroke.getKeyStroke(KeyEvent.VK_W, Toolkit + .getDefaultToolkit().getMenuShortcutKeyMask(), false); + action = new AbstractAction() + { + @Override + public void actionPerformed(ActionEvent e) + { + 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) { - for (Entry acc : ((AlignFrame) getTopComponent()) + /* + * Get all accelerator keys in the top frame (the bottom should be + * identical) and override each one. + */ + for (Entry acc : ((AlignFrame) getTopFrame()) .getAccelerators().entrySet()) { + overrideKeyBinding(acc); + } + } + } - final KeyStroke ks = acc.getKey(); - this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(ks, ks); - this.getActionMap().put(ks, new AbstractAction() + /** + * Overrides an AlignFrame key accelerator with our version which delegates to + * the action listener in whichever frame has the mouse (and does nothing if + * neither has). + * + * @param acc + */ + private void overrideKeyBinding(Entry acc) + { + final KeyStroke ks = acc.getKey(); + InputMap inputMap = this.getInputMap(JComponent.WHEN_FOCUSED); + inputMap.put(ks, ks); + this.getActionMap().put(ks, new AbstractAction() + { + @Override + public void actionPerformed(ActionEvent e) + { + Component c = getFrameAtMouse(); + if (c != null && c instanceof AlignFrame) { - @Override - public void actionPerformed(ActionEvent e) + for (ActionListener a : ((AlignFrame) c).getAccelerators() + .get(ks).getActionListeners()) { - Component c = getComponentAtMouse(); - if (c instanceof AlignFrame) - { - for (ActionListener a : ((AlignFrame) c).getAccelerators() - .get(ks).getActionListeners()) - { - - a.actionPerformed(null); - } - } + a.actionPerformed(null); } - }); + } } - /* - * Disable unwanted here - */ - // X expand views - wrecks the split pane view - KeyStroke key_X = KeyStroke.getKeyStroke(KeyEvent.VK_X, 0, false); - disableAccelerator(key_X); + }); + } + + /** + * Replace an accelerator key's action with the specified action. + * + * @param ks + */ + protected void overrideKeyBinding(KeyStroke ks, AbstractAction action) + { + this.getActionMap().put(ks, action); + overrideMenuItem(ks, action); + } + + /** + * Create and link new views (with matching names) in both panes. + *

+ * 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. + */ + protected void newView_actionPerformed() + { + System.out.println("newView " + this.hashCode()); + 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()) + { + topFrame.setDisplayedView(newTopPanel); + } + + newBottomPanel.av.viewName = newTopPanel.av.viewName; + newTopPanel.av.setCodingComplement(newBottomPanel.av); + + final StructureSelectionManager ssm = StructureSelectionManager + .getStructureSelectionManager(Desktop.instance); + ssm.addCommandListener(newTopPanel.av); + ssm.addCommandListener(newBottomPanel.av); + } + + /** + * Close the currently selected view in both panes. If there is only one view, + * close this split frame. + */ + protected void closeView_actionPerformed() + { + int viewCount = ((AlignFrame) getTopFrame()).getAlignPanels().size(); + if (viewCount < 2) + { + close(); + return; + } + + AlignmentPanel topPanel = ((AlignFrame) getTopFrame()).alignPanel; + AlignmentPanel bottomPanel = ((AlignFrame) getBottomFrame()).alignPanel; + + ((AlignFrame) getTopFrame()).closeView(topPanel); + ((AlignFrame) getBottomFrame()).closeView(bottomPanel); + + } + + /** + * Close child frames and this split frame. + */ + public void close() + { + ((AlignFrame) getTopFrame()).closeMenuItem_actionPerformed(true); + ((AlignFrame) getBottomFrame()).closeMenuItem_actionPerformed(true); + try + { + this.setClosed(true); + } catch (PropertyVetoException e) + { + // ignore } } /** - * Ugly hack for Proof of Concept that disables the key binding in this frame - * _and_ disables the bound menu item _and_ removes the key accelerator in the - * child frames. + * Replace AlignFrame 'expand views' action with SplitFrame version. + */ + protected void overrideExpandViews() + { + KeyStroke key_X = KeyStroke.getKeyStroke(KeyEvent.VK_X, 0, false); + AbstractAction action = new AbstractAction() + { + @Override + public void actionPerformed(ActionEvent e) + { + expandViews_actionPerformed(); + } + }; + overrideMenuItem(key_X, action); + } + + /** + * Replace AlignFrame 'gather views' action with SplitFrame version. + */ + protected void overrideGatherViews() + { + KeyStroke key_G = KeyStroke.getKeyStroke(KeyEvent.VK_G, 0, false); + AbstractAction action = new AbstractAction() + { + @Override + public void actionPerformed(ActionEvent e) + { + gatherViews_actionPerformed(); + } + }; + overrideMenuItem(key_G, action); + } + + /** + * Override the menu action associated with the keystroke in the child frames, + * replacing it with the given action. * - * @param key + * @param ks + * @param action */ - protected void disableAccelerator(KeyStroke key) + private void overrideMenuItem(KeyStroke ks, AbstractAction action) { - disableAccelerator(key, getTopComponent()); - disableAccelerator(key, getBottomComponent()); - this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).remove(key); + overrideMenuItem(ks, action, getTopFrame()); + overrideMenuItem(ks, action, getBottomFrame()); } /** - * Disable the menu item for which this key is the accelerator, also removes - * its action listeners to prevent key accelerator working. + * Override the menu action associated with the keystroke in one child frame, + * replacing it with the given action. Mwahahahaha. * * @param key + * @param action * @param comp */ - private void disableAccelerator(KeyStroke key, JComponent comp) + private void overrideMenuItem(KeyStroke key, final AbstractAction action, + JComponent comp) { - // HACKED ONLY FOR PROOF OF CONCEPT - // Proper solution might involve explicit 'configure menu' method on - // AlignFrame, or - // changing key listeners to key bindings in AlignFrame, or both if (comp instanceof AlignFrame) { JMenuItem mi = ((AlignFrame) comp).getAccelerators().get(key); if (mi != null) { - mi.setEnabled(false); for (ActionListener al : mi.getActionListeners()) { mi.removeActionListener(al); } + mi.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + action.actionPerformed(e); + } + }); } } } + + /** + * Expand any multiple views (which are always in pairs) into separate split + * frames. + */ + protected void expandViews_actionPerformed() + { + Desktop.instance.explodeViews(this); + } + + /** + * Gather any other SplitFrame views of this alignment back in as multiple + * (pairs of) views in this SplitFrame. + */ + protected void gatherViews_actionPerformed() + { + Desktop.instance.gatherViews(this); + } } diff --git a/src/jalview/jbgui/GAlignFrame.java b/src/jalview/jbgui/GAlignFrame.java index 1cfc73c..2d23e6b 100755 --- a/src/jalview/jbgui/GAlignFrame.java +++ b/src/jalview/jbgui/GAlignFrame.java @@ -3253,4 +3253,15 @@ public class GAlignFrame extends JInternalFrame { return this.accelerators; } + + /** + * Returns the selected index of the tabbed pane, or -1 if none selected + * (including the case where the tabbed pane has not been made visible). + * + * @return + */ + public int getTabIndex() + { + return tabbedPane.getSelectedIndex(); + } } diff --git a/src/jalview/jbgui/GSplitFrame.java b/src/jalview/jbgui/GSplitFrame.java index 281a93e..40cce0d 100644 --- a/src/jalview/jbgui/GSplitFrame.java +++ b/src/jalview/jbgui/GSplitFrame.java @@ -1,6 +1,5 @@ package jalview.jbgui; -import javax.swing.JComponent; import javax.swing.JInternalFrame; import javax.swing.JSplitPane; @@ -8,9 +7,9 @@ public class GSplitFrame extends JInternalFrame { private static final long serialVersionUID = 1L; - private JComponent topComponent; + private GAlignFrame topFrame; - private JComponent bottomComponent; + private GAlignFrame bottomFrame; /** * Constructor @@ -18,10 +17,10 @@ public class GSplitFrame extends JInternalFrame * @param top * @param bottom */ - public GSplitFrame(JComponent top, JComponent bottom) + public GSplitFrame(GAlignFrame top, GAlignFrame bottom) { - this.topComponent = top; - this.bottomComponent = bottom; + this.topFrame = top; + this.bottomFrame = bottom; JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, top, bottom); splitPane.setVisible(true); @@ -31,13 +30,13 @@ public class GSplitFrame extends JInternalFrame splitPane.setDividerSize(0); } - public JComponent getTopComponent() + public GAlignFrame getTopFrame() { - return topComponent; + return topFrame; } - public JComponent getBottomComponent() + public GAlignFrame getBottomFrame() { - return bottomComponent; + return bottomFrame; } } diff --git a/test/jalview/structure/StructureSelectionManagerTest.java b/test/jalview/structure/StructureSelectionManagerTest.java new file mode 100644 index 0000000..487ef2c --- /dev/null +++ b/test/jalview/structure/StructureSelectionManagerTest.java @@ -0,0 +1,175 @@ +package jalview.structure; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import jalview.datamodel.AlignedCodonFrame; + +import java.util.HashSet; +import java.util.Set; + +import org.junit.Before; +import org.junit.Test; + +public class StructureSelectionManagerTest +{ + private StructureSelectionManager ssm; + + @Before + public void setUp() + { + ssm = new StructureSelectionManager(); + } + + @Test + public void testAddMapping() + { + AlignedCodonFrame acf1 = new AlignedCodonFrame(); + AlignedCodonFrame acf2 = new AlignedCodonFrame(); + + /* + * One mapping only. + */ + ssm.addMapping(acf1); + assertEquals(1, ssm.seqmappings.size()); + assertTrue(ssm.seqmappings.contains(acf1)); + assertEquals(1, ssm.seqMappingRefCounts.size()); + assertEquals(1, ssm.seqMappingRefCounts.get(acf1).intValue()); + + /* + * A second mapping. + */ + ssm.addMapping(acf2); + assertEquals(2, ssm.seqmappings.size()); + assertTrue(ssm.seqmappings.contains(acf1)); + assertTrue(ssm.seqmappings.contains(acf2)); + assertEquals(2, ssm.seqMappingRefCounts.size()); + assertEquals(1, ssm.seqMappingRefCounts.get(acf1).intValue()); + assertEquals(1, ssm.seqMappingRefCounts.get(acf2).intValue()); + + /* + * A second reference to the first mapping. + */ + ssm.addMapping(acf1); + assertEquals(2, ssm.seqmappings.size()); + assertTrue(ssm.seqmappings.contains(acf1)); + assertTrue(ssm.seqmappings.contains(acf2)); + assertEquals(2, ssm.seqMappingRefCounts.size()); + assertEquals(2, ssm.seqMappingRefCounts.get(acf1).intValue()); + assertEquals(1, ssm.seqMappingRefCounts.get(acf2).intValue()); + } + + @Test + public void testAddMappings() + { + AlignedCodonFrame acf1 = new AlignedCodonFrame(); + AlignedCodonFrame acf2 = new AlignedCodonFrame(); + AlignedCodonFrame acf3 = new AlignedCodonFrame(); + + Set set1 = new HashSet(); + set1.add(acf1); + set1.add(acf2); + Set set2 = new HashSet(); + set2.add(acf2); + set2.add(acf3); + + /* + * Adding both sets adds acf2 twice and acf1 and acf3 once each. + */ + ssm.addMappings(set1); + ssm.addMappings(set2); + + assertEquals(3, ssm.seqmappings.size()); + assertTrue(ssm.seqmappings.contains(acf1)); + assertTrue(ssm.seqmappings.contains(acf2)); + assertTrue(ssm.seqmappings.contains(acf3)); + assertEquals(3, ssm.seqMappingRefCounts.size()); + assertEquals(1, ssm.seqMappingRefCounts.get(acf1).intValue()); + assertEquals(2, ssm.seqMappingRefCounts.get(acf2).intValue()); + assertEquals(1, ssm.seqMappingRefCounts.get(acf3).intValue()); + } + + @Test + public void testRemoveMapping() + { + AlignedCodonFrame acf1 = new AlignedCodonFrame(); + AlignedCodonFrame acf2 = new AlignedCodonFrame(); + ssm.addMapping(acf1); + + /* + * Add one and remove it. + */ + ssm.removeMapping(acf1); + ssm.removeMapping(acf2); + assertEquals(0, ssm.seqmappings.size()); + assertEquals(0, ssm.seqMappingRefCounts.size()); + + /* + * Add one twice and remove it once. + */ + ssm.addMapping(acf1); + ssm.addMapping(acf2); + ssm.addMapping(acf1); + ssm.removeMapping(acf1); + assertEquals(2, ssm.seqmappings.size()); + assertTrue(ssm.seqmappings.contains(acf1)); + assertTrue(ssm.seqmappings.contains(acf2)); + assertEquals(2, ssm.seqMappingRefCounts.size()); + assertEquals(1, ssm.seqMappingRefCounts.get(acf1).intValue()); + assertEquals(1, ssm.seqMappingRefCounts.get(acf2).intValue()); + + /* + * Remove both once more to clear the set. + */ + ssm.removeMapping(acf1); + ssm.removeMapping(acf2); + assertEquals(0, ssm.seqmappings.size()); + assertEquals(0, ssm.seqMappingRefCounts.size()); + } + + @Test + public void testRemoveMappings() + { + AlignedCodonFrame acf1 = new AlignedCodonFrame(); + AlignedCodonFrame acf2 = new AlignedCodonFrame(); + AlignedCodonFrame acf3 = new AlignedCodonFrame(); + + /* + * Initial ref counts are 3/2/1: + */ + ssm.addMapping(acf1); + ssm.addMapping(acf1); + ssm.addMapping(acf1); + ssm.addMapping(acf2); + ssm.addMapping(acf2); + ssm.addMapping(acf3); + + Set set1 = new HashSet(); + set1.add(acf1); + set1.add(acf2); + Set set2 = new HashSet(); + set2.add(acf2); + set2.add(acf3); + + /* + * Remove one ref each to acf1, acf2, counts are now 2/1/1: + */ + ssm.removeMappings(set1); + assertEquals(3, ssm.seqmappings.size()); + assertTrue(ssm.seqmappings.contains(acf1)); + assertTrue(ssm.seqmappings.contains(acf2)); + assertTrue(ssm.seqmappings.contains(acf3)); + assertEquals(3, ssm.seqMappingRefCounts.size()); + assertEquals(2, ssm.seqMappingRefCounts.get(acf1).intValue()); + assertEquals(1, ssm.seqMappingRefCounts.get(acf2).intValue()); + assertEquals(1, ssm.seqMappingRefCounts.get(acf3).intValue()); + + /* + * Remove one ref each to acf2, acf3 - they are removed + */ + ssm.removeMappings(set2); + assertEquals(1, ssm.seqmappings.size()); + assertTrue(ssm.seqmappings.contains(acf1)); + assertEquals(1, ssm.seqMappingRefCounts.size()); + assertEquals(2, ssm.seqMappingRefCounts.get(acf1).intValue()); + } +}