1 //------------------------------------------------------------------------------
2 // <copyright file="ColorConverter.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
6 
7 /*
8  */
9 namespace System.Drawing {
10     using System.Runtime.Serialization.Formatters;
11     using System.Runtime.InteropServices;
12     using System.Diagnostics;
13     using System.Diagnostics.CodeAnalysis;
14     using Microsoft.Win32;
15     using System.Collections;
16     using System.ComponentModel;
17     using System.ComponentModel.Design.Serialization;
18     using System.Globalization;
19     using System.Reflection;
20     using System.Threading;
21 
22     /// <include file='doc\ColorConverter.uex' path='docs/doc[@for="ColorConverter"]/*' />
23     /// <devdoc>
24     ///      ColorConverter is a class that can be used to convert
25     ///      colors from one data type to another.  Access this
26     ///      class through the TypeDescriptor.
27     /// </devdoc>
28     public class ColorConverter : TypeConverter {
29         private static string ColorConstantsLock = "colorConstants";
30         private static Hashtable colorConstants;
31         private static string SystemColorConstantsLock = "systemColorConstants";
32         private static Hashtable systemColorConstants;
33         private static string ValuesLock = "values";
34         private static StandardValuesCollection values;
35 
36         /// <include file='doc\ColorConverter.uex' path='docs/doc[@for="ColorConverter.ColorConverter"]/*' />
37         /// <devdoc>
38         ///    <para>[To be supplied.]</para>
39         /// </devdoc>
ColorConverter()40         public ColorConverter() {
41         }
42 
43         /// <include file='doc\ColorConverter.uex' path='docs/doc[@for="ColorConverter.Colors"]/*' />
44         /// <devdoc>
45         ///      Hashtable of color / value pairs (color name is key)
46         ///      for standard colors.
47         /// </devdoc>
48         private static Hashtable Colors {
49             get {
50                 if (colorConstants == null) {
51                     lock(ColorConstantsLock) {
52                         if (colorConstants == null) {
53                             Hashtable tempHash = new Hashtable(StringComparer.OrdinalIgnoreCase);
54                             FillConstants(tempHash, typeof(Color));
55                             colorConstants = tempHash;
56                         }
57                     }
58                 }
59 
60                 return colorConstants;
61             }
62         }
63 
64         /// <include file='doc\ColorConverter.uex' path='docs/doc[@for="ColorConverter.SystemColors"]/*' />
65         /// <devdoc>
66         ///      Hashtable of color / value pairs (color name is key)
67         ///      for system colors.
68         /// </devdoc>
69         private static Hashtable SystemColors {
70             get {
71                 if (systemColorConstants == null) {
72                     lock (SystemColorConstantsLock) {
73                         if (systemColorConstants == null) {
74                             Hashtable tempHash = new Hashtable(StringComparer.OrdinalIgnoreCase);
75                             FillConstants(tempHash, typeof(System.Drawing.SystemColors));
76                             systemColorConstants = tempHash;
77                         }
78                     }
79                 }
80 
81                 return systemColorConstants;
82             }
83         }
84 
85         /// <include file='doc\ColorConverter.uex' path='docs/doc[@for="ColorConverter.CanConvertFrom"]/*' />
86         /// <devdoc>
87         ///      Determines if this converter can convert an object in the given source
88         ///      type to the native type of the converter.
89         /// </devdoc>
CanConvertFrom(ITypeDescriptorContext context, Type sourceType)90         public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) {
91             if (sourceType == typeof(string)) {
92                 return true;
93             }
94             return base.CanConvertFrom(context, sourceType);
95         }
96 
97         /// <include file='doc\ColorConverter.uex' path='docs/doc[@for="ColorConverter.CanConvertTo"]/*' />
98         /// <devdoc>
99         ///    <para>Gets a value indicating whether this converter can
100         ///       convert an object to the given destination type using the context.</para>
101         /// </devdoc>
CanConvertTo(ITypeDescriptorContext context, Type destinationType)102         public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) {
103             if (destinationType == typeof(InstanceDescriptor)) {
104                 return true;
105             }
106             return base.CanConvertTo(context, destinationType);
107         }
108 
GetNamedColor(string name)109         internal static object GetNamedColor(string name) {
110             object color = null;
111             // First, check to see if this is a standard name.
112             //
113             color = Colors[name];
114             if (color != null) {
115                 return color;
116             }
117             // Ok, how about a system color?
118             //
119             color = SystemColors[name];
120             return color;
121         }
122 
123         /// <include file='doc\ColorConverter.uex' path='docs/doc[@for="ColorConverter.ConvertFrom"]/*' />
124         /// <devdoc>
125         ///      Converts the given object to the converter's native type.
126         /// </devdoc>
127         [SuppressMessage("Microsoft.Performance", "CA1808:AvoidCallsThatBoxValueTypes")]
ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)128         public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) {
129             string strValue = value as string;
130             if (strValue != null) {
131                 object obj = null;
132                 string text = strValue.Trim();
133 
134                 if (text.Length == 0) {
135                     obj = Color.Empty;
136                 }
137                 else {
138                     // First, check to see if this is a standard name.
139                     //
140                     obj = GetNamedColor(text);
141 
142                     if (obj == null) {
143                         if (culture == null) {
144                             culture = CultureInfo.CurrentCulture;
145                         }
146 
147                         char sep = culture.TextInfo.ListSeparator[0];
148                         bool tryMappingToKnownColor = true;
149 
150                         TypeConverter intConverter = TypeDescriptor.GetConverter(typeof(int));
151 
152                         // If the value is a 6 digit hex number only, then
153                         // we want to treat the Alpha as 255, not 0
154                         //
155                         if (text.IndexOf(sep) == -1) {
156 
157                             // text can be '' (empty quoted string)
158                             if (text.Length >= 2 && (text[0] == '\'' || text[0] == '"') && text[0] == text[text.Length -1]) {
159                                 // In quotes means a named value
160                                 string colorName = text.Substring(1, text.Length - 2);
161                                 obj = Color.FromName(colorName);
162                                 tryMappingToKnownColor = false;
163                             }
164                             else if ((text.Length == 7 && text[0] == '#') ||
165                                      (text.Length == 8 && (text.StartsWith("0x") || text.StartsWith("0X"))) ||
166                                      (text.Length == 8 && (text.StartsWith("&h") || text.StartsWith("&H")))) {
167                                 // Note: ConvertFromString will raise exception if value cannot be converted.
168                                 obj = Color.FromArgb(unchecked((int)(0xFF000000 | (uint)(int)intConverter.ConvertFromString(context, culture, text))));
169                             }
170                         }
171 
172                         // Nope.  Parse the RGBA from the text.
173                         //
174                         if (obj == null) {
175                             string[] tokens = text.Split(new char[] {sep});
176                             int[] values = new int[tokens.Length];
177                             for (int i = 0; i < values.Length; i++) {
178                                 values[i] = unchecked((int)intConverter.ConvertFromString(context, culture, tokens[i]));
179                             }
180 
181                             // We should now have a number of parsed integer values.
182                             // We support 1, 3, or 4 arguments:
183                             //
184                             // 1 -- full ARGB encoded
185                             // 3 -- RGB
186                             // 4 -- ARGB
187                             //
188                             switch (values.Length) {
189                                 case 1:
190                                     obj = Color.FromArgb(values[0]);
191                                     break;
192 
193                                 case 3:
194                                     obj = Color.FromArgb(values[0], values[1], values[2]);
195                                     break;
196 
197                                 case 4:
198                                     obj = Color.FromArgb(values[0], values[1], values[2], values[3]);
199                                     break;
200                             }
201                             tryMappingToKnownColor = true;
202                         }
203 
204                         if ((obj != null) && tryMappingToKnownColor) {
205 
206                             // Now check to see if this color matches one of our known colors.
207                             // If it does, then substitute it.  We can only do this for "Colors"
208                             // because system colors morph with user settings.
209                             //
210                             int targetARGB = ((Color)obj).ToArgb();
211 
212                             foreach (Color c in Colors.Values) {
213                                 if (c.ToArgb() == targetARGB) {
214                                     obj = c;
215                                     break;
216                                 }
217                             }
218                         }
219                     }
220 
221                     if (obj == null) {
222                         throw new ArgumentException(SR.Format(SR.InvalidColor, text));
223                     }
224                 }
225                 return obj;
226             }
227             return base.ConvertFrom(context, culture, value);
228         }
229 
230         /// <include file='doc\ColorConverter.uex' path='docs/doc[@for="ColorConverter.ConvertTo"]/*' />
231         /// <devdoc>
232         ///      Converts the given object to another type.  The most common types to convert
233         ///      are to and from a string object.  The default implementation will make a call
234         ///      to ToString on the object if the object is valid and if the destination
235         ///      type is string.  If this cannot convert to the desitnation type, this will
236         ///      throw a NotSupportedException.
237         /// </devdoc>
ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)238         public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) {
239             if (destinationType == null) {
240                 throw new ArgumentNullException("destinationType");
241             }
242 
243             if( value is Color ){
244                 if (destinationType == typeof(string)) {
245                     Color c = (Color)value;
246 
247                     if (c == Color.Empty) {
248                         return string.Empty;
249                     }
250                     else {
251                         // If this is a known color, then Color can provide its own
252                         // name.  Otherwise, we fabricate an ARGB value for it.
253                         //
254                         if (c.IsKnownColor) {
255                             return c.Name;
256                         }
257                         else if (c.IsNamedColor) {
258                             return "'" + c.Name + "'";
259                         }
260                         else {
261                             if (culture == null) {
262                                 culture = CultureInfo.CurrentCulture;
263                             }
264                             string sep = culture.TextInfo.ListSeparator + " ";
265                             TypeConverter intConverter = TypeDescriptor.GetConverter(typeof(int));
266                             string[] args;
267                             int nArg = 0;
268 
269                             if (c.A < 255) {
270                                 args = new string[4];
271                                 args[nArg++] = intConverter.ConvertToString(context, culture, (object)c.A);
272                             }
273                             else {
274                                 args = new string[3];
275                             }
276 
277                             // Note: ConvertToString will raise exception if value cannot be converted.
278                             args[nArg++] = intConverter.ConvertToString(context, culture, (object)c.R);
279                             args[nArg++] = intConverter.ConvertToString(context, culture, (object)c.G);
280                             args[nArg++] = intConverter.ConvertToString(context, culture, (object)c.B);
281 
282                             // Now slam all of these together with the fantastic Join
283                             // method.
284                             //
285                             return string.Join(sep, args);
286                         }
287                     }
288                 }
289                 if (destinationType == typeof(InstanceDescriptor)) {
290                     MemberInfo member = null;
291                     object[] args = null;
292 
293                     Color c = (Color)value;
294 
295                     if (c.IsEmpty) {
296                         member = typeof(Color).GetField("Empty");
297                     }
298                     else if (c.IsSystemColor) {
299                         member = typeof(SystemColors).GetProperty(c.Name);
300                     }
301                     else if (c.IsKnownColor) {
302                         member = typeof(Color).GetProperty(c.Name);
303                     }
304                     else if (c.A != 255) {
305                         member = typeof(Color).GetMethod("FromArgb", new Type[] {typeof(int), typeof(int), typeof(int), typeof(int)});
306                         args = new object[] {c.A, c.R, c.G, c.B};
307                     }
308                     else if (c.IsNamedColor) {
309                         member = typeof(Color).GetMethod("FromName", new Type[] {typeof(string)});
310                         args = new object[] {c.Name};
311                     }
312                     else {
313                         member = typeof(Color).GetMethod("FromArgb", new Type[] {typeof(int), typeof(int), typeof(int)});
314                         args = new object[] {c.R, c.G, c.B};
315                     }
316 
317                     Debug.Assert(member != null, "Could not convert color to member.  Did someone change method name / signature and not update Colorconverter?");
318                     if (member != null) {
319                         return new InstanceDescriptor(member, args);
320                     }
321                     else {
322                         return null;
323                     }
324                 }
325             }
326 
327             return base.ConvertTo(context, culture, value, destinationType);
328         }
329 
330         /// <include file='doc\ColorConverter.uex' path='docs/doc[@for="ColorConverter.FillConstants"]/*' />
331         /// <devdoc>
332         ///      Fills the given hashtable with field name / value pairs.  It walks all public static
333         ///      properties of enumType that have a property type of Color.
334         /// </devdoc>
FillConstants(Hashtable hash, Type enumType)335         private static void FillConstants(Hashtable hash, Type enumType) {
336             MethodAttributes attrs = MethodAttributes.Public | MethodAttributes.Static;
337             PropertyInfo[] props = enumType.GetProperties();
338 
339             for (int i = 0; i < props.Length; i++) {
340                 PropertyInfo prop = props[i];
341                 if (prop.PropertyType == typeof(Color)) {
342                     MethodInfo method = prop.GetGetMethod();
343                     if (method != null && (method.Attributes & attrs) == attrs) {
344                         object[] tempIndex = null;
345                         hash[prop.Name] = prop.GetValue(null, tempIndex);
346                     }
347                 }
348             }
349         }
350 
351         /// <include file='doc\ColorConverter.uex' path='docs/doc[@for="ColorConverter.GetStandardValues"]/*' />
352         /// <devdoc>
353         ///      Retrieves a collection containing a set of standard values
354         ///      for the data type this validator is designed for.  This
355         ///      will return null if the data type does not support a
356         ///      standard set of values.
357         /// </devdoc>
GetStandardValues(ITypeDescriptorContext context)358         public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) {
359             if (values == null) {
360                 lock (ValuesLock) {
361                     if (values == null) {
362 
363                        // We must take the value from each hashtable and combine them.
364                        //
365                        ArrayList arrayValues = new ArrayList();
366                        arrayValues.AddRange(Colors.Values);
367                        arrayValues.AddRange(SystemColors.Values);
368 
369                        // Now, we have a couple of colors that have the same names but
370                        // are identical values.  Look for these and remove them.  Too
371                        // bad this is n^2.
372                        //
373                        int count = arrayValues.Count;
374                        for (int i = 0; i < count - 1; i++) {
375                            for (int j = i + 1; j < count; j++) {
376                                if (arrayValues[i].Equals(arrayValues[j])) {
377                                    // Remove this item!
378                                    //
379                                    arrayValues.RemoveAt(j);
380                                    count--;
381                                    j--;
382                                }
383                            }
384                        }
385 
386                        // Sort the array.
387                        //
388                        arrayValues.Sort(0, arrayValues.Count, new ColorComparer());
389                        values = new StandardValuesCollection(arrayValues.ToArray());
390                     }
391                 }
392             }
393 
394             return values;
395         }
396 
397         /// <include file='doc\ColorConverter.uex' path='docs/doc[@for="ColorConverter.GetStandardValuesSupported"]/*' />
398         /// <devdoc>
399         ///      Determines if this object supports a standard set of values
400         ///      that can be picked from a list.
401         /// </devdoc>
GetStandardValuesSupported(ITypeDescriptorContext context)402         public override bool GetStandardValuesSupported(ITypeDescriptorContext context) {
403             return true;
404         }
405 
406         /// <include file='doc\ColorConverter.uex' path='docs/doc[@for="ColorConverter.ColorComparer"]/*' />
407         /// <devdoc>
408         ///      IComparer for color values.  This takes color values but compares their
409         ///      names.
410         /// </devdoc>
411         private class ColorComparer : IComparer {
412 
Compare(object left, object right)413             public int Compare(object left, object right) {
414                 Color cLeft = (Color)left;
415                 Color cRight = (Color)right;
416                 return string.Compare(cLeft.Name, cRight.Name, false, CultureInfo.InvariantCulture);
417             }
418         }
419     }
420 }
421 
422 
423 
424