JAL-3148 add all options to colour menu
[jalview.git] / src / jalview / gui / ColourMenuHelper.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.gui;
22
23 import jalview.bin.Cache;
24 import jalview.datamodel.AnnotatedCollectionI;
25 import jalview.schemes.ColourSchemeI;
26 import jalview.schemes.ColourSchemeLoader;
27 import jalview.schemes.ColourSchemes;
28 import jalview.schemes.ResidueColourScheme;
29 import jalview.schemes.UserColourScheme;
30 import jalview.util.MessageManager;
31
32 import java.awt.Component;
33 import java.awt.event.ActionEvent;
34 import java.awt.event.ActionListener;
35 import java.awt.event.MouseAdapter;
36 import java.awt.event.MouseEvent;
37
38 import javax.swing.ButtonGroup;
39 import javax.swing.JMenu;
40 import javax.swing.JRadioButtonMenuItem;
41
42 public class ColourMenuHelper
43 {
44   public interface ColourChangeListener
45   {
46     /**
47      * Change colour scheme to the selected scheme
48      * 
49      * @param name
50      *          the registered (unique) name of a colour scheme
51      */
52     void changeColour_actionPerformed(String name);
53   }
54
55   /**
56    * Adds items to the colour menu, as mutually exclusive members of a button
57    * group. The callback handler is responsible for the action on selecting any
58    * of these options. The callback method receives the name of the selected
59    * colour, or "None" or "User Defined". This method returns the ButtonGroup to
60    * which items were added.
61    * <ul>
62    * <li>None</li>
63    * <li>Clustal</li>
64    * <li>...other 'built-in' colours</li>
65    * <li>...any user-defined colours</li>
66    * <li>User Defined..(only for AlignFrame menu)</li>
67    * </ul>
68    * 
69    * @param colourMenu
70    *          the menu to attach items to
71    * @param client
72    *          a callback to handle menu selection
73    * @param coll
74    *          the data the menu is being built for
75    */
76   public static ButtonGroup addMenuItems(final JMenu colourMenu,
77                         final ColourChangeListener client, AnnotatedCollectionI coll)
78   {
79     /*
80      * ButtonGroup groups those items whose 
81      * selection is mutually exclusive
82      */
83     ButtonGroup colours = new ButtonGroup();
84
85         JRadioButtonMenuItem noColourmenuItem = new JRadioButtonMenuItem(MessageManager.getString("label.none"));
86         noColourmenuItem.setName(ResidueColourScheme.NONE);
87         noColourmenuItem.addActionListener(new ActionListener() 
88         {
89           @Override
90           public void actionPerformed(ActionEvent e) 
91           {
92             client.changeColour_actionPerformed(ResidueColourScheme.NONE);
93           }
94         });
95         colourMenu.add(noColourmenuItem);
96         colours.add(noColourmenuItem);
97
98     /*
99      * scan registered colour schemes (built-in or user-defined)
100      * and add them to the menu (in the order they were registered)
101      */
102     Iterable<ColourSchemeI> colourSchemes = ColourSchemes.getInstance()
103             .getColourSchemes();
104     for (ColourSchemeI scheme : colourSchemes)
105     {
106       /*
107        * button text is i18n'd but the name is the canonical name of
108        * the colour scheme (inspected in setColourSelected())
109        */
110       final String name = scheme.getSchemeName();
111       String label = MessageManager.getStringOrReturn(
112               "label.colourScheme_" + name.toLowerCase().replace(" ", "_"),
113               name);
114       final JRadioButtonMenuItem radioItem = new JRadioButtonMenuItem(
115               label);
116       radioItem.setName(name);
117       radioItem.setEnabled(scheme.isApplicableTo(coll));
118       if (scheme instanceof UserColourScheme)
119       {
120         /*
121          * user-defined colour scheme loaded on startup or during the
122          * Jalview session; right-click on this offers the option to
123          * remove it as a colour choice (unless currently selected)
124          */
125         radioItem.addMouseListener(new MouseAdapter()
126         {
127           @Override
128           public void mousePressed(MouseEvent evt)
129           {
130             if (evt.isPopupTrigger() && !radioItem.isSelected()) // Mac
131             {
132               offerRemoval();
133             }
134           }
135
136           @Override
137           public void mouseReleased(MouseEvent evt)
138           {
139             if (evt.isPopupTrigger() && !radioItem.isSelected()) // Windows
140             {
141               offerRemoval();
142             }
143           }
144
145           void offerRemoval()
146           {
147             ActionListener al = radioItem.getActionListeners()[0];
148             radioItem.removeActionListener(al);
149             int option = JvOptionPane.showInternalConfirmDialog(
150                     Desktop.desktop,
151                     MessageManager
152                             .getString("label.remove_from_default_list"),
153                     MessageManager
154                             .getString("label.remove_user_defined_colour"),
155                     JvOptionPane.YES_NO_OPTION);
156             if (option == JvOptionPane.YES_OPTION)
157             {
158               ColourSchemes.getInstance()
159                       .removeColourScheme(radioItem.getName());
160               colourMenu.remove(radioItem);
161               updatePreferences();
162             }
163             else
164             {
165               radioItem.addActionListener(al);
166             }
167           }
168         });
169       }
170       radioItem.addActionListener(new ActionListener()
171       {
172         @Override
173         public void actionPerformed(ActionEvent evt)
174         {
175           client.changeColour_actionPerformed(name);
176         }
177       });
178       colourMenu.add(radioItem);
179       colours.add(radioItem);
180     }
181
182     /*
183      * only add the option to load/configure a user-defined colour
184      * to the AlignFrame colour menu
185      */
186     if (client instanceof AlignFrame)
187     {
188       final String label = MessageManager.getString("action.user_defined");
189       JRadioButtonMenuItem userDefinedColour = new JRadioButtonMenuItem(
190               label);
191       userDefinedColour.setName(ResidueColourScheme.USER_DEFINED_MENU);
192       userDefinedColour.addActionListener(new ActionListener()
193       {
194         @Override
195         public void actionPerformed(ActionEvent e)
196         {
197           client.changeColour_actionPerformed(
198                   ResidueColourScheme.USER_DEFINED_MENU);
199         }
200       });
201       colourMenu.add(userDefinedColour);
202       colours.add(userDefinedColour);
203     }
204
205     return colours;
206   }
207
208   /**
209    * Marks as selected the colour menu item matching the given colour scheme, or
210    * the first item ('None') if no match is found. If the colour scheme is a
211    * user defined scheme, but not in the menu (this arises if a new scheme is
212    * defined and applied but not saved to file), then menu option "User
213    * Defined.." is selected.
214    * 
215    * @param colourMenu
216    * @param cs
217    */
218   public static void setColourSelected(JMenu colourMenu, ColourSchemeI cs)
219   {
220     String colourName = cs == null ? ResidueColourScheme.NONE
221             : cs.getSchemeName();
222
223     JRadioButtonMenuItem none = null;
224     JRadioButtonMenuItem userDefined = null;
225
226     /*
227      * select the radio button whose name matches the colour name
228      * (not the button text, as it may be internationalised)
229      */
230     for (Component menuItem : colourMenu.getMenuComponents())
231     {
232       if (menuItem instanceof JRadioButtonMenuItem)
233       {
234         JRadioButtonMenuItem radioButton = (JRadioButtonMenuItem) menuItem;
235         String buttonName = radioButton.getName();
236         if (buttonName.equals(colourName))
237         {
238           radioButton.setSelected(true);
239           return;
240         }
241         if (ResidueColourScheme.NONE.equals(buttonName))
242         {
243           none = radioButton;
244         }
245         if (ResidueColourScheme.USER_DEFINED_MENU.equals(buttonName))
246         {
247           userDefined = radioButton;
248         }
249       }
250     }
251
252     /*
253      * no match by name; select User Defined.. if current scheme is a 
254      * user defined one, else select None
255      */
256     if (cs instanceof UserColourScheme && userDefined != null)
257     {
258       userDefined.setSelected(true);
259     }
260     else if (none != null)
261     {
262       none.setSelected(true);
263     }
264   }
265
266   /**
267    * Updates the USER_DEFINE_COLOURS preference to remove any de-registered
268    * colour scheme
269    */
270   static void updatePreferences()
271   {
272     StringBuilder coloursFound = new StringBuilder();
273     String[] files = Cache.getProperty("USER_DEFINED_COLOURS").split("\\|");
274
275     /*
276      * the property does not include the scheme name, it is in the file;
277      * so just load the colour schemes and discard any whose name is not
278      * registered
279      */
280     for (String file : files)
281     {
282       try
283       {
284         UserColourScheme ucs = ColourSchemeLoader.loadColourScheme(file);
285         if (ucs != null
286                 && ColourSchemes.getInstance().nameExists(ucs.getName()))
287         {
288           if (coloursFound.length() > 0)
289           {
290             coloursFound.append("|");
291           }
292           coloursFound.append(file);
293         }
294       } catch (Exception ex)
295       {
296         System.out.println("Error loading User ColourFile\n" + ex);
297       }
298     }
299
300     if (coloursFound.toString().length() > 1)
301     {
302       Cache.setProperty("USER_DEFINED_COLOURS", coloursFound.toString());
303     }
304     else
305     {
306       Cache.applicationProperties.remove("USER_DEFINED_COLOURS");
307     }
308   }
309 }