File: System.Activities.Presentation\System\Activities\Presentation\View\Selection.cs
Project: ndp\cdf\src\NetFx40\Tools\System.Activities.Presentation.csproj (System.Activities.Presentation)
namespace System.Activities.Presentation.View {
 
    using System.Runtime;
    using System.Diagnostics.CodeAnalysis;
    using System.Activities.Presentation;
    using System.Activities.Presentation.Model;
 
    using System.Activities.Presentation.Internal.Properties;
 
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.ComponentModel;
 
    /// <summary>
    /// The Selection class defines a selection of objects.  Selections
    /// consist of zero or more objects.  The first object in a selection
    /// is defined as the "primary" selection, which is used when
    /// one object in a group must be used as a key.
    /// </summary>
    [SuppressMessage(FxCop.Category.Naming, "CA1724:TypeNamesShouldNotMatchNamespaces",
        Justification = "Code imported from Cider; keeping changes to a minimum as it impacts xaml files as well")]
    public class Selection : ContextItem
    {
 
        private ICollection<ModelItem> _selectedObjects;
 
        /// <summary>
        /// Creates an empty Selection object.
        /// </summary>
        public Selection() {
            _selectedObjects = new ModelItem[0];
        }
 
        /// <summary>
        /// Creates a collection object comprising the given
        /// selected objects.  The first object in the enumeration
        /// is considered the "primary" selection.
        /// </summary>
        /// <param name="selectedObjects">An enumeration of objects that should be selected.</param>
        /// <exception cref="ArgumentNullException">If selectedObjects is null.</exception>
        public Selection(IEnumerable<ModelItem> selectedObjects) {
            if (selectedObjects == null) {
                throw FxTrace.Exception.ArgumentNull("selectedObjects");
            }
 
            List<ModelItem> selection = new List<ModelItem>();
            selection.AddRange(selectedObjects);
            _selectedObjects = selection;
        }
 
        /// <summary>
        /// Creates a collection object comprising the given
        /// selected objects.  The first object in the enumeration
        /// is considered the "primary" selection.
        /// </summary>
        /// <param name="selectedObjects">An enumeration of objects that should be selected.</param>
        /// <param name="match">If provided, only those objects in selectedObjects that match the predicate will be added to the selection.</param>
        /// <exception cref="ArgumentNullException">If selectedObjects or match is null.</exception>
        public Selection(IEnumerable<ModelItem> selectedObjects, Predicate<ModelItem> match) {
            if (selectedObjects == null) throw FxTrace.Exception.ArgumentNull("selectedObjects");
            if (match == null) throw FxTrace.Exception.ArgumentNull("match");
 
            List<ModelItem> selection = new List<ModelItem>();
            foreach (ModelItem o in selectedObjects) {
                if (match(o)) {
                    selection.Add(o);
                }
            }
 
            _selectedObjects = selection;
        }
 
        /// <summary>
        /// Creates a collection object comprising the given
        /// selected objects.  The first object in the enumeration
        /// is considered the "primary" selection.
        /// </summary>
        /// <param name="selectedObjects">An enumeration of objects that should be selected.</param>
        /// <exception cref="ArgumentNullException">If selectedObjects is null.</exception>
        public Selection(IEnumerable selectedObjects) {
            if (selectedObjects == null) throw FxTrace.Exception.ArgumentNull("selectedObjects");
 
            List<ModelItem> selection = new List<ModelItem>();
            foreach (object o in selectedObjects) {
                ModelItem item = o as ModelItem;
                if (item != null) {
                    selection.Add(item);
                }
            }
 
            _selectedObjects = selection;
        }
 
        /// <summary>
        /// Creates a collection object comprising the given
        /// selected objects.  The first object in the enumeration
        /// is considered the "primary" selection.
        /// </summary>
        /// <param name="selectedObjects">An enumeration of objects that should be selected.</param>
        /// <param name="match">If provided, only those objects in selectedObjects that match the predicate will be added to the selection.</param>
        /// <exception cref="ArgumentNullException">If selectedObjects is null.</exception>
        public Selection(IEnumerable selectedObjects, Predicate<ModelItem> match) {
            if (selectedObjects == null) throw FxTrace.Exception.ArgumentNull("selectedObjects");
            if (match == null) throw FxTrace.Exception.ArgumentNull("match");
 
            List<ModelItem> selection = new List<ModelItem>();
            foreach (object o in selectedObjects) {
                ModelItem item = o as ModelItem;
                if (item != null && match(item)) {
                    selection.Add(item);
                }
            }
 
            _selectedObjects = selection;
        }
 
        /// <summary>
        /// Creates a collection object comprising the given
        /// objects.  The first object is considered the "primary"
        /// selection.
        /// </summary>
        /// <param name="selectedObjects">A parameter array of objects that should be selected.</param>
        /// <exception cref="ArgumentNullException">If selectedObjects is null.</exception>
        public Selection(params ModelItem[] selectedObjects)
            : this((IEnumerable<ModelItem>)selectedObjects) {
        }
 
        /// <summary>
        /// The primary selection.  Some functions require a "key"
        /// element.  For example, an "align lefts" command needs
        /// to know which element's "left" to align to.
        /// </summary>
        public ModelItem PrimarySelection {
            get {
                foreach (ModelItem obj in _selectedObjects) {
                    return obj;
                }
 
                return null;
            }
        }
 
        /// <summary>
        /// The enumeration of selected objects.
        /// </summary>
        public IEnumerable<ModelItem> SelectedObjects {
            get {
                return _selectedObjects;
            }
        }
 
        /// <summary>
        /// The number of objects that are currently selected into
        /// this selection.
        /// </summary>
        public int SelectionCount {
            get { return _selectedObjects.Count; }
        }
 
        /// <summary>
        /// Override of ContextItem's ItemType
        /// property.  The ItemType of Selection is
        /// always "typeof(Selection)".
        /// </summary>
        public sealed override Type ItemType {
            get {
                return typeof(Selection);
            }
        }
        
        
        /// <summary>
        /// Selection helper method.  This takes the existing selection in the
        /// context and selects an item into it.  If the item is already in the
        /// selection the selection is preserved and the item is promoted
        /// to the primary selection.
        /// </summary>
        /// <param name="context">The editing context to apply this selection change to.</param>
        /// <param name="itemToSelect">The item to select.</param>
        /// <returns>A Selection object that contains the new selection.</returns>
        /// <exception cref="ArgumentNullException">If context or itemToSelect is null.</exception>
        public static Selection Select(EditingContext context, ModelItem itemToSelect) {
 
            if (context == null) throw FxTrace.Exception.ArgumentNull("context");
            if (itemToSelect == null) throw FxTrace.Exception.ArgumentNull("itemToSelect");
 
            Selection existing = context.Items.GetValue<Selection>();
 
            // short cut if we're already in the right state.
            if (existing.PrimarySelection == itemToSelect) {
                return existing;
            }
 
            Selection selection = null;
 
            foreach (ModelItem obj in existing.SelectedObjects) {
                if (obj == itemToSelect) {
                    List<ModelItem> list = new List<ModelItem>(existing.SelectedObjects);
                    list.Remove(itemToSelect);
                    list.Insert(0, itemToSelect);
                    selection = new Selection(list);
                }
            }
 
            if (selection == null) {
                selection = new Selection(itemToSelect);
            }
            
            context.Items.SetValue(selection);
            return selection;
        }
 
        /// <summary>
        /// Selection helper method.  This sets itemToSelect into the selection.
        /// Any existing items are deselected.
        /// </summary>
        /// <param name="context">The editing context to apply this selection change to.</param>
        /// <param name="itemToSelect">The item to select.</param>
        /// <returns>A Selection object that contains the new selection.</returns>
        /// <exception cref="ArgumentNullException">If context or itemToSelect is null.</exception>
        public static Selection SelectOnly(EditingContext context, ModelItem itemToSelect) {
 
            if (context == null) throw FxTrace.Exception.ArgumentNull("context");
            if (itemToSelect == null) throw FxTrace.Exception.ArgumentNull("itemToSelect");
 
            // Check to see if only this object is selected.  If so, bail.
            Selection existing = context.Items.GetValue<Selection>();
            if (existing.PrimarySelection == itemToSelect) {
                IEnumerator<ModelItem> en = existing.SelectedObjects.GetEnumerator();
                en.MoveNext();
                if (!en.MoveNext()) {
                    return existing;
                }
            }
 
            DesignerPerfEventProvider designerPerfEventProvider = context.Services.GetService<DesignerPerfEventProvider>();
            if (designerPerfEventProvider != null)
            {
                designerPerfEventProvider.SelectionChangedStart();
            }
 
            Selection selection = new Selection(itemToSelect);
            context.Items.SetValue(selection);
            return selection;
        }
 
        /// <summary>
        /// Helper method that subscribes to selection change events.
        /// </summary>
        /// <param name="context">The editing context to listen to.</param>
        /// <param name="handler">The handler to be invoked when the selection changes.</param>
        public static void Subscribe(EditingContext context, SubscribeContextCallback<Selection> handler) {
            if (context == null) throw FxTrace.Exception.ArgumentNull("context");
            if (handler == null) throw FxTrace.Exception.ArgumentNull("handler");
            context.Items.Subscribe<Selection>(handler);
        }
 
        /// <summary>
        /// Selection helper method.  This takes the existing selection in the
        /// context and creates a new selection that contains the toggled
        /// state of the item.  If the item is to be
        /// added to the selection, it is added as the primary selection.
        /// </summary>
        /// <param name="context">The editing context to apply this selection change to.</param>
        /// <param name="itemToToggle">The item to toggle selection for.</param>
        /// <returns>A Selection object that contains the new selection.</returns>
        /// <exception cref="ArgumentNullException">If context or itemToToggle is null.</exception>
        public static Selection Toggle(EditingContext context, ModelItem itemToToggle) {
            
            if (context == null) throw FxTrace.Exception.ArgumentNull("context");
            if (itemToToggle == null) throw FxTrace.Exception.ArgumentNull("itemToToggle");
 
            Selection existing = context.Items.GetValue<Selection>();
 
            // Is the item already in the selection?  If so, remove it.
            // If not, add it to the beginning.
 
            List<ModelItem> list = new List<ModelItem>(existing.SelectedObjects);
            if (list.Contains(itemToToggle)) {
                list.Remove(itemToToggle);
            }
            else {
                list.Insert(0, itemToToggle);
            }
 
            Selection selection = new Selection(list);
            context.Items.SetValue(selection);
            return selection;
        }
 
        /// <summary>
        /// Selection helper method.  This takes the existing selection in the
        /// context and creates a new selection that contains the original
        /// selection and the itemToAdd.  If itemToAdd is already in the 
        /// original selection it is promoted to the primary selection.
        /// </summary>
        /// <param name="context">The editing context to apply this selection change to.</param>
        /// <param name="itemToAdd">The item to add to the selection.</param>
        /// <returns>A Selection object that contains the new selection.</returns>
        /// <exception cref="ArgumentNullException">If context or itemToAdd is null.</exception>
        public static Selection Union(EditingContext context, ModelItem itemToAdd) {
 
            if (context == null) throw FxTrace.Exception.ArgumentNull("context");
            if (itemToAdd == null) throw FxTrace.Exception.ArgumentNull("itemToAdd");
 
            Selection existing = context.Items.GetValue<Selection>();
 
            // short cut if we're already in the right state.
            if (existing.PrimarySelection == itemToAdd) {
                return existing;
            }
 
            // Is the item already in the selection?  If not, add it.
            List<ModelItem> list = new List<ModelItem>(existing.SelectedObjects);
            if (list.Contains(itemToAdd)) {
                list.Remove(itemToAdd);
            }
 
            list.Insert(0, itemToAdd);
            Selection selection = new Selection(list);
            context.Items.SetValue(selection);
            return selection;
        }
 
        internal static bool MultipleObjectsSelected(EditingContext context)
        {
            Selection selection = context.Items.GetValue<Selection>();
            if (selection != null && selection.SelectionCount > 1)
            {
                return true;
            }
            return false;
        }
 
        internal static bool IsSelection(ModelItem item)
        {
            PropertyDescriptor descriptor = TypeDescriptor.GetProperties(item)["IsSelection"];
            if (descriptor != null)
            {
                return (bool)descriptor.GetValue(item);
            }
            return false;
        }
 
        internal static bool IsPrimarySelection(ModelItem item)
        {
            PropertyDescriptor descriptor = TypeDescriptor.GetProperties(item)["IsPrimarySelection"];
            if (descriptor != null)
            {
                return (bool)descriptor.GetValue(item);
            }
            return false;
        }
 
        /// <summary>
        /// Helper method that removes a previously added selection change event.
        /// </summary>
        /// <param name="context">The editing context to listen to.</param>
        /// <param name="handler">The handler to be invoked when the selection changes.</param>
        public static void Unsubscribe(EditingContext context, SubscribeContextCallback<Selection> handler) {
            if (context == null) throw FxTrace.Exception.ArgumentNull("context");
            if (handler == null) throw FxTrace.Exception.ArgumentNull("handler");
            context.Items.Unsubscribe<Selection>(handler);
        }
    }
}