File: System.Activities.Presentation\System\Activities\Presentation\View\TypeBrowser.xaml.cs
Project: ndp\cdf\src\NetFx40\Tools\System.Activities.Presentation.csproj (System.Activities.Presentation)
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//----------------------------------------------------------------
namespace System.Activities.Presentation.View
{
    using System;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Windows;
    using System.Windows.Automation;
    using System.Windows.Automation.Peers;
    using System.Windows.Controls;
    using System.Windows.Input;
    using System.Collections.Specialized;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.Text;
    using System.ComponentModel.Design;
    using System.Activities.Presentation.Hosting;
    using System.Windows.Threading;
    using Microsoft.Activities.Presentation;
 
    internal sealed partial class TypeBrowser : DialogWindow
    {
        static readonly DependencyProperty SelectedTypeProperty =
            DependencyProperty.Register("SelectedType",
            typeof(Type),
            typeof(TypeBrowser),
            new UIPropertyMetadata());
 
        static readonly DependencyProperty HasGenericTypesProperty =
            DependencyProperty.Register("HasGenericTypes",
            typeof(bool),
            typeof(TypeBrowser));
 
        static readonly DependencyProperty GenericTypeNameProperty =
            DependencyProperty.Register("GenericTypeName",
            typeof(String),
            typeof(TypeBrowser));
 
        static readonly DependencyProperty GenericTypeMappingProperty =
            DependencyProperty.Register("GenericTypeMapping",
            typeof(ObservableCollection<TypeKeyValue>),
            typeof(TypeBrowser));
 
        static readonly DependencyProperty ConcreteTypeProperty =
            DependencyProperty.Register("ConcreteType",
            typeof(Type),
            typeof(TypeBrowser));
 
        static Size size = Size.Empty;
 
        SearchAction currentSearch = null;
 
        ObservableCollection<AssemblyNode> localAssemblies;
        ObservableCollection<AssemblyNode> referenceAssemblies;
        AssemblyContextControlItem assemblyContext;
        Func<Type, bool> filter;
        DesignerPerfEventProvider perfEventProvider;
 
        public TypeBrowser(AssemblyContextControlItem assemblyContext, EditingContext context, Func<Type, bool> filter)
        {
            this.assemblyContext = assemblyContext;
            this.Context = context;
            this.filter = filter;
            SetValue(GenericTypeMappingProperty, new ObservableCollection<TypeKeyValue>());
            GenericTypeMapping.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(GenericTypeMappingCollectionChanged);
            InitializeComponent();
            this.typeEntryTextBox.Focus();
 
            if (!size.IsEmpty)
            {
                this.Height = size.Height;
                this.Width = size.Width;
            }
 
            this.SizeChanged += new SizeChangedEventHandler(TypeBrowser_SizeChanged);
 
            this.HelpKeyword = HelpKeywords.TypeBrowser;
            this.perfEventProvider = new DesignerPerfEventProvider();
        }
 
        static void TypeBrowser_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            TypeBrowser.size = e.NewSize;
        }
 
        Type SelectedType
        {
            get { return (Type)GetValue(SelectedTypeProperty); }
            set { SetValue(SelectedTypeProperty, value); }
        }
 
        bool HasGenericTypes
        {
            get { return (bool)GetValue(HasGenericTypesProperty); }
            set { SetValue(HasGenericTypesProperty, value); }
        }
 
        string GenericTypeName
        {
            get { return (string)GetValue(GenericTypeNameProperty); }
            set { SetValue(GenericTypeNameProperty, value); }
        }
 
        ObservableCollection<TypeKeyValue> GenericTypeMapping
        {
            get { return (ObservableCollection<TypeKeyValue>)GetValue(GenericTypeMappingProperty); }
            set { SetValue(GenericTypeMappingProperty, value); }
        }
 
        public Type ConcreteType
        {
            get { return (Type)GetValue(ConcreteTypeProperty); }
            private set { SetValue(ConcreteTypeProperty, value); }
        }
 
        public ObservableCollection<AssemblyNode> LocalAssemblies
        {
            get
            {
                if (null == this.localAssemblies)
                {
                    this.localAssemblies = new ObservableCollection<AssemblyNode>();
                    if (null != this.assemblyContext)
                    {
                        if (null != this.assemblyContext.LocalAssemblyName)
                        {
                            IMultiTargetingSupportService multiTargetingSupportService = this.Context.Services.GetService<IMultiTargetingSupportService>();
                            Assembly local = AssemblyContextControlItem.GetAssembly(this.assemblyContext.LocalAssemblyName, multiTargetingSupportService);
                            if (local != null)
                            {
                                this.localAssemblies.Add(new AssemblyNode(local, true, this.filter, this.Context));
                            }
                        }
                    }
 
                    if (this.localAssemblies.Count == 0)
                    {
                        this.LocalAssembly.Visibility = Visibility.Collapsed;
                    }
                    else
                    {
                        this.LocalAssembly.Visibility = Visibility.Visible;
                    }
                }
                return this.localAssemblies;
            }
        }
 
        public ObservableCollection<AssemblyNode> ReferenceAssemblies
        {
            get
            {
                if (null == this.referenceAssemblies)
                {
                    this.referenceAssemblies = new ObservableCollection<AssemblyNode>();
                    if (null != this.assemblyContext)
                    {
                        IMultiTargetingSupportService multiTargetingSupportService = this.Context.Services.GetService<IMultiTargetingSupportService>();
                        IEnumerable<Assembly> assemblies = this.assemblyContext.GetEnvironmentAssemblies(multiTargetingSupportService);
                        foreach (Assembly assembly in assemblies.OrderBy<Assembly, string>(p => p.FullName))
                        {
                            this.referenceAssemblies.Add(new AssemblyNode(assembly, false, this.filter, this.Context));
                        }
                    }
                }
                return this.referenceAssemblies;
            }
        }
 
        public bool? ShowDialog(DependencyObject owner)
        {
            WindowHelperService.TrySetWindowOwner(owner, this.Context, this);
            this.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(() => { this.perfEventProvider.TypeBrowserApplicationIdleAfterShowDialog(); }));
            return base.ShowDialog();
        }
 
        protected override void OnKeyDown(KeyEventArgs e)
        {
            if (e.Key == Key.Escape)
            {
                this.DialogResult = false;
                e.Handled = true;
            }
            else if (e.Key == Key.Enter && null != SelectedType)
            {
                OnDialogClose();
                e.Handled = true;
            }
            else
            {
                base.OnKeyDown(e);
            }
        }
 
        protected override System.Windows.Automation.Peers.AutomationPeer OnCreateAutomationPeer()
        {
            if (LocalAppContextSwitches.UseLegacyAccessibilityFeatures)
            {
                return new UIElementAutomationPeer(this);
            }
            return new TypeBrowserAutomationPeer(this);
        }
 
        private void GenericTypeMappingCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            this.HasGenericTypes = GenericTypeMapping.Count > 0 ? true : false;
            if (this.HasGenericTypes)
            {
                string strName = this.SelectedType.FullName;
                this.GenericTypeName = strName.Substring(0, strName.Length - 2) + " <";
            }
            else
            {
                this.GenericTypeName = null;
            }
        }
 
        private Type ResolveType(out string errorTitle, out string errorMessage)
        {
            errorTitle = null;
            errorMessage = null;
            Type result = this.SelectedType;
 
            try
            {
                IMultiTargetingSupportService multiTargetingSupport = this.Context.Services.GetService<IMultiTargetingSupportService>();
                if (multiTargetingSupport != null)
                {
                    result = multiTargetingSupport.GetRuntimeType(result);
                }
 
                if (result == null)
                {
                    errorTitle = SR.TypeBrowserErrorMessageTitle;
                    errorMessage = SR.TypeBrowserError;
                    return null;
                }
 
                if (result.IsGenericTypeDefinition)
                {
                    bool isValid = true;
                    //get number of generic parameters in edited type
                    Type[] arguments = new Type[this.GenericTypeMapping.Count];
 
                    //for each argument, get resolved type
                    for (int i = 0; i < this.GenericTypeMapping.Count && isValid; ++i)
                    {
                        arguments[i] = this.GenericTypeMapping[i].GetConcreteType();
                        if (multiTargetingSupport != null && arguments[i] != null)
                        {
                            arguments[i] = multiTargetingSupport.GetRuntimeType(arguments[i]);
                        }
                        isValid = isValid && (null != arguments[i]);
                    }
 
                    //if all parameters are resolved, create concrete type
                    if (isValid)
                    {
                        result = result.MakeGenericType(arguments);
                    }
                    else
                    {
                        errorTitle = SR.TypeBrowserErrorMessageTitle;
                        errorMessage = SR.TypeResolverError;
                        result = null;
                    }
                }
            }
            catch (ArgumentException err)
            {
                errorTitle = err.GetType().Name;
                errorMessage = err.Message;
                return null;
            }
 
            return result;
        }
 
        private void OnOkClick(object sender, RoutedEventArgs args)
        {
            OnDialogClose();
        }
 
        private void OnCancelClick(object sender, RoutedEventArgs args)
        {
            this.DialogResult = false;
        }
 
        private void OnTypeDoubleClick(object sender, RoutedEventArgs args)
        {
            if (((System.Windows.Input.MouseButtonEventArgs)(args)).ChangedButton == MouseButton.Left)
            {
                TypeNode entry = ((TreeViewItem)sender).Header as TypeNode;
                if (null != entry && entry.Data is Type)
                {
                    OnDialogClose();
                    args.Handled = true;
                }
            }
        }
 
        private void OnDialogClose()
        {
            string errorTitle = null;
            string errorMessage = null;
 
            Type type = ResolveType(out errorTitle, out errorMessage);
            if (null != type)
            {
                this.ConcreteType = type;
                this.DialogResult = true;
                this.perfEventProvider.TypeBrowserOkPressed();
            }
            else
            {
                MessageBox.Show(errorMessage, errorTitle, MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }
 
        private void OnTypeBrowserClickStart(object sender, RoutedEventArgs args)
        {
            TreeViewItem item = sender as TreeViewItem;
            NamespaceNode ns = null;
            if (null != item)
            {
                ns = item.Header as NamespaceNode;
            }
 
            if (null != ns && null == ns.Tag)
            {
                ns.Tag = string.Empty;
                Mouse.OverrideCursor = Cursors.Wait;
            }
        }
 
        private void OnTypeBrowserClickEnd(object sender, RoutedEventArgs args)
        {
            TreeViewItem item = sender as TreeViewItem;
            if (null != item && item.Header is AssemblyNode && null != Mouse.OverrideCursor)
            {
                Mouse.OverrideCursor = null;
            }
        }
 
        private void OnTypeSearchTextChanged(object sender, TextChangedEventArgs e)
        {
            string searchText = ((TextBox)sender).Text;
 
            SearchAction newSearch = new SearchAction(searchText, this.localAssemblies, this.referenceAssemblies);
            newSearch.Completed += delegate(object s, EventArgs args)
            {
                SearchAction senderAction = s as SearchAction;
                SearchAction currentAction = this.currentSearch;
                this.currentSearch = null;
 
                if (senderAction == currentAction)
                {
                    TypeNode match = ((SearchActionEventArgs)args).Result as TypeNode;
 
                    UpdateSelectedItem(match);
                    if (match != null)
                    {
                        match.IsSelected = true;
                    }
                }
            };
 
            if (this.currentSearch != null)
            {
                this.currentSearch.Abort();
            }
 
            ClearSelection();
 
            this.currentSearch = newSearch;
            this.currentSearch.Run();
        }
 
        private void ClearSelection()
        {
            TypeNode currentSelection = this.typesTreeView.SelectedItem as TypeNode;
            if (currentSelection != null)
            {
                currentSelection.IsSelected = false;
            }
        }
 
        private void OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            TypeNode entry = ((System.Windows.Controls.TreeView)sender).SelectedItem as TypeNode;
            if (entry != null && this.SelectedType != entry.Data as Type)
            {
                UpdateSelectedItem(entry);
                if (null != this.SelectedType)
                {
                    typeEntryTextBox.TextChanged -= new TextChangedEventHandler(OnTypeSearchTextChanged);
                    typeEntryTextBox.Text = TypeNameHelper.GetDisplayName(this.SelectedType, true);
                    typeEntryTextBox.TextChanged += new TextChangedEventHandler(OnTypeSearchTextChanged);
                }
            }
        }
 
        private void UpdateSelectedItem(TypeNode entry)
        {
            GenericTypeMapping.Clear();
 
            SelectedType = (null != entry ? entry.Data as Type : null);
 
            if (null != this.SelectedType)
            {
                if (this.SelectedType.IsGenericTypeDefinition)
                {
                    this.ConcreteType = null;
                    Type[] generics = this.SelectedType.GetGenericArguments();
                    foreach (Type t in generics)
                    {
                        this.GenericTypeMapping.Add(new TypeKeyValue(t, null));
                    }
                }
                else
                {
                    this.ConcreteType = this.SelectedType;
                }
            }
            else
            {
                this.ConcreteType = null;
            }
        }
 
        internal class Node : INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler PropertyChanged;
 
            private bool isExpanded;
            private bool isSelected;
            private Visibility visibility;
 
            protected Node()
            {
                this.isExpanded = false;
                this.isSelected = false;
                this.visibility = Visibility.Visible;
            }
 
            protected void Notify(string property)
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(property));
                }
            }
 
            public bool IsExpanded
            {
                get
                {
                    return this.isExpanded;
                }
                set
                {
                    if (this.isExpanded != value)
                    {
                        this.isExpanded = value;
                        Notify("IsExpanded");
                    }
                }
            }
 
            public bool IsSelected
            {
                get
                {
                    return this.isSelected;
                }
                set
                {
                    if (this.isSelected != value)
                    {
                        this.isSelected = value;
                        Notify("IsSelected");
                    }
                }
            }
 
            public Visibility Visibility
            {
                get
                {
                    return this.visibility;
                }
                set
                {
                    if (this.visibility != value)
                    {
                        this.visibility = value;
                        Notify("Visibility");
                    }
                }
            }
 
            public object Tag
            {
                get;
                set;
            }
        }
 
        internal class AssemblyNode : Node
        {
            string displayName;
            Assembly assembly;
            bool isLocal;
            Func<Type, bool> filter;
            EditingContext context;
 
            public AssemblyNode(Assembly assembly, bool isLocal, Func<Type, bool> filter, EditingContext context)
            {
                if (null == assembly)
                {
                    throw FxTrace.Exception.AsError(new ArgumentNullException("assembly"));
                }
                this.assembly = assembly;
                this.isLocal = isLocal;
                this.displayName = GetDisplayName(this.assembly.GetName());
                this.filter = filter;
                this.context = context;
            }
 
            private static string GetDisplayName(AssemblyName name)
            {
                StringBuilder sb = new StringBuilder();
                if (name != null && name.Name != null)
                {
                    sb.Append(name.Name);
                    if (name.Version != null)
                    {
                        sb.Append(" [");
                        sb.Append(name.Version.Major);
                        sb.Append(".");
                        sb.Append(name.Version.Minor);
                        sb.Append(".");
                        sb.Append(name.Version.Build);
                        sb.Append(".");
                        sb.Append(name.Version.Revision);
                        sb.Append("]");
                    }
                }
                return sb.ToString();
            }
 
            public string DisplayName
            {
                get { return this.displayName; }
            }
 
            private ObservableCollection<NamespaceNode> namespaces;
            public ObservableCollection<NamespaceNode> Namespaces
            {
                get
                {
                    if (namespaces == null)
                    {
                        namespaces = new ObservableCollection<NamespaceNode>();
 
                        try
                        {
                            Func<Type, bool> typeFilter = this.filter;
                            IMultiTargetingSupportService multiTargetingSupport = this.context.Services.GetService<IMultiTargetingSupportService>();
                            if (multiTargetingSupport != null && typeFilter != null)
                            {
                                typeFilter = (type) => this.filter(multiTargetingSupport.GetRuntimeType(type));
                            }
 
                            var exportedTypes =
                                from type in (this.isLocal ? this.assembly.GetTypes() : this.assembly.GetExportedTypes())
                                where (type.IsPublic && type.IsVisible && (typeFilter == null || typeFilter(type)))
                                orderby type.Namespace, type.Name
                                select type;
 
                            NamespaceNode lastNamespace = null;
                            foreach (Type type in exportedTypes)
                            {
                                if (lastNamespace == null || !StringComparer.OrdinalIgnoreCase.Equals(lastNamespace.DisplayName, type.Namespace))
                                {
                                    lastNamespace = new NamespaceNode(type.Namespace);
                                    namespaces.Add(lastNamespace);
                                }
 
                                lastNamespace.Types.Add(new TypeNode(type));
                            }
                        }
                        catch (NotSupportedException)
                        {
                            //Dynamic (in memory) assemblies will throw exception when this method is called
                            //that's the reason i'm swollowing that exception
                        }
 
                    }
                    return namespaces;
                }
            }
 
            public string Data
            {
                get { return this.displayName; }
            }
 
            public override string ToString()
            {
                return this.displayName;
            }
        }
 
        internal class NamespaceNode : Node
        {
 
            private string displayName;
            public string DisplayName
            {
                get
                {
                    return this.displayName;
                }
            }
 
            private ObservableCollection<TypeNode> types;
            public ObservableCollection<TypeNode> Types
            {
                get
                {
                    return this.types;
                }
            }
 
            public string Data
            {
                get { return this.displayName; }
            }
 
            public NamespaceNode(string name)
            {
                this.displayName = name;
                this.types = new ObservableCollection<TypeNode>();
            }
        }
 
        internal class TypeNode : Node
        {
            private Type type;
 
            private string displayName;
            public string DisplayName
            {
                get
                {
                    return this.displayName;
                }
            }
 
            private string fullName;
            public string FullName
            {
                get
                {
                    return this.fullName;
                }
            }
 
            public Type Data
            {
                get { return this.type; }
            }
 
            public TypeNode(Type type)
            {
                this.type = type;
                this.displayName = TypeNameHelper.GetDisplayName(type, false);
                this.fullName = TypeNameHelper.GetDisplayName(type, true);
            }
        }
 
        private class SearchActionEventArgs : EventArgs
        {
            public object Result
            {
                get;
                set;
            }
        }
 
        private class SearchAction
        {
            private string searchText;
            Collection<AssemblyNode>[] range;
            DispatcherOperation dispatcherOperation;
 
            public event EventHandler Completed;
 
            public SearchAction(string searchText, Collection<AssemblyNode> localAssemblies, Collection<AssemblyNode> referenceAssemblies)
            {
                this.searchText = searchText;
                this.range = new Collection<AssemblyNode>[] { localAssemblies, referenceAssemblies };
            }
 
 
            public void Run()
            {
                this.dispatcherOperation = Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Input, new Func<TypeNode>(this.OnRun));
                this.dispatcherOperation.Completed += this.OnCompleted;
            }
 
            public bool Abort()
            {
                if (this.dispatcherOperation != null)
                {
                    return this.dispatcherOperation.Abort();
                }
                return true;
            }
 
            private TypeNode OnRun()
            {
                bool noSearch = string.IsNullOrEmpty(searchText);
                Func<TypeNode, string, bool> matchAlgorithm = SearchAction.MatchShortName;
 
                TypeNode match = null;
                TypeNode firstCandidate = null;
                bool tooManyCandiates = false;
 
                if (!noSearch && searchText.Contains('.'))
                {
                    matchAlgorithm = SearchAction.MatchFullName;
                }
 
                foreach (Collection<AssemblyNode> assemblies in this.range)
                {
                    foreach (AssemblyNode assembly in assemblies)
                    {
                        Visibility assemblyVisibility = Visibility.Collapsed;
                        bool assemblyIsExpanded = false;
 
                        if (noSearch)
                        {
                            assemblyVisibility = Visibility.Visible;
                        }
 
                        foreach (NamespaceNode ns in assembly.Namespaces)
                        {
                            Visibility namespaceVisibility = Visibility.Collapsed;
                            bool namespaceIsExpanded = false;
 
                            if (noSearch)
                            {
                                namespaceVisibility = Visibility.Visible;
                            }
 
                            foreach (TypeNode entry in ns.Types)
                            {
                                if (noSearch)
                                {
                                    entry.Visibility = Visibility.Visible;
                                }
                                else if (matchAlgorithm(entry, searchText))
                                {
                                    entry.Visibility = Visibility.Visible;
                                    assemblyVisibility = Visibility.Visible;
                                    assemblyIsExpanded = true;
                                    namespaceVisibility = Visibility.Visible;
                                    namespaceIsExpanded = true;
 
                                    if (string.Equals(searchText, entry.FullName, StringComparison.OrdinalIgnoreCase))
                                    {
                                        match = entry;
                                    }
 
                                    if (firstCandidate == null)
                                    {
                                        firstCandidate = entry;
                                    }
                                    else if (!tooManyCandiates)
                                    {
                                        tooManyCandiates = true;
                                    }
                                }
                                else
                                {
                                    entry.Visibility = Visibility.Collapsed;
                                }
                            }
 
                            if (searchText.Contains('.') && ns.DisplayName != null && ns.DisplayName.StartsWith(searchText, StringComparison.OrdinalIgnoreCase))
                            {
                                namespaceIsExpanded = false;
                            }
 
                            if (namespaceIsExpanded)
                            {
                                ns.Tag = string.Empty;
                            }
 
                            ns.Visibility = namespaceVisibility;
                            ns.IsExpanded = namespaceIsExpanded;
                        }
 
                        assembly.Visibility = assemblyVisibility;
                        assembly.IsExpanded = assemblyIsExpanded;
                    }
                }
 
                if (match == null && !tooManyCandiates)
                {
                    match = firstCandidate;
                }
 
                return match;
            }
 
            private void OnCompleted(object sender, EventArgs args)
            {
                this.dispatcherOperation.Completed -= this.OnCompleted;
                if (this.Completed != null)
                {
                    SearchActionEventArgs arg = new SearchActionEventArgs();
                    arg.Result = this.dispatcherOperation.Result;
                    this.Completed(this, arg);
                }
            }
 
            // "abc.def" matches regex ^.*\.abc\.def.* or ^abc\.def.*, but does not match ^.*[^.]abc\.def.*
            private static bool MatchFullName(TypeNode type, string searchText)
            {
                if (searchText.StartsWith(".", StringComparison.OrdinalIgnoreCase))
                {
                    return -1 != type.FullName.IndexOf(searchText, StringComparison.OrdinalIgnoreCase);
                }
 
                if (type.FullName.StartsWith(searchText, StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }
 
                if (-1 != type.FullName.IndexOf("." + searchText, StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }
 
                return false;
            }
 
            private static bool MatchShortName(TypeNode type, string searchText)
            {
                return type.DisplayName.StartsWith(searchText, StringComparison.OrdinalIgnoreCase);
            }
        }
    }
 
    class TypeBrowserAutomationPeer : UIElementAutomationPeer
    {
 
        public TypeBrowserAutomationPeer(TypeBrowser owner)
            : base(owner)
        {
        }
 
        protected override AutomationControlType GetAutomationControlTypeCore()
        {
            return AutomationControlType.Window;
        }
 
        protected override string GetNameCore()
        {
            return ((TypeBrowser)this.Owner).Resources["TypeBrowserAutomationName"] as string;
        }
    }
}