Merge branch 'features/JAL-2371collectionColourScheme' into develop
authorJim Procter <jprocter@issues.jalview.org>
Tue, 7 Feb 2017 18:15:14 +0000 (18:15 +0000)
committerJim Procter <jprocter@issues.jalview.org>
Tue, 7 Feb 2017 18:15:14 +0000 (18:15 +0000)
1  2 
src/jalview/gui/UserDefinedColours.java
src/jalview/jbgui/GUserDefinedColours.java

  package jalview.gui;
  
  import jalview.api.structures.JalviewStructureDisplayI;
+ import jalview.bin.Cache;
  import jalview.datamodel.SequenceGroup;
  import jalview.io.JalviewFileChooser;
  import jalview.io.JalviewFileView;
  import jalview.jbgui.GUserDefinedColours;
+ import jalview.schemabinding.version2.Colour;
+ import jalview.schemabinding.version2.JalviewUserColours;
  import jalview.schemes.ColourSchemeI;
+ import jalview.schemes.ColourSchemes;
  import jalview.schemes.ResidueProperties;
  import jalview.schemes.UserColourScheme;
  import jalview.util.ColorUtils;
+ import jalview.util.Format;
  import jalview.util.MessageManager;
  
  import java.awt.Color;
  import java.awt.Font;
+ import java.awt.Insets;
  import java.awt.event.ActionEvent;
+ import java.awt.event.MouseAdapter;
  import java.awt.event.MouseEvent;
  import java.io.File;
- import java.io.FileInputStream;
  import java.io.FileOutputStream;
  import java.io.OutputStreamWriter;
  import java.io.PrintWriter;
  import java.util.ArrayList;
- import java.util.Hashtable;
- import java.util.StringTokenizer;
+ import java.util.List;
  
  import javax.swing.JButton;
  import javax.swing.JInternalFrame;
@@@ -60,6 -64,13 +64,13 @@@ import javax.swing.event.ChangeListener
  public class UserDefinedColours extends GUserDefinedColours implements
          ChangeListener
  {
+   private static final Font VERDANA_BOLD_10 = new Font("Verdana",
+           Font.BOLD, 10);
+   public static final String USER_DEFINED_COLOURS = "USER_DEFINED_COLOURS";
+   private static final String LAST_DIRECTORY = "LAST_DIRECTORY";
    private static final int MY_FRAME_HEIGHT = 420;
  
    private static final int MY_FRAME_WIDTH = 810;
  
    SequenceGroup seqGroup;
  
+   List<JButton> selectedButtons;
    ColourSchemeI oldColourScheme;
  
    JInternalFrame frame;
  
-   JalviewStructureDisplayI jmol;
+   JalviewStructureDisplayI structureViewer;
  
-   ArrayList<JButton> upperCaseButtons;
+   List<JButton> upperCaseButtons;
  
-   ArrayList<JButton> lowerCaseButtons;
+   List<JButton> lowerCaseButtons;
  
    /**
     * Creates a new UserDefinedColours object.
     * 
     * @param ap
-    *          DOCUMENT ME!
     * @param sg
-    *          DOCUMENT ME!
     */
    public UserDefinedColours(AlignmentPanel ap, SequenceGroup sg)
    {
  
      if (seqGroup != null)
      {
-       oldColourScheme = seqGroup.cs;
+       oldColourScheme = seqGroup.getColourScheme();
      }
      else
      {
  
      if (oldColourScheme instanceof UserColourScheme)
      {
-       schemeName.setText(((UserColourScheme) oldColourScheme).getName());
+       schemeName.setText(oldColourScheme.getSchemeName());
        if (((UserColourScheme) oldColourScheme).getLowerCaseColours() != null)
        {
          caseSensitive.setSelected(true);
      showFrame();
    }
  
-   public UserDefinedColours(JalviewStructureDisplayI jmol,
+   public UserDefinedColours(JalviewStructureDisplayI viewer,
            ColourSchemeI oldcs)
    {
      super();
-     this.jmol = jmol;
+     this.structureViewer = viewer;
  
      colorChooser.getSelectionModel().addChangeListener(this);
  
  
      if (oldColourScheme instanceof UserColourScheme)
      {
-       schemeName.setText(((UserColourScheme) oldColourScheme).getName());
+       schemeName.setText(((UserColourScheme) oldColourScheme)
+               .getSchemeName());
      }
  
      resetButtonPanel(false);
      }
    }
  
-   void resetButtonPanel(boolean caseSensitive)
+   /**
+    * Rebuilds the panel with coloured buttons for residues. If not case
+    * sensitive colours, show 3-letter amino acid code as button text. If case
+    * sensitive, just show the single letter code, in order to make space for the
+    * additional buttons.
+    * 
+    * @param isCaseSensitive
+    */
+   void resetButtonPanel(boolean isCaseSensitive)
    {
      buttonPanel.removeAll();
  
        upperCaseButtons = new ArrayList<JButton>();
      }
  
-     JButton button;
-     String label;
      for (int i = 0; i < 20; i++)
      {
-       if (caseSensitive)
-       {
-         label = ResidueProperties.aa[i];
-       }
-       else
-       {
-         label = ResidueProperties.aa2Triplet.get(ResidueProperties.aa[i])
-                 .toString();
-       }
-       button = makeButton(label, ResidueProperties.aa[i], upperCaseButtons,
-               i);
+       String label = isCaseSensitive ? ResidueProperties.aa[i]
+               : ResidueProperties.aa2Triplet.get(ResidueProperties.aa[i])
+                       .toString();
+       JButton button = makeButton(label, ResidueProperties.aa[i],
+               upperCaseButtons, i);
        buttonPanel.add(button);
      }
  
      buttonPanel.add(makeButton("X", "X", upperCaseButtons, 22));
      buttonPanel.add(makeButton("Gap", "-", upperCaseButtons, 23));
  
-     if (!caseSensitive)
+     if (!isCaseSensitive)
      {
        gridLayout.setRows(6);
        gridLayout.setColumns(4);
        {
          int row = i / cols + 1;
          int index = (row * cols) + i;
-         button = makeButton(ResidueProperties.aa[i].toLowerCase(),
+         JButton button = makeButton(ResidueProperties.aa[i].toLowerCase(),
                  ResidueProperties.aa[i].toLowerCase(), lowerCaseButtons, i);
  
          buttonPanel.add(button, index);
        }
      }
  
-     if (caseSensitive)
+     if (isCaseSensitive)
      {
        buttonPanel.add(makeButton("b", "b", lowerCaseButtons, 20));
        buttonPanel.add(makeButton("z", "z", lowerCaseButtons, 21));
      // codes
      if (this.frame != null)
      {
-       int newWidth = caseSensitive ? MY_FRAME_WIDTH_CASE_SENSITIVE
+       int newWidth = isCaseSensitive ? MY_FRAME_WIDTH_CASE_SENSITIVE
                : MY_FRAME_WIDTH;
        this.frame.setSize(newWidth, this.frame.getHeight());
      }
    }
  
    /**
 -   * DOCUMENT ME!
 +   * ChangeListener handler for when a colour is picked in the colour chooser.
 +   * The action is to apply the colour to all selected buttons as their
 +   * background colour. Foreground colour (text) is set to a lighter shade in
 +   * order to highlight which buttons are selected. If 'Lower Case Colour' is
 +   * active, then the colour is applied to all lower case buttons (as well as
 +   * the Lower Case Colour button itself).
     * 
     * @param evt
 -   *          DOCUMENT ME!
     */
    @Override
    public void stateChanged(ChangeEvent evt)
    {
 -    if (selectedButtons != null)
 +    JButton button = null;
 +    final Color newColour = colorChooser.getColor();
 +    for (int i = 0; i < selectedButtons.size(); i++)
      {
 -      JButton button = null;
 -      final Color newColour = colorChooser.getColor();
 -      for (int i = 0; i < selectedButtons.size(); i++)
 +      button = selectedButtons.get(i);
 +      button.setBackground(newColour);
 +      button.setForeground(ColorUtils.brighterThan(newColour));
 +    }
 +    if (button == lcaseColour)
 +    {
 +      button.setForeground(Color.black);
 +      for (int i = 0; i < lowerCaseButtons.size(); i++)
        {
 -        button = selectedButtons.get(i);
 +        button = lowerCaseButtons.get(i);
          button.setBackground(newColour);
 -        button.setForeground(ColorUtils.brighterThan(newColour));
 -      }
 -      if (button == lcaseColour)
 -      {
 -        for (int i = 0; i < lowerCaseButtons.size(); i++)
 -        {
 -          button = lowerCaseButtons.get(i);
 -          button.setBackground(newColour);
 -          button.setForeground(ColorUtils.brighterThan(button
 -                  .getBackground()));
 -        }
 +        button.setForeground(ColorUtils.brighterThan(button.getBackground()));
        }
      }
    }
     */
    public void colourButtonPressed(MouseEvent e)
    {
 -    if (selectedButtons == null)
 -    {
 -      selectedButtons = new ArrayList<JButton>();
 -    }
 -
      JButton pressed = (JButton) e.getSource();
  
      if (e.isShiftDown())
    }
  
    /**
-    * DOCUMENT ME!
+    * A helper method to update or make a colour button, whose background colour
+    * is the associated colour, and text colour a darker shade of the same. If
+    * the button is already in the list, then its text and margins are updated,
+    * if not then it is created and added. This method supports toggling between
+    * case-sensitive and case-insensitive button panels. The case-sensitive
+    * version has abbreviated button text in order to fit in more buttons.
     * 
     * @param label
-    *          DOCUMENT ME!
-    * @param aa
-    *          DOCUMENT ME!
+    * @param residue
+    * @param the
+    *          list of buttons
+    * @param buttonIndex
+    *          the button's position in the list
     */
-   JButton makeButton(String label, String aa,
-           ArrayList<JButton> caseSensitiveButtons, int buttonIndex)
+   JButton makeButton(String label, String residue, List<JButton> buttons,
+           int buttonIndex)
    {
      final JButton button;
      Color col;
  
-     if (buttonIndex < caseSensitiveButtons.size())
+     if (buttonIndex < buttons.size())
      {
-       button = caseSensitiveButtons.get(buttonIndex);
+       button = buttons.get(buttonIndex);
        col = button.getBackground();
      }
      else
      {
        button = new JButton();
-       button.addMouseListener(new java.awt.event.MouseAdapter()
+       button.addMouseListener(new MouseAdapter()
        {
          @Override
          public void mouseClicked(MouseEvent e)
          }
        });
  
-       caseSensitiveButtons.add(button);
+       buttons.add(button);
  
+       /*
+        * make initial button colour that of the current colour scheme,
+        * if it is a simple per-residue colouring, else white
+        */
        col = Color.white;
-       if (oldColourScheme != null)
+       if (oldColourScheme != null && oldColourScheme.isSimple())
        {
-         try
-         {
-           col = oldColourScheme.findColour(aa.charAt(0), -1, null);
-         } catch (Exception ex)
-         {
-         }
+         col = oldColourScheme.findColour(residue.charAt(0), 0, null, null,
+                 0f);
        }
      }
  
      if (caseSensitive.isSelected())
      {
-       button.setMargin(new java.awt.Insets(2, 2, 2, 2));
+       button.setMargin(new Insets(2, 2, 2, 2));
      }
      else
      {
-       button.setMargin(new java.awt.Insets(2, 14, 2, 14));
+       button.setMargin(new Insets(2, 14, 2, 14));
      }
  
      button.setOpaque(true); // required for the next line to have effect
      button.setBackground(col);
      button.setText(label);
      button.setForeground(ColorUtils.darkerThan(col));
-     button.setFont(new java.awt.Font("Verdana", Font.BOLD, 10));
+     button.setFont(VERDANA_BOLD_10);
  
      return button;
    }
  
    /**
-    * DOCUMENT ME!
-    * 
-    * @param e
-    *          DOCUMENT ME!
+    * On 'OK', check that at least one colour has been assigned to a residue (and
+    * if not issue a warning), and apply the chosen colour scheme and close the
+    * panel.
     */
    @Override
-   protected void okButton_actionPerformed(ActionEvent e)
+   protected void okButton_actionPerformed()
    {
      if (isNoSelectionMade())
      {
      }
      else
      {
-       applyButton_actionPerformed(null);
+       applyButton_actionPerformed();
  
        try
        {
    }
  
    /**
-    * DOCUMENT ME!
-    * 
-    * @param e
-    *          DOCUMENT ME!
+    * Applies the current colour scheme to the alignment, sequence group or
+    * structure view.
     */
    @Override
-   protected void applyButton_actionPerformed(ActionEvent e)
+   protected void applyButton_actionPerformed()
    {
      if (isNoSelectionMade())
      {
  
      }
      UserColourScheme ucs = getSchemeFromButtons();
  
      if (seqGroup != null)
      {
-       seqGroup.cs = ucs;
+       seqGroup.setColourScheme(ucs);
        ap.paintAlignment(true);
      }
      else if (ap != null)
      {
        ap.alignFrame.changeColour(ucs);
      }
-     else if (jmol != null)
+     else if (structureViewer != null)
      {
-       jmol.setJalviewColourScheme(ucs);
+       structureViewer.setJalviewColourScheme(ucs);
      }
    }
  
      }
  
      UserColourScheme ucs = new UserColourScheme(newColours);
+     ucs.setName(schemeName.getText());
  
      if (caseSensitive.isSelected())
      {
        ucs.setLowerCaseColours(newColours);
      }
  
-     if (ap != null)
-     {
-       ucs.setThreshold(0, ap.av.isIgnoreGapsConsensus());
-     }
+     // if (ap != null)
+     // {
+     // ucs.setThreshold(0, ap.av.isIgnoreGapsConsensus());
+     // }
  
      return ucs;
    }
  
      int value = chooser.showOpenDialog(this);
  
-     if (value == JalviewFileChooser.APPROVE_OPTION)
+     if (value != JalviewFileChooser.APPROVE_OPTION)
      {
-       File choice = chooser.getSelectedFile();
-       jalview.bin.Cache.setProperty("LAST_DIRECTORY", choice.getParent());
-       String defaultColours = jalview.bin.Cache.getDefault(
-               "USER_DEFINED_COLOURS", choice.getPath());
-       if (defaultColours.indexOf(choice.getPath()) == -1)
-       {
-         defaultColours = defaultColours.concat("|")
-                 .concat(choice.getPath());
-       }
-       jalview.bin.Cache.setProperty("USER_DEFINED_COLOURS", defaultColours);
-       UserColourScheme ucs = loadColours(choice.getAbsolutePath());
-       Color[] colors = ucs.getColours();
-       schemeName.setText(ucs.getName());
+       return;
+     }
+     File choice = chooser.getSelectedFile();
+     Cache.setProperty(LAST_DIRECTORY, choice.getParent());
  
-       if (ucs.getLowerCaseColours() != null)
-       {
-         caseSensitive.setSelected(true);
-         lcaseColour.setEnabled(true);
-         resetButtonPanel(true);
-         for (int i = 0; i < lowerCaseButtons.size(); i++)
-         {
-           JButton button = lowerCaseButtons.get(i);
-           button.setBackground(ucs.getLowerCaseColours()[i]);
-         }
+     UserColourScheme ucs = ColourSchemes.loadColourScheme(choice
+             .getAbsolutePath());
+     Color[] colors = ucs.getColours();
+     schemeName.setText(ucs.getSchemeName());
  
-       }
-       else
-       {
-         caseSensitive.setSelected(false);
-         lcaseColour.setEnabled(false);
-         resetButtonPanel(false);
-       }
-       for (int i = 0; i < upperCaseButtons.size(); i++)
+     if (ucs.getLowerCaseColours() != null)
+     {
+       caseSensitive.setSelected(true);
+       lcaseColour.setEnabled(true);
+       resetButtonPanel(true);
+       for (int i = 0; i < lowerCaseButtons.size(); i++)
        {
-         JButton button = upperCaseButtons.get(i);
-         button.setBackground(colors[i]);
+         JButton button = lowerCaseButtons.get(i);
+         button.setBackground(ucs.getLowerCaseColours()[i]);
        }
+     }
+     else
+     {
+       caseSensitive.setSelected(false);
+       lcaseColour.setEnabled(false);
+       resetButtonPanel(false);
+     }
  
+     for (int i = 0; i < upperCaseButtons.size(); i++)
+     {
+       JButton button = upperCaseButtons.get(i);
+       button.setBackground(colors[i]);
      }
+     addNewColourScheme(choice.getPath());
    }
  
    /**
-    * DOCUMENT ME!
+    * Loads the user-defined colour scheme from the first file listed in property
+    * "USER_DEFINED_COLOURS". If this fails, returns an all-white colour scheme.
     * 
-    * @return DOCUMENT ME!
+    * @return
     */
    public static UserColourScheme loadDefaultColours()
    {
      UserColourScheme ret = null;
  
-     String colours = jalview.bin.Cache.getProperty("USER_DEFINED_COLOURS");
+     String colours = Cache.getProperty(USER_DEFINED_COLOURS);
      if (colours != null)
      {
        if (colours.indexOf("|") > -1)
        {
          colours = colours.substring(0, colours.indexOf("|"));
        }
-       ret = loadColours(colours);
+       ret = ColourSchemes.loadColourScheme(colours);
      }
  
      if (ret == null)
      {
-       Color[] newColours = new Color[24];
-       for (int i = 0; i < 24; i++)
-       {
-         newColours[i] = Color.white;
-       }
-       ret = new UserColourScheme(newColours);
+       ret = new UserColourScheme("white");
      }
  
      return ret;
    /**
     * DOCUMENT ME!
     * 
-    * @param file
-    *          DOCUMENT ME!
-    * 
-    * @return DOCUMENT ME!
-    */
-   static UserColourScheme loadColours(String file)
-   {
-     UserColourScheme ucs = null;
-     Color[] newColours = null;
-     try
-     {
-       InputStreamReader in = new InputStreamReader(
-               new FileInputStream(file), "UTF-8");
-       jalview.schemabinding.version2.JalviewUserColours jucs = new jalview.schemabinding.version2.JalviewUserColours();
-       org.exolab.castor.xml.Unmarshaller unmar = new org.exolab.castor.xml.Unmarshaller(
-               jucs);
-       jucs = (jalview.schemabinding.version2.JalviewUserColours) unmar
-               .unmarshal(in);
-       newColours = new Color[24];
-       Color[] lowerCase = null;
-       boolean caseSensitive = false;
-       String name;
-       int index;
-       for (int i = 0; i < jucs.getColourCount(); i++)
-       {
-         name = jucs.getColour(i).getName();
-         if (ResidueProperties.aa3Hash.containsKey(name))
-         {
-           index = ResidueProperties.aa3Hash.get(name).intValue();
-         }
-         else
-         {
-           index = ResidueProperties.aaIndex[name.charAt(0)];
-         }
-         if (index == -1)
-         {
-           continue;
-         }
-         if (name.toLowerCase().equals(name))
-         {
-           if (lowerCase == null)
-           {
-             lowerCase = new Color[23];
-           }
-           caseSensitive = true;
-           lowerCase[index] = new Color(Integer.parseInt(jucs.getColour(i)
-                   .getRGB(), 16));
-         }
-         else
-         {
-           newColours[index] = new Color(Integer.parseInt(jucs.getColour(i)
-                   .getRGB(), 16));
-         }
-       }
-       if (newColours != null)
-       {
-         ucs = new UserColourScheme(newColours);
-         ucs.setName(jucs.getSchemeName());
-         if (caseSensitive)
-         {
-           ucs.setLowerCaseColours(lowerCase);
-         }
-       }
-     } catch (Exception ex)
-     {
-       // Could be Archive Jalview format
-       try
-       {
-         InputStreamReader in = new InputStreamReader(new FileInputStream(
-                 file), "UTF-8");
-         jalview.binding.JalviewUserColours jucs = new jalview.binding.JalviewUserColours();
-         jucs = jucs.unmarshal(in);
-         newColours = new Color[jucs.getColourCount()];
-         for (int i = 0; i < 24; i++)
-         {
-           newColours[i] = new Color(Integer.parseInt(jucs.getColour(i)
-                   .getRGB(), 16));
-         }
-         if (newColours != null)
-         {
-           ucs = new UserColourScheme(newColours);
-           ucs.setName(jucs.getSchemeName());
-         }
-       } catch (Exception ex2)
-       {
-         ex2.printStackTrace();
-       }
-       if (newColours == null)
-       {
-         System.out.println("Error loading User ColourFile\n" + ex);
-       }
-     }
-     return ucs;
-   }
-   /**
-    * DOCUMENT ME!
-    * 
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    protected void savebutton_actionPerformed(ActionEvent e)
    {
-     if (schemeName.getText().trim().length() < 1)
+     String name = schemeName.getText().trim();
+     if (name.length() < 1)
      {
        JvOptionPane.showInternalMessageDialog(Desktop.desktop, MessageManager
                .getString("label.user_colour_scheme_must_have_name"),
        return;
      }
  
-     if (userColourSchemes != null
-             && userColourSchemes.containsKey(schemeName.getText()))
+     if (ColourSchemes.getInstance().nameExists(name))
      {
        int reply = JvOptionPane.showInternalConfirmDialog(Desktop.desktop,
                MessageManager.formatMessage(
                        "label.colour_scheme_exists_overwrite", new Object[] {
-                           schemeName.getText(), schemeName.getText() }),
+                           name, name }),
                MessageManager.getString("label.duplicate_scheme_name"),
                JvOptionPane.YES_NO_OPTION);
        if (reply != JvOptionPane.YES_OPTION)
        {
          return;
        }
-       userColourSchemes.remove(schemeName.getText());
+       ColourSchemes.getInstance().removeColourScheme(name);
      }
      JalviewFileChooser chooser = new JalviewFileChooser("jc",
              "Jalview User Colours");
  
-     chooser.setFileView(new JalviewFileView());
+     JalviewFileView fileView = new JalviewFileView();
+     chooser.setFileView(fileView);
      chooser.setDialogTitle(MessageManager
              .getString("label.save_colour_scheme"));
      chooser.setToolTipText(MessageManager.getString("action.save"));
  
      if (value == JalviewFileChooser.APPROVE_OPTION)
      {
-       String choice = chooser.getSelectedFile().getPath();
-       String defaultColours = jalview.bin.Cache.getDefault(
-               "USER_DEFINED_COLOURS", choice);
-       if (defaultColours.indexOf(choice) == -1)
-       {
-         if (defaultColours.length() > 0)
-         {
-           defaultColours = defaultColours.concat("|");
-         }
-         defaultColours = defaultColours.concat(choice);
-       }
-       userColourSchemes.put(schemeName.getText(), getSchemeFromButtons());
-       ap.alignFrame.updateUserColourMenu();
-       jalview.bin.Cache.setProperty("USER_DEFINED_COLOURS", defaultColours);
-       jalview.schemabinding.version2.JalviewUserColours ucs = new jalview.schemabinding.version2.JalviewUserColours();
-       ucs.setSchemeName(schemeName.getText());
-       try
-       {
-         PrintWriter out = new PrintWriter(new OutputStreamWriter(
-                 new FileOutputStream(choice), "UTF-8"));
-         for (int i = 0; i < buttonPanel.getComponentCount(); i++)
-         {
-           JButton button = (JButton) buttonPanel.getComponent(i);
-           jalview.schemabinding.version2.Colour col = new jalview.schemabinding.version2.Colour();
-           col.setName(button.getText());
-           col.setRGB(jalview.util.Format.getHexString(button
-                   .getBackground()));
-           ucs.addColour(col);
-         }
-         ucs.marshal(out);
-         out.close();
-       } catch (Exception ex)
-       {
-         ex.printStackTrace();
-       }
+       File file = chooser.getSelectedFile();
+       addNewColourScheme(file.getPath());
+       saveToFile(file);
      }
    }
  
    /**
-    * DOCUMENT ME!
+    * Adds the current colour scheme to the Jalview properties file so it is
+    * loaded on next startup, and updates the Colour menu in the parent
+    * AlignFrame (if there is one). Note this action does not including applying
+    * the colour scheme.
     * 
-    * @param e
-    *          DOCUMENT ME!
+    * @param filePath
     */
-   @Override
-   protected void cancelButton_actionPerformed(ActionEvent e)
+   protected void addNewColourScheme(String filePath)
    {
-     if (ap != null)
+     /*
+      * update the delimited list of user defined colour files in
+      * Jalview property USER_DEFINED_COLOURS
+      */
+     String defaultColours = Cache
+             .getDefault(USER_DEFINED_COLOURS, filePath);
+     if (defaultColours.indexOf(filePath) == -1)
      {
-       if (seqGroup != null)
+       if (defaultColours.length() > 0)
        {
-         seqGroup.cs = oldColourScheme;
+         defaultColours = defaultColours.concat("|");
        }
-       else if (ap != null)
-       {
-         ap.av.setGlobalColourScheme(oldColourScheme);
-       }
-       ap.paintAlignment(true);
+       defaultColours = defaultColours.concat(filePath);
      }
+     Cache.setProperty(USER_DEFINED_COLOURS, defaultColours);
  
-     if (jmol != null)
-     {
-       jmol.setJalviewColourScheme(oldColourScheme);
-     }
+     /*
+      * construct and register the colour scheme
+      */
+     UserColourScheme ucs = getSchemeFromButtons();
+     ColourSchemes.getInstance().registerColourScheme(ucs);
  
-     try
-     {
-       frame.setClosed(true);
-     } catch (Exception ex)
+     /*
+      * update the Colour menu items
+      */
+     if (ap != null)
      {
+       ap.alignFrame.buildColourMenu();
      }
    }
  
-   static Hashtable userColourSchemes;
-   public static Hashtable getUserColourSchemes()
-   {
-     return userColourSchemes;
-   }
-   public static void initUserColourSchemes(String files)
+   /**
+    * Saves the colour scheme to file in XML format
+    * 
+    * @param path
+    */
+   protected void saveToFile(File toFile)
    {
-     userColourSchemes = new Hashtable();
-     if (files == null || files.length() == 0)
+     /*
+      * build a Java model of colour scheme as XML, and 
+      * marshal to file
+      */
+     JalviewUserColours ucs = new JalviewUserColours();
+     ucs.setSchemeName(schemeName.getText());
+     try
      {
-       return;
-     }
+       PrintWriter out = new PrintWriter(new OutputStreamWriter(
+               new FileOutputStream(toFile), "UTF-8"));
  
-     // In case colours can't be loaded, we'll remove them
-     // from the default list here.
-     StringBuffer coloursFound = new StringBuffer();
-     StringTokenizer st = new StringTokenizer(files, "|");
-     while (st.hasMoreElements())
-     {
-       String file = st.nextToken();
-       try
-       {
-         UserColourScheme ucs = loadColours(file);
-         if (ucs != null)
-         {
-           if (coloursFound.length() > 0)
-           {
-             coloursFound.append("|");
-           }
-           coloursFound.append(file);
-           userColourSchemes.put(ucs.getName(), ucs);
-         }
-       } catch (Exception ex)
+       for (int i = 0; i < buttonPanel.getComponentCount(); i++)
        {
-         System.out.println("Error loading User ColourFile\n" + ex);
+         JButton button = (JButton) buttonPanel.getComponent(i);
+         Colour col = new Colour();
+         col.setName(button.getText());
+         col.setRGB(Format.getHexString(button.getBackground()));
+         ucs.addColour(col);
        }
-     }
-     if (!files.equals(coloursFound.toString()))
+       ucs.marshal(out);
+       out.close();
+     } catch (Exception ex)
      {
-       if (coloursFound.toString().length() > 1)
-       {
-         jalview.bin.Cache.setProperty("USER_DEFINED_COLOURS",
-                 coloursFound.toString());
-       }
-       else
-       {
-         jalview.bin.Cache.applicationProperties
-                 .remove("USER_DEFINED_COLOURS");
-       }
+       ex.printStackTrace();
      }
    }
  
-   public static void removeColourFromDefaults(String target)
+   /**
+    * On cancel, restores the colour scheme before the dialogue was opened
+    * 
+    * @param e
+    */
+   @Override
+   protected void cancelButton_actionPerformed(ActionEvent e)
    {
-     // The only way to find colours by name is to load them in
-     // In case colours can't be loaded, we'll remove them
-     // from the default list here.
-     userColourSchemes = new Hashtable();
-     StringBuffer coloursFound = new StringBuffer();
-     StringTokenizer st = new StringTokenizer(
-             jalview.bin.Cache.getProperty("USER_DEFINED_COLOURS"), "|");
-     while (st.hasMoreElements())
+     if (ap != null)
      {
-       String file = st.nextToken();
-       try
+       if (seqGroup != null)
        {
-         UserColourScheme ucs = loadColours(file);
-         if (ucs != null && !ucs.getName().equals(target))
-         {
-           if (coloursFound.length() > 0)
-           {
-             coloursFound.append("|");
-           }
-           coloursFound.append(file);
-           userColourSchemes.put(ucs.getName(), ucs);
-         }
-       } catch (Exception ex)
+         seqGroup.setColourScheme(oldColourScheme);
+       }
+       else
        {
-         System.out.println("Error loading User ColourFile\n" + ex);
+         ap.alignFrame.changeColour(oldColourScheme);
        }
+       ap.paintAlignment(true);
      }
  
-     if (coloursFound.toString().length() > 1)
+     if (structureViewer != null)
      {
-       jalview.bin.Cache.setProperty("USER_DEFINED_COLOURS",
-               coloursFound.toString());
+       structureViewer.setJalviewColourScheme(oldColourScheme);
      }
-     else
+     try
+     {
+       frame.setClosed(true);
+     } catch (Exception ex)
      {
-       jalview.bin.Cache.applicationProperties
-               .remove("USER_DEFINED_COLOURS");
      }
    }
  
    @Override
    public void caseSensitive_actionPerformed(ActionEvent e)
    {
 -    resetButtonPanel(caseSensitive.isSelected());
 -    lcaseColour.setEnabled(caseSensitive.isSelected());
 +    boolean selected = caseSensitive.isSelected();
 +    resetButtonPanel(selected);
 +    lcaseColour.setEnabled(selected);
 +    lcaseColour.setForeground(Color.GRAY);
    }
  
 +  /**
 +   * Action on clicking 'Lower case colour', which results in changing colour of
 +   * all lower-case buttons when a colour is picked. A second click of the
 +   * button turns off this behaviour.
 +   */
    @Override
    public void lcaseColour_actionPerformed(ActionEvent e)
    {
 -    if (selectedButtons == null)
 +    boolean enable = !selectedButtons.contains(lcaseColour);
 +    selectedButtons.clear();
 +    if (enable)
      {
 -      selectedButtons = new ArrayList<JButton>();
 +      selectedButtons.add(lcaseColour);
 +      lcaseColour.setForeground(lowerCaseButtons.get(0).getForeground());
 +      lcaseColour.setForeground(Color.black);
      }
      else
      {
 -      selectedButtons.clear();
 +      lcaseColour.setBackground(Color.white);
 +      lcaseColour.setForeground(Color.gray);
      }
 -    selectedButtons.add(lcaseColour);
    }
  }
@@@ -32,8 -32,6 +32,8 @@@ import java.awt.GridBagLayout
  import java.awt.GridLayout;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
 +import java.util.ArrayList;
 +import java.util.List;
  
  import javax.swing.JButton;
  import javax.swing.JCheckBox;
@@@ -106,8 -104,6 +106,8 @@@ public class GUserDefinedColours extend
  
    protected JButton lcaseColour = new JButton();
  
 +  protected List<JButton> selectedButtons;
 +
    /**
     * Creates a new GUserDefinedColours object.
     */
      okButton.setText(MessageManager.getString("action.ok"));
      okButton.addActionListener(new java.awt.event.ActionListener()
      {
+       @Override
        public void actionPerformed(ActionEvent e)
        {
-         okButton_actionPerformed(e);
+         okButton_actionPerformed();
        }
      });
      applyButton.setFont(new java.awt.Font("Verdana", 0, 11));
      applyButton.setText(MessageManager.getString("action.apply"));
-     applyButton.addActionListener(new java.awt.event.ActionListener()
+     applyButton.addActionListener(new ActionListener()
      {
+       @Override
        public void actionPerformed(ActionEvent e)
        {
-         applyButton_actionPerformed(e);
+         applyButton_actionPerformed();
        }
      });
      loadbutton.setFont(new java.awt.Font("Verdana", 0, 11));
      loadbutton.setText(MessageManager.getString("action.load_scheme"));
      loadbutton.addActionListener(new java.awt.event.ActionListener()
      {
+       @Override
        public void actionPerformed(ActionEvent e)
        {
          loadbutton_actionPerformed(e);
      savebutton.setText(MessageManager.getString("action.save_scheme"));
      savebutton.addActionListener(new java.awt.event.ActionListener()
      {
+       @Override
        public void actionPerformed(ActionEvent e)
        {
          savebutton_actionPerformed(e);
      cancelButton.setText(MessageManager.getString("action.cancel"));
      cancelButton.addActionListener(new java.awt.event.ActionListener()
      {
+       @Override
        public void actionPerformed(ActionEvent e)
        {
          cancelButton_actionPerformed(e);
      caseSensitive.setText(MessageManager.getString("label.case_sensitive"));
      caseSensitive.addActionListener(new ActionListener()
      {
+       @Override
        public void actionPerformed(ActionEvent e)
        {
          caseSensitive_actionPerformed(e);
              .setText(MessageManager.getString("label.lower_case_colour"));
      lcaseColour.addActionListener(new ActionListener()
      {
+       @Override
        public void actionPerformed(ActionEvent e)
        {
          lcaseColour_actionPerformed(e);
        colorChooser
                .setChooserPanels(new AbstractColorChooserPanel[] { choosers[0] });
      }
 +
 +    selectedButtons = new ArrayList<JButton>();
    }
  
    /**
     * DOCUMENT ME!
-    * 
-    * @param e
-    *          DOCUMENT ME!
     */
-   protected void okButton_actionPerformed(ActionEvent e)
+   protected void okButton_actionPerformed()
    {
    }
  
    /**
     * DOCUMENT ME!
-    * 
-    * @param e
-    *          DOCUMENT ME!
     */
-   protected void applyButton_actionPerformed(ActionEvent e)
+   protected void applyButton_actionPerformed()
    {
    }