Merge branch 'develop' into bug/JAL-2255_seq-fetcher-broken-on-linux
[jalview.git] / src / jalview / gui / ColourMenuHelper.java
diff --git a/src/jalview/gui/ColourMenuHelper.java b/src/jalview/gui/ColourMenuHelper.java
new file mode 100644 (file)
index 0000000..19ad939
--- /dev/null
@@ -0,0 +1,287 @@
+package jalview.gui;
+
+import jalview.bin.Cache;
+import jalview.datamodel.AnnotatedCollectionI;
+import jalview.schemes.ColourSchemeI;
+import jalview.schemes.ColourSchemeLoader;
+import jalview.schemes.ColourSchemes;
+import jalview.schemes.ResidueColourScheme;
+import jalview.schemes.UserColourScheme;
+import jalview.util.MessageManager;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+
+import javax.swing.ButtonGroup;
+import javax.swing.JMenu;
+import javax.swing.JRadioButtonMenuItem;
+
+public class ColourMenuHelper
+{
+  public interface ColourChangeListener
+  {
+    void changeColour_actionPerformed(String name);
+  }
+
+  /**
+   * Adds items to the colour menu, as mutually exclusive members of a button
+   * group. The callback handler is responsible for the action on selecting any
+   * of these options. The callback method receives the name of the selected
+   * colour, or "None" or "User Defined". This method returns the ButtonGroup to
+   * which items were added.
+   * <ul>
+   * <li>None</li>
+   * <li>Clustal</li>
+   * <li>...other 'built-in' colours</li>
+   * <li>...any user-defined colours</li>
+   * <li>User Defined..</li>
+   * </ul>
+   * 
+   * @param colourMenu
+   *          the menu to attach items to
+   * @param client
+   *          a callback to handle menu selection
+   * @param coll
+   *          the data the menu is being built for
+   * @param simpleOnly
+   *          if true, only simple per-residue colour schemes are included
+   */
+  public static ButtonGroup addMenuItems(final JMenu colourMenu,
+          final ColourChangeListener client, AnnotatedCollectionI coll,
+          boolean simpleOnly)
+  {
+    /*
+     * ButtonGroup groups those items whose 
+     * selection is mutually exclusive
+     */
+    ButtonGroup colours = new ButtonGroup();
+
+    if (!simpleOnly)
+    {
+      JRadioButtonMenuItem noColourmenuItem = new JRadioButtonMenuItem(
+              MessageManager.getString("label.none"));
+      noColourmenuItem.setName(ResidueColourScheme.NONE);
+      noColourmenuItem.addActionListener(new ActionListener()
+      {
+        @Override
+        public void actionPerformed(ActionEvent e)
+        {
+          client.changeColour_actionPerformed(ResidueColourScheme.NONE);
+        }
+      });
+      colourMenu.add(noColourmenuItem);
+      colours.add(noColourmenuItem);
+    }
+
+    /*
+     * scan registered colour schemes (built-in or user-defined
+     * and add them to the menu (in the order they were registered)
+     */
+    Iterable<ColourSchemeI> colourSchemes = ColourSchemes.getInstance()
+            .getColourSchemes();
+    for (ColourSchemeI scheme : colourSchemes)
+    {
+      if (simpleOnly && !scheme.isSimple())
+      {
+        continue;
+      }
+
+      /*
+       * button text is i18n'd but the name is the canonical name of
+       * the colour scheme (inspected in setColourSelected())
+       */
+      final String name = scheme.getSchemeName();
+      String label = MessageManager.getStringOrReturn("label.colourScheme_"
+              + name.toLowerCase().replace(" ", "_"), name);
+      final JRadioButtonMenuItem radioItem = new JRadioButtonMenuItem(label);
+      radioItem.setName(name);
+      radioItem.setEnabled(scheme.isApplicableTo(coll));
+      if (scheme instanceof UserColourScheme)
+      {
+        /*
+         * user-defined colour scheme loaded on startup or during the
+         * Jalview session; right-click on this offers the option to
+         * remove it as a colour choice
+         */
+        radioItem.addMouseListener(new MouseAdapter()
+        {
+          @Override
+          public void mousePressed(MouseEvent evt)
+          {
+            if (evt.isPopupTrigger()) // Mac
+            {
+              offerRemoval();
+            }
+          }
+
+          @Override
+          public void mouseReleased(MouseEvent evt)
+          {
+            if (evt.isPopupTrigger()) // Windows
+            {
+              offerRemoval();
+            }
+          }
+
+          void offerRemoval()
+          {
+            ActionListener al = radioItem.getActionListeners()[0];
+            radioItem.removeActionListener(al);
+            int option = JvOptionPane.showInternalConfirmDialog(
+                    Desktop.desktop, MessageManager
+                            .getString("label.remove_from_default_list"),
+                    MessageManager
+                            .getString("label.remove_user_defined_colour"),
+                    JvOptionPane.YES_NO_OPTION);
+            if (option == JvOptionPane.YES_OPTION)
+            {
+              ColourSchemes.getInstance().removeColourScheme(
+                      radioItem.getName());
+              colourMenu.remove(radioItem);
+              updatePreferences();
+            }
+            else
+            {
+              radioItem.addActionListener(al);
+            }
+          }
+        });
+      }
+      radioItem.addActionListener(new ActionListener()
+      {
+        @Override
+        public void actionPerformed(ActionEvent evt)
+        {
+          client.changeColour_actionPerformed(name);
+        }
+      });
+      colourMenu.add(radioItem);
+      colours.add(radioItem);
+    }
+
+    /*
+     * only add the option to load/configure a user-defined colour
+     * to the AlignFrame colour menu
+     */
+    if (client instanceof AlignFrame)
+    {
+      final String label = MessageManager.getString("action.user_defined");
+      JRadioButtonMenuItem userDefinedColour = new JRadioButtonMenuItem(
+              label);
+      userDefinedColour.addActionListener(new ActionListener()
+      {
+        @Override
+        public void actionPerformed(ActionEvent e)
+        {
+          client.changeColour_actionPerformed(ResidueColourScheme.USER_DEFINED);
+        }
+      });
+      colourMenu.add(userDefinedColour);
+      colours.add(userDefinedColour);
+    }
+
+    return colours;
+  }
+
+  /**
+   * Marks as selected the colour menu item matching the given name, or the
+   * first item ('None') if no match is found
+   * 
+   * @param colourMenu
+   * @param colourName
+   */
+  public static void setColourSelected(JMenu colourMenu, String colourName)
+  {
+    if (colourName == null)
+    {
+      return;
+    }
+
+    JRadioButtonMenuItem none = null;
+
+    /*
+     * select the radio button whose name matches the colour name
+     * (not the button text, as it may be internationalised)
+     */
+    for (Component menuItem : colourMenu.getMenuComponents())
+    {
+      if (menuItem instanceof JRadioButtonMenuItem)
+      {
+        String buttonName = ((JRadioButtonMenuItem) menuItem).getName();
+        if (colourName.equals(buttonName))
+        {
+          ((JRadioButtonMenuItem) menuItem).setSelected(true);
+          return;
+        }
+        if (ResidueColourScheme.NONE.equals(buttonName))
+        {
+          none = (JRadioButtonMenuItem) menuItem;
+        }
+      }
+    }
+    if (none != null)
+    {
+      none.setSelected(true);
+    }
+  }
+
+  /**
+   * Marks as selected the colour menu item matching the given colour scheme, or
+   * the first item ('None') if no match is found
+   * 
+   * @param colourMenu
+   * @param cs
+   */
+  public static void setColourSelected(JMenu colourMenu, ColourSchemeI cs)
+  {
+    setColourSelected(colourMenu, cs == null ? ResidueColourScheme.NONE
+            : cs.getSchemeName());
+  }
+
+  /**
+   * Updates the USER_DEFINE_COLOURS preference to remove any de-registered
+   * colour scheme
+   */
+  static void updatePreferences()
+  {
+    StringBuilder coloursFound = new StringBuilder();
+    String[] files = Cache.getProperty("USER_DEFINED_COLOURS").split("\\|");
+
+    /*
+     * the property does not include the scheme name, it is in the file;
+     * so just load the colour schemes and discard any whose name is not
+     * registered
+     */
+    for (String file : files)
+    {
+      try
+      {
+        UserColourScheme ucs = ColourSchemeLoader.loadColourScheme(file);
+        if (ucs != null
+                && ColourSchemes.getInstance().nameExists(ucs.getName()))
+        {
+          if (coloursFound.length() > 0)
+          {
+            coloursFound.append("|");
+          }
+          coloursFound.append(file);
+        }
+      } catch (Exception ex)
+      {
+        System.out.println("Error loading User ColourFile\n" + ex);
+      }
+    }
+
+    if (coloursFound.toString().length() > 1)
+    {
+      Cache.setProperty("USER_DEFINED_COLOURS", coloursFound.toString());
+    }
+    else
+    {
+      Cache.applicationProperties.remove("USER_DEFINED_COLOURS");
+    }
+  }
+}