9479ea6f16c38520b0ceb1a68bc9e38cea31ff8b
[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    * @param simpleOnly
76    *          if true, only simple per-residue colour schemes are included
77    */
78   public static ButtonGroup addMenuItems(final JMenu colourMenu,
79           final ColourChangeListener client, AnnotatedCollectionI coll,
80           boolean simpleOnly)
81   {
82     /*
83      * ButtonGroup groups those items whose 
84      * selection is mutually exclusive
85      */
86     ButtonGroup colours = new ButtonGroup();
87
88     if (!simpleOnly)
89     {
90       JRadioButtonMenuItem noColourmenuItem = new JRadioButtonMenuItem(
91               MessageManager.getString("label.none"));
92       noColourmenuItem.setName(ResidueColourScheme.NONE);
93       noColourmenuItem.addActionListener(new ActionListener()
94       {
95         @Override
96         public void actionPerformed(ActionEvent e)
97         {
98           client.changeColour_actionPerformed(ResidueColourScheme.NONE);
99         }
100       });
101       colourMenu.add(noColourmenuItem);
102       colours.add(noColourmenuItem);
103     }
104
105     /*
106      * scan registered colour schemes (built-in or user-defined)
107      * and add them to the menu (in the order they were registered)
108      */
109     Iterable<ColourSchemeI> colourSchemes = ColourSchemes.getInstance()
110             .getColourSchemes();
111     for (ColourSchemeI scheme : colourSchemes)
112     {
113       if (simpleOnly && !scheme.isSimple())
114       {
115         continue;
116       }
117
118       /*
119        * button text is i18n'd but the name is the canonical name of
120        * the colour scheme (inspected in setColourSelected())
121        */
122       final String name = scheme.getSchemeName();
123       String label = MessageManager.getStringOrReturn(
124               "label.colourScheme_", name.toLowerCase().replace(" ", "_"));
125       final JRadioButtonMenuItem radioItem = new JRadioButtonMenuItem(
126               label);
127       radioItem.setName(name);
128       radioItem.setEnabled(scheme.isApplicableTo(coll));
129       if (scheme instanceof UserColourScheme)
130       {
131         /*
132          * user-defined colour scheme loaded on startup or during the
133          * Jalview session; right-click on this offers the option to
134          * remove it as a colour choice (unless currently selected)
135          */
136         radioItem.addMouseListener(new MouseAdapter()
137         {
138           @Override
139           public void mousePressed(MouseEvent evt)
140           {
141             if (evt.isPopupTrigger() && !radioItem.isSelected()) // Mac
142             {
143               offerRemoval();
144             }
145           }
146
147           @Override
148           public void mouseReleased(MouseEvent evt)
149           {
150             if (evt.isPopupTrigger() && !radioItem.isSelected()) // Windows
151             {
152               offerRemoval();
153             }
154           }
155
156           void offerRemoval()
157           {
158             ActionListener al = radioItem.getActionListeners()[0];
159             radioItem.removeActionListener(al);
160             int option = JvOptionPane.showInternalConfirmDialog(
161                     Desktop.desktop,
162                     MessageManager
163                             .getString("label.remove_from_default_list"),
164                     MessageManager
165                             .getString("label.remove_user_defined_colour"),
166                     JvOptionPane.YES_NO_OPTION);
167             if (option == JvOptionPane.YES_OPTION)
168             {
169               ColourSchemes.getInstance()
170                       .removeColourScheme(radioItem.getName());
171               colourMenu.remove(radioItem);
172               updatePreferences();
173             }
174             else
175             {
176               radioItem.addActionListener(al);
177             }
178           }
179         });
180       }
181       radioItem.addActionListener(new ActionListener()
182       {
183         @Override
184         public void actionPerformed(ActionEvent evt)
185         {
186           client.changeColour_actionPerformed(name);
187         }
188       });
189       colourMenu.add(radioItem);
190       colours.add(radioItem);
191     }
192
193     /*
194      * only add the option to load/configure a user-defined colour
195      * to the AlignFrame colour menu
196      */
197     if (client instanceof AlignFrame)
198     {
199       final String label = MessageManager.getString("action.user_defined");
200       JRadioButtonMenuItem userDefinedColour = new JRadioButtonMenuItem(
201               label);
202       userDefinedColour.setName(ResidueColourScheme.USER_DEFINED_MENU);
203       userDefinedColour.addActionListener(new ActionListener()
204       {
205         @Override
206         public void actionPerformed(ActionEvent e)
207         {
208           client.changeColour_actionPerformed(
209                   ResidueColourScheme.USER_DEFINED_MENU);
210         }
211       });
212       colourMenu.add(userDefinedColour);
213       colours.add(userDefinedColour);
214     }
215
216     return colours;
217   }
218
219   /**
220    * Marks as selected the colour menu item matching the given colour scheme, or
221    * the first item ('None') if no match is found. If the colour scheme is a
222    * user defined scheme, but not in the menu (this arises if a new scheme is
223    * defined and applied but not saved to file), then menu option "User
224    * Defined.." is selected.
225    * 
226    * @param colourMenu
227    * @param cs
228    */
229   public static void setColourSelected(JMenu colourMenu, ColourSchemeI cs)
230   {
231     String colourName = cs == null ? ResidueColourScheme.NONE
232             : cs.getSchemeName();
233
234     JRadioButtonMenuItem none = null;
235     JRadioButtonMenuItem userDefined = null;
236
237     /*
238      * select the radio button whose name matches the colour name
239      * (not the button text, as it may be internationalised)
240      */
241     for (Component menuItem : colourMenu.getMenuComponents())
242     {
243       if (menuItem instanceof JRadioButtonMenuItem)
244       {
245         JRadioButtonMenuItem radioButton = (JRadioButtonMenuItem) menuItem;
246         String buttonName = radioButton.getName();
247         if (buttonName.equals(colourName))
248         {
249           radioButton.setSelected(true);
250           return;
251         }
252         if (ResidueColourScheme.NONE.equals(buttonName))
253         {
254           none = radioButton;
255         }
256         if (ResidueColourScheme.USER_DEFINED_MENU.equals(buttonName))
257         {
258           userDefined = radioButton;
259         }
260       }
261     }
262
263     /*
264      * no match by name; select User Defined.. if current scheme is a 
265      * user defined one, else select None
266      */
267     if (cs instanceof UserColourScheme && userDefined != null)
268     {
269       userDefined.setSelected(true);
270     }
271     else if (none != null)
272     {
273       none.setSelected(true);
274     }
275   }
276
277   /**
278    * Updates the USER_DEFINE_COLOURS preference to remove any de-registered
279    * colour scheme
280    */
281   static void updatePreferences()
282   {
283     StringBuilder coloursFound = new StringBuilder();
284     String[] files = Cache.getProperty("USER_DEFINED_COLOURS").split("\\|");
285
286     /*
287      * the property does not include the scheme name, it is in the file;
288      * so just load the colour schemes and discard any whose name is not
289      * registered
290      */
291     for (String file : files)
292     {
293       try
294       {
295         UserColourScheme ucs = ColourSchemeLoader.loadColourScheme(file);
296         if (ucs != null
297                 && ColourSchemes.getInstance().nameExists(ucs.getName()))
298         {
299           if (coloursFound.length() > 0)
300           {
301             coloursFound.append("|");
302           }
303           coloursFound.append(file);
304         }
305       } catch (Exception ex)
306       {
307         System.out.println("Error loading User ColourFile\n" + ex);
308       }
309     }
310
311     if (coloursFound.toString().length() > 1)
312     {
313       Cache.setProperty("USER_DEFINED_COLOURS", coloursFound.toString());
314     }
315     else
316     {
317       Cache.applicationProperties.remove("USER_DEFINED_COLOURS");
318     }
319   }
320 }