JAL-3691 automatic insertion of Locale.ROOT to toUpperCase() and toLowerCase() and...
[jalview.git] / src / jalview / schemes / UserColourScheme.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.schemes;
22
23 import java.util.Locale;
24
25 import jalview.api.AlignViewportI;
26 import jalview.datamodel.AnnotatedCollectionI;
27 import jalview.util.ColorUtils;
28 import jalview.util.StringUtils;
29
30 import java.awt.Color;
31 import java.util.ArrayList;
32 import java.util.Collections;
33 import java.util.HashMap;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Map.Entry;
37 import java.util.StringTokenizer;
38
39 public class UserColourScheme extends ResidueColourScheme
40 {
41   /*
42    * lookup (by symbol index) of lower case colours (if configured)
43    */
44   Color[] lowerCaseColours;
45
46   protected String schemeName;
47
48   public UserColourScheme()
49   {
50     super(ResidueProperties.aaIndex);
51   }
52
53   public UserColourScheme(Color[] newColors)
54   {
55     super(ResidueProperties.aaIndex);
56     colors = newColors;
57   }
58
59   @Override
60   public ColourSchemeI getInstance(AlignViewportI view,
61           AnnotatedCollectionI sg)
62   {
63     return new UserColourScheme(this);
64   }
65
66   /**
67    * Copy constructor
68    * 
69    * @return
70    */
71   protected UserColourScheme(UserColourScheme from)
72   {
73     this(from.colors);
74     schemeName = from.schemeName;
75     if (from.lowerCaseColours != null)
76     {
77       lowerCaseColours = new Color[from.lowerCaseColours.length];
78       System.arraycopy(from.lowerCaseColours, 0, lowerCaseColours, 0,
79               from.lowerCaseColours.length);
80     }
81   }
82
83   /**
84    * Constructor for an animino acid colour scheme. The colour specification may
85    * be one of
86    * <ul>
87    * <li>an AWT colour name e.g. red</li>
88    * <li>an AWT hex rgb colour e.g. ff2288</li>
89    * <li>residue colours list e.g. D,E=red;K,R,H=0022FF;c=yellow</li>
90    * </ul>
91    * 
92    * @param colour
93    */
94   public UserColourScheme(String colour)
95   {
96     super(ResidueProperties.aaIndex);
97
98     if (colour.contains("="))
99     {
100       /*
101        * a list of colours per residue(s)
102        */
103       parseAppletParameter(colour);
104       return;
105     }
106
107     Color col = ColorUtils.parseColourString(colour);
108
109     if (col == null)
110     {
111       System.out.println("Making colour from name: " + colour);
112       col = ColorUtils.createColourFromName(colour);
113     }
114
115     setAll(col);
116     schemeName = colour;
117   }
118
119   /**
120    * Sets all symbols to the specified colour
121    * 
122    * @param col
123    */
124   protected void setAll(Color col)
125   {
126     if (symbolIndex == null)
127     {
128       return;
129     }
130     int max = 0;
131     for (int index : symbolIndex)
132     {
133       max = Math.max(max, index);
134     }
135     colors = new Color[max + 1];
136     for (int i = 0; i <= max; i++)
137     {
138       colors[i] = col;
139     }
140   }
141
142   public Color[] getColours()
143   {
144     return colors;
145   }
146
147   public Color[] getLowerCaseColours()
148   {
149     return lowerCaseColours;
150   }
151
152   public void setName(String name)
153   {
154     schemeName = name;
155   }
156
157   public String getName()
158   {
159     return schemeName;
160   }
161
162   /**
163    * Parse and save residue colours specified as (for example)
164    * 
165    * <pre>
166    *     D,E=red; K,R,H=0022FF; c=100,50,75
167    * </pre>
168    * 
169    * This should be a semi-colon separated list of colours, which may be defined
170    * by colour name, hex value or comma-separated RGB triple. Each colour is
171    * defined for a comma-separated list of amino acid single letter codes. (Note
172    * that this also allows a colour scheme to be defined for ACGT, but not for
173    * U.)
174    * 
175    * @param paramValue
176    */
177   void parseAppletParameter(String paramValue)
178   {
179     setAll(Color.white);
180
181     StringTokenizer st = new StringTokenizer(paramValue, ";");
182     StringTokenizer st2;
183     String token = null, colour, residues;
184     try
185     {
186       while (st.hasMoreElements())
187       {
188         token = st.nextToken().trim();
189         residues = token.substring(0, token.indexOf("="));
190         colour = token.substring(token.indexOf("=") + 1);
191
192         st2 = new StringTokenizer(residues, " ,");
193         while (st2.hasMoreTokens())
194         {
195           String residue = st2.nextToken();
196
197           int colIndex = ResidueProperties.aaIndex[residue.charAt(0)];
198           if (colIndex == -1)
199           {
200             continue;
201           }
202
203           if (residue.equalsIgnoreCase("lowerCase"))
204           {
205             if (lowerCaseColours == null)
206             {
207               lowerCaseColours = new Color[colors.length];
208             }
209             for (int i = 0; i < lowerCaseColours.length; i++)
210             {
211               if (lowerCaseColours[i] == null)
212               {
213                 lowerCaseColours[i] = ColorUtils.parseColourString(colour);
214               }
215             }
216
217             continue;
218           }
219
220           if (residue.equals(residue.toLowerCase(Locale.ROOT)))
221           {
222             if (lowerCaseColours == null)
223             {
224               lowerCaseColours = new Color[colors.length];
225             }
226             lowerCaseColours[colIndex] = ColorUtils
227                     .parseColourString(colour);
228           }
229           else
230           {
231             colors[colIndex] = ColorUtils.parseColourString(colour);
232           }
233         }
234       }
235     } catch (Exception ex)
236     {
237       System.out.println(
238               "Error parsing userDefinedColours:\n" + token + "\n" + ex);
239     }
240
241   }
242
243   public void setLowerCaseColours(Color[] lcolours)
244   {
245     lowerCaseColours = lcolours;
246   }
247
248   /**
249    * Returns the colour for the given residue character. If the residue is
250    * lower-case, and there is a specific colour defined for lower case, that
251    * colour is returned, else the colour for the upper case residue.
252    */
253   @Override
254   public Color findColour(char c)
255   {
256     if ('a' <= c && c <= 'z' && lowerCaseColours != null)
257     {
258       Color colour = lowerCaseColours[symbolIndex[c]];
259       if (colour != null)
260       {
261         return colour;
262       }
263     }
264     return super.findColour(c);
265   }
266
267   /**
268    * Answers the customised name of the colour scheme, if it has one, else "User
269    * Defined"
270    */
271   @Override
272   public String getSchemeName()
273   {
274     if (schemeName != null && schemeName.length() > 0)
275     {
276       return schemeName;
277     }
278     return ResidueColourScheme.USER_DEFINED;
279   }
280
281   /**
282    * Generate an applet colour parameter like A,C,D=12ffe9;Q,W=2393fd;w=9178dd
283    * 
284    * @return
285    */
286   public String toAppletParameter()
287   {
288     /*
289      * step 1: build a map from colours to the symbol(s) that have the colour
290      */
291     Map<Color, List<String>> colours = new HashMap<>();
292
293     for (char symbol = 'A'; symbol <= 'Z'; symbol++)
294     {
295       String residue = String.valueOf(symbol);
296       int index = symbolIndex[symbol];
297       Color c = colors[index];
298       if (c != null && !c.equals(Color.white))
299       {
300         if (colours.get(c) == null)
301         {
302           colours.put(c, new ArrayList<String>());
303         }
304         colours.get(c).add(residue);
305       }
306       if (lowerCaseColours != null)
307       {
308         c = lowerCaseColours[index];
309         if (c != null && !c.equals(Color.white))
310         {
311           residue = residue.toLowerCase(Locale.ROOT);
312           if (colours.get(c) == null)
313           {
314             colours.put(c, new ArrayList<String>());
315           }
316           colours.get(c).add(residue);
317         }
318       }
319     }
320
321     /*
322      * step 2: make a list of { A,G,R=12f9d6 } residues/colour specs
323      */
324     List<String> residueColours = new ArrayList<>();
325     for (Entry<Color, List<String>> cols : colours.entrySet())
326     {
327       boolean first = true;
328       StringBuilder sb = new StringBuilder();
329       for (String residue : cols.getValue())
330       {
331         if (!first)
332         {
333           sb.append(",");
334         }
335         sb.append(residue);
336         first = false;
337       }
338       sb.append("=");
339       /*
340        * get color as hex value, dropping the alpha (ff) part
341        */
342       String hexString = Integer.toHexString(cols.getKey().getRGB())
343               .substring(2);
344       sb.append(hexString);
345       residueColours.add(sb.toString());
346     }
347
348     /*
349      * sort and output
350      */
351     Collections.sort(residueColours);
352     return StringUtils.listToDelimitedString(residueColours, ";");
353   }
354
355   @Override
356   public boolean hasGapColour()
357   {
358     return (findColour(' ') != null);
359   }
360 }