JAL-4034 Use the new non-modal JDialog for 3DBeacons uniprot lookup confirmation...
[jalview.git] / src / jalview / gui / FeatureSettings.java
index 5aa9499..bb15b55 100644 (file)
  */
 package jalview.gui;
 
-import jalview.api.AlignViewportI;
-import jalview.api.FeatureColourI;
-import jalview.api.FeatureSettingsControllerI;
-import jalview.api.ViewStyleI;
-import jalview.datamodel.AlignmentI;
-import jalview.datamodel.SequenceI;
-import jalview.datamodel.features.FeatureMatcher;
-import jalview.datamodel.features.FeatureMatcherI;
-import jalview.datamodel.features.FeatureMatcherSet;
-import jalview.datamodel.features.FeatureMatcherSetI;
-import jalview.gui.Help.HelpId;
-import jalview.gui.JalviewColourChooser.ColourChooserListener;
-import jalview.io.JalviewFileChooser;
-import jalview.io.JalviewFileView;
-import jalview.schemes.FeatureColour;
-import jalview.util.MessageManager;
-import jalview.util.Platform;
-import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
-import jalview.viewmodel.styles.ViewStyle;
-import jalview.xml.binding.jalview.JalviewUserColours;
-import jalview.xml.binding.jalview.JalviewUserColours.Colour;
-import jalview.xml.binding.jalview.JalviewUserColours.Filter;
-import jalview.xml.binding.jalview.ObjectFactory;
+import java.util.Locale;
 
 import java.awt.BorderLayout;
 import java.awt.Color;
@@ -112,8 +90,35 @@ import javax.xml.bind.Marshaller;
 import javax.xml.stream.XMLInputFactory;
 import javax.xml.stream.XMLStreamReader;
 
+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;
+import jalview.datamodel.features.FeatureMatcherI;
+import jalview.datamodel.features.FeatureMatcherSet;
+import jalview.datamodel.features.FeatureMatcherSetI;
+import jalview.gui.Help.HelpId;
+import jalview.gui.JalviewColourChooser.ColourChooserListener;
+import jalview.io.JalviewFileChooser;
+import jalview.io.JalviewFileView;
+import jalview.schemes.FeatureColour;
+import jalview.util.MessageManager;
+import jalview.util.Platform;
+import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
+import jalview.viewmodel.styles.ViewStyle;
+import jalview.xml.binding.jalview.JalviewUserColours;
+import jalview.xml.binding.jalview.JalviewUserColours.Colour;
+import jalview.xml.binding.jalview.JalviewUserColours.Filter;
+import jalview.xml.binding.jalview.ObjectFactory;
+
 public class FeatureSettings extends JPanel
-        implements FeatureSettingsControllerI
+        implements FeatureSettingsControllerI, FeatureSettingsControllerGuiI
 {
   private static final String SEQUENCE_FEATURE_COLOURS = MessageManager
           .getString("label.sequence_feature_colours");
@@ -147,7 +152,7 @@ public class FeatureSettings extends JPanel
    */
   Object[][] originalData;
 
-  float originalTransparency;
+  private float originalTransparency;
 
   private ViewStyleI originalViewStyle;
 
@@ -163,6 +168,10 @@ public class FeatureSettings extends JPanel
 
   JSlider transparency = new JSlider();
 
+  private JCheckBox showComplementOnTop;
+
+  private JCheckBox showComplement;
+
   /*
    * when true, constructor is still executing - so ignore UI events
    */
@@ -175,13 +184,40 @@ public class FeatureSettings extends JPanel
   /*
    * true when Feature Settings are updating from feature renderer
    */
-  boolean handlingUpdate = false;
+  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
    * 
@@ -192,13 +228,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
     {
@@ -356,7 +386,6 @@ public class FeatureSettings extends JPanel
     }
 
     discoverAllFeatureData();
-    final PropertyChangeListener change;
     final FeatureSettings fs = this;
     fr.addPropertyChangeListener(change = new PropertyChangeListener()
     {
@@ -374,28 +403,89 @@ public class FeatureSettings extends JPanel
 
     });
 
-    frame = new JInternalFrame();
-    frame.setContentPane(this);
-    Desktop.addInternalFrame(frame,
-            MessageManager.getString("label.sequence_feature_settings"),
-            600, Platform.isAMacAndNotJS() ? 480 : 450);
-    frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
+    SplitContainerI splitframe = af.getSplitViewContainer();
+    if (splitframe != null)
+    {
+      frame = null; // keeps eclipse happy
+      splitframe.addFeatureSettingsUI(this);
+    }
+    else
+    {
+      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.isAMacAndNotJS())
+        {
+          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;
+  }
+
+  /**
    * Constructs and shows a popup menu of possible actions on the selected row
    * and feature type
    * 
@@ -410,106 +500,26 @@ public class FeatureSettings extends JPanel
     JPopupMenu men = new JPopupMenu(MessageManager
             .formatMessage("label.settings_for_param", new String[]
             { type }));
-    final FeatureColourI featureColour = (FeatureColourI) typeCol;
-
-    /*
-     * menu option to select (or deselect) variable colour
-     */
-    final JCheckBoxMenuItem variableColourCB = new JCheckBoxMenuItem(
-            MessageManager.getString("label.variable_colour"));
-    variableColourCB.setSelected(!featureColour.isSimpleColour());
-    men.add(variableColourCB);
-
-    /*
-     * checkbox action listener doubles up as listener to OK
-     * from the variable colour / filters dialog
-     */
-    variableColourCB.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        if (e.getSource() == variableColourCB)
-        {
-          men.setVisible(true); // BH 2018 for JavaScript because this is a
-                                // checkbox
-          men.setVisible(false); // BH 2018 for JavaScript because this is a
-                                 // checkbox
-          if (featureColour.isSimpleColour())
-          {
-            /*
-             * toggle simple colour to variable colour - show dialog
-             */
-            FeatureTypeSettings fc = new FeatureTypeSettings(fr, type);
-            fc.addActionListener(this);
-          }
-          else
-          {
-            /*
-             * toggle variable to simple colour - show colour chooser
-             */
-            String title = MessageManager
-                    .formatMessage("label.select_colour_for", type);
-            ColourChooserListener listener = new ColourChooserListener()
-            {
-              @Override
-              public void colourSelected(Color c)
-              {
-                table.setValueAt(new FeatureColour(c), rowSelected,
-                        COLOUR_COLUMN);
-                table.validate();
-                updateFeatureRenderer(
-                        ((FeatureTableModel) table.getModel()).getData(),
-                        false);
-              }
-            };
-            JalviewColourChooser.showColourChooser(FeatureSettings.this,
-                    title, featureColour.getMaxColour(), listener);
-          }
-        }
-        else
-        {
-          if (e.getSource() instanceof FeatureTypeSettings)
-          {
-            /*
-             * update after OK in feature colour dialog; the updated
-             * colour will have already been set in the FeatureRenderer
-             */
-            FeatureColourI fci = fr.getFeatureColours().get(type);
-            table.setValueAt(fci, rowSelected, COLOUR_COLUMN);
-            // BH 2018 setting a table value does not invalidate it.
-            // System.out.println("FeatureSettings is valied" +
-            // table.validate();
-          }
-        }
-      }
-    });
-
-    men.addSeparator();
 
     JMenuItem scr = new JMenuItem(
             MessageManager.getString("label.sort_by_score"));
     men.add(scr);
     scr.addActionListener(new ActionListener()
     {
-
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        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)
       {
-        af.avc.sortAlignmentByFeatureDensity(Arrays.asList(new String[]
-                { type }));
+        sortByDensity(Arrays.asList(new String[] { type }));
       }
     });
     men.add(dens);
@@ -567,6 +577,47 @@ public class FeatureSettings extends JPanel
     men.show(table, pt.x, pt.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()
   {
@@ -1151,11 +1202,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)
     {
     }
@@ -1206,6 +1284,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());
 
@@ -1239,26 +1320,34 @@ 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);
+        }
       }
     });
 
@@ -1278,30 +1367,49 @@ 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();
+        }
       }
     });
 
@@ -1350,13 +1458,13 @@ public class FeatureSettings extends JPanel
             MessageManager.getString("label.transparency_tip"));
 
     boolean nucleotide = af.getViewport().getAlignment().isNucleotide();
-    String text = MessageManager.formatMessage("label.show_linked_features",
-            nucleotide
-                    ? MessageManager.getString("label.protein")
-                            .toLowerCase()
-                    : "CDS");
-    JCheckBox showComplement = new JCheckBox(text);
-    showComplement.setSelected(af.getViewport().isShowComplementFeatures());
+    String text = MessageManager
+            .formatMessage("label.show_linked_features",
+                    nucleotide
+                            ? MessageManager.getString("label.protein")
+                                    .toLowerCase(Locale.ROOT)
+                            : "CDS");
+    showComplement = new JCheckBox(text);
     showComplement.addActionListener(new ActionListener()
     {
       @Override
@@ -1368,10 +1476,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
@@ -1383,6 +1489,8 @@ public class FeatureSettings extends JPanel
       }
     });
 
+    updateComplementButtons();
+
     JPanel lowerPanel = new JPanel(new GridLayout(1, 2));
     bigPanel.add(lowerPanel, BorderLayout.SOUTH);
 
@@ -1393,9 +1501,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)
@@ -1440,7 +1547,7 @@ public class FeatureSettings extends JPanel
    * 
    * @param fcol
    * @param withHint
-   *                   if true include 'click to edit' and similar text
+   *          if true include 'click to edit' and similar text
    * @return
    */
   public static String getColorTooltip(FeatureColourI fcol,
@@ -1992,6 +2099,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