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