JAL-3438 spotless for 2.11.2.0
[jalview.git] / src / jalview / gui / FeatureSettings.java
index 9dbeb8a..bb15b55 100644 (file)
  */
 package jalview.gui;
 
-import jalview.api.AlignViewportI;
-import jalview.api.FeatureColourI;
-import jalview.api.FeatureSettingsControllerI;
-import jalview.datamodel.AlignmentI;
-import jalview.datamodel.SequenceI;
-import jalview.datamodel.features.FeatureMatcherI;
-import jalview.datamodel.features.FeatureMatcherSet;
-import jalview.datamodel.features.FeatureMatcherSetI;
-import jalview.gui.Help.HelpId;
-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.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;
@@ -81,8 +63,7 @@ import javax.swing.BorderFactory;
 import javax.swing.Icon;
 import javax.swing.JButton;
 import javax.swing.JCheckBox;
-import javax.swing.JColorChooser;
-import javax.swing.JDialog;
+import javax.swing.JCheckBoxMenuItem;
 import javax.swing.JInternalFrame;
 import javax.swing.JLabel;
 import javax.swing.JLayeredPane;
@@ -94,9 +75,12 @@ import javax.swing.JSlider;
 import javax.swing.JTable;
 import javax.swing.ListSelectionModel;
 import javax.swing.SwingConstants;
+import javax.swing.ToolTipManager;
+import javax.swing.border.Border;
 import javax.swing.event.ChangeEvent;
 import javax.swing.event.ChangeListener;
 import javax.swing.table.AbstractTableModel;
+import javax.swing.table.JTableHeader;
 import javax.swing.table.TableCellEditor;
 import javax.swing.table.TableCellRenderer;
 import javax.swing.table.TableColumn;
@@ -106,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");
@@ -129,6 +140,9 @@ public class FeatureSettings extends JPanel
 
   private static final int MIN_HEIGHT = 400;
 
+  private final static String BASE_TOOLTIP = MessageManager
+          .getString("label.click_to_edit");
+
   final FeatureRenderer fr;
 
   public final AlignFrame af;
@@ -140,6 +154,8 @@ public class FeatureSettings extends JPanel
 
   private float originalTransparency;
 
+  private ViewStyleI originalViewStyle;
+
   private Map<String, FeatureMatcherSetI> originalFilters;
 
   final JInternalFrame frame;
@@ -152,9 +168,9 @@ public class FeatureSettings extends JPanel
 
   JSlider transparency = new JSlider();
 
-  JCheckBox showComplement;
+  private JCheckBox showComplementOnTop;
 
-  JCheckBox showComplementOnTop;
+  private JCheckBox showComplement;
 
   /*
    * when true, constructor is still executing - so ignore UI events
@@ -163,12 +179,6 @@ public class FeatureSettings extends JPanel
 
   int selectedRow = -1;
 
-  JButton fetchDAS = new JButton();
-
-  JButton saveDAS = new JButton();
-
-  JButton cancelDAS = new JButton();
-
   boolean resettingTable = false;
 
   /*
@@ -177,10 +187,37 @@ 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
    * 
@@ -191,12 +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
+    storeOriginalSettings();
 
     try
     {
@@ -213,14 +245,20 @@ public class FeatureSettings extends JPanel
       {
         String tip = null;
         int column = table.columnAtPoint(e.getPoint());
+        int row = table.rowAtPoint(e.getPoint());
+
         switch (column)
         {
         case TYPE_COLUMN:
           tip = JvSwingUtils.wrapTooltip(true, MessageManager
                   .getString("label.feature_settings_click_drag"));
           break;
+        case COLOUR_COLUMN:
+          FeatureColourI colour = (FeatureColourI) table.getValueAt(row,
+                  column);
+          tip = getColorTooltip(colour, true);
+          break;
         case FILTER_COLUMN:
-          int row = table.rowAtPoint(e.getPoint());
           FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
                   column);
           tip = o.isEmpty()
@@ -231,27 +269,42 @@ public class FeatureSettings extends JPanel
         default:
           break;
         }
+
         return tip;
       }
+
+      /**
+       * Position the tooltip near the bottom edge of, and half way across, the
+       * current cell
+       */
+      @Override
+      public Point getToolTipLocation(MouseEvent e)
+      {
+        Point point = e.getPoint();
+        int column = table.columnAtPoint(point);
+        int row = table.rowAtPoint(point);
+        Rectangle r = getCellRect(row, column, false);
+        Point loc = new Point(r.x + r.width / 2, r.y + r.height - 3);
+        return loc;
+      }
     };
-    table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));
+    JTableHeader tableHeader = table.getTableHeader();
+    tableHeader.setFont(new Font("Verdana", Font.PLAIN, 12));
+    tableHeader.setReorderingAllowed(false);
     table.setFont(new Font("Verdana", Font.PLAIN, 12));
-
-    // table.setDefaultRenderer(Color.class, new ColorRenderer());
-    // table.setDefaultEditor(Color.class, new ColorEditor(this));
-    //
-    table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
+    ToolTipManager.sharedInstance().registerComponent(table);
+    table.setDefaultEditor(FeatureColour.class, new ColorEditor());
     table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
 
-    table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor(this));
+    table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor());
     table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
 
     TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
-            new ColorRenderer(), new ColorEditor(this));
+            new ColorRenderer(), new ColorEditor());
     table.addColumn(colourColumn);
 
     TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
-            new FilterRenderer(), new FilterEditor(this));
+            new FilterRenderer(), new FilterEditor());
     table.addColumn(filterColumn);
 
     table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
@@ -261,21 +314,23 @@ 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())
         {
           Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
-          popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
-                  evt.getY());
+          showPopupMenu(selectedRow, type, colour, evt.getPoint());
         }
-        else if (evt.getClickCount() == 2)
+        else if (evt.getClickCount() == 2
+                && table.columnAtPoint(pt) == TYPE_COLUMN)
         {
           boolean invertSelection = evt.isAltDown();
           boolean toggleSelection = Platform.isControlDown(evt);
           boolean extendSelection = evt.isShiftDown();
           fr.ap.alignFrame.avc.markColumnsContainingFeatures(
                   invertSelection, extendSelection, toggleSelection, type);
+          fr.ap.av.sendSelection();
         }
       }
 
@@ -288,8 +343,7 @@ public class FeatureSettings extends JPanel
         {
           String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
           Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
-          popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
-                  evt.getY());
+          showPopupMenu(selectedRow, type, colour, evt.getPoint());
         }
       }
     });
@@ -332,7 +386,6 @@ public class FeatureSettings extends JPanel
     }
 
     discoverAllFeatureData();
-    final PropertyChangeListener change;
     final FeatureSettings fs = this;
     fr.addPropertyChangeListener(change = new PropertyChangeListener()
     {
@@ -350,74 +403,124 @@ 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.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;
   }
 
-  protected void popupSort(final int rowSelected, final String type,
-          final Object typeCol, final Map<String, float[][]> minmax, int x,
-          int y)
+  /**
+   * 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()
   {
-    final FeatureColourI featureColour = (FeatureColourI) typeCol;
+    fr.removePropertyChangeListener(change);
+    change = null;
+  }
 
+  /**
+   * Constructs and shows a popup menu of possible actions on the selected row
+   * and feature type
+   * 
+   * @param rowSelected
+   * @param type
+   * @param typeCol
+   * @param pt
+   */
+  protected void showPopupMenu(final int rowSelected, final String type,
+          final Object typeCol, final Point pt)
+  {
     JPopupMenu men = new JPopupMenu(MessageManager
             .formatMessage("label.settings_for_param", new String[]
             { type }));
+
     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);
 
@@ -430,6 +533,7 @@ public class FeatureSettings extends JPanel
       {
         fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
                 false, type);
+        fr.ap.av.sendSelection();
       }
     });
     JMenuItem clearCols = new JMenuItem(MessageManager
@@ -441,6 +545,7 @@ public class FeatureSettings extends JPanel
       {
         fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
                 false, type);
+        fr.ap.av.sendSelection();
       }
     });
     JMenuItem hideCols = new JMenuItem(
@@ -451,6 +556,7 @@ public class FeatureSettings extends JPanel
       public void actionPerformed(ActionEvent arg0)
       {
         fr.ap.alignFrame.hideFeatureColumns(type, true);
+        fr.ap.av.sendSelection();
       }
     });
     JMenuItem hideOtherCols = new JMenuItem(
@@ -461,13 +567,55 @@ public class FeatureSettings extends JPanel
       public void actionPerformed(ActionEvent arg0)
       {
         fr.ap.alignFrame.hideFeatureColumns(type, false);
+        fr.ap.av.sendSelection();
       }
     });
     men.add(selCols);
     men.add(clearCols);
     men.add(hideCols);
     men.add(hideOtherCols);
-    men.show(table, x, y);
+    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
@@ -622,7 +770,7 @@ public class FeatureSettings extends JPanel
         data[dataIndex][FILTER_COLUMN] = featureFilter == null
                 ? new FeatureMatcherSet()
                 : featureFilter;
-        data[dataIndex][SHOW_COLUMN] = new Boolean(
+        data[dataIndex][SHOW_COLUMN] = Boolean.valueOf(
                 af.getViewport().getFeaturesDisplayed().isVisible(type));
         dataIndex++;
         displayableTypes.remove(type);
@@ -649,7 +797,7 @@ public class FeatureSettings extends JPanel
       data[dataIndex][FILTER_COLUMN] = featureFilter == null
               ? new FeatureMatcherSet()
               : featureFilter;
-      data[dataIndex][SHOW_COLUMN] = new Boolean(true);
+      data[dataIndex][SHOW_COLUMN] = Boolean.valueOf(true);
       dataIndex++;
       displayableTypes.remove(type);
     }
@@ -680,8 +828,8 @@ public class FeatureSettings extends JPanel
   }
 
   /**
-   * Updates 'originalData' (used for restore on Cancel) if we detect that changes
-   * have been made outwith this dialog
+   * Updates 'originalData' (used for restore on Cancel) if we detect that
+   * changes have been made outwith this dialog
    * <ul>
    * <li>a new feature type added (and made visible)</li>
    * <li>a feature colour changed (in the Amend Features dialog)</li>
@@ -745,8 +893,8 @@ public class FeatureSettings extends JPanel
 
   /**
    * Remove from the groups panel any checkboxes for groups that are not in the
-   * foundGroups set. This enables removing a group from the display when the last
-   * feature in that group is deleted.
+   * foundGroups set. This enables removing a group from the display when the
+   * last feature in that group is deleted.
    * 
    * @param foundGroups
    */
@@ -801,14 +949,16 @@ public class FeatureSettings extends JPanel
     chooser.setDialogTitle(
             MessageManager.getString("label.load_feature_colours"));
     chooser.setToolTipText(MessageManager.getString("action.load"));
-
-    int value = chooser.showOpenDialog(this);
-
-    if (value == JalviewFileChooser.APPROVE_OPTION)
+    chooser.setResponseHandler(0, new Runnable()
     {
-      File file = chooser.getSelectedFile();
-      load(file);
-    }
+      @Override
+      public void run()
+      {
+        File file = chooser.getSelectedFile();
+        load(file);
+      }
+    });
+    chooser.showOpenDialog(this);
   }
 
   /**
@@ -868,8 +1018,7 @@ public class FeatureSettings extends JPanel
       if (table != null)
       {
         resetTable(null);
-        Object[][] data = ((FeatureTableModel) table.getModel())
-                .getData();
+        Object[][] data = ((FeatureTableModel) table.getModel()).getData();
         ensureOrder(data);
         updateFeatureRenderer(data, false);
         table.repaint();
@@ -892,12 +1041,11 @@ public class FeatureSettings extends JPanel
     chooser.setDialogTitle(
             MessageManager.getString("label.save_feature_colours"));
     chooser.setToolTipText(MessageManager.getString("action.save"));
-
-    int value = chooser.showSaveDialog(this);
-
-    if (value == JalviewFileChooser.APPROVE_OPTION)
+    int option = chooser.showSaveDialog(this);
+    if (option == JalviewFileChooser.APPROVE_OPTION)
     {
-      save(chooser.getSelectedFile());
+      File file = chooser.getSelectedFile();
+      save(file);
     }
   }
 
@@ -912,8 +1060,8 @@ public class FeatureSettings extends JPanel
     ucs.setSchemeName("Sequence Features");
     try
     {
-      PrintWriter out = new PrintWriter(new OutputStreamWriter(
-              new FileOutputStream(file), "UTF-8"));
+      PrintWriter out = new PrintWriter(
+              new OutputStreamWriter(new FileOutputStream(file), "UTF-8"));
 
       /*
        * sort feature types by colour order, from 0 (highest)
@@ -950,11 +1098,11 @@ public class FeatureSettings extends JPanel
         FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
         if (filter != null && !filter.isEmpty())
         {
-          Iterator<FeatureMatcherI> iterator = filter.getMatchers().iterator();
+          Iterator<FeatureMatcherI> iterator = filter.getMatchers()
+                  .iterator();
           FeatureMatcherI firstMatcher = iterator.next();
           jalview.xml.binding.jalview.FeatureMatcherSet ms = jalview.project.Jalview2XML
-                  .marshalFilter(firstMatcher, iterator,
-                  filter.isAnded());
+                  .marshalFilter(firstMatcher, iterator, filter.isAnded());
           Filter filterModel = new Filter();
           filterModel.setFeatureType(featureType);
           filterModel.setMatcherSet(ms);
@@ -1036,7 +1184,8 @@ public class FeatureSettings extends JPanel
       else
       {
         width[i] /= max; // normalize
-        fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
+        fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for
+                                                                // later
       }
       if (i > 0)
       {
@@ -1053,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)
     {
     }
@@ -1070,13 +1246,13 @@ public class FeatureSettings extends JPanel
   }
 
   /**
-   * Update the priority order of features; only repaint if this changed the order
-   * of visible features
+   * Update the priority order of features; only repaint if this changed the
+   * order of visible features
    * 
    * @param data
    * @param visibleNew
    */
-  private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
+  void updateFeatureRenderer(Object[][] data, boolean visibleNew)
   {
     FeatureSettingsBean[] rowData = getTableAsBeans(data);
 
@@ -1108,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());
 
@@ -1141,47 +1320,39 @@ 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);
-      }
-    });
-
-    JButton help = new JButton(MessageManager.getString("action.help"));
-    help.setFont(JvSwingUtils.getLabelFont());
-    help.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        try
+        if (canSortBy(byDensityLabel))
         {
-          Help.showHelpWindow(HelpId.SequenceFeatureSettings);
-        } catch (HelpSetException e1)
-        {
-          e1.printStackTrace();
+          sortByDensity(null);
         }
       }
     });
+
+    JButton help = new JButton(MessageManager.getString("action.help"));
     help.setFont(JvSwingUtils.getLabelFont());
-    help.setText(MessageManager.getString("action.help"));
     help.addActionListener(new ActionListener()
     {
       @Override
@@ -1196,29 +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);
-        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();
+        }
       }
     });
 
@@ -1267,9 +1458,13 @@ public class FeatureSettings extends JPanel
             MessageManager.getString("label.transparency_tip"));
 
     boolean nucleotide = af.getViewport().getAlignment().isNucleotide();
-    showComplement = new JCheckBox(
-            "Show " + (nucleotide ? "protein" : "CDS") + " features");
-    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
@@ -1281,9 +1476,8 @@ public class FeatureSettings extends JPanel
       }
     });
 
-    showComplementOnTop = new JCheckBox("on top");
-    showComplementOnTop
-            .setSelected(af.getViewport().isShowComplementFeaturesOnTop());
+    showComplementOnTop = new JCheckBox(
+            MessageManager.getString("label.on_top"));
     showComplementOnTop.addActionListener(new ActionListener()
     {
       @Override
@@ -1295,6 +1489,8 @@ public class FeatureSettings extends JPanel
       }
     });
 
+    updateComplementButtons();
+
     JPanel lowerPanel = new JPanel(new GridLayout(1, 2));
     bigPanel.add(lowerPanel, BorderLayout.SOUTH);
 
@@ -1305,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)
@@ -1347,6 +1542,90 @@ public class FeatureSettings extends JPanel
     }
   }
 
+  /**
+   * Answers a suitable tooltip to show on the colour cell of the table
+   * 
+   * @param fcol
+   * @param withHint
+   *          if true include 'click to edit' and similar text
+   * @return
+   */
+  public static String getColorTooltip(FeatureColourI fcol,
+          boolean withHint)
+  {
+    if (fcol == null)
+    {
+      return null;
+    }
+    if (fcol.isSimpleColour())
+    {
+      return withHint ? BASE_TOOLTIP : null;
+    }
+    String description = fcol.getDescription();
+    description = description.replaceAll("<", "&lt;");
+    description = description.replaceAll(">", "&gt;");
+    StringBuilder tt = new StringBuilder(description);
+    if (withHint)
+    {
+      tt.append("<br>").append(BASE_TOOLTIP).append("</br>");
+    }
+    return JvSwingUtils.wrapTooltip(true, tt.toString());
+  }
+
+  public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
+          int w, int h)
+  {
+    boolean thr = false;
+    StringBuilder tx = new StringBuilder();
+
+    if (gcol.isColourByAttribute())
+    {
+      tx.append(FeatureMatcher
+              .toAttributeDisplayName(gcol.getAttributeName()));
+    }
+    else if (!gcol.isColourByLabel())
+    {
+      tx.append(MessageManager.getString("label.score"));
+    }
+    tx.append(" ");
+    if (gcol.isAboveThreshold())
+    {
+      thr = true;
+      tx.append(">");
+    }
+    if (gcol.isBelowThreshold())
+    {
+      thr = true;
+      tx.append("<");
+    }
+    if (gcol.isColourByLabel())
+    {
+      if (thr)
+      {
+        tx.append(" ");
+      }
+      if (!gcol.isColourByAttribute())
+      {
+        tx.append("Label");
+      }
+      comp.setIcon(null);
+    }
+    else
+    {
+      Color newColor = gcol.getMaxColour();
+      comp.setBackground(newColor);
+      // System.err.println("Width is " + w / 2);
+      Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
+      comp.setIcon(ficon);
+      // tt+="RGB value: Max (" + newColor.getRed() + ", "
+      // + newColor.getGreen() + ", " + newColor.getBlue()
+      // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
+      // + ", " + minCol.getBlue() + ")");
+    }
+    comp.setHorizontalAlignment(SwingConstants.CENTER);
+    comp.setText(tx.toString());
+  }
+
   // ///////////////////////////////////////////////////////////////////////
   // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
   // ///////////////////////////////////////////////////////////////////////
@@ -1441,11 +1720,9 @@ public class FeatureSettings extends JPanel
 
   class ColorRenderer extends JLabel implements TableCellRenderer
   {
-    javax.swing.border.Border unselectedBorder = null;
-
-    javax.swing.border.Border selectedBorder = null;
+    Border unselectedBorder = null;
 
-    final String baseTT = "Click to edit, right/apple click for menu.";
+    Border selectedBorder = null;
 
     public ColorRenderer()
     {
@@ -1460,7 +1737,6 @@ public class FeatureSettings extends JPanel
     {
       FeatureColourI cellColour = (FeatureColourI) color;
       setOpaque(true);
-      setToolTipText(baseTT);
       setBackground(tbl.getBackground());
       if (!cellColour.isSimpleColour())
       {
@@ -1567,82 +1843,10 @@ public class FeatureSettings extends JPanel
     renderGraduatedColor(comp, gcol, w, h);
   }
 
-  public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
-          int w, int h)
-  {
-    boolean thr = false;
-    StringBuilder tt = new StringBuilder();
-    StringBuilder tx = new StringBuilder();
-
-    if (gcol.isColourByAttribute())
-    {
-      tx.append(String.join(":", gcol.getAttributeName()));
-    }
-    else if (!gcol.isColourByLabel())
-    {
-      tx.append(MessageManager.getString("label.score"));
-    }
-    tx.append(" ");
-    if (gcol.isAboveThreshold())
-    {
-      thr = true;
-      tx.append(">");
-      tt.append("Thresholded (Above ").append(gcol.getThreshold())
-              .append(") ");
-    }
-    if (gcol.isBelowThreshold())
-    {
-      thr = true;
-      tx.append("<");
-      tt.append("Thresholded (Below ").append(gcol.getThreshold())
-              .append(") ");
-    }
-    if (gcol.isColourByLabel())
-    {
-      tt.append("Coloured by label text. ").append(tt);
-      if (thr)
-      {
-        tx.append(" ");
-      }
-      if (!gcol.isColourByAttribute())
-      {
-        tx.append("Label");
-      }
-      comp.setIcon(null);
-    }
-    else
-    {
-      Color newColor = gcol.getMaxColour();
-      comp.setBackground(newColor);
-      // System.err.println("Width is " + w / 2);
-      Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
-      comp.setIcon(ficon);
-      // tt+="RGB value: Max (" + newColor.getRed() + ", "
-      // + newColor.getGreen() + ", " + newColor.getBlue()
-      // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
-      // + ", " + minCol.getBlue() + ")");
-    }
-    comp.setHorizontalAlignment(SwingConstants.CENTER);
-    comp.setText(tx.toString());
-    if (tt.length() > 0)
-    {
-      if (comp.getToolTipText() == null)
-      {
-        comp.setToolTipText(tt.toString());
-      }
-      else
-      {
-        comp.setToolTipText(
-                tt.append(" ").append(comp.getToolTipText()).toString());
-      }
-    }
-  }
-
+  @SuppressWarnings("serial")
   class ColorEditor extends AbstractCellEditor
           implements TableCellEditor, ActionListener
   {
-    FeatureSettings me;
-
     FeatureColourI currentColor;
 
     FeatureTypeSettings chooser;
@@ -1651,17 +1855,12 @@ public class FeatureSettings extends JPanel
 
     JButton button;
 
-    JColorChooser colorChooser;
-
-    JDialog dialog;
-
     protected static final String EDIT = "edit";
 
     int rowSelected = 0;
 
-    public ColorEditor(FeatureSettings me)
+    public ColorEditor()
     {
-      this.me = me;
       // Set up the editor (from the table's point of view),
       // which is a button.
       // This button brings up the color chooser dialog,
@@ -1670,81 +1869,99 @@ public class FeatureSettings extends JPanel
       button.setActionCommand(EDIT);
       button.addActionListener(this);
       button.setBorderPainted(false);
-      // Set up the dialog that the button brings up.
-      colorChooser = new JColorChooser();
-      dialog = JColorChooser.createDialog(button,
-              MessageManager.getString("label.select_colour"), true, // modal
-              colorChooser, this, // OK button handler
-              null); // no CANCEL button handler
     }
 
     /**
-     * Handles events from the editor button and from the dialog's OK button.
+     * Handles events from the editor button, and from the colour/filters
+     * dialog's OK button
      */
     @Override
     public void actionPerformed(ActionEvent e)
     {
-      // todo test e.getSource() instead here
-      if (EDIT.equals(e.getActionCommand()))
+      if (button == e.getSource())
       {
-        // The user has clicked the cell, so
-        // bring up the dialog.
         if (currentColor.isSimpleColour())
         {
-          // bring up simple color chooser
-          button.setBackground(currentColor.getColour());
-          colorChooser.setColor(currentColor.getColour());
-          dialog.setVisible(true);
+          /*
+           * simple colour chooser
+           */
+          String ttl = MessageManager
+                  .formatMessage("label.select_colour_for", type);
+          ColourChooserListener listener = new ColourChooserListener()
+          {
+            @Override
+            public void colourSelected(Color c)
+            {
+              currentColor = new FeatureColour(c);
+              table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
+              fireEditingStopped();
+            }
+
+            @Override
+            public void cancel()
+            {
+              fireEditingStopped();
+            }
+          };
+          JalviewColourChooser.showColourChooser(button, ttl,
+                  currentColor.getColour(), listener);
         }
         else
         {
-          // bring up graduated chooser.
-          chooser = new FeatureTypeSettings(me.fr, type);
+          /*
+           * variable colour and filters dialog
+           */
+          chooser = new FeatureTypeSettings(fr, type);
+          if (!Platform.isJS())
           /**
-           * @j2sNative
+           * Java only
+           * 
+           * @j2sIgnore
            */
           {
             chooser.setRequestFocusEnabled(true);
             chooser.requestFocus();
           }
           chooser.addActionListener(this);
-          // Make the renderer reappear.
           fireEditingStopped();
         }
       }
       else
       {
-        if (currentColor.isSimpleColour())
-        {
-          /*
-           * read off colour picked in colour chooser after OK pressed
-           */
-          currentColor = new FeatureColour(colorChooser.getColor());
-          me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
-        }
-        else
+        /*
+         * after OK in variable colour dialog, any changes to colour 
+         * (or filters!) are already set in FeatureRenderer, so just
+         * update table data without triggering updateFeatureRenderer
+         */
+        currentColor = fr.getFeatureColours().get(type);
+        FeatureMatcherSetI currentFilter = fr.getFeatureFilter(type);
+        if (currentFilter == null)
         {
-          /*
-           * after OK in variable colour dialog, any changes to colour 
-           * (or filters!) are already set in FeatureRenderer, so just
-           * update table data without triggering updateFeatureRenderer
-           */
-          currentColor = fr.getFeatureColours().get(type);
-          FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
-          if (currentFilter == null)
-          {
-            currentFilter = new FeatureMatcherSet();
-          }
-          Object[] data = ((FeatureTableModel) table.getModel())
-                  .getData()[rowSelected];
-          data[COLOUR_COLUMN] = currentColor;
-          data[FILTER_COLUMN] = currentFilter;
+          currentFilter = new FeatureMatcherSet();
         }
+        Object[] data = ((FeatureTableModel) table.getModel())
+                .getData()[rowSelected];
+        data[COLOUR_COLUMN] = currentColor;
+        data[FILTER_COLUMN] = currentFilter;
         fireEditingStopped();
-        me.table.validate();
+        // SwingJS needs an explicit repaint() here,
+        // rather than relying upon no validation having
+        // occurred since the stopEditing call was made.
+        // Its laying out has not been stopped by the modal frame
+        table.validate();
+        table.repaint();
       }
     }
 
+    /**
+     * Override allows access to this method from anonymous inner classes
+     */
+    @Override
+    protected void fireEditingStopped()
+    {
+      super.fireEditingStopped();
+    }
+
     // Implement the one CellEditor method that AbstractCellEditor doesn't.
     @Override
     public Object getCellEditorValue()
@@ -1754,14 +1971,14 @@ public class FeatureSettings extends JPanel
 
     // Implement the one method defined by TableCellEditor.
     @Override
-    public Component getTableCellEditorComponent(JTable theTable, Object value,
-            boolean isSelected, int row, int column)
+    public Component getTableCellEditorComponent(JTable theTable,
+            Object value, boolean isSelected, int row, int column)
     {
       currentColor = (FeatureColourI) value;
       this.rowSelected = row;
-      type = me.table.getValueAt(row, TYPE_COLUMN).toString();
+      type = table.getValueAt(row, TYPE_COLUMN).toString();
       button.setOpaque(true);
-      button.setBackground(me.getBackground());
+      button.setBackground(FeatureSettings.this.getBackground());
       if (!currentColor.isSimpleColour())
       {
         JLabel btn = new JLabel();
@@ -1783,14 +2000,14 @@ public class FeatureSettings extends JPanel
 
   /**
    * The cell editor for the Filter column. It displays the text of any filters
-   * for the feature type in that row (in full as a tooltip, possible abbreviated
-   * as display text). On click in the cell, opens the Feature Display Settings
-   * dialog at the Filters tab.
+   * for the feature type in that row (in full as a tooltip, possible
+   * abbreviated as display text). On click in the cell, opens the Feature
+   * Display Settings dialog at the Filters tab.
    */
+  @SuppressWarnings("serial")
   class FilterEditor extends AbstractCellEditor
           implements TableCellEditor, ActionListener
   {
-    FeatureSettings me;
 
     FeatureMatcherSetI currentFilter;
 
@@ -1804,9 +2021,8 @@ public class FeatureSettings extends JPanel
 
     int rowSelected = 0;
 
-    public FilterEditor(FeatureSettings me)
+    public FilterEditor()
     {
-      this.me = me;
       button = new JButton();
       button.setActionCommand(EDIT);
       button.addActionListener(this);
@@ -1821,7 +2037,7 @@ public class FeatureSettings extends JPanel
     {
       if (button == e.getSource())
       {
-        FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
+        FeatureTypeSettings chooser = new FeatureTypeSettings(fr, type);
         chooser.addActionListener(this);
         chooser.setRequestFocusEnabled(true);
         chooser.requestFocus();
@@ -1843,17 +2059,23 @@ public class FeatureSettings extends JPanel
          * update table data without triggering updateFeatureRenderer
          */
         FeatureColourI currentColor = fr.getFeatureColours().get(type);
-        currentFilter = me.fr.getFeatureFilter(type);
+        currentFilter = fr.getFeatureFilter(type);
         if (currentFilter == null)
         {
           currentFilter = new FeatureMatcherSet();
         }
+
         Object[] data = ((FeatureTableModel) table.getModel())
                 .getData()[rowSelected];
         data[COLOUR_COLUMN] = currentColor;
         data[FILTER_COLUMN] = currentFilter;
         fireEditingStopped();
-        me.table.validate();
+        // SwingJS needs an explicit repaint() here,
+        // rather than relying upon no validation having
+        // occurred since the stopEditing call was made.
+        // Its laying out has not been stopped by the modal frame
+        table.validate();
+        table.repaint();
       }
     }
 
@@ -1864,20 +2086,40 @@ public class FeatureSettings extends JPanel
     }
 
     @Override
-    public Component getTableCellEditorComponent(JTable theTable, Object value,
-            boolean isSelected, int row, int column)
+    public Component getTableCellEditorComponent(JTable theTable,
+            Object value, boolean isSelected, int row, int column)
     {
       currentFilter = (FeatureMatcherSetI) value;
       this.rowSelected = row;
-      type = me.table.getValueAt(row, TYPE_COLUMN).toString();
+      type = table.getValueAt(row, TYPE_COLUMN).toString();
       button.setOpaque(true);
-      button.setBackground(me.getBackground());
+      button.setBackground(FeatureSettings.this.getBackground());
       button.setText(currentFilter.toString());
-      button.setToolTipText(currentFilter.toString());
       button.setIcon(null);
       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
@@ -1957,7 +2199,8 @@ class FeatureIcon implements Icon
         g.fillRect(s1, 0, e1 - s1, height);
       }
       g.setColor(gcol.getMaxColour());
-      g.fillRect(0, e1, width - e1, height);
+      // g.fillRect(0, e1, width - e1, height); // BH 2018
+      g.fillRect(e1, 0, width - e1, height);
     }
   }
 }