File: System\Windows\Input\InputLanguageManager.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore)
// 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.Windows.Threading;
using System.Globalization;
using MS.Win32;
using System.ComponentModel;
 
namespace System.Windows.Input
{
    /// <summary>
    ///     The InputLanguageManager class is responsible for mmanaging 
    ///     the input language in Avalon.
    /// </summary>
    public sealed class InputLanguageManager : DispatcherObject
    {
        /// <summary>
        /// This is the property of the preferred input language of the 
        /// element. 
        /// When the element get focus, this input language is selected 
        /// automatically.
        /// </summary>
        public static readonly DependencyProperty InputLanguageProperty =
                    DependencyProperty.RegisterAttached(
                        "InputLanguage",
                        typeof(CultureInfo),
                        typeof(InputLanguageManager),
                        new PropertyMetadata(CultureInfo.InvariantCulture)
                        );
 
        /// <summary>
        /// Setter for InputLanguage DependencyProperty
        /// </summary>
        public static void SetInputLanguage(DependencyObject target, CultureInfo inputLanguage)
        {
            ArgumentNullException.ThrowIfNull(target);
 
            target.SetValue(InputLanguageProperty, inputLanguage);
        }
 
        /// <summary>
        /// Getter for InputLanguage DependencyProperty
        /// </summary>
        [AttachedPropertyBrowsableForType(typeof(DependencyObject))]
        [TypeConverter(typeof(System.Windows.CultureInfoIetfLanguageTagConverter))]
        public static CultureInfo GetInputLanguage(DependencyObject target)
        {
            ArgumentNullException.ThrowIfNull(target);
 
            return (CultureInfo)(target.GetValue(InputLanguageProperty));
        }
 
 
        /// <summary>
        /// If this is true, the previous inputlanguage is restored 
        /// when the element with InputLanguage dynamicproperty lose the focus.
        /// This property is ignored if InputLanguage dynamic property is
        ///  not available in the element.
        /// </summary>
        public static readonly DependencyProperty RestoreInputLanguageProperty =
                    DependencyProperty.RegisterAttached(
                        "RestoreInputLanguage",
                        typeof(bool),
                        typeof(InputLanguageManager),
                        new PropertyMetadata(false)
                        );
 
        /// <summary>
        /// Setter for RestoreInputLanguage DependencyProperty
        /// </summary>
        public static void SetRestoreInputLanguage(DependencyObject target, bool restore)
        {
            ArgumentNullException.ThrowIfNull(target);
 
            target.SetValue(RestoreInputLanguageProperty, restore);
        }
 
        /// <summary>
        /// Getter for RestoreInputLanguage DependencyProperty
        /// </summary>
        [AttachedPropertyBrowsableForType(typeof(DependencyObject))]
        public static bool GetRestoreInputLanguage(DependencyObject target)
        {
            ArgumentNullException.ThrowIfNull(target);
 
            return (bool)(target.GetValue(RestoreInputLanguageProperty));
        }
 
 
        //------------------------------------------------------
        //
        //  Constructors
        //
        //------------------------------------------------------
 
        private InputLanguageManager()
        {
            // register our default input language source.
            RegisterInputLanguageSource(new InputLanguageSource(this));
        }
 
        //------------------------------------------------------
        //
        //  Public Methods
        //
        //------------------------------------------------------
 
        #region Public Methods
 
        /// <summary>
        ///     Registers an input language soruces with the input 
        ///     input language manager.
        /// </summary>
        /// <param name="inputLanguageSource">
        ///     The input language source to register.
        /// </param>
        public void RegisterInputLanguageSource(IInputLanguageSource inputLanguageSource)
        {
            ArgumentNullException.ThrowIfNull(inputLanguageSource);
 
            _source = inputLanguageSource;
 
            if (((_InputLanguageChanged != null) || 
                 (_InputLanguageChanging != null)) &&
                IsMultipleKeyboardLayout)
                _source.Initialize();
 
            return;
        }
 
        /// <summary>
        ///     Report the input language is changed from the source.
        /// </summary>
        /// <param name="newLanguageId">
        ///     The new language id.
        /// </param>
        /// <param name="previousLanguageId">
        ///     The previous language id.
        /// </param>
        public void ReportInputLanguageChanged(
                        CultureInfo newLanguageId, 
                        CultureInfo previousLanguageId)
        {
            ArgumentNullException.ThrowIfNull(newLanguageId);
 
            ArgumentNullException.ThrowIfNull(previousLanguageId);
 
            //
            // if this language change was not done by SetFocus() and
            // InputLanguage Property of the element, we clear _previousLanguageId.
            //
            if (!previousLanguageId.Equals(_previousLanguageId))
            {
                _previousLanguageId = null;
            }
 
            if (_InputLanguageChanged != null)
            {
                InputLanguageChangedEventArgs args = new InputLanguageChangedEventArgs(newLanguageId, previousLanguageId);
 
                // Stability Review: Task#32416
                //   - No state to be restored even exception happens while this callback.
                _InputLanguageChanged(this, args);
            }
        }
 
        /// <summary>
        ///     Report the input language is being changed from the source.
        /// </summary>
        /// <param name="newLanguageId">
        ///     The new language id.
        /// </param>
        /// <param name="previousLanguageId">
        ///     The previous language id.
        /// </param>
        public bool ReportInputLanguageChanging(
                        CultureInfo newLanguageId, 
                        CultureInfo previousLanguageId)
        {
            ArgumentNullException.ThrowIfNull(newLanguageId);
 
            ArgumentNullException.ThrowIfNull(previousLanguageId);
 
            bool accepted = true;
 
            if (_InputLanguageChanging != null)
            {
                InputLanguageChangingEventArgs args = new InputLanguageChangingEventArgs(newLanguageId, previousLanguageId);
 
                // Stability Review: Task#32416
                //   - No state to be restored even exception happens while this callback.
                _InputLanguageChanging(this, args);
 
                accepted = args.Rejected ? false : true;
            }
            return accepted;
        }
 
        #endregion Public Methods        
 
        //------------------------------------------------------
        //
        //  Public Operators
        //
        //------------------------------------------------------
 
        //------------------------------------------------------
        //
        //  Public Properties
        //
        //------------------------------------------------------
 
        /// <summary>
        ///     Return the input language manager associated 
        ///     with the current context.
        /// </summary>
        public static InputLanguageManager Current
        {
            get
            {
                // InputLanguageManager for the current Dispatcher is stored in InputMethod of
                // the current Dispatcher.
                if(InputMethod.Current.InputLanguageManager == null)
                {
                    InputMethod.Current.InputLanguageManager = new InputLanguageManager();
                }
                return InputMethod.Current.InputLanguageManager;
            }
        }
 
        /// <summary>
        ///     This accesses the input language associated 
        ///     with the current context.
        /// </summary>
        [TypeConverter(typeof(System.Windows.CultureInfoIetfLanguageTagConverter))]
        public CultureInfo CurrentInputLanguage
        {
            get
            {
                // If the source is available, we should use it.
                if (_source != null)
                {
                    return _source.CurrentInputLanguage;
                }
 
                IntPtr hkl = SafeNativeMethods.GetKeyboardLayout(0);
                if (hkl == IntPtr.Zero)
                {
                    return CultureInfo.InvariantCulture;
                }
 
                // This needs to work before source is attached.
                return new CultureInfo((short)NativeMethods.IntPtrToInt32(hkl));
            }
            set
            {
                ArgumentNullException.ThrowIfNull(value);
 
                SetSourceCurrentLanguageId(value);
            }
        }
 
        /// <summary>
        /// Return enumerator for available input languages.
        /// </summary>
        public IEnumerable AvailableInputLanguages 
        {
            get
            {
                if (_source == null)
                {
                    return null;
                }
 
                return _source.InputLanguageList;
            }
        }
 
        //------------------------------------------------------
        //
        //  Public Events
        //
        //------------------------------------------------------
 
        /// <summary>
        ///     This is an event when the input language is changed.
        /// </summary>
        public event InputLanguageEventHandler InputLanguageChanged
        {
            add
            {
                ArgumentNullException.ThrowIfNull(value);
 
                if ((_InputLanguageChanged == null) && 
                    (_InputLanguageChanging == null) &&
                    IsMultipleKeyboardLayout &&
                    (_source != null))
                    _source.Initialize();
 
                _InputLanguageChanged += value;
            }
            remove
            {
                ArgumentNullException.ThrowIfNull(value);
 
                _InputLanguageChanged -= value;
                if ((_InputLanguageChanged == null) && 
                    (_InputLanguageChanging == null) &&
                    IsMultipleKeyboardLayout &&
                    (_source != null))
                    _source.Uninitialize();
            }
        }
 
        /// <summary>
        ///     This is an event when the input language is being changed.
        /// </summary>
        public event InputLanguageEventHandler InputLanguageChanging
        {
            add
            {
                ArgumentNullException.ThrowIfNull(value);
 
                if ((_InputLanguageChanged == null) && 
                    (_InputLanguageChanging == null) &&
                    IsMultipleKeyboardLayout &&
                    (_source != null))
                    _source.Initialize();
 
                _InputLanguageChanging += value;
            }
            remove
            {
                ArgumentNullException.ThrowIfNull(value);
 
                _InputLanguageChanging -= value;
                if ((_InputLanguageChanged == null) && 
                    (_InputLanguageChanging == null) &&
                    IsMultipleKeyboardLayout &&
                    (_source != null))
                    _source.Uninitialize();
            }
        }
 
 
        //------------------------------------------------------
        //
        //  Protected Methods
        //
        //------------------------------------------------------
 
 
        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------
 
        #region Internal Methods
 
        /// <summary>
        ///     When keyboard device gets focus, this is called.
        ///     We will check the preferred input language of focus element.
        /// </summary>
        internal void Focus(DependencyObject focus, DependencyObject focused)
        {
            CultureInfo culture = null;
 
            if (focus != null)
            {
                //
                // Check the InputLanguageProperty of the focus element.
                //
                culture = (CultureInfo)focus.GetValue(InputLanguageProperty);
            }
            
            if ((culture == null) ||
                (culture.Equals(CultureInfo.InvariantCulture)))
            {
                //
                // If the focus element does not have InputLanguage property,
                // we may need to restore the previous input language.
                //
                if (focused != null)
                {
                    if ((_previousLanguageId != null) &&
                        (bool)focused.GetValue(RestoreInputLanguageProperty))
                    {
                        //
                        // set the current language id of source.
                        //
                        SetSourceCurrentLanguageId(_previousLanguageId);
                    }
                    _previousLanguageId = null;
                }
            }
            else
            {
                // Cache the previous language id.
                // _previousLanguageId can be clear during SetSourceCurrentLanguageId() because the call back
                // ReportInputLanguageChanged() is called. We need to remember the current input language
                // to update _previousLanguageId.
                CultureInfo previousLanguageId = _source.CurrentInputLanguage;
 
                //
                // set the current language id of source.
                //
                SetSourceCurrentLanguageId(culture);
 
                _previousLanguageId = previousLanguageId;
            }
        }
 
 
        #endregion Internal methods
        
        //------------------------------------------------------
        //
        //  Internal Properties
        //
        //------------------------------------------------------
        
        #region Internal Properties
 
        internal IInputLanguageSource Source
        {
            get
            {
                return _source;
            }
        }
 
        /// <summary>
        ///     This checks if there is two or more keyboard layouts.
        /// </summary>
        static internal bool IsMultipleKeyboardLayout
        {
            get
            {
                int count = SafeNativeMethods.GetKeyboardLayoutList(0, null);
 
                return (count > 1);
            }
        }
 
        #endregion Internal Properties
 
        //------------------------------------------------------
        //
        //  Private Methods
        //
        //------------------------------------------------------
 
        private void SetSourceCurrentLanguageId(CultureInfo languageId)
        {
            if (_source == null)
            {
                throw new InvalidOperationException(SR.InputLanguageManager_NotReadyToChangeCurrentLanguage);
            }
 
            _source.CurrentInputLanguage = languageId;
        }
 
        //------------------------------------------------------
        //
        //  Private Events
        //
        //------------------------------------------------------
 
        /// <summary>
        ///     This is an event when the input language is changed.
        /// </summary>
        private event InputLanguageEventHandler _InputLanguageChanged;
 
        /// <summary>
        ///     This is an event when the input language is being changed.
        /// </summary>
        private event InputLanguageEventHandler _InputLanguageChanging;
 
        //------------------------------------------------------
        //
        //  Private Fields
        //
        //------------------------------------------------------
                
        #region Private Fields
 
        // the previous input language id this is used for restring input language at
        // losing focus.
        private CultureInfo _previousLanguageId;
 
        // The reference to the source of input language.
        private IInputLanguageSource _source;
 
        #endregion Private Fields
    }
 
    /// <summary>
    ///     This is a delegate for InputLanguageChanged and 
    ///     InputLanguageChanging events.
    /// </summary>
    public delegate void InputLanguageEventHandler(Object sender, InputLanguageEventArgs e);
}