File: cdf\src\NetFx40\Tools\System.Activities.Presentation\System\Activities\Presentation\Base\Core\Internal\PropertyEditing\Selection\PropertySelection.cs
Project: ndp\System.Data.csproj (System.Data)
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//----------------------------------------------------------------
namespace System.Activities.Presentation.Internal.PropertyEditing.Selection
{
    using System;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
    using System.Windows;
    using System.Windows.Input;
    using System.Windows.Media;
 
    using System.Runtime;
    using System.Activities.Presentation.Internal.PropertyEditing.Selection;
    using System.Activities.Presentation;
 
    // <summary>
    // This is a container for attached properties used by PropertyInspector to track and manage
    // property selection.  It is public because WPF requires that attached properties used in XAML
    // be declared by public classes.
    // </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    static class PropertySelection
    {
 
        private static readonly DependencyPropertyKey IsSelectedPropertyKey = DependencyProperty.RegisterAttachedReadOnly(
            "IsSelected",
            typeof(bool),
            typeof(PropertySelection),
            new PropertyMetadata(false));
 
        // <summary>
        // Attached, ReadOnly DP that we use to mark objects as selected.  If they care, they can then render
        // themselves differently.
        // </summary>
        internal static readonly DependencyProperty IsSelectedProperty = IsSelectedPropertyKey.DependencyProperty;
 
        // <summary>
        // Attached DP that we use in XAML to mark elements that can be selected.
        // </summary>
        internal static readonly DependencyProperty SelectionStopProperty = DependencyProperty.RegisterAttached(
            "SelectionStop",
            typeof(ISelectionStop),
            typeof(PropertySelection),
            new PropertyMetadata(null));
 
        // <summary>
        // Attached DP used in conjunction with SelectionStop DP.  It specifies the FrameworkElement to hook into
        // in order to handle double-click events to control the expanded / collapsed state of its parent SelectionStop.
        // </summary>
        internal static readonly DependencyProperty IsSelectionStopDoubleClickTargetProperty = DependencyProperty.RegisterAttached(
            "IsSelectionStopDoubleClickTarget",
            typeof(bool),
            typeof(PropertySelection),
            new PropertyMetadata(false, new PropertyChangedCallback(OnIsSelectionStopDoubleClickTargetChanged)));
 
        // <summary>
        // Attached DP that we use in XAML to mark elements as selection scopes - meaning selection
        // won't spill beyond the scope of the marked element.
        // </summary>
        internal static readonly DependencyProperty IsSelectionScopeProperty = DependencyProperty.RegisterAttached(
            "IsSelectionScope",
            typeof(bool),
            typeof(PropertySelection),
            new PropertyMetadata(false));
 
        // <summary>
        // Attached property we use to route non-navigational key strokes from one FrameworkElement to
        // another.  When this property is set on a FrameworkElement, we hook into its KeyDown event
        // and send any unhandled, non-navigational key strokes to the FrameworkElement specified
        // by this property.  The target FrameworkElement must be focusable or have a focusable child.
        // When the first eligible key stroke is detected, the focus will be shifted to the focusable
        // element and the key stroke will be sent to it.
        // </summary>
        internal static readonly DependencyProperty KeyDownTargetProperty = DependencyProperty.RegisterAttached(
            "KeyDownTarget",
            typeof(FrameworkElement),
            typeof(PropertySelection),
            new PropertyMetadata(null, new PropertyChangedCallback(OnKeyDownTargetChanged)));
 
        // Constant that determines how deep in the visual tree we search for SelectionStops that
        // are children or neighbors of a given element (usually one that the user clicked on) before
        // giving up.  This constant is UI-dependent.
        private const int MaxSearchDepth = 11;
 
        // <summary>
        // Gets PropertySelection.IsSelected property from the specified DependencyObject
        // </summary>
        // <param name="obj">DependencyObject to examine</param>
        // <returns>Value of the IsSelected property</returns>
        internal static bool GetIsSelected(DependencyObject obj)
        {
            if (obj == null)
            {
                throw FxTrace.Exception.ArgumentNull("obj");
            }
 
            return (bool)obj.GetValue(IsSelectedProperty);
        }
 
        // Private (internal) setter that we use to mark objects as selected from within CategoryList class
        //
        internal static void SetIsSelected(DependencyObject obj, bool value)
        {
            if (obj == null)
            {
                throw FxTrace.Exception.ArgumentNull("obj");
            }
 
            obj.SetValue(IsSelectedPropertyKey, value);
        }
 
 
        // SelectionStop Attached DP
 
        // <summary>
        // Gets PropertySelection.SelectionStop property from the specified DependencyObject
        // </summary>
        // <param name="obj">DependencyObject to examine</param>
        // <returns>Value of the SelectionStop property.</returns>
        internal static ISelectionStop GetSelectionStop(DependencyObject obj)
        {
            if (obj == null)
            {
                throw FxTrace.Exception.ArgumentNull("obj");
            }
 
            return (ISelectionStop)obj.GetValue(SelectionStopProperty);
        }
 
        // <summary>
        // Sets PropertySelection.SelectionStop property on the specified DependencyObject
        // </summary>
        // <param name="obj">DependencyObject to modify</param>
        // <param name="value">New value of SelectionStop</param>
        internal static void SetSelectionStop(DependencyObject obj, ISelectionStop value)
        {
            if (obj == null)
            {
                throw FxTrace.Exception.ArgumentNull("obj");
            }
 
            obj.SetValue(SelectionStopProperty, value);
        }
 
        // <summary>
        // Clears PropertySelection.SelectionStop property from the specified DependencyObject
        // </summary>
        // <param name="obj">DependencyObject to clear</param>
        internal static void ClearSelectionStop(DependencyObject obj)
        {
            if (obj == null)
            {
                throw FxTrace.Exception.ArgumentNull("obj");
            }
 
            obj.ClearValue(SelectionStopProperty);
        }
 
 
        // IsSelectionStopDoubleClickTarget Attached DP
 
        // <summary>
        // Gets PropertySelection.IsSelectionStopDoubleClickTarget property from the specified DependencyObject
        // </summary>
        // <param name="obj">DependencyObject to examine</param>
        // <returns>Value of the IsSelectionStopDoubleClickTarget property.</returns>
        internal static bool GetIsSelectionStopDoubleClickTarget(DependencyObject obj)
        {
            if (obj == null)
            {
                throw FxTrace.Exception.ArgumentNull("obj");
            }
 
            return (bool)obj.GetValue(IsSelectionStopDoubleClickTargetProperty);
        }
 
        // <summary>
        // Sets PropertySelection.IsSelectionStopDoubleClickTarget property on the specified DependencyObject
        // </summary>
        // <param name="obj">DependencyObject to modify</param>
        // <param name="value">New value of IsSelectionStopDoubleClickTarget</param>
        internal static void SetIsSelectionStopDoubleClickTarget(DependencyObject obj, bool value)
        {
            if (obj == null)
            {
                throw FxTrace.Exception.ArgumentNull("obj");
            }
 
            obj.SetValue(IsSelectionStopDoubleClickTargetProperty, value);
        }
 
        // <summary>
        // Clears PropertySelection.IsSelectionStopDoubleClickTarget property from the specified DependencyObject
        // </summary>
        // <param name="obj">DependencyObject to modify</param>
        internal static void ClearIsSelectionStopDoubleClickTarget(DependencyObject obj)
        {
            if (obj == null)
            {
                throw FxTrace.Exception.ArgumentNull("obj");
            }
 
            obj.ClearValue(IsSelectionStopDoubleClickTargetProperty);
        }
 
        // Called when some object gets specified as the SelectionStop double-click target:
        //
        //      * Hook into the MouseDown event so that we can detect double-clicks and automatically
        //        expand or collapse the corresponding SelectionStop, if possible
        //
        private static void OnIsSelectionStopDoubleClickTargetChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            FrameworkElement target = sender as FrameworkElement;
            if (target == null)
            {
                return;
            }
 
            if (bool.Equals(e.OldValue, false) && bool.Equals(e.NewValue, true))
            {
                AddDoubleClickHandler(target);
            }
            else if (bool.Equals(e.OldValue, true) && bool.Equals(e.NewValue, false))
            {
                RemoveDoubleClickHandler(target);
            }
        }
 
        // Called when some SelectionStop double-click target gets unloaded:
        //
        //      * Unhook from events so that we don't prevent garbage collection
        //
        private static void OnSelectionStopDoubleClickTargetUnloaded(object sender, RoutedEventArgs e)
        {
            FrameworkElement target = sender as FrameworkElement;
            Fx.Assert(target != null, "sender parameter should not be null");
 
            if (target == null)
            {
                return;
            }
 
            RemoveDoubleClickHandler(target);
        }
 
        // Called when the UI object representing a SelectionStop gets clicked:
        //
        //      * If this is a double-click and the SelectionStop can be expanded / collapsed,
        //        expand / collapse the SelectionStop
        //
        private static void OnSelectionStopDoubleClickTargetMouseDown(object sender, MouseButtonEventArgs e)
        {
            DependencyObject target = e.OriginalSource as DependencyObject;
            if (target == null)
            {
                return;
            }
 
            if (e.ClickCount > 1)
            {
 
                FrameworkElement parentSelectionStopVisual = PropertySelection.FindParentSelectionStop<FrameworkElement>(target);
                if (parentSelectionStopVisual != null)
                {
 
                    ISelectionStop parentSelectionStop = PropertySelection.GetSelectionStop(parentSelectionStopVisual);
                    if (parentSelectionStop != null && parentSelectionStop.IsExpandable)
                    {
                        parentSelectionStop.IsExpanded = !parentSelectionStop.IsExpanded;
                    }
                }
            }
        }
 
        private static void AddDoubleClickHandler(FrameworkElement target)
        {
            target.AddHandler(UIElement.MouseDownEvent, new MouseButtonEventHandler(OnSelectionStopDoubleClickTargetMouseDown), false);
            target.Unloaded += new RoutedEventHandler(OnSelectionStopDoubleClickTargetUnloaded);
        }
 
        private static void RemoveDoubleClickHandler(FrameworkElement target)
        {
            target.Unloaded -= new RoutedEventHandler(OnSelectionStopDoubleClickTargetUnloaded);
            target.RemoveHandler(UIElement.MouseDownEvent, new MouseButtonEventHandler(OnSelectionStopDoubleClickTargetMouseDown));
        }
 
 
        // IsSelectionScope Attached DP
 
        // <summary>
        // Gets PropertySelection.IsSelectionScope property from the specified DependencyObject
        // </summary>
        // <param name="obj">DependencyObject to examine</param>
        // <returns>Value of the IsSelectionScope property.</returns>
        internal static bool GetIsSelectionScope(DependencyObject obj)
        {
            if (obj == null)
            {
                throw FxTrace.Exception.ArgumentNull("obj");
            }
 
            return (bool)obj.GetValue(IsSelectionScopeProperty);
        }
 
        // <summary>
        // Sets PropertySelection.IsSelectionScope property on the specified DependencyObject
        // </summary>
        // <param name="obj">DependencyObject to modify</param>
        // <param name="value">New value of IsSelectionScope</param>
        internal static void SetIsSelectionScope(DependencyObject obj, bool value)
        {
            if (obj == null)
            {
                throw FxTrace.Exception.ArgumentNull("obj");
            }
 
            obj.SetValue(IsSelectionScopeProperty, value);
        }
 
        // KeyDownTarget Attached DP
 
        // <summary>
        // Gets PropertySelection.KeyDownTarget property from the specified DependencyObject
        // </summary>
        // <param name="obj">DependencyObject to examine</param>
        // <returns>Value of the KeyDownTarget property.</returns>
        internal static FrameworkElement GetKeyDownTarget(DependencyObject obj)
        {
            if (obj == null)
            {
                throw FxTrace.Exception.ArgumentNull("obj");
            }
 
            return (FrameworkElement)obj.GetValue(KeyDownTargetProperty);
        }
 
        // <summary>
        // Sets PropertySelection.KeyDownTarget property on the specified DependencyObject
        // </summary>
        // <param name="obj">DependencyObject to modify</param>
        // <param name="value">New value of KeyDownTarget</param>
        internal static void SetKeyDownTarget(DependencyObject obj, FrameworkElement value)
        {
            if (obj == null)
            {
                throw FxTrace.Exception.ArgumentNull("obj");
            }
 
            obj.SetValue(KeyDownTargetProperty, value);
        }
 
        // Called when some FrameworkElement gets specified as the target for KeyDown RoutedEvents -
        // hook into / unhook from the KeyDown event of the source
        private static void OnKeyDownTargetChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            FrameworkElement target = sender as FrameworkElement;
            if (target == null)
            {
                return;
            }
 
            if (e.OldValue != null && e.NewValue == null)
            {
                RemoveKeyStrokeHandlers(target);
            }
            else if (e.NewValue != null && e.OldValue == null)
            {
                AddKeyStrokeHandlers(target);
            }
        }
 
        // Called when a KeyDownTarget gets unloaded -
        // unhook from events so that we don't prevent garbage collection
        private static void OnKeyDownTargetUnloaded(object sender, RoutedEventArgs e)
        {
            FrameworkElement target = sender as FrameworkElement;
            Fx.Assert(target != null, "sender parameter should not be null");
 
            if (target == null)
            {
                return;
            }
 
            RemoveKeyStrokeHandlers(target);
        }
 
        // Called when a KeyDownTarget is specified and a KeyDown event is detected on the source
        private static void OnKeyDownTargetKeyDown(object sender, KeyEventArgs e)
        {
 
            // Ignore handled events
            if (e.Handled)
            {
                return;
            }
 
            // Ignore navigation keys
            if (e.Key == Key.Left || e.Key == Key.Right || e.Key == Key.Up || e.Key == Key.Down ||
                e.Key == Key.Tab || e.Key == Key.Escape || e.Key == Key.Return || e.Key == Key.Enter ||
                e.Key == Key.PageUp || e.Key == Key.PageDown || e.Key == Key.Home || e.Key == Key.End || e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl)
            {
                return;
            }
 
            if (Keyboard.Modifiers == ModifierKeys.Control)
            {
                return;
            }
 
 
 
            DependencyObject keySender = sender as DependencyObject;
            Fx.Assert(keySender != null, "keySender should not be null");
            if (keySender == null)
            {
                return;
            }
 
            FrameworkElement keyTarget = GetKeyDownTarget(keySender);
            Fx.Assert(keyTarget != null, "keyTarget should not be null");
            if (keyTarget == null)
            {
                return;
            }
 
            // Find a focusable element on the target, set focus to it, and send the keys over
            FrameworkElement focusable = VisualTreeUtils.FindFocusableElement<FrameworkElement>(keyTarget);
            if (focusable != null && focusable == Keyboard.Focus(focusable))
            {
                focusable.RaiseEvent(e);
            }
        }
 
        private static void AddKeyStrokeHandlers(FrameworkElement target)
        {
            target.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDownTargetKeyDown), false);
            target.Unloaded += new RoutedEventHandler(OnKeyDownTargetUnloaded);
        }
 
        private static void RemoveKeyStrokeHandlers(FrameworkElement target)
        {
            target.Unloaded -= new RoutedEventHandler(OnKeyDownTargetUnloaded);
            target.RemoveHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDownTargetKeyDown));
        }
 
 
        // <summary>
        // Returns the closest parent (or the element itself) marked as a SelectionStop.
        // </summary>
        // <typeparam name="T">Type of element to look for</typeparam>
        // <param name="element">Element to examine</param>
        // <returns>The closest parent (or the element itself) marked as a SelectionStop;
        // null if not found.</returns>
        internal static T FindParentSelectionStop<T>(DependencyObject element) where T : DependencyObject
        {
            if (element == null)
            {
                return null;
            }
 
            do
            {
                // IsEligibleSelectionStop already checks for visibility, so we don't need to
                // to do a specific check somewhere else in this loop
                if (IsEligibleSelectionStop<T>(element))
                {
                    return (T)element;
                }
 
                element = VisualTreeHelper.GetParent(element);
            } while (element != null);
 
            return null;
        }
 
        // <summary>
        // Returns the closest neighbor in the given direction marked as a SelectionStop.
        // </summary>
        // <typeparam name="T">Type of element to look for</typeparam>
        // <param name="element">Element to examine</param>
        // <param name="direction">Direction to search in</param>
        // <returns>The closest neighboring element in the given direction marked as a IsSelectionStop,
        // if found, null otherwise.</returns>
        internal static T FindNeighborSelectionStop<T>(DependencyObject element, SearchDirection direction) where T : DependencyObject
        {
 
            if (element == null)
            {
                throw FxTrace.Exception.ArgumentNull("element");
            }
 
            T neighbor;
            int maxSearchDepth = MaxSearchDepth;
 
            // If we are looking for the NEXT element and we can dig deeper, start by digging deeper
            // before trying to look for any siblings.
            //
            if (direction == SearchDirection.Next && IsExpanded(element))
            {
                neighbor = FindChildSelectionStop<T>(element, 0, VisualTreeHelper.GetChildrenCount(element) - 1, direction, maxSearchDepth, MatchDirection.Down);
 
                if (neighbor != null)
                {
                    return neighbor;
                }
            }
 
            int childIndex, childrenCount, childDepth;
            bool isParentSelectionStop, isParentSelectionScope = false;
            DependencyObject parent = element;
 
            while (true)
            {
                while (true)
                {
                    // If we reached the selection scope, don't try to go beyond it
                    if (isParentSelectionScope)
                    {
                        return null;
                    }
 
                    parent = GetEligibleParent(parent, out childIndex, out childrenCount, out childDepth, out isParentSelectionStop, out isParentSelectionScope);
                    maxSearchDepth += childDepth;
 
                    if (parent == null)
                    {
                        return null;
                    }
 
                    if (direction == SearchDirection.Next && (childIndex + 1) >= childrenCount)
                    {
                        continue;
                    }
 
                    if (direction == SearchDirection.Previous && isParentSelectionStop == false && (childIndex < 1))
                    {
                        continue;
                    }
 
                    break;
                }
 
                // If we get here, that means we found a SelectionStop on which we need to look for children that are
                // SelectionStops themselves.  The first such child found should be returned.  Otherwise, if no such child
                // is found, we potentially look at the node itself and return it OR we repeat the process and keep looking
                // for a better parent.
 
                int leftIndex, rightIndex;
                MatchDirection matchDirection;
 
                if (direction == SearchDirection.Previous)
                {
                    leftIndex = 0;
                    rightIndex = childIndex - 1;
                    matchDirection = MatchDirection.Up;
                }
                else
                {
                    leftIndex = childIndex + 1;
                    rightIndex = childrenCount - 1;
                    matchDirection = MatchDirection.Down;
                }
 
                neighbor = FindChildSelectionStop<T>(parent, leftIndex, rightIndex, direction, maxSearchDepth, matchDirection);
                if (neighbor != null)
                {
                    return neighbor;
                }
 
                if (direction == SearchDirection.Previous &&
                    IsEligibleSelectionStop<T>(parent))
                {
                    return (T)parent;
                }
            }
        }
 
        // Helper method used from GetNeighborSelectionStop()
        // Returns a parent DependencyObject of the specified element that is
        // 
        //  * Visible AND
        //  * ( Marked with a SelectionStop OR
        //  *   Marked with IsSelectionScope = true OR
        //  *   Has more than one child )
        //
        private static DependencyObject GetEligibleParent(DependencyObject element, out int childIndex, out int childrenCount, out int childDepth, out bool isSelectionStop, out bool isSelectionScope)
        {
            childDepth = 0;
            isSelectionStop = false;
            isSelectionScope = false;
            bool isVisible;
 
            do
            {
                element = VisualTreeUtils.GetIndexedVisualParent(element, out childrenCount, out childIndex);
                isSelectionStop = element == null ? false : (GetSelectionStop(element) != null);
                isSelectionScope = element == null ? false : GetIsSelectionScope(element);
                isVisible = VisualTreeUtils.IsVisible(element as UIElement);
 
                childDepth++;
            }
            while (
                element != null &&
                (isVisible == false ||
                (isSelectionStop == false &&
                isSelectionScope == false &&
                childrenCount < 2)));
 
            return element;
        }
 
        // Helper method that performs a recursive, depth-first search of children starting at the specified parent,
        // looking for any children that conform to the specified Type and are marked with a SelectionStop
        //
        private static T FindChildSelectionStop<T>(DependencyObject parent, int leftIndex, int rightIndex, SearchDirection iterationDirection, int maxDepth, MatchDirection matchDirection)
            where T : DependencyObject
        {
 
            if (parent == null || maxDepth <= 0)
            {
                return null;
            }
 
            int step = iterationDirection == SearchDirection.Next ? 1 : -1;
            int index = iterationDirection == SearchDirection.Next ? leftIndex : rightIndex;
 
            for (; index >= leftIndex && index <= rightIndex; index = index + step)
            {
 
                DependencyObject child = VisualTreeHelper.GetChild(parent, index);
 
                // If MatchDirection is set to Down, do an eligibility match BEFORE we dive down into
                // more children.
                //
                if (matchDirection == MatchDirection.Down && IsEligibleSelectionStop<T>(child))
                {
                    return (T)child;
                }
 
                // If this child is not an eligible SelectionStop because it is not visible,
                // there is no point digging down to get to more children.
                //
                if (!VisualTreeUtils.IsVisible(child as UIElement))
                {
                    continue;
                }
 
                int grandChildrenCount = VisualTreeHelper.GetChildrenCount(child);
                if (grandChildrenCount > 0 && IsExpanded(child))
                {
                    T element = FindChildSelectionStop<T>(child, 0, grandChildrenCount - 1, iterationDirection, maxDepth - 1, matchDirection);
 
                    if (element != null)
                    {
                        return element;
                    }
                }
 
                // If MatchDirection is set to Up, do an eligibility match AFTER we tried diving into
                // more children and failed to find something we could return.
                //
                if (matchDirection == MatchDirection.Up && IsEligibleSelectionStop<T>(child))
                {
                    return (T)child;
                }
            }
 
            return null;
        }
 
        // Helper method that returns false if the given element is a collapsed SelectionStop,
        // true otherwise.
        //
        private static bool IsExpanded(DependencyObject element)
        {
            ISelectionStop selectionStop = PropertySelection.GetSelectionStop(element);
            return selectionStop == null || selectionStop.IsExpanded;
        }
 
        // Helper method that return true if the given element is marked with a SelectionStop,
        // if it derives from the specified Type, and if it is Visible (assuming it derives from UIElement)
        //
        private static bool IsEligibleSelectionStop<T>(DependencyObject element) where T : DependencyObject
        {
            return (GetSelectionStop(element) != null) && typeof(T).IsAssignableFrom(element.GetType()) && VisualTreeUtils.IsVisible(element as UIElement);
        }
 
        // <summary>
        // Private enum we use to specify whether FindSelectionStopChild() should return any matches
        // as it drills down into the visual tree (Down) or whether it should wait on looking at
        // matches until it's bubbling back up again (Up).
        // </summary>
        private enum MatchDirection
        {
            Down,
            Up
        }
 
        // IsSelected ReadOnly, Attached DP
    }
}