Merge branch 'bug/JAL-2983negativeSliderMin' into releases/Release_2_11_1_Branch
[jalview.git] / src / jalview / gui / FeatureSettings.java
index 441dca7..aeac959 100644 (file)
  */
 package jalview.gui;
 
+import jalview.api.AlignViewControllerGuiI;
 import jalview.api.AlignViewportI;
 import jalview.api.FeatureColourI;
 import jalview.api.FeatureSettingsControllerI;
+import jalview.api.SplitContainerI;
 import jalview.api.ViewStyleI;
+import jalview.controller.FeatureSettingsControllerGuiI;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.SequenceI;
 import jalview.datamodel.features.FeatureMatcher;
@@ -112,7 +115,7 @@ import javax.xml.stream.XMLInputFactory;
 import javax.xml.stream.XMLStreamReader;
 
 public class FeatureSettings extends JPanel
-        implements FeatureSettingsControllerI
+        implements FeatureSettingsControllerI, FeatureSettingsControllerGuiI
 {
   private static final String SEQUENCE_FEATURE_COLOURS = MessageManager
           .getString("label.sequence_feature_colours");
@@ -159,7 +162,11 @@ public class FeatureSettings extends JPanel
 
   JPanel groupPanel;
 
-  JSlider transparency = new JSlider();
+  JSlider transparency= new JSlider();
+
+  private JCheckBox showComplementOnTop;
+
+  private JCheckBox showComplement;
 
   /*
    * when true, constructor is still executing - so ignore UI events
@@ -176,10 +183,36 @@ public class FeatureSettings extends JPanel
   private boolean handlingUpdate = false;
 
   /*
+   * a change listener to ensure the dialog is updated if
+   * FeatureRenderer discovers new features
+   */
+  private PropertyChangeListener change;
+
+  /*
    * holds {featureCount, totalExtent} for each feature type
    */
   Map<String, float[]> typeWidth = null;
 
+  private void storeOriginalSettings()
+  {
+    // save transparency for restore on Cancel
+    originalTransparency = fr.getTransparency();
+
+    updateTransparencySliderFromFR();
+
+    originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
+    originalViewStyle = new ViewStyle(af.viewport.getViewStyle());
+  }
+
+  private void updateTransparencySliderFromFR()
+  {
+    boolean incon = inConstruction;
+    inConstruction = true;
+
+    int transparencyAsPercent = (int) (fr.getTransparency() * 100);
+    transparency.setValue(100 - transparencyAsPercent);
+    inConstruction = incon;
+  }
   /**
    * Constructor
    * 
@@ -190,13 +223,7 @@ public class FeatureSettings extends JPanel
     this.af = alignFrame;
     fr = af.getFeatureRenderer();
 
-    // save transparency for restore on Cancel
-    originalTransparency = fr.getTransparency();
-    int originalTransparencyAsPercent = (int) (originalTransparency * 100);
-    transparency.setMaximum(100 - originalTransparencyAsPercent);
-
-    originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
-    originalViewStyle = new ViewStyle(af.viewport.getViewStyle());
+    storeOriginalSettings();
 
     try
     {
@@ -282,7 +309,8 @@ public class FeatureSettings extends JPanel
       @Override
       public void mousePressed(MouseEvent evt)
       {
-        selectedRow = table.rowAtPoint(evt.getPoint());
+        Point pt = evt.getPoint();
+        selectedRow = table.rowAtPoint(pt);
         String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
         if (evt.isPopupTrigger())
         {
@@ -290,7 +318,8 @@ public class FeatureSettings extends JPanel
           popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
                   evt.getY());
         }
-        else if (evt.getClickCount() == 2)
+        else if (evt.getClickCount() == 2
+                && table.columnAtPoint(pt) == TYPE_COLUMN)
         {
           boolean invertSelection = evt.isAltDown();
           boolean toggleSelection = Platform.isControlDown(evt);
@@ -354,7 +383,6 @@ public class FeatureSettings extends JPanel
     }
 
     discoverAllFeatureData();
-    final PropertyChangeListener change;
     final FeatureSettings fs = this;
     fr.addPropertyChangeListener(change = new PropertyChangeListener()
     {
@@ -371,37 +399,88 @@ public class FeatureSettings extends JPanel
       }
 
     });
-
-    frame = new JInternalFrame();
-    frame.setContentPane(this);
-    if (Platform.isAMac())
+    SplitContainerI splitframe = af.getSplitViewContainer();
+    if (splitframe != null)
     {
-      Desktop.addInternalFrame(frame,
-              MessageManager.getString("label.sequence_feature_settings"),
-              600, 480);
+      frame = null; // keeps eclipse happy
+      splitframe.addFeatureSettingsUI(this);
     }
     else
     {
-      Desktop.addInternalFrame(frame,
-              MessageManager.getString("label.sequence_feature_settings"),
-              600, 450);
-    }
-    frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
+      frame = new JInternalFrame();
+      frame.setContentPane(this);
+      Rectangle bounds = af.getFeatureSettingsGeometry();
+      String title;
+      if (af.getAlignPanels().size() > 1 || Desktop.getAlignmentPanels(
+              af.alignPanel.av.getSequenceSetId()).length > 1)
+      {
+        title = MessageManager.formatMessage(
+                "label.sequence_feature_settings_for_view",
+                af.alignPanel.getViewName());
+      }
+      else
+      {
+        title = MessageManager.getString("label.sequence_feature_settings");
+      }
+      if (bounds == null)
+      {
+        if (Platform.isAMac())
+        {
+          Desktop.addInternalFrame(frame, title, 600, 480);
+        }
+        else
+        {
+          Desktop.addInternalFrame(frame, title, 600, 450);
+        }
+      }
+      else
+      {
+        Desktop.addInternalFrame(frame, title,
+                false, bounds.width, bounds.height);
+        frame.setBounds(bounds);
+        frame.setVisible(true);
+      }
+      frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
 
-    frame.addInternalFrameListener(
-            new javax.swing.event.InternalFrameAdapter()
-            {
-              @Override
-              public void internalFrameClosed(
-                      javax.swing.event.InternalFrameEvent evt)
+      frame.addInternalFrameListener(
+              new javax.swing.event.InternalFrameAdapter()
               {
-                fr.removePropertyChangeListener(change);
-              };
-            });
-    frame.setLayer(JLayeredPane.PALETTE_LAYER);
+                @Override
+                public void internalFrameClosed(
+                        javax.swing.event.InternalFrameEvent evt)
+                {
+                  featureSettings_isClosed();
+                };
+              });
+      frame.setLayer(JLayeredPane.PALETTE_LAYER);
+    }
     inConstruction = false;
   }
 
+  /**
+   * Sets the state of buttons to show complement features from viewport
+   * settings
+   */
+  private void updateComplementButtons()
+  {
+    showComplement.setSelected(af.getViewport().isShowComplementFeatures());
+    showComplementOnTop
+            .setSelected(af.getViewport().isShowComplementFeaturesOnTop());
+  }
+
+  @Override
+  public AlignViewControllerGuiI getAlignframe()
+  {
+    return af;
+  }
+
+  @Override
+  public void featureSettings_isClosed()
+  {
+    fr.removePropertyChangeListener(change);
+    change = null;
+  }
+
   protected void popupSort(final int rowSelected, final String type,
           final Object typeCol, final Map<String, float[][]> minmax, int x,
           int y)
@@ -412,32 +491,23 @@ public class FeatureSettings extends JPanel
     JMenuItem scr = new JMenuItem(
             MessageManager.getString("label.sort_by_score"));
     men.add(scr);
-    final FeatureSettings me = this;
     scr.addActionListener(new ActionListener()
     {
-
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        me.af.avc
-                .sortAlignmentByFeatureScore(Arrays.asList(new String[]
-                { type }));
+        sortByScore(Arrays.asList(new String[] { type }));
       }
-
     });
     JMenuItem dens = new JMenuItem(
             MessageManager.getString("label.sort_by_density"));
     dens.addActionListener(new ActionListener()
     {
-
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        me.af.avc
-                .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
-                { type }));
+        sortByDensity(Arrays.asList(new String[] { type }));
       }
-
     });
     men.add(dens);
 
@@ -494,6 +564,47 @@ public class FeatureSettings extends JPanel
     men.show(table, x, y);
   }
 
+  /**
+   * Sort the sequences in the alignment by the number of features for the given
+   * feature types (or all features if null)
+   * 
+   * @param featureTypes
+   */
+  protected void sortByDensity(List<String> featureTypes)
+  {
+    af.avc.sortAlignmentByFeatureDensity(featureTypes);
+  }
+
+  /**
+   * Sort the sequences in the alignment by average score for the given feature
+   * types (or all features if null)
+   * 
+   * @param featureTypes
+   */
+  protected void sortByScore(List<String> featureTypes)
+  {
+    af.avc.sortAlignmentByFeatureScore(featureTypes);
+  }
+
+  /**
+   * Returns true if at least one feature type is visible. Else shows a warning
+   * dialog and returns false.
+   * 
+   * @param title
+   * @return
+   */
+  private boolean canSortBy(String title)
+  {
+    if (fr.getDisplayedFeatureTypes().isEmpty())
+    {
+      JvOptionPane.showMessageDialog(this,
+              MessageManager.getString("label.no_features_to_sort_by"),
+              title, JvOptionPane.OK_OPTION);
+      return false;
+    }
+    return true;
+  }
+
   @Override
   synchronized public void discoverAllFeatureData()
   {
@@ -1077,11 +1188,38 @@ public class FeatureSettings extends JPanel
     table.repaint();
   }
 
+  /**
+   * close ourselves but leave any existing UI handlers (e.g a CDS/Protein tabbed
+   * feature settings dialog) intact
+   */
+  public void closeOldSettings()
+  {
+    closeDialog(false);
+  }
+
+  /**
+   * close the feature settings dialog (and any containing frame)
+   */
   public void close()
   {
+    closeDialog(true);
+  }
+
+  private void closeDialog(boolean closeContainingFrame)
+  {
     try
     {
-      frame.setClosed(true);
+      if (frame != null)
+      {
+        af.setFeatureSettingsGeometry(frame.getBounds());
+        frame.setClosed(true);
+      }
+      else
+      {
+        SplitContainerI sc = af.getSplitViewContainer();
+        sc.closeFeatureSettings(this, closeContainingFrame);
+        af.featureSettings = null;
+      }
     } catch (Exception exe)
     {
     }
@@ -1132,6 +1270,9 @@ public class FeatureSettings extends JPanel
   {
     this.setLayout(new BorderLayout());
 
+    final boolean hasComplement = af.getViewport()
+            .getCodingComplement() != null;
+
     JPanel settingsPane = new JPanel();
     settingsPane.setLayout(new BorderLayout());
 
@@ -1165,26 +1306,32 @@ public class FeatureSettings extends JPanel
       }
     });
 
-    JButton sortByScore = new JButton(
-            MessageManager.getString("label.seq_sort_by_score"));
+    final String byScoreLabel = MessageManager.getString("label.seq_sort_by_score");
+    JButton sortByScore = new JButton(byScoreLabel);
     sortByScore.setFont(JvSwingUtils.getLabelFont());
     sortByScore.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        af.avc.sortAlignmentByFeatureScore(null);
+        if (canSortBy(byScoreLabel))
+        {
+          sortByScore(null);
+        }
       }
     });
-    JButton sortByDens = new JButton(
-            MessageManager.getString("label.sequence_sort_by_density"));
+    final String byDensityLabel = MessageManager.getString("label.sequence_sort_by_density");
+    JButton sortByDens = new JButton(byDensityLabel);
     sortByDens.setFont(JvSwingUtils.getLabelFont());
     sortByDens.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        af.avc.sortAlignmentByFeatureDensity(null);
+        if (canSortBy(byDensityLabel))
+        {
+          sortByDensity(null);
+        }
       }
     });
 
@@ -1204,30 +1351,48 @@ public class FeatureSettings extends JPanel
         }
       }
     });
-
-    JButton cancel = new JButton(MessageManager.getString("action.cancel"));
+    // Cancel for a SplitFrame should just revert changes to the currently displayed
+    // settings. May want to do this for either or both - so need a splitview
+    // feature settings cancel/OK.
+    JButton cancel = new JButton(MessageManager
+            .getString(hasComplement ? "action.revert" : "action.cancel"));
+    cancel.setToolTipText(MessageManager.getString(hasComplement
+            ? "action.undo_changes_to_feature_settings"
+            : "action.undo_changes_to_feature_settings_and_close_the_dialog"));
     cancel.setFont(JvSwingUtils.getLabelFont());
+    // TODO: disable cancel (and apply!) until current settings are different
     cancel.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        fr.setTransparency(originalTransparency);
-        fr.setFeatureFilters(originalFilters);
-        updateFeatureRenderer(originalData);
-        af.getViewport().setViewStyle(originalViewStyle);
-        close();
+        revert();
+        refreshDisplay();
+        if (!hasComplement)
+        {
+          close();
+        }
       }
     });
-
-    JButton ok = new JButton(MessageManager.getString("action.ok"));
+    // Cancel for the whole dialog should cancel both CDS and Protein.
+    // OK for an individual feature settings just applies changes, but dialog
+    // remains open
+    JButton ok = new JButton(MessageManager
+            .getString(hasComplement ? "action.apply" : "action.ok"));
     ok.setFont(JvSwingUtils.getLabelFont());
     ok.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        close();
+        if (!hasComplement)
+        {
+          close();
+        }
+        else
+        {
+          storeOriginalSettings();
+        }
       }
     });
 
@@ -1281,8 +1446,7 @@ public class FeatureSettings extends JPanel
                     ? MessageManager.getString("label.protein")
                             .toLowerCase()
                     : "CDS");
-    JCheckBox showComplement = new JCheckBox(text);
-    showComplement.setSelected(af.getViewport().isShowComplementFeatures());
+    showComplement = new JCheckBox(text);
     showComplement.addActionListener(new ActionListener()
     {
       @Override
@@ -1294,10 +1458,8 @@ public class FeatureSettings extends JPanel
       }
     });
 
-    JCheckBox showComplementOnTop = new JCheckBox(
+    showComplementOnTop = new JCheckBox(
             MessageManager.getString("label.on_top"));
-    showComplementOnTop
-            .setSelected(af.getViewport().isShowComplementFeaturesOnTop());
     showComplementOnTop.addActionListener(new ActionListener()
     {
       @Override
@@ -1309,6 +1471,8 @@ public class FeatureSettings extends JPanel
       }
     });
 
+    updateComplementButtons();
+
     JPanel lowerPanel = new JPanel(new GridLayout(1, 2));
     bigPanel.add(lowerPanel, BorderLayout.SOUTH);
 
@@ -1319,9 +1483,8 @@ public class FeatureSettings extends JPanel
     transbuttons.add(sortByDens);
     transbuttons.add(help);
 
-    boolean hasComplement = af.getViewport().getCodingComplement() != null;
     JPanel transPanelLeft = new JPanel(
-            new GridLayout(hasComplement ? 3 : 2, 1));
+            new GridLayout(hasComplement ? 4 : 2, 1));
     transPanelLeft.add(new JLabel(" Colour transparency" + ":"));
     transPanelLeft.add(transparency);
     if (hasComplement)
@@ -1901,6 +2064,27 @@ public class FeatureSettings extends JPanel
       return button;
     }
   }
+
+  public boolean isOpen()
+  {
+    if (af.getSplitViewContainer() != null)
+    {
+      return af.getSplitViewContainer().isFeatureSettingsOpen();
+    }
+    return frame != null && !frame.isClosed();
+  }
+
+  @Override
+  public void revert()
+  {
+    fr.setTransparency(originalTransparency);
+    fr.setFeatureFilters(originalFilters);
+    updateFeatureRenderer(originalData);
+    af.getViewport().setViewStyle(originalViewStyle);
+    updateTransparencySliderFromFR();
+    updateComplementButtons();
+    refreshDisplay();
+  }
 }
 
 class FeatureIcon implements Icon