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