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