JAL-3116 parse EMBL XML with JAXB (todo: update unit tests)
[jalview.git] / src / jalview / gui / SplitFrame.java
index 4e2187e..a0d31cf 100644 (file)
@@ -1,6 +1,6 @@
 /*
- * Jalview - A Sequence Alignment Editor and Viewer (Version 2.9)
- * Copyright (C) 2015 The Jalview Authors
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
  * 
  * This file is part of Jalview.
  * 
 package jalview.gui;
 
 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.util.Platform;
 import jalview.viewmodel.AlignmentViewport;
 
 import java.awt.Component;
@@ -36,6 +36,8 @@ import java.awt.event.KeyAdapter;
 import java.awt.event.KeyEvent;
 import java.awt.event.KeyListener;
 import java.beans.PropertyVetoException;
+import java.util.Arrays;
+import java.util.List;
 import java.util.Map.Entry;
 
 import javax.swing.AbstractAction;
@@ -60,6 +62,16 @@ import javax.swing.event.InternalFrameEvent;
  */
 public class SplitFrame extends GSplitFrame implements SplitContainerI
 {
+  private static final int WINDOWS_INSETS_WIDTH = 28; // tbc
+
+  private static final int MAC_INSETS_WIDTH = 28;
+
+  private static final int WINDOWS_INSETS_HEIGHT = 50; // tbc
+
+  private static final int MAC_INSETS_HEIGHT = 50;
+
+  private static final int DESKTOP_DECORATORS_HEIGHT = 65;
+
   private static final long serialVersionUID = 1L;
 
   public SplitFrame(GAlignFrame top, GAlignFrame bottom)
@@ -81,12 +93,19 @@ public class SplitFrame extends GSplitFrame implements SplitContainerI
     ((AlignFrame) getTopFrame()).getViewport().setCodingComplement(
             ((AlignFrame) getBottomFrame()).getViewport());
 
-    int width = ((AlignFrame) getTopFrame()).getWidth();
-    // about 50 pixels for the SplitFrame's title bar etc
+    /*
+     * estimate width and height of SplitFrame; this.getInsets() doesn't seem to
+     * give the full additional size (a few pixels short)
+     */
+    int widthFudge = Platform.isAMac() ? MAC_INSETS_WIDTH
+            : WINDOWS_INSETS_WIDTH;
+    int heightFudge = Platform.isAMac() ? MAC_INSETS_HEIGHT
+            : WINDOWS_INSETS_HEIGHT;
+    int width = ((AlignFrame) getTopFrame()).getWidth() + widthFudge;
     int height = ((AlignFrame) getTopFrame()).getHeight()
-            + ((AlignFrame) getBottomFrame()).getHeight() + 50;
-    // about 65 pixels for Desktop decorators on Windows
-    height = Math.min(height, Desktop.instance.getHeight() - 65);
+            + ((AlignFrame) getBottomFrame()).getHeight() + DIVIDER_SIZE
+            + heightFudge;
+    height = fitHeightToDesktop(height);
     setSize(width, height);
 
     adjustLayout();
@@ -101,6 +120,28 @@ public class SplitFrame extends GSplitFrame implements SplitContainerI
   }
 
   /**
+   * Reduce the height if too large to fit in the Desktop. Also adjust the
+   * divider location in proportion.
+   * 
+   * @param height
+   *          in pixels
+   * @return original or reduced height
+   */
+  public int fitHeightToDesktop(int height)
+  {
+    // allow about 65 pixels for Desktop decorators on Windows
+
+    int newHeight = Math.min(height,
+            Desktop.instance.getHeight() - DESKTOP_DECORATORS_HEIGHT);
+    if (newHeight != height)
+    {
+      int oldDividerLocation = getDividerLocation();
+      setDividerLocation(oldDividerLocation * newHeight / height);
+    }
+    return newHeight;
+  }
+
+  /**
    * Set the top and bottom frames to listen to each others Commands (e.g. Edit,
    * Order).
    */
@@ -147,14 +188,90 @@ public class SplitFrame extends GSplitFrame implements SplitContainerI
             : (!bottomAlignment.isNucleotide() ? bottomViewport : null);
     if (protein != null && cdna != null)
     {
-      ViewStyleI vs = protein.getViewStyle();
-      int scale = vs.isScaleProteinAsCdna() ? 3 : 1;
-      vs.setCharWidth(scale * cdna.getViewStyle().getCharWidth());
-      protein.setViewStyle(vs);
+      int scale = protein.isScaleProteinAsCdna() ? 3 : 1;
+      protein.setCharWidth(scale * cdna.getViewStyle().getCharWidth());
     }
   }
 
   /**
+   * Adjusts the divider for a sensible split of the real estate (for example,
+   * when many transcripts are shown with a single protein). This should only be
+   * called after the split pane has been laid out (made visible) so it has a
+   * height. The aim is to avoid unnecessary vertical scroll bars, while
+   * ensuring that at least 2 sequences are visible in each panel.
+   * <p>
+   * Once laid out, the user may choose to customise as they wish, so this
+   * method is not called again after the initial layout.
+   */
+  protected void adjustInitialLayout()
+  {
+    AlignFrame topFrame = (AlignFrame) getTopFrame();
+    AlignFrame bottomFrame = (AlignFrame) getBottomFrame();
+
+    /*
+     * recompute layout of top and bottom panels to reflect their
+     * actual (rather than requested) height
+     */
+    topFrame.alignPanel.adjustAnnotationHeight();
+    bottomFrame.alignPanel.adjustAnnotationHeight();
+
+    final AlignViewport topViewport = topFrame.viewport;
+    final AlignViewport bottomViewport = bottomFrame.viewport;
+    final AlignmentI topAlignment = topViewport.getAlignment();
+    final AlignmentI bottomAlignment = bottomViewport.getAlignment();
+    boolean topAnnotations = topViewport.isShowAnnotation();
+    boolean bottomAnnotations = bottomViewport.isShowAnnotation();
+    // TODO need number of visible sequences here, not #sequences - how?
+    int topCount = topAlignment.getHeight();
+    int bottomCount = bottomAlignment.getHeight();
+    int topCharHeight = topViewport.getViewStyle().getCharHeight();
+    int bottomCharHeight = bottomViewport.getViewStyle().getCharHeight();
+
+    /*
+     * calculate the minimum ratio that leaves at least the height 
+     * of two sequences (after rounding) visible in the top panel
+     */
+    int topPanelHeight = topFrame.getHeight();
+    int bottomPanelHeight = bottomFrame.getHeight();
+    int topSequencesHeight = topFrame.alignPanel.getSeqPanel().seqCanvas
+            .getHeight();
+    int topPanelMinHeight = topPanelHeight
+            - Math.max(0, topSequencesHeight - 3 * topCharHeight);
+    double totalHeight = (double) topPanelHeight + bottomPanelHeight;
+    double minRatio = topPanelMinHeight / totalHeight;
+
+    /*
+     * calculate the maximum ratio that leaves at least the height 
+     * of two sequences (after rounding) visible in the bottom panel
+     */
+    int bottomSequencesHeight = bottomFrame.alignPanel.getSeqPanel().seqCanvas
+            .getHeight();
+    int bottomPanelMinHeight = bottomPanelHeight
+            - Math.max(0, bottomSequencesHeight - 3 * bottomCharHeight);
+    double maxRatio = (totalHeight - bottomPanelMinHeight) / totalHeight;
+
+    /*
+     * estimate ratio of (topFrameContent / bottomFrameContent)
+     */
+    int insets = Platform.isAMac() ? MAC_INSETS_HEIGHT
+            : WINDOWS_INSETS_HEIGHT;
+    // allow 3 'rows' for scale, scrollbar, status bar
+    int topHeight = insets + (3 + topCount) * topCharHeight
+            + (topAnnotations ? topViewport.calcPanelHeight() : 0);
+    int bottomHeight = insets + (3 + bottomCount) * bottomCharHeight
+            + (bottomAnnotations ? bottomViewport.calcPanelHeight() : 0);
+    double ratio = ((double) topHeight)
+            / (double) (topHeight + bottomHeight);
+
+    /*
+     * limit ratio to avoid concealing all sequences
+     */
+    ratio = Math.min(ratio, maxRatio);
+    ratio = Math.max(ratio, minRatio);
+    setRelativeDividerLocation(ratio);
+  }
+
+  /**
    * Add a listener to tidy up when the frame is closed.
    */
   protected void addCloseFrameListener()
@@ -256,6 +373,7 @@ public class SplitFrame extends GSplitFrame implements SplitContainerI
         actioned = true;
         e.consume();
       }
+      break;
     default:
     }
     return actioned;
@@ -293,8 +411,8 @@ public class SplitFrame extends GSplitFrame implements SplitContainerI
     /*
      * Ctrl-W / Cmd-W - close view or window
      */
-    KeyStroke key_cmdW = KeyStroke.getKeyStroke(KeyEvent.VK_W, Toolkit
-            .getDefaultToolkit().getMenuShortcutKeyMask(), false);
+    KeyStroke key_cmdW = KeyStroke.getKeyStroke(KeyEvent.VK_W,
+            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
     action = new AbstractAction()
     {
       @Override
@@ -314,8 +432,8 @@ public class SplitFrame extends GSplitFrame implements SplitContainerI
     /*
      * Ctrl-T / Cmd-T open new view
      */
-    KeyStroke key_cmdT = KeyStroke.getKeyStroke(KeyEvent.VK_T, Toolkit
-            .getDefaultToolkit().getMenuShortcutKeyMask(), false);
+    KeyStroke key_cmdT = KeyStroke.getKeyStroke(KeyEvent.VK_T,
+            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
     AbstractAction action = new AbstractAction()
     {
       @Override
@@ -368,8 +486,8 @@ public class SplitFrame extends GSplitFrame implements SplitContainerI
         Component c = getFrameAtMouse();
         if (c != null && c instanceof AlignFrame)
         {
-          for (ActionListener a : ((AlignFrame) c).getAccelerators()
-                  .get(ks).getActionListeners())
+          for (ActionListener a : ((AlignFrame) c).getAccelerators().get(ks)
+                  .getActionListeners())
           {
             a.actionPerformed(null);
           }
@@ -421,7 +539,7 @@ public class SplitFrame extends GSplitFrame implements SplitContainerI
       topFrame.setDisplayedView(newTopPanel);
     }
 
-    newBottomPanel.av.viewName = newTopPanel.av.viewName;
+    newBottomPanel.av.setViewName(newTopPanel.av.getViewName());
     newTopPanel.av.setCodingComplement(newBottomPanel.av);
 
     /*
@@ -632,6 +750,19 @@ public class SplitFrame extends GSplitFrame implements SplitContainerI
   }
 
   /**
+   * return the AlignFrames held by this container
+   * 
+   * @return { Top alignFrame (Usually CDS), Bottom AlignFrame (Usually
+   *         Protein)}
+   */
+  public List<AlignFrame> getAlignFrames()
+  {
+    return Arrays
+            .asList(new AlignFrame[]
+            { (AlignFrame) getTopFrame(), (AlignFrame) getBottomFrame() });
+  }
+
+  /**
    * Replace Cmd-F Find action with our version. This is necessary because the
    * 'default' Finder searches in the first AlignFrame it finds. We need it to
    * search in the half of the SplitFrame that has the mouse.
@@ -641,8 +772,8 @@ public class SplitFrame extends GSplitFrame implements SplitContainerI
     /*
      * Ctrl-F / Cmd-F open Finder dialog, 'focused' on the right alignment
      */
-    KeyStroke key_cmdF = KeyStroke.getKeyStroke(KeyEvent.VK_F, Toolkit
-            .getDefaultToolkit().getMenuShortcutKeyMask(), false);
+    KeyStroke key_cmdF = KeyStroke.getKeyStroke(KeyEvent.VK_F,
+            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
     AbstractAction action = new AbstractAction()
     {
       @Override