// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.Text; namespace System.Windows.Forms { /// /// Provides a type converter to convert objects to and from various /// other representations. /// public class KeysConverter : TypeConverter, IComparer { private IDictionary _keyNames; private List _displayOrder; private StandardValuesCollection _values; private void Initialize() { _keyNames = new Dictionary(34); _displayOrder = new List(34); AddKey("Enter", Keys.Return); AddKey("F12", Keys.F12); AddKey("F11", Keys.F11); AddKey("F10", Keys.F10); AddKey("End", Keys.End); AddKey("Ctrl", Keys.Control); AddKey("F8", Keys.F8); AddKey("F9", Keys.F9); AddKey("Alt", Keys.Alt); AddKey("F4", Keys.F4); AddKey("F5", Keys.F5); AddKey("F6", Keys.F6); AddKey("F7", Keys.F7); AddKey("F1", Keys.F1); AddKey("F2", Keys.F2); AddKey("F3", Keys.F3); AddKey("PageDown", Keys.Next); AddKey("Insert", Keys.Insert); AddKey("Home", Keys.Home); AddKey("Del", Keys.Delete); AddKey("Shift", Keys.Shift); AddKey("PageUp", Keys.Prior); AddKey("Back", Keys.Back); //new whidbey keys follow here... // Add string mappings for these values (numbers 0-9) so that the keyboard shortcuts // will be displayed properly in menus. AddKey("0", Keys.D0); AddKey("1", Keys.D1); AddKey("2", Keys.D2); AddKey("3", Keys.D3); AddKey("4", Keys.D4); AddKey("5", Keys.D5); AddKey("6", Keys.D6); AddKey("7", Keys.D7); AddKey("8", Keys.D8); AddKey("9", Keys.D9); void AddKey(string key, Keys value) { _keyNames[key] = value; _displayOrder.Add(key); } } /// /// Access to a lookup table of name/value pairs for keys. These are localized /// names. /// private IDictionary KeyNames { get { if (_keyNames is null) { Debug.Assert(_displayOrder is null); Initialize(); } return _keyNames; } } private List DisplayOrder { get { if (_displayOrder is null) { Debug.Assert(_keyNames is null); Initialize(); } return _displayOrder; } } /// /// Determines if this converter can convert an object in the given source /// type to the native type of the converter. /// public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { if (sourceType == typeof(string) || sourceType == typeof(Enum[])) { return true; } return base.CanConvertFrom(context, sourceType); } /// /// Gets a value indicating whether this converter can /// convert an object to the given destination type using the context. /// public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { if (destinationType == typeof(Enum[])) { return true; } return base.CanConvertTo(context, destinationType); } /// /// Compares two key values for equivalence. /// public int Compare(object a, object b) { return string.Compare(ConvertToString(a), ConvertToString(b), false, CultureInfo.InvariantCulture); } /// /// Converts the given object to the converter's native type. /// public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { if (value is string valueAsString) { string text = valueAsString.Trim(); if (text.Length == 0) { return null; } else { // Parse an array of key tokens. string[] tokens = text.Split(new char[] { '+' }); for (int i = 0; i < tokens.Length; i++) { tokens[i] = tokens[i].Trim(); } // Now lookup each key token in our key hashtable. Keys key = (Keys)0; bool foundKeyCode = false; for (int i = 0; i < tokens.Length; i++) { if (!KeyNames.TryGetValue(tokens[i], out Keys currentKey)) { // Key was not found in our dictionary. See if it is a valid value in // the Keys enum. currentKey = (Keys)Enum.Parse(typeof(Keys), tokens[i]); } if ((currentKey & Keys.KeyCode) != 0) { // We found a match. If we have previously found a // key code, then check to see that this guy // isn't a key code (it is illegal to have, say, // "A + B" if (foundKeyCode) { throw new FormatException("InvalidKeyCombination"); } foundKeyCode = true; } // Now OR the key into our current key key |= currentKey; } return (object)key; } } else if (value is Enum[] valueAsEnumArray) { long finalValue = 0; foreach (Enum e in valueAsEnumArray) { finalValue |= Convert.ToInt64(e, CultureInfo.InvariantCulture); } return Enum.ToObject(typeof(Keys), finalValue); } return base.ConvertFrom(context, culture, value); } /// /// Converts the given object to another type. The most common types to convert /// are to and from a string object. The default implementation will make a call /// to ToString on the object if the object is valid and if the destination /// type is string. If this cannot convert to the destination type, this will /// throw a NotSupportedException. /// public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { if (destinationType == null) throw new ArgumentNullException(); if (value is Keys || value is int) { bool asString = destinationType == typeof(string); bool asEnum = false; if (!asString) { asEnum = destinationType == typeof(Enum[]); } if (asString || asEnum) { Keys key = (Keys)value; bool added = false; ArrayList terms = new ArrayList(); Keys modifiers = (key & Keys.Modifiers); // First, iterate through and do the modifiers. These are // additive, so we support things like Ctrl + Alt for (int i = 0; i < DisplayOrder.Count; i++) { string keyString = DisplayOrder[i]; Keys keyValue = _keyNames[keyString]; if (((int)keyValue & (int)modifiers) != 0) { if (asString) { if (added) { terms.Add("+"); } terms.Add(keyString); } else { terms.Add(keyValue); } added = true; } } // Now reset and do the key values. Here, we quit if // we find a match. Keys keyOnly = key & Keys.KeyCode; bool foundKey = false; if (added && asString) { terms.Add("+"); } for (int i = 0; i < DisplayOrder.Count; i++) { string keyString = DisplayOrder[i]; Keys keyValue = _keyNames[keyString]; if (keyValue.Equals(keyOnly)) { if (asString) { terms.Add(keyString); } else { terms.Add(keyValue); } added = true; foundKey = true; break; } } // Finally, if the key wasn't in our list, add it to // the end anyway. Here we just pull the key value out // of the enum. if (!foundKey && Enum.IsDefined(typeof(Keys), (int)keyOnly)) { if (asString) { terms.Add(((Enum)keyOnly).ToString()); } else { terms.Add((Enum)keyOnly); } } if (asString) { StringBuilder b = new StringBuilder(32); foreach (string t in terms) { b.Append(t); } return b.ToString(); } else { return (Enum[])terms.ToArray(typeof(Enum)); } } } return base.ConvertTo(context, culture, value, destinationType); } /// /// Retrieves a collection containing a set of standard values /// for the data type this validator is designed for. This /// will return null if the data type does not support a /// standard set of values. /// public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) { if (_values is null) { ArrayList list = new ArrayList(); ICollection keys = KeyNames.Values; foreach (object o in keys) { list.Add(o); } list.Sort(this); _values = new StandardValuesCollection(list.ToArray()); } return _values; } /// /// Determines if the list of standard values returned from /// GetStandardValues is an exclusive list. If the list /// is exclusive, then no other values are valid, such as /// in an enum data type. If the list is not exclusive, /// then there are other valid values besides the list of /// standard values GetStandardValues provides. /// public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) { return false; } /// /// Determines if this object supports a standard set of values /// that can be picked from a list. /// public override bool GetStandardValuesSupported(ITypeDescriptorContext context) { return true; } } }