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