File: cdf\src\NetFx40\Tools\System.Activities.Presentation\System\Activities\Presentation\WorkflowItemsPresenter.cs
Project: ndp\System.Data.csproj (System.Data)
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//----------------------------------------------------------------
 
#pragma warning disable 618
 
namespace System.Activities.Presentation
{
    using System.Activities.Presentation.Internal.PropertyEditing;
    using System.Activities.Presentation.Model;
    using System.Activities.Presentation.Services;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.Linq;
    using System.Runtime;
    using System.Windows;
    using System.Windows.Automation.Peers;
    using System.Windows.Controls;
    using System.Windows.Input;
    using System.Windows.Markup;
    using System.Windows.Media;
    using System.Windows.Threading;
    using System.Activities.Presentation.View;
    using System.Windows.Shapes;
 
    // This is similar to the WorkflowItemPresenter , but its an edit box for collections. It supports drag drop, and delete.
    // it auto refreshes the collection on collection changed events.
    public class WorkflowItemsPresenter : ContentControl, IMultipleDragEnabledCompositeView
    {
 
        public static readonly DependencyProperty HintTextProperty =
            DependencyProperty.Register("HintText", typeof(string), typeof(WorkflowItemsPresenter), new UIPropertyMetadata(String.Empty, new PropertyChangedCallback(WorkflowItemsPresenter.OnHintTextChanged)));
 
        public static readonly DependencyProperty ItemsProperty =
            DependencyProperty.Register("Items", typeof(ModelItemCollection), typeof(WorkflowItemsPresenter), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(WorkflowItemsPresenter.OnItemsChanged)));
 
        public static readonly DependencyProperty SpacerTemplateProperty =
            DependencyProperty.Register("SpacerTemplate", typeof(DataTemplate), typeof(WorkflowItemsPresenter), new UIPropertyMetadata(null));
 
        public static readonly DependencyProperty HeaderTemplateProperty =
            DependencyProperty.Register("HeaderTemplate", typeof(DataTemplate), typeof(WorkflowItemsPresenter), new UIPropertyMetadata(null));
 
        public static readonly DependencyProperty FooterTemplateProperty =
            DependencyProperty.Register("FooterTemplate", typeof(DataTemplate), typeof(WorkflowItemsPresenter), new UIPropertyMetadata(null));
 
        public static readonly DependencyProperty ItemsPanelProperty =
            DependencyProperty.Register("ItemsPanel", typeof(ItemsPanelTemplate), typeof(WorkflowItemsPresenter), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(WorkflowItemsPresenter.OnItemsPanelChanged)));
 
        public static readonly DependencyProperty IndexProperty =
            DependencyProperty.RegisterAttached("Index", typeof(int), typeof(WorkflowItemsPresenter), new UIPropertyMetadata(addAtEndMarker));
 
        public static readonly DependencyProperty AllowedItemTypeProperty =
            DependencyProperty.Register("AllowedItemType", typeof(Type), typeof(WorkflowItemsPresenter), new UIPropertyMetadata(typeof(object)));
 
        public static readonly DependencyProperty IsDefaultContainerProperty =
            DependencyProperty.Register("IsDefaultContainer", typeof(bool), typeof(WorkflowItemsPresenter), new UIPropertyMetadata(false));
 
        public static readonly DependencyProperty DroppingTypeResolvingOptionsProperty =
            DependencyProperty.Register("DroppingTypeResolvingOptions", typeof(TypeResolvingOptions), typeof(WorkflowItemsPresenter));
 
 
        const int addAtEndMarker = -2;
 
        int selectedSpacerIndex;
 
 
        ItemsControl panel;
        Grid hintTextGrid;
        EditingContext context = null;
        bool isRegisteredWithParent = false;
        bool populateOnLoad = false;
        bool handleSpacerGotKeyboardFocus = false;
        Grid outerGrid;
 
        public WorkflowItemsPresenter()
        {
            panel = new ItemsControl();
            panel.Focusable = false;
            hintTextGrid = new Grid();
            hintTextGrid.Focusable = false;
            hintTextGrid.Background = Brushes.Transparent;
            hintTextGrid.DataContext = this;
            hintTextGrid.SetBinding(Grid.MinHeightProperty, "MinHeight");
            hintTextGrid.SetBinding(Grid.MinWidthProperty, "MinWidth");
            TextBlock text = new TextBlock();
            text.Focusable = false;
            text.SetBinding(TextBlock.TextProperty, "HintText");
            text.HorizontalAlignment = HorizontalAlignment.Center;
            text.VerticalAlignment = VerticalAlignment.Center;
            text.DataContext = this;
            text.Foreground = new SolidColorBrush(SystemColors.GrayTextColor);
            text.FontStyle = FontStyles.Italic;
            ((IAddChild)hintTextGrid).AddChild(text);
 
            this.outerGrid = new Grid()
            {
                RowDefinitions = { new RowDefinition(), new RowDefinition() },
                ColumnDefinitions = { new ColumnDefinition() }
            };
            Grid.SetRow(this.panel, 0);
            Grid.SetColumn(this.panel, 0);
            Grid.SetRow(this.hintTextGrid, 1);
            Grid.SetColumn(this.hintTextGrid, 0);
            this.outerGrid.Children.Add(panel);
            this.outerGrid.Children.Add(hintTextGrid);
        }
 
 
        public Type AllowedItemType
        {
            get { return (Type)GetValue(AllowedItemTypeProperty); }
            set { SetValue(AllowedItemTypeProperty, value); }
        }
 
        public string HintText
        {
            get { return (string)GetValue(HintTextProperty); }
            set { SetValue(HintTextProperty, value); }
        }
 
        [Fx.Tag.KnownXamlExternal]
        public DataTemplate SpacerTemplate
        {
            get { return (DataTemplate)GetValue(SpacerTemplateProperty); }
            set { SetValue(SpacerTemplateProperty, value); }
        }
 
        [Fx.Tag.KnownXamlExternal]
        public DataTemplate HeaderTemplate
        {
            get { return (DataTemplate)GetValue(HeaderTemplateProperty); }
            set { SetValue(HeaderTemplateProperty, value); }
        }
 
        [Fx.Tag.KnownXamlExternal]
        public DataTemplate FooterTemplate
        {
            get { return (DataTemplate)GetValue(FooterTemplateProperty); }
            set { SetValue(FooterTemplateProperty, value); }
        }
 
        [Fx.Tag.KnownXamlExternal]
        public ItemsPanelTemplate ItemsPanel
        {
            get { return (ItemsPanelTemplate)GetValue(ItemsPanelProperty); }
            set { SetValue(ItemsPanelProperty, value); }
        }
 
        [SuppressMessage(FxCop.Category.Usage, FxCop.Rule.CollectionPropertiesShouldBeReadOnly,
            Justification = "Setter is provided to enable setting this property in code.")]
        [Fx.Tag.KnownXamlExternal]
        public ModelItemCollection Items
        {
            get { return (ModelItemCollection)GetValue(ItemsProperty); }
            set { SetValue(ItemsProperty, value); }
        }
 
        EditingContext Context
        {
            get
            {
                if (context == null)
                {
                    IModelTreeItem modelTreeItem = this.Items as IModelTreeItem;
                    if (modelTreeItem != null)
                    {
                        this.context = modelTreeItem.ModelTreeManager.Context;
                    }
                }
                return context;
            }
        }
 
        public bool IsDefaultContainer
        {
            get { return (bool)GetValue(IsDefaultContainerProperty); }
            set { SetValue(IsDefaultContainerProperty, value); }
        }
 
        [Fx.Tag.KnownXamlExternal]
        public TypeResolvingOptions DroppingTypeResolvingOptions
        {
            get { return (TypeResolvingOptions)GetValue(DroppingTypeResolvingOptionsProperty); }
            set { SetValue(DroppingTypeResolvingOptionsProperty, value); }
        }
 
        static void OnHintTextChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
        {
            WorkflowItemsPresenter itemsPresenter = (WorkflowItemsPresenter)dependencyObject;
            itemsPresenter.UpdateHintTextVisibility(e.NewValue as string);
        }
 
        static void OnItemsChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
        {
            WorkflowItemsPresenter itemsPresenter = (WorkflowItemsPresenter)dependencyObject;
            itemsPresenter.OnItemsChanged((ModelItemCollection)e.OldValue, (ModelItemCollection)e.NewValue);
        }
 
        static void OnItemsPanelChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
        {
            WorkflowItemsPresenter itemsPresenter = (WorkflowItemsPresenter)dependencyObject;
            itemsPresenter.panel.ItemsPanel = (ItemsPanelTemplate)e.NewValue;
        }
 
        void OnItemsChanged(ModelItemCollection oldItemsCollection, ModelItemCollection newItemsCollection)
        {
            if (oldItemsCollection != null)
            {
                oldItemsCollection.CollectionChanged -= this.OnCollectionChanged;
            }
 
            if (newItemsCollection != null)
            {
                newItemsCollection.CollectionChanged += this.OnCollectionChanged;
            }
 
            if (!isRegisteredWithParent)
            {
                CutCopyPasteHelper.RegisterWithParentViewElement(this);
                isRegisteredWithParent = true;
            }
            populateOnLoad = false;
            PopulateContent();
        }
 
        void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            // if this.Items is null, and we are getting a collection changed that 
            // means this event some how happened before this can get the unloaded event
            // and unsubscribe from this event.
            if (this.Items == null)
            {
                return;
            }
            bool fullRepopulateNeeded = true;
 
            // when one item is dropped into this items presenter focus on the new view element for it.
            if (e.Action == NotifyCollectionChangedAction.Add
                && e.NewItems != null
                && e.NewItems.Count == 1)
            {
                // insert itemview and spacer
                fullRepopulateNeeded = false;
                int itemViewIndex = GetViewIndexForItem(e.NewStartingIndex);
                VirtualizedContainerService containerService = this.Context.Services.GetService<VirtualizedContainerService>();
                UIElement itemView = containerService.GetContainer((ModelItem)e.NewItems[0], this);
                this.panel.Items.Insert(itemViewIndex, itemView as UIElement);
                // index 2 + i*2 + 1 is spacer i+1
                FrameworkElement spacer = CreateSpacer();
                this.panel.Items.Insert(itemViewIndex + 1, spacer);
 
 
                ModelItem insertedItem = (ModelItem)e.NewItems[0];
                this.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, (Action)(() =>
                {
                    UIElement view = (UIElement)insertedItem.View;
                    if (view != null)
                    {
                        Keyboard.Focus(view);
                    }
                }));
            }
 
            else if (e.Action == NotifyCollectionChangedAction.Remove)
            {
                if (e.OldItems != null && e.OldItems.Count == 1)
                {
                    fullRepopulateNeeded = false;
                    int itemViewIndex = GetViewIndexForItem(e.OldStartingIndex);
                    this.panel.Items.RemoveAt(itemViewIndex);
                    //remove spacer also
                    this.panel.Items.RemoveAt(itemViewIndex);
                }
 
                if (this.Items.Count == 0)
                {
                    fullRepopulateNeeded = true;
                }
 
                // deselect removed items
                if (this.Context != null)
                {
                    IList<ModelItem> selectedItems = this.Context.Items.GetValue<Selection>().SelectedObjects.ToList();
                    foreach (ModelItem selectedAndRemovedItem in selectedItems.Intersect(e.OldItems.Cast<ModelItem>()))
                    {
                        Selection.Toggle(this.Context, selectedAndRemovedItem);
                    }
                }
            }
            if (this.Items.Count > 0)
            {
                this.hintTextGrid.Visibility = Visibility.Collapsed;
            }
            else
            {
                this.hintTextGrid.Visibility = Visibility.Visible;
            }
 
            if (fullRepopulateNeeded)
            {
                PopulateContent();
            }
        }
 
        protected override void OnInitialized(EventArgs e)
        {
            base.OnInitialized(e);
            this.AllowDrop = true;
            this.Content = outerGrid;
            if (this.ItemsPanel != null)
            {
                this.panel.ItemsPanel = this.ItemsPanel;
            }
 
            ICompositeViewEvents containerEvents = null;
            bool isDefault = false;
 
            this.Loaded += (s, eventArgs) =>
            {
                isDefault = this.IsDefaultContainer;
                selectedSpacerIndex = addAtEndMarker;
                DependencyObject parent = VisualTreeHelper.GetParent(this);
                while (null != parent && !typeof(ICompositeViewEvents).IsAssignableFrom(parent.GetType()))
                {
                    parent = VisualTreeHelper.GetParent(parent);
                }
                containerEvents = parent as ICompositeViewEvents;
                if (null != containerEvents)
                {
                    if (isDefault)
                    {
                        containerEvents.RegisterDefaultCompositeView(this);
                    }
                    else
                    {
                        containerEvents.RegisterCompositeView(this);
                    }
                }
                if (this.Items != null)
                {
                    //UnRegistering because of 137896: Inside tab control multiple Loaded events happen without an Unloaded event.
                    this.Items.CollectionChanged -= new NotifyCollectionChangedEventHandler(OnCollectionChanged);
                    this.Items.CollectionChanged += new NotifyCollectionChangedEventHandler(OnCollectionChanged);
                }
                if (populateOnLoad)
                {
                    this.PopulateContent();
                }
            };
 
            this.Unloaded += (s, eventArgs) =>
            {
                if (null != containerEvents)
                {
                    if (isDefault)
                    {
                        containerEvents.UnregisterDefaultCompositeView(this);
                    }
                    else
                    {
                        containerEvents.UnregisterCompositeView(this);
 
                    }
                }
                if (this.Items != null)
                {
                    this.Items.CollectionChanged -= new NotifyCollectionChangedEventHandler(OnCollectionChanged);
                }
                populateOnLoad = true;
            };
        }
 
        void PopulateContent()
        {
            this.panel.Items.Clear();
 
            if (this.Items != null)
            {
 
                // index 0 is header.
                ContentControl header = new ContentControl();
                header.Focusable = false;
                header.ContentTemplate = this.HeaderTemplate;
                header.SetValue(IndexProperty, 0);
                header.Drop += new DragEventHandler(OnSpacerDrop);
                this.panel.Items.Add(header);
 
                // index 1 is first spacer
                FrameworkElement startSpacer = CreateSpacer();
                this.panel.Items.Add(startSpacer);
 
                foreach (ModelItem item in this.Items)
                {
                    // index 2 + i*2 is itemView i
                    VirtualizedContainerService containerService = this.Context.Services.GetService<VirtualizedContainerService>();
                    UIElement itemView = containerService.GetContainer(item, this);
                    this.panel.Items.Add(itemView as UIElement);
                    // index 2 + i*2 + 1 is spacer i+1
                    FrameworkElement spacer = CreateSpacer();
                    this.panel.Items.Add(spacer);
                }
                // index 2 + count*2 is footer 
                ContentControl footer = new ContentControl();
                footer.ContentTemplate = this.FooterTemplate;
                footer.Focusable = true;
                footer.IsHitTestVisible = true;
                footer.IsTabStop = true;
                footer.SetValue(IndexProperty, addAtEndMarker);
                footer.Drop += new DragEventHandler(OnSpacerDrop);
                footer.LostFocus += new RoutedEventHandler(OnSpacerLostFocus);
                footer.GotFocus += new RoutedEventHandler(OnSpacerGotFocus);
                this.panel.Items.Add(footer);
                footer.Focusable = false;
            }
            UpdateHintTextVisibility(HintText);
        }
 
        int GetViewIndexForItem(int itemIndex)
        {
            return 2 + itemIndex * 2;
        }
 
        int GetViewIndexForSpacer(int spacerIndex)
        {
            return 2 + spacerIndex * 2 + 1;
        }
 
        int GetSpacerIndex(int viewIndex)
        {
            if (viewIndex == 1)
            {
                return 0;
            }
            else
            {
                return (viewIndex - 3) / 2 + 1;
            }
        }
 
        void UpdateHintTextVisibility(string hintText)
        {
            if (this.hintTextGrid != null && this.Items != null)
            {
                this.hintTextGrid.Visibility = (this.Items.Count == 0 && !string.IsNullOrEmpty(hintText)) ? Visibility.Visible : Visibility.Collapsed;
            }
        }
 
        private IList<object> GetOrderMetaData(List<ModelItem> items)
        {
            List<ModelItem> sortedList = this.SortSelectedItems(new List<ModelItem>(items));
            this.CheckListConsistentAndThrow(items, sortedList);
            return sortedList.Select((m) => m.GetCurrentValue()).ToList();
        }
 
        private FrameworkElement CreateSpacer()
        {
            FrameworkElement spacer = (this.SpacerTemplate != null) ? (FrameworkElement)this.SpacerTemplate.LoadContent() : new Rectangle();
            spacer.IsHitTestVisible = true;
            Control spacerControl = spacer as Control;
            if (spacerControl != null)
            {
                spacerControl.IsTabStop = true;
            }
            spacer.Drop += new DragEventHandler(OnSpacerDrop);
            spacer.LostFocus += new RoutedEventHandler(OnSpacerLostFocus);
            spacer.GotFocus += new RoutedEventHandler(OnSpacerGotFocus);
            spacer.GotKeyboardFocus += new KeyboardFocusChangedEventHandler(OnSpacerGotKeyboardFocus);
            spacer.MouseDown += new MouseButtonEventHandler(OnSpacerMouseDown);
            return spacer;
        }
 
        void OnSpacerDrop(object sender, DragEventArgs e)
        {
            int index = GetSpacerIndexFromView(sender);
            OnItemsDropped(e, index);
        }
 
        private int GetSpacerIndexFromView(object sender)
        {
            if (((DependencyObject)sender).ReadLocalValue(IndexProperty) != DependencyProperty.UnsetValue)
            {
                int index = (int)((DependencyObject)sender).GetValue(IndexProperty);
                return index;
            }
            else
            {
                return GetSpacerIndex(this.panel.Items.IndexOf(sender));
            }
        }
 
        void OnSpacerGotFocus(object sender, RoutedEventArgs e)
        {
            int index = GetSpacerIndexFromView(sender);
            selectedSpacerIndex = index;
        }
 
        void OnSpacerGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
        {
            // Handle the event so that it won't be routed to the containing designer to affect selection
            if (handleSpacerGotKeyboardFocus)
            {
                e.Handled = true;
            }
        }
 
        void OnSpacerLostFocus(object sender, RoutedEventArgs e)
        {
            int index = GetSpacerIndexFromView(sender);
            selectedSpacerIndex = addAtEndMarker;
        }
 
        void OnSpacerMouseDown(object sender, MouseButtonEventArgs e)
        {
            // do not move focus if it's a ctrl right click.
            if (e.RightButton == MouseButtonState.Pressed)
            {
                if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
                {
                    return;
                }
            }
 
            // Schedule the Keyboard.Focus command to let it execute later than WorkflowViewElement.OnMouseDown
            this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)(() =>
            {
                this.handleSpacerGotKeyboardFocus = true;
                Keyboard.Focus((FrameworkElement)sender);
                this.handleSpacerGotKeyboardFocus = false;
            }));
        }
 
        private bool ShouldMoveItems(List<ModelItem> sortedModelItems, int index)
        {
            if (sortedModelItems.Count == 0)
            {
                return false;
            }
 
            // Should move if the items are not next to each other
            if (!AreItemsConsecutive(sortedModelItems))
            {
                return true;
            }
 
            // Should not move if the new position is just before the first item or just after the last item or between them.
            return index < this.Items.IndexOf(sortedModelItems[0]) || index > this.Items.IndexOf(sortedModelItems.Last()) + 1;
        }
 
        private bool AreItemsConsecutive(List<ModelItem> sortedModelItems)
        {
            Fx.Assert(sortedModelItems.Count > 0, "Should have at least one item.");
            int oldIndex = this.Items.IndexOf(sortedModelItems[0]);
            foreach (ModelItem item in sortedModelItems)
            {
                if (oldIndex != this.Items.IndexOf(item))
                {
                    return false;
                }
                oldIndex++;
            }
            return true;
        }
 
        public List<ModelItem> SortSelectedItems(List<ModelItem> selectedItems)
        {
            if (selectedItems == null)
            {
                throw FxTrace.Exception.ArgumentNull("selectedItems");
            }
            if (selectedItems.Count < 2)
            {
                return selectedItems;
            }
 
            List<ModelItem> list = new List<ModelItem>();
            // If the performance here is bad, we can use HashSet for selectedItems
            // to improve
            foreach (ModelItem item in this.Items)
            {
                int index = selectedItems.IndexOf(item);
                if (index >= 0)
                {
                    // use the reference in selectedItems.
                    list.Add(selectedItems[index]);
                }
            }
 
            // in case passing some items that are not in
            // my container.
            if (list.Count != selectedItems.Count)
            {
                // throw FxTrace.Exception.
                throw FxTrace.Exception.AsError(new ArgumentException(SR.Error_CantFindItemInWIsP));
            }
            return list;
        }
 
        public void OnItemsMoved(List<ModelItem> movedItems)
        {
            if (movedItems == null)
            {
                throw FxTrace.Exception.ArgumentNull("movedItems");
            }
 
            DragDropHelper.ValidateItemsAreOnView(movedItems, this.Items);
            this.OnItemsDelete(movedItems);
        }
 
        void OnItemsDropped(DragEventArgs e, int index)
        {
            ModelItemHelper.TryCreateImmediateEditingScopeAndExecute(this.Items.GetEditingContext(), System.Activities.Presentation.SR.CollectionAddEditingScopeDescription, (es) =>
            {
                DragDropHelper.SetDragDropCompletedEffects(e, DragDropEffects.None);
                List<object> droppedObjects = new List<object>(DragDropHelper.GetDroppedObjects(this, e, Context));
                List<WorkflowViewElement> movedViewElements = new List<WorkflowViewElement>();
 
                List<object> externalMoveList = new List<object>();
                List<ModelItem> internalMoveList = new List<ModelItem>();
 
                // Step 1: Sort the list
                List<object> sortedDroppingList = DragDropHelper.SortSelectedObjects(droppedObjects);
 
 
                // Step 2: Categorize dropped objects by their source container.
                foreach (object droppedObject in sortedDroppingList)
                {
                    ModelItem modelItem = droppedObject as ModelItem;
                    WorkflowViewElement view = (modelItem == null) ? null : (modelItem.View as WorkflowViewElement);
                    if (view == null)
                    {
                        externalMoveList.Add(droppedObject);
                        continue;
                    }
                    UIElement container = DragDropHelper.GetCompositeView(view);
                    if (container == this)
                    {
                        internalMoveList.Add(modelItem);
                        continue;
                    }
                    movedViewElements.Add(view);
                    externalMoveList.Add(droppedObject);
                }
 
                // Step 3: Internal movement
                if (this.ShouldMoveItems(internalMoveList, index))
                {
                    foreach (ModelItem modelItem in internalMoveList)
                    {
                        int oldIndex = this.Items.IndexOf(modelItem);
                        this.Items.Remove(modelItem);
 
                        //if element is placed ahead of old location, decrement the index not to include moved object
                        if (oldIndex < index)
                        {
                            this.InsertItem(index - 1, modelItem);
                        }
                        else
                        {
                            this.InsertItem(index, modelItem);
                            index++;
                        }
                    }
                }
 
                // Step 4: External move and drop from toolbox
                foreach (object droppedObject in externalMoveList)
                {
                    if (!this.IsDropAllowed(droppedObject))
                    {
                        continue;
                    }
                    this.InsertItem(index++, droppedObject);
                    DragDropHelper.SetDragDropCompletedEffects(e, DragDropEffects.Move);
                }
                DragDropHelper.SetDragDropMovedViewElements(e, movedViewElements);
                e.Handled = true;
                if (es != null)
                {
                    es.Complete();
                }
            });
        }
 
        private void CheckListConsistentAndThrow(List<ModelItem> src, List<ModelItem> copied)
        {
            bool valid = DragDropHelper.AreListsIdenticalExceptOrder(src, copied);
            if (!valid)
            {
                throw FxTrace.Exception.AsError(new InvalidOperationException(SR.Error_BadOutputFromSortSelectedItems));
            }
        }
 
        private bool IsDropAllowed(object droppedObject)
        {
            bool isDropAllowed = false;
            ModelItem modelItem = droppedObject as ModelItem;
            if (modelItem != null && !IsInParentChain(modelItem))
            {
                if (this.AllowedItemType.IsAssignableFrom(modelItem.ItemType))
                {
                    isDropAllowed = true;
                }
            }
            else if (droppedObject is Type && this.AllowedItemType.IsAssignableFrom((Type)droppedObject))
            {
                isDropAllowed = true;
            }
            else
            {
                if (this.AllowedItemType.IsAssignableFrom(droppedObject.GetType()))
                {
                    isDropAllowed = true;
                }
            }
            return isDropAllowed;
        }
 
        private bool IsInParentChain(ModelItem droppedModelItem)
        {
            bool isInParentChain = false;
            ModelItem parentModelItem = this.Items;
            while (parentModelItem != null)
            {
                if (parentModelItem == droppedModelItem)
                {
                    isInParentChain = true;
                    break;
                }
                parentModelItem = parentModelItem.Parent;
            }
            return isInParentChain;
        }
 
        void InsertItem(int index, object droppedObject)
        {
            ModelItem insertedItem = null;
            if (index == addAtEndMarker)
            {
                insertedItem = this.Items.Add(droppedObject);
            }
            else
            {
                insertedItem = this.Items.Insert(index, droppedObject);
            }
            if (insertedItem != null)
            {
                Selection.SelectOnly(this.Context, insertedItem);
            }
        }
 
        protected override void OnDrop(DragEventArgs e)
        {
            int index = addAtEndMarker;
            WorkflowViewElement dropTarget = null;
            if (e.OriginalSource is WorkflowViewElement)
            {
                dropTarget = (WorkflowViewElement)e.OriginalSource;
            }
            else
            {
                dropTarget = VisualTreeUtils.FindFocusableParent<WorkflowViewElement>((UIElement)e.OriginalSource);
            }
 
            if (null != dropTarget && null != dropTarget.ModelItem)
            {
                int targetIndex = this.Items.IndexOf(dropTarget.ModelItem);
                if (-1 != targetIndex)
                {
                    index = targetIndex + 1;
                }
            }
            OnItemsDropped(e, index);
            base.OnDrop(e);
        }
 
        void OnDrag(DragEventArgs e)
        {
            if (!e.Handled)
            {
                if (!DragDropHelper.AllowDrop(e.Data, this.Context, this.AllowedItemType))
                {
                    e.Effects = DragDropEffects.None;
                }
                e.Handled = true;
            }
        }
 
        protected override void OnDragEnter(DragEventArgs e)
        {
            this.OnDrag(e);
            base.OnDragEnter(e);
        }
 
        protected override void OnDragOver(DragEventArgs e)
        {
            this.OnDrag(e);
            base.OnDragOver(e);
        }
 
 
        public void OnItemMoved(ModelItem modelItem)
        {
            if (this.Items.Contains(modelItem))
            {
                this.Items.Remove(modelItem);
            }
        }
 
        protected override AutomationPeer OnCreateAutomationPeer()
        {
            return new WorkflowItemsPresenterAutomationPeer(this);
        }
 
        public object OnItemsCut(List<ModelItem> itemsToCut)
        {
            List<object> orderMetaData = GetOrderMetaData(itemsToCut).ToList();
            foreach (ModelItem item in itemsToCut)
            {
                this.Items.Remove(item);
                this.Context.Items.SetValue(new Selection(new ArrayList()));
            }
            return orderMetaData;
        }
       
        public object OnItemsCopied(List<ModelItem> itemsToCopy)
        {
            return this.GetOrderMetaData(itemsToCopy);
        }
 
        public void OnItemsPasted(List<object> itemsToPaste, List<object> metaData, Point pastePoint, WorkflowViewElement pastePointReference)
        {
            // first see if a spacer is selected.
            int index = this.selectedSpacerIndex;
            // else see if we can paste after a selected child
            if (index < 0)
            {
                Selection currentSelection = this.Context.Items.GetValue<Selection>();
                index = this.Items.IndexOf(currentSelection.PrimarySelection);
                //paste after the selected child
                if (index >= 0)
                {
                    index++;
                }
            }
            if (index < 0)
            {
                index = addAtEndMarker;
            }
 
            IList<object> mergedItemsToPaste = CutCopyPasteHelper.SortFromMetaData(itemsToPaste, metaData);
 
            List<ModelItem> modelItemsToSelect = new List<ModelItem>();
 
            foreach (object itemToPaste in mergedItemsToPaste)
            {
                if (IsDropAllowed(itemToPaste))
                {
                    if (index == addAtEndMarker)
                    {
                        modelItemsToSelect.Add(this.Items.Add(itemToPaste));
                    }
                    else
                    {
                        modelItemsToSelect.Add(this.Items.Insert(index, itemToPaste));
                    }
                    if (index >= 0)
                    {
                        index++;
                    }
                }
            }
 
            this.Dispatcher.BeginInvoke(
                new Action(() =>
                {
                    this.Context.Items.SetValue(new Selection(modelItemsToSelect));
                }),
                Windows.Threading.DispatcherPriority.ApplicationIdle,
                null);
        }
 
        public void OnItemsDelete(List<ModelItem> itemsToDelete)
        {
            if (null != itemsToDelete)
            {
                itemsToDelete.ForEach(p =>
                {
                    if (null != this.Items && this.Items.Contains(p))
                    {
                        this.Items.Remove(p);
                    }
                }
                    );
            }
        }
 
        public bool CanPasteItems(List<object> itemsToPaste)
        {
            bool result = false;
            if (null != itemsToPaste && itemsToPaste.Count > 0)
            {
                result = itemsToPaste.All(p => this.IsDropAllowed(p));
            }
            return result;
        }
 
        class WorkflowItemsPresenterAutomationPeer : UIElementAutomationPeer
        {
            WorkflowItemsPresenter owner;
 
            public WorkflowItemsPresenterAutomationPeer(WorkflowItemsPresenter owner)
                : base(owner)
            {
                this.owner = owner;
            }
 
            protected override AutomationControlType GetAutomationControlTypeCore()
            {
                return AutomationControlType.Custom;
            }
 
            protected override string GetAutomationIdCore()
            {
                string automationId = base.GetAutomationIdCore();
                if (string.IsNullOrEmpty(automationId))
                {
                    automationId = base.GetNameCore();
                    if (string.IsNullOrEmpty(automationId))
                    {
                        automationId = this.owner.GetType().Name;
                    }
                }
                return automationId;
            }
 
            protected override string GetNameCore()
            {
                // Return an empty string if some activites are dropped on the presenter
                if (owner.Items.Count > 0)
                {
                    return string.Empty;
                }
                string name = base.GetNameCore();
                if (string.IsNullOrEmpty(name))
                {
                    name = this.owner.HintText;
                }
                return name;
            }
 
            protected override string GetClassNameCore()
            {
                return this.owner.GetType().Name;
            }
        }
 
    }
}