9c9290fa6ce867ed7cadeab1baa3e6eee2679c49
[jalview.git] / src / jalview / gui / UserDefinedColours.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.api.structures.JalviewStructureDisplayI;
24 import jalview.bin.Cache;
25 import jalview.datamodel.SequenceGroup;
26 import jalview.io.JalviewFileChooser;
27 import jalview.io.JalviewFileView;
28 import jalview.jbgui.GUserDefinedColours;
29 import jalview.schemabinding.version2.Colour;
30 import jalview.schemabinding.version2.JalviewUserColours;
31 import jalview.schemes.ColourSchemeI;
32 import jalview.schemes.ColourSchemes;
33 import jalview.schemes.ResidueProperties;
34 import jalview.schemes.UserColourScheme;
35 import jalview.util.ColorUtils;
36 import jalview.util.Format;
37 import jalview.util.MessageManager;
38
39 import java.awt.Color;
40 import java.awt.Font;
41 import java.awt.Insets;
42 import java.awt.event.ActionEvent;
43 import java.awt.event.MouseAdapter;
44 import java.awt.event.MouseEvent;
45 import java.io.File;
46 import java.io.FileInputStream;
47 import java.io.FileOutputStream;
48 import java.io.InputStreamReader;
49 import java.io.OutputStreamWriter;
50 import java.io.PrintWriter;
51 import java.util.ArrayList;
52 import java.util.List;
53 import java.util.SortedMap;
54 import java.util.StringTokenizer;
55 import java.util.TreeMap;
56
57 import javax.swing.JButton;
58 import javax.swing.JInternalFrame;
59 import javax.swing.event.ChangeEvent;
60 import javax.swing.event.ChangeListener;
61
62 /**
63  * This panel allows the user to assign colours to Amino Acid residue codes, and
64  * save the colour scheme.
65  * 
66  * @author Andrew Waterhouse
67  * @author Mungo Carstairs
68  */
69 public class UserDefinedColours extends GUserDefinedColours implements
70         ChangeListener
71 {
72   private static final Font VERDANA_BOLD_10 = new Font("Verdana", Font.BOLD, 10);
73
74   private static final String USER_DEFINED_COLOURS = "USER_DEFINED_COLOURS";
75
76   private static final String LAST_DIRECTORY = "LAST_DIRECTORY";
77
78   private static final int MY_FRAME_HEIGHT = 420;
79
80   private static final int MY_FRAME_WIDTH = 810;
81
82   private static final int MY_FRAME_WIDTH_CASE_SENSITIVE = 970;
83
84   static SortedMap<String, UserColourScheme> userColourSchemes;
85
86   AlignmentPanel ap;
87
88   SequenceGroup seqGroup;
89
90   List<JButton> selectedButtons;
91
92   ColourSchemeI oldColourScheme;
93
94   JInternalFrame frame;
95
96   JalviewStructureDisplayI structureViewer;
97
98   List<JButton> upperCaseButtons;
99
100   List<JButton> lowerCaseButtons;
101
102   /**
103    * Creates a new UserDefinedColours object.
104    * 
105    * @param ap
106    * @param sg
107    */
108   public UserDefinedColours(AlignmentPanel ap, SequenceGroup sg)
109   {
110     super();
111
112     lcaseColour.setEnabled(false);
113
114     this.ap = ap;
115     seqGroup = sg;
116
117     if (seqGroup != null)
118     {
119       oldColourScheme = seqGroup.cs;
120     }
121     else
122     {
123       oldColourScheme = ap.av.getGlobalColourScheme();
124     }
125
126     if (oldColourScheme instanceof UserColourScheme)
127     {
128       schemeName.setText(((UserColourScheme) oldColourScheme).getSchemeName());
129       if (((UserColourScheme) oldColourScheme).getLowerCaseColours() != null)
130       {
131         caseSensitive.setSelected(true);
132         lcaseColour.setEnabled(true);
133         resetButtonPanel(true);
134       }
135       else
136       {
137         resetButtonPanel(false);
138       }
139     }
140     else
141     {
142       resetButtonPanel(false);
143     }
144
145     showFrame();
146   }
147
148   public UserDefinedColours(JalviewStructureDisplayI viewer,
149           ColourSchemeI oldcs)
150   {
151     super();
152     this.structureViewer = viewer;
153
154     colorChooser.getSelectionModel().addChangeListener(this);
155
156     oldColourScheme = oldcs;
157
158     if (oldColourScheme instanceof UserColourScheme)
159     {
160       schemeName.setText(((UserColourScheme) oldColourScheme).getSchemeName());
161     }
162
163     resetButtonPanel(false);
164
165     showFrame();
166
167   }
168
169   void showFrame()
170   {
171     colorChooser.getSelectionModel().addChangeListener(this);
172     frame = new JInternalFrame();
173     frame.setContentPane(this);
174     Desktop.addInternalFrame(frame,
175             MessageManager.getString("label.user_defined_colours"),
176             MY_FRAME_WIDTH, MY_FRAME_HEIGHT, true);
177
178     if (seqGroup != null)
179     {
180       frame.setTitle(frame.getTitle() + " (" + seqGroup.getName() + ")");
181     }
182   }
183
184   void resetButtonPanel(boolean caseSensitive)
185   {
186     buttonPanel.removeAll();
187
188     if (upperCaseButtons == null)
189     {
190       upperCaseButtons = new ArrayList<JButton>();
191     }
192
193     JButton button;
194     String label;
195     for (int i = 0; i < 20; i++)
196     {
197       if (caseSensitive)
198       {
199         label = ResidueProperties.aa[i];
200       }
201       else
202       {
203         label = ResidueProperties.aa2Triplet.get(ResidueProperties.aa[i])
204                 .toString();
205       }
206
207       button = makeButton(label, ResidueProperties.aa[i], upperCaseButtons,
208               i);
209
210       buttonPanel.add(button);
211     }
212
213     buttonPanel.add(makeButton("B", "B", upperCaseButtons, 20));
214     buttonPanel.add(makeButton("Z", "Z", upperCaseButtons, 21));
215     buttonPanel.add(makeButton("X", "X", upperCaseButtons, 22));
216     buttonPanel.add(makeButton("Gap", "-", upperCaseButtons, 23));
217
218     if (!caseSensitive)
219     {
220       gridLayout.setRows(6);
221       gridLayout.setColumns(4);
222     }
223     else
224     {
225       gridLayout.setRows(7);
226       int cols = 7;
227       gridLayout.setColumns(cols + 1);
228
229       if (lowerCaseButtons == null)
230       {
231         lowerCaseButtons = new ArrayList<JButton>();
232       }
233
234       for (int i = 0; i < 20; i++)
235       {
236         int row = i / cols + 1;
237         int index = (row * cols) + i;
238         button = makeButton(ResidueProperties.aa[i].toLowerCase(),
239                 ResidueProperties.aa[i].toLowerCase(), lowerCaseButtons, i);
240
241         buttonPanel.add(button, index);
242       }
243     }
244
245     if (caseSensitive)
246     {
247       buttonPanel.add(makeButton("b", "b", lowerCaseButtons, 20));
248       buttonPanel.add(makeButton("z", "z", lowerCaseButtons, 21));
249       buttonPanel.add(makeButton("x", "x", lowerCaseButtons, 22));
250     }
251
252     // JAL-1360 widen the frame dynamically to accommodate case-sensitive AA
253     // codes
254     if (this.frame != null)
255     {
256       int newWidth = caseSensitive ? MY_FRAME_WIDTH_CASE_SENSITIVE
257               : MY_FRAME_WIDTH;
258       this.frame.setSize(newWidth, this.frame.getHeight());
259     }
260
261     buttonPanel.validate();
262     validate();
263   }
264
265   /**
266    * DOCUMENT ME!
267    * 
268    * @param evt
269    *          DOCUMENT ME!
270    */
271   @Override
272   public void stateChanged(ChangeEvent evt)
273   {
274     if (selectedButtons != null)
275     {
276       JButton button = null;
277       final Color newColour = colorChooser.getColor();
278       for (int i = 0; i < selectedButtons.size(); i++)
279       {
280         button = selectedButtons.get(i);
281         button.setBackground(newColour);
282         button.setForeground(ColorUtils.brighterThan(newColour));
283       }
284       if (button == lcaseColour)
285       {
286         for (int i = 0; i < lowerCaseButtons.size(); i++)
287         {
288           button = lowerCaseButtons.get(i);
289           button.setBackground(newColour);
290           button.setForeground(ColorUtils.brighterThan(button
291                   .getBackground()));
292         }
293       }
294     }
295   }
296
297   /**
298    * Performs actions when a residue button is clicked. This manages the button
299    * selection set (highlighted by brighter foreground text).
300    * <p>
301    * On select button(s) with Ctrl/click or Shift/click: set button foreground
302    * text to brighter than background.
303    * <p>
304    * On unselect button(s) with Ctrl/click on selected, or click to release
305    * current selection: reset foreground text to darker than background.
306    * <p>
307    * Simple click: clear selection (resetting foreground to darker); set clicked
308    * button foreground to brighter
309    * <p>
310    * Finally, synchronize the colour chooser to the colour of the first button
311    * in the selected set.
312    * 
313    * @param e
314    */
315   public void colourButtonPressed(MouseEvent e)
316   {
317     if (selectedButtons == null)
318     {
319       selectedButtons = new ArrayList<JButton>();
320     }
321
322     JButton pressed = (JButton) e.getSource();
323
324     if (e.isShiftDown())
325     {
326       JButton start, end = (JButton) e.getSource();
327       if (selectedButtons.size() > 0)
328       {
329         start = selectedButtons.get(selectedButtons.size() - 1);
330       }
331       else
332       {
333         start = (JButton) e.getSource();
334       }
335
336       int startIndex = 0, endIndex = 0;
337       for (int b = 0; b < buttonPanel.getComponentCount(); b++)
338       {
339         if (buttonPanel.getComponent(b) == start)
340         {
341           startIndex = b;
342         }
343         if (buttonPanel.getComponent(b) == end)
344         {
345           endIndex = b;
346         }
347       }
348
349       if (startIndex > endIndex)
350       {
351         int temp = startIndex;
352         startIndex = endIndex;
353         endIndex = temp;
354       }
355
356       for (int b = startIndex; b <= endIndex; b++)
357       {
358         JButton button = (JButton) buttonPanel.getComponent(b);
359         if (!selectedButtons.contains(button))
360         {
361           button.setForeground(ColorUtils.brighterThan(button
362                   .getBackground()));
363           selectedButtons.add(button);
364         }
365       }
366     }
367     else if (!e.isControlDown())
368     {
369       for (int b = 0; b < selectedButtons.size(); b++)
370       {
371         JButton button = selectedButtons.get(b);
372         button.setForeground(ColorUtils.darkerThan(button.getBackground()));
373       }
374       selectedButtons.clear();
375       pressed.setForeground(ColorUtils.brighterThan(pressed.getBackground()));
376       selectedButtons.add(pressed);
377
378     }
379     else if (e.isControlDown())
380     {
381       if (selectedButtons.contains(pressed))
382       {
383         pressed.setForeground(ColorUtils.darkerThan(pressed.getBackground()));
384         selectedButtons.remove(pressed);
385       }
386       else
387       {
388         pressed.setForeground(ColorUtils.brighterThan(pressed
389                 .getBackground()));
390         selectedButtons.add(pressed);
391       }
392     }
393
394     if (selectedButtons.size() > 0)
395     {
396       colorChooser.setColor((selectedButtons.get(0)).getBackground());
397     }
398   }
399
400   /**
401    * A helper method to update or make a colour button, whose background colour
402    * is the associated colour, and text colour a darker shade of the same. If
403    * the button is already in the list, then its text and margins are updated,
404    * if not then it is created and added. This method supports toggling between
405    * case-sensitive and case-insensitive button panels. The case-sensitive
406    * version has abbreviated button text in order to fit in more buttons.
407    * 
408    * @param label
409    * @param residue
410    * @param the
411    *          list of buttons
412    * @param buttonIndex
413    *          the button's position in the list
414    */
415   JButton makeButton(String label, String residue,
416           List<JButton> buttons, int buttonIndex)
417   {
418     final JButton button;
419     Color col;
420
421     if (buttonIndex < buttons.size())
422     {
423       button = buttons.get(buttonIndex);
424       col = button.getBackground();
425     }
426     else
427     {
428       button = new JButton();
429       button.addMouseListener(new MouseAdapter()
430       {
431         @Override
432         public void mouseClicked(MouseEvent e)
433         {
434           colourButtonPressed(e);
435         }
436       });
437
438       buttons.add(button);
439
440       col = Color.white;
441       if (oldColourScheme != null)
442       {
443         try
444         {
445           col = oldColourScheme.findColour(residue.charAt(0), -1, null);
446         } catch (Exception ex)
447         {
448         }
449       }
450     }
451
452     if (caseSensitive.isSelected())
453     {
454       button.setMargin(new Insets(2, 2, 2, 2));
455     }
456     else
457     {
458       button.setMargin(new Insets(2, 14, 2, 14));
459     }
460
461     button.setOpaque(true); // required for the next line to have effect
462     button.setBackground(col);
463     button.setText(label);
464     button.setForeground(ColorUtils.darkerThan(col));
465     button.setFont(VERDANA_BOLD_10);
466
467     return button;
468   }
469
470   /**
471    * On 'OK', check that at least one colour has been assigned to a residue (and
472    * if not issue a warning), and apply the chosen colour scheme and close the
473    * panel.
474    */
475   @Override
476   protected void okButton_actionPerformed()
477   {
478     if (isNoSelectionMade())
479     {
480       JvOptionPane.showMessageDialog(Desktop.desktop, MessageManager
481               .getString("label.no_colour_selection_in_scheme"),
482               MessageManager.getString("label.no_colour_selection_warn"),
483               JvOptionPane.WARNING_MESSAGE);
484     }
485     else
486     {
487       applyButton_actionPerformed();
488
489       try
490       {
491         frame.setClosed(true);
492       } catch (Exception ex)
493       {
494       }
495     }
496   }
497
498   /**
499    * Returns true if the user has not made any colour selection (including if
500    * 'case-sensitive' selected and no lower-case colour chosen).
501    * 
502    * @return
503    */
504   protected boolean isNoSelectionMade()
505   {
506     final boolean noUpperCaseSelected = upperCaseButtons == null
507             || upperCaseButtons.isEmpty();
508     final boolean noLowerCaseSelected = caseSensitive.isSelected()
509             && (lowerCaseButtons == null || lowerCaseButtons.isEmpty());
510     final boolean noSelectionMade = noUpperCaseSelected
511             || noLowerCaseSelected;
512     return noSelectionMade;
513   }
514
515   /**
516    * Applies the current colour scheme to the alignment, sequence group or
517    * structure view.
518    */
519   @Override
520   protected void applyButton_actionPerformed()
521   {
522     if (isNoSelectionMade())
523     {
524       JvOptionPane.showMessageDialog(Desktop.desktop, MessageManager
525               .getString("label.no_colour_selection_in_scheme"),
526               MessageManager.getString("label.no_colour_selection_warn"),
527               JvOptionPane.WARNING_MESSAGE);
528
529     }
530     UserColourScheme ucs = getSchemeFromButtons();
531
532     if (seqGroup != null)
533     {
534       seqGroup.cs = ucs;
535       ap.paintAlignment(true);
536     }
537     else if (ap != null)
538     {
539       ap.alignFrame.changeColour(ucs);
540     }
541     else if (structureViewer != null)
542     {
543       structureViewer.setJalviewColourScheme(ucs);
544     }
545   }
546
547   UserColourScheme getSchemeFromButtons()
548   {
549
550     Color[] newColours = new Color[24];
551
552     int length = upperCaseButtons.size();
553     if (length < 24)
554     {
555       int i = 0;
556       for (JButton btn : upperCaseButtons)
557       {
558         newColours[i] = btn.getBackground();
559         i++;
560       }
561     }
562     else
563     {
564       for (int i = 0; i < 24; i++)
565       {
566         JButton button = upperCaseButtons.get(i);
567         newColours[i] = button.getBackground();
568       }
569     }
570
571     UserColourScheme ucs = new UserColourScheme(newColours);
572     ucs.setName(schemeName.getText());
573
574     if (caseSensitive.isSelected())
575     {
576       newColours = new Color[23];
577       length = lowerCaseButtons.size();
578       if (length < 23)
579       {
580         int i = 0;
581         for (JButton btn : lowerCaseButtons)
582         {
583           newColours[i] = btn.getBackground();
584           i++;
585         }
586       }
587       else
588       {
589         for (int i = 0; i < 23; i++)
590         {
591           JButton button = lowerCaseButtons.get(i);
592           newColours[i] = button.getBackground();
593         }
594       }
595       ucs.setLowerCaseColours(newColours);
596     }
597
598     if (ap != null)
599     {
600       ucs.setThreshold(0, ap.av.isIgnoreGapsConsensus());
601     }
602
603     return ucs;
604   }
605
606   /**
607    * DOCUMENT ME!
608    * 
609    * @param e
610    *          DOCUMENT ME!
611    */
612   @Override
613   protected void loadbutton_actionPerformed(ActionEvent e)
614   {
615     upperCaseButtons = new ArrayList<JButton>();
616     lowerCaseButtons = new ArrayList<JButton>();
617
618     JalviewFileChooser chooser = new JalviewFileChooser(
619             Cache.getProperty(LAST_DIRECTORY), "jc", "Jalview User Colours");
620     chooser.setFileView(new JalviewFileView());
621     chooser.setDialogTitle(MessageManager
622             .getString("label.load_colour_scheme"));
623     chooser.setToolTipText(MessageManager.getString("action.load"));
624
625     int value = chooser.showOpenDialog(this);
626
627     if (value != JalviewFileChooser.APPROVE_OPTION)
628     {
629       return;
630     }
631     File choice = chooser.getSelectedFile();
632     Cache.setProperty(LAST_DIRECTORY, choice.getParent());
633
634     UserColourScheme ucs = loadColours(choice.getAbsolutePath());
635     Color[] colors = ucs.getColours();
636     schemeName.setText(ucs.getSchemeName());
637
638     if (ucs.getLowerCaseColours() != null)
639     {
640       caseSensitive.setSelected(true);
641       lcaseColour.setEnabled(true);
642       resetButtonPanel(true);
643       for (int i = 0; i < lowerCaseButtons.size(); i++)
644       {
645         JButton button = lowerCaseButtons.get(i);
646         button.setBackground(ucs.getLowerCaseColours()[i]);
647       }
648     }
649     else
650     {
651       caseSensitive.setSelected(false);
652       lcaseColour.setEnabled(false);
653       resetButtonPanel(false);
654     }
655
656     for (int i = 0; i < upperCaseButtons.size(); i++)
657     {
658       JButton button = upperCaseButtons.get(i);
659       button.setBackground(colors[i]);
660     }
661
662     addNewColourScheme(choice.getPath());
663   }
664
665   /**
666    * DOCUMENT ME!
667    * 
668    * @return DOCUMENT ME!
669    */
670   public static UserColourScheme loadDefaultColours()
671   {
672     UserColourScheme ret = null;
673
674     String colours = Cache.getProperty(USER_DEFINED_COLOURS);
675     if (colours != null)
676     {
677       if (colours.indexOf("|") > -1)
678       {
679         colours = colours.substring(0, colours.indexOf("|"));
680       }
681
682       ret = loadColours(colours);
683     }
684
685     if (ret == null)
686     {
687       Color[] newColours = new Color[24];
688       for (int i = 0; i < 24; i++)
689       {
690         newColours[i] = Color.white;
691       }
692       ret = new UserColourScheme(newColours);
693     }
694
695     return ret;
696   }
697
698   /**
699    * DOCUMENT ME!
700    * 
701    * @param file
702    *          DOCUMENT ME!
703    * 
704    * @return DOCUMENT ME!
705    */
706   static UserColourScheme loadColours(String file)
707   {
708     UserColourScheme ucs = null;
709     Color[] newColours = null;
710     try
711     {
712       InputStreamReader in = new InputStreamReader(
713               new FileInputStream(file), "UTF-8");
714
715       jalview.schemabinding.version2.JalviewUserColours jucs = new jalview.schemabinding.version2.JalviewUserColours();
716
717       org.exolab.castor.xml.Unmarshaller unmar = new org.exolab.castor.xml.Unmarshaller(
718               jucs);
719       jucs = (jalview.schemabinding.version2.JalviewUserColours) unmar
720               .unmarshal(in);
721
722       newColours = new Color[24];
723
724       Color[] lowerCase = null;
725       boolean caseSensitive = false;
726
727       String name;
728       int index;
729       for (int i = 0; i < jucs.getColourCount(); i++)
730       {
731         name = jucs.getColour(i).getName();
732         if (ResidueProperties.aa3Hash.containsKey(name))
733         {
734           index = ResidueProperties.aa3Hash.get(name).intValue();
735         }
736         else
737         {
738           index = ResidueProperties.aaIndex[name.charAt(0)];
739         }
740         if (index == -1)
741         {
742           continue;
743         }
744
745         if (name.toLowerCase().equals(name))
746         {
747           if (lowerCase == null)
748           {
749             lowerCase = new Color[23];
750           }
751           caseSensitive = true;
752           lowerCase[index] = new Color(Integer.parseInt(jucs.getColour(i)
753                   .getRGB(), 16));
754         }
755         else
756         {
757           newColours[index] = new Color(Integer.parseInt(jucs.getColour(i)
758                   .getRGB(), 16));
759         }
760       }
761
762       if (newColours != null)
763       {
764         ucs = new UserColourScheme(newColours);
765         ucs.setName(jucs.getSchemeName());
766         if (caseSensitive)
767         {
768           ucs.setLowerCaseColours(lowerCase);
769         }
770       }
771
772     } catch (Exception ex)
773     {
774       // Could be Archive Jalview format
775       try
776       {
777         InputStreamReader in = new InputStreamReader(new FileInputStream(
778                 file), "UTF-8");
779
780         jalview.binding.JalviewUserColours jucs = new jalview.binding.JalviewUserColours();
781
782         jucs = jucs.unmarshal(in);
783
784         newColours = new Color[jucs.getColourCount()];
785
786         for (int i = 0; i < 24; i++)
787         {
788           newColours[i] = new Color(Integer.parseInt(jucs.getColour(i)
789                   .getRGB(), 16));
790         }
791         if (newColours != null)
792         {
793           ucs = new UserColourScheme(newColours);
794           ucs.setName(jucs.getSchemeName());
795         }
796       } catch (Exception ex2)
797       {
798         ex2.printStackTrace();
799       }
800
801       if (newColours == null)
802       {
803         System.out.println("Error loading User ColourFile\n" + ex);
804       }
805     }
806
807     return ucs;
808   }
809
810   /**
811    * DOCUMENT ME!
812    * 
813    * @param e
814    *          DOCUMENT ME!
815    */
816   @Override
817   protected void savebutton_actionPerformed(ActionEvent e)
818   {
819     if (schemeName.getText().trim().length() < 1)
820     {
821       JvOptionPane.showInternalMessageDialog(Desktop.desktop, MessageManager
822               .getString("label.user_colour_scheme_must_have_name"),
823               MessageManager.getString("label.no_name_colour_scheme"),
824               JvOptionPane.WARNING_MESSAGE);
825       return;
826     }
827
828     if (userColourSchemes != null
829             && userColourSchemes.containsKey(schemeName.getText()))
830     {
831       int reply = JvOptionPane.showInternalConfirmDialog(Desktop.desktop,
832               MessageManager.formatMessage(
833                       "label.colour_scheme_exists_overwrite", new Object[] {
834                           schemeName.getText(), schemeName.getText() }),
835               MessageManager.getString("label.duplicate_scheme_name"),
836               JvOptionPane.YES_NO_OPTION);
837       if (reply != JvOptionPane.YES_OPTION)
838       {
839         return;
840       }
841
842       userColourSchemes.remove(schemeName.getText());
843     }
844     JalviewFileChooser chooser = new JalviewFileChooser(
845             Cache.getProperty(LAST_DIRECTORY), "jc",
846             "Jalview User Colours");
847
848     chooser.setFileView(new JalviewFileView());
849     chooser.setDialogTitle(MessageManager
850             .getString("label.save_colour_scheme"));
851     chooser.setToolTipText(MessageManager.getString("action.save"));
852
853     int value = chooser.showSaveDialog(this);
854
855     if (value == JalviewFileChooser.APPROVE_OPTION)
856     {
857       String choice = chooser.getSelectedFile().getPath();
858       addNewColourScheme(choice);
859       saveToFile(choice);
860     }
861   }
862
863   /**
864    * Adds the current colour scheme to the Jalview properties file so it is
865    * loaded on next startup, and updates the Colour menu in the parent
866    * AlignFrame (if there is one). Note this action does not including applying
867    * the colour scheme.
868    * 
869    * @param filePath
870    */
871   protected void addNewColourScheme(String filePath)
872   {
873     /*
874      * update the delimited list of user defined colour files in
875      * Jalview property USER_DEFINED_COLOURS
876      */
877     String defaultColours = Cache
878             .getDefault(USER_DEFINED_COLOURS, filePath);
879     if (defaultColours.indexOf(filePath) == -1)
880     {
881       if (defaultColours.length() > 0)
882       {
883         defaultColours = defaultColours.concat("|");
884       }
885       defaultColours = defaultColours.concat(filePath);
886     }
887     Cache.setProperty(USER_DEFINED_COLOURS, defaultColours);
888
889     /*
890      * add to the cache in this object
891      */
892     UserColourScheme ucs = getSchemeFromButtons();
893     String name = schemeName.getText();
894     userColourSchemes.put(name, ucs);
895
896     ColourSchemes.getInstance().registerColourScheme(ucs);
897
898     /*
899      * update the Colour menu items
900      */
901     if (ap != null)
902     {
903       ap.alignFrame.buildColourMenu();
904     }
905   }
906
907   /**
908    * Saves the colour scheme to file in XML format
909    * 
910    * @param filePath
911    */
912   protected void saveToFile(String filePath)
913   {
914     /*
915      * build a Java model of colour scheme as XML, and 
916      * marshal to file
917      */
918     JalviewUserColours ucs = new JalviewUserColours();
919     ucs.setSchemeName(schemeName.getText());
920     try
921     {
922       PrintWriter out = new PrintWriter(new OutputStreamWriter(
923               new FileOutputStream(filePath), "UTF-8"));
924
925       for (int i = 0; i < buttonPanel.getComponentCount(); i++)
926       {
927         JButton button = (JButton) buttonPanel.getComponent(i);
928         Colour col = new Colour();
929         col.setName(button.getText());
930         col.setRGB(Format.getHexString(button.getBackground()));
931         ucs.addColour(col);
932       }
933       ucs.marshal(out);
934       out.close();
935     } catch (Exception ex)
936     {
937       ex.printStackTrace();
938     }
939   }
940
941   /**
942    * On cancel, restores the colour scheme before the dialogue was opened
943    * 
944    * @param e
945    */
946   @Override
947   protected void cancelButton_actionPerformed(ActionEvent e)
948   {
949     if (ap != null)
950     {
951       if (seqGroup != null)
952       {
953         seqGroup.cs = oldColourScheme;
954       }
955       else
956       {
957         ap.alignFrame.changeColour(oldColourScheme);
958       }
959       ap.paintAlignment(true);
960     }
961
962     if (structureViewer != null)
963     {
964       structureViewer.setJalviewColourScheme(oldColourScheme);
965     }
966
967     try
968     {
969       frame.setClosed(true);
970     } catch (Exception ex)
971     {
972     }
973   }
974
975   public static SortedMap<String, UserColourScheme> getUserColourSchemes()
976   {
977     return userColourSchemes;
978   }
979
980   public static void initUserColourSchemes(String files)
981   {
982     userColourSchemes = new TreeMap<String, UserColourScheme>();
983
984     if (files == null || files.length() == 0)
985     {
986       return;
987     }
988
989     // In case colours can't be loaded, we'll remove them
990     // from the default list here.
991     StringBuffer coloursFound = new StringBuffer();
992     StringTokenizer st = new StringTokenizer(files, "|");
993     while (st.hasMoreElements())
994     {
995       String file = st.nextToken();
996       try
997       {
998         UserColourScheme ucs = loadColours(file);
999         if (ucs != null)
1000         {
1001           if (coloursFound.length() > 0)
1002           {
1003             coloursFound.append("|");
1004           }
1005           coloursFound.append(file);
1006           String name = ucs.getName();
1007           userColourSchemes.put(name, ucs);
1008           ColourSchemes.getInstance().registerColourScheme(ucs);
1009         }
1010       } catch (Exception ex)
1011       {
1012         System.out.println("Error loading User ColourFile\n" + ex);
1013       }
1014     }
1015     if (!files.equals(coloursFound.toString()))
1016     {
1017       if (coloursFound.toString().length() > 1)
1018       {
1019         Cache.setProperty(USER_DEFINED_COLOURS, coloursFound.toString());
1020       }
1021       else
1022       {
1023         Cache.applicationProperties.remove(USER_DEFINED_COLOURS);
1024       }
1025     }
1026   }
1027
1028   public static void removeColourFromDefaults(String target)
1029   {
1030     // The only way to find colours by name is to load them in
1031     // In case colours can't be loaded, we'll remove them
1032     // from the default list here.
1033
1034     userColourSchemes = new TreeMap<String, UserColourScheme>();
1035
1036     StringBuffer coloursFound = new StringBuffer();
1037     StringTokenizer st = new StringTokenizer(
1038             Cache.getProperty(USER_DEFINED_COLOURS), "|");
1039
1040     while (st.hasMoreElements())
1041     {
1042       String file = st.nextToken();
1043       try
1044       {
1045         UserColourScheme ucs = loadColours(file);
1046         if (ucs != null && !ucs.getSchemeName().equals(target))
1047         {
1048           if (coloursFound.length() > 0)
1049           {
1050             coloursFound.append("|");
1051           }
1052           coloursFound.append(file);
1053           userColourSchemes.put(ucs.getSchemeName(), ucs);
1054         }
1055       } catch (Exception ex)
1056       {
1057         System.out.println("Error loading User ColourFile\n" + ex);
1058       }
1059     }
1060
1061     if (coloursFound.toString().length() > 1)
1062     {
1063       Cache.setProperty(USER_DEFINED_COLOURS, coloursFound.toString());
1064     }
1065     else
1066     {
1067       Cache.applicationProperties.remove(USER_DEFINED_COLOURS);
1068     }
1069
1070   }
1071
1072   @Override
1073   public void caseSensitive_actionPerformed(ActionEvent e)
1074   {
1075     resetButtonPanel(caseSensitive.isSelected());
1076     lcaseColour.setEnabled(caseSensitive.isSelected());
1077   }
1078
1079   @Override
1080   public void lcaseColour_actionPerformed(ActionEvent e)
1081   {
1082     if (selectedButtons == null)
1083     {
1084       selectedButtons = new ArrayList<JButton>();
1085     }
1086     else
1087     {
1088       selectedButtons.clear();
1089     }
1090     selectedButtons.add(lcaseColour);
1091   }
1092 }