Пример #1
0
        internal bool InsertAfter(GeneratorNode insert)
        {
            if (insert == null)
            {
                throw new DataGridInternalException("GeneratorNode is null.");
            }

            var insertionCount = default(int);
            var chainLength    = default(int);
            var insertLast     = GeneratorNodeHelper.EvaluateChain(insert, out insertionCount, out chainLength);

            var nextNode = m_state.Node.Next;

            if (nextNode != null)
            {
                nextNode.Previous = insertLast;
            }

            insertLast.Next   = nextNode;
            insert.Previous   = m_state.Node;
            m_state.Node.Next = insert;

            // Move the current node to the last node inserted
            if (!this.MoveToNextBy(chainLength))
            {
                throw new DataGridInternalException("Unable to move to the requested generator index.");
            }

            return(true);
        }
Пример #2
0
        internal static GeneratorNode EvaluateChain(GeneratorNode startNode, out int totalChildCount, out int chainLength)
        {
            if (startNode == null)
            {
                throw new ArgumentNullException("startNode");
            }

            var current = new State(startNode, 0, 0);

            totalChildCount = startNode.ItemCount;
            chainLength     = 1;

            while (true)
            {
                if (!GeneratorNodeHelper.MoveToNext(ref current))
                {
                    break;
                }

                totalChildCount += current.Node.ItemCount;
                chainLength++;
            }

            return(current.Node);
        }
Пример #3
0
        public bool InsertAfter(GeneratorNode insert)
        {
            if (insert == null)
            {
                throw new DataGridInternalException();
            }

            int           insertionCount;
            int           chainLength;
            GeneratorNode insertLast = GeneratorNodeHelper.EvaluateChain(insert, out insertionCount, out chainLength);

            if (m_currentNode.Next != null)
            {
                m_currentNode.Next.Previous = insertLast;
            }

            insertLast.Next    = m_currentNode.Next;
            insert.Previous    = m_currentNode;
            m_currentNode.Next = insert;

            // Move the current node to the last node inserted
            if (!this.MoveToNextBy(chainLength))
            {
                throw new DataGridInternalException();
            }

            return(true);
        }
Пример #4
0
        public bool MoveToFollowing()
        {
            bool retval = false;
            GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper(m_currentNode, m_index, m_sourceDataIndex);

            while (!retval)
            {
                retval = nodeHelper.MoveToNext();

                if (!retval)
                {
                    if (!nodeHelper.MoveToParent())
                    {
                        //cannot move to parent and could not move to next, this is the end of the chain.
                        break;
                    }
                }
            }

            if (retval)
            {
                m_currentNode     = nodeHelper.CurrentNode;
                m_index           = nodeHelper.Index;
                m_sourceDataIndex = nodeHelper.SourceDataIndex;
            }

            return(retval);
        }
Пример #5
0
        //Note: this function will not check for the presence of the item in the details for Items nodes.
        internal bool AbsoluteFindItem(object item)
        {
            // This method will search through nodes, even those collapsed for the item.
            var current = m_state;

            while (true)
            {
                var itemsNode = current.Node as CollectionGeneratorNode;
                if ((itemsNode != null) && itemsNode.Items.Contains(item))
                {
                    break;
                }

                // If we reach this point, it's because the item we are looking
                // for is not in this node... Try to access the child
                if (!GeneratorNodeHelper.MoveToChild(ref current, false))
                {
                    // Try "advancing" to the next item.
                    if (!GeneratorNodeHelper.MoveToFollowing(ref current))
                    {
                        return(false);
                    }
                }
            }

            m_state = current;

            this.EnsureState();

            return(true);
        }
Пример #6
0
        internal bool MoveBackward()
        {
            var startNode = m_state.Node;

            while (true)
            {
                if (!(m_state.Node is GroupGeneratorNode) && (m_state.Node != startNode) && (m_state.Node.ItemCount != 0))
                {
                    break;
                }

                if (!GeneratorNodeHelper.MoveToChild(ref m_state))
                {
                    if (!GeneratorNodeHelper.MoveToPreceding(ref m_state))
                    {
                        return(false);
                    }
                }
                else
                {
                    GeneratorNodeHelper.MoveToEnd(ref m_state);
                }
            }

            this.EnsureState();

            return(true);
        }
Пример #7
0
        internal bool MoveToNextBy(int count)
        {
            var success = GeneratorNodeHelper.MoveToNext(ref m_state, count);

            this.EnsureState();

            return(success);
        }
Пример #8
0
        internal bool MoveToPrevious()
        {
            var success = GeneratorNodeHelper.MoveToPrevious(ref m_state);

            this.EnsureState();

            return(success);
        }
Пример #9
0
        internal bool MoveToChild(bool skipItemLessGroupNodes)
        {
            var success = GeneratorNodeHelper.MoveToChild(ref m_state, skipItemLessGroupNodes);

            this.EnsureState();

            return(success);
        }
Пример #10
0
        internal bool FindNodeForIndex(int index)
        {
            var success = GeneratorNodeHelper.FindNodeForIndex(ref m_state, index);

            this.EnsureState();

            return(success);
        }
Пример #11
0
        internal bool MoveToFirst()
        {
            GeneratorNodeHelper.MoveToFirst(ref m_state);

            this.EnsureState();

            return(true);
        }
Пример #12
0
        internal int FindItem(object item)
        {
            var current = m_state;

            while (true)
            {
                var itemsNode = current.Node as ItemsGeneratorNode;
                if (itemsNode != null)
                {
                    var index = itemsNode.Items.IndexOf(item);
                    if (index >= 0)
                    {
                        index += itemsNode.CountDetailsBeforeDataIndex(index);

                        // Item is directly from this items node... then return the appropriate index!
                        m_state = current;

                        this.EnsureState();

                        return(index + current.Index);
                    }

                    // If the item is from a detail, then I don't want to "use" it!!!
                    // but continue looping.... to find occurances of this item somewhere else in the tree.
                }
                else
                {
                    var collectionNode = current.Node as CollectionGeneratorNode;
                    if (collectionNode != null)
                    {
                        var index = collectionNode.IndexOf(item);
                        if (index >= 0)
                        {
                            m_state = current;

                            this.EnsureState();

                            return(index + current.Index);
                        }
                    }
                }

                // If we reach this point, it's because the item we are looking
                // for is not in this node... Try to access the child
                if (!GeneratorNodeHelper.MoveToChild(ref current))
                {
                    // Try "advancing" to the next item.
                    if (!GeneratorNodeHelper.MoveToFollowing(ref current))
                    {
                        break;
                    }
                }
            }

            return(-1);
        }
Пример #13
0
        internal bool MoveToEnd()
        {
            // We call MoveToNext instead of MoveToEnd because MoveToEnd includes the indexes of the last node.
            while (GeneratorNodeHelper.MoveToNext(ref m_state))
            {
            }

            this.EnsureState();

            return(true);
        }
Пример #14
0
        private static bool MoveToFollowing(ref State state)
        {
            while (!GeneratorNodeHelper.MoveToNext(ref state))
            {
                if (!GeneratorNodeHelper.MoveToParent(ref state))
                {
                    return(false);
                }
            }

            return(true);
        }
Пример #15
0
        private static bool MoveToPreceding(ref State state)
        {
            while (!GeneratorNodeHelper.MoveToPrevious(ref state))
            {
                if (!GeneratorNodeHelper.MoveToParent(ref state))
                {
                    return(false);
                }
            }

            return(true);
        }
Пример #16
0
 private void NotifyImmediateChildren(bool value)
 {
     if (this.Child != null)
     {
         GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper(this.Child, 0, 0); //index not important
         nodeHelper.MoveToEnd();
         do
         {
             nodeHelper.CurrentNode.NotifyExpansionStateChanged(value);
         }while(nodeHelper.MoveToPrevious() == true);
     }
 }
Пример #17
0
        internal bool FindNode(GeneratorNode node)
        {
            if (node == null)
            {
                throw new ArgumentNullException("node");
            }

            var success = GeneratorNodeHelper.FindNode(ref m_state, node);

            this.EnsureState();

            return(success);
        }
Пример #18
0
        //Note: this function will not check for the presence of the group in the details for Items nodes.
        internal bool FindGroup(CollectionViewGroup group)
        {
            if (group == null)
            {
                return(false);
            }

            var current = m_state;

            while (true)
            {
                var groupNode = current.Node as GroupGeneratorNode;
                if (groupNode != null)
                {
                    if (groupNode.CollectionViewGroup == group)
                    {
                        break;
                    }

                    if (!groupNode.CollectionViewGroup.IsBottomLevel)
                    {
                        if (!GeneratorNodeHelper.MoveToChild(ref current, false))
                        {
                            return(false);
                        }
                    }
                    else
                    {
                        if (!GeneratorNodeHelper.MoveToFollowing(ref current))
                        {
                            return(false);
                        }
                    }
                }
                else
                {
                    // There is nothing under a non GroupGeneratorNode, try to move Next node in the list.
                    if (!GeneratorNodeHelper.MoveToFollowing(ref current))
                    {
                        return(false);
                    }
                }
            }

            m_state = current;

            this.EnsureState();

            return(true);
        }
Пример #19
0
        private static bool MoveToParent(ref State state)
        {
            if (state.Node.Level == 0)
            {
                return(false);
            }

            GeneratorNodeHelper.MoveToFirst(ref state);

            state.Node = state.Node.Parent;
            Debug.Assert(state.Node != null);

            return(true);
        }
        internal ReadOnlyCollection <Group> GetImmediateUIGroups(int generatorCurrentGeneration)
        {
            if ((m_cachedGeneratorCurrentGeneration != generatorCurrentGeneration) ||
                (m_readOnlyImmediateUIGroups == null))
            {
                Debug.WriteLineIf(((m_cachedGeneratorCurrentGeneration != generatorCurrentGeneration) && (m_readOnlyImmediateUIGroups == null)),
                                  "Creating Groups collection since generator version differs AND ReadOnlyCollection is null.");

                Debug.WriteLineIf(((m_cachedGeneratorCurrentGeneration != generatorCurrentGeneration) && (m_readOnlyImmediateUIGroups != null)),
                                  "Creating Groups collection since generator version differs.");

                Debug.WriteLineIf(((m_cachedGeneratorCurrentGeneration == generatorCurrentGeneration) && (m_readOnlyImmediateUIGroups == null)),
                                  "Creating Groups collection even if generator version is the same, since ReadOnlyCollection is null.");

                m_cachedGeneratorCurrentGeneration = generatorCurrentGeneration;

                // Ensure collections.
                if (m_immediateUIGroups == null)
                {
                    Debug.Assert(m_readOnlyImmediateUIGroups == null);
                    m_immediateUIGroups         = new Collection <Group>();
                    m_readOnlyImmediateUIGroups = new ReadOnlyCollection <Group>(m_immediateUIGroups);
                }
                else
                {
                    Debug.Assert(m_readOnlyImmediateUIGroups != null);
                    m_immediateUIGroups.Clear();
                }

                // Recalculate.
                GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper(this, 0, 0); //index is not important

                while (nodeHelper.MoveToNext())
                {
                    GroupGeneratorNode groupGeneratorNode = nodeHelper.CurrentNode as GroupGeneratorNode;

                    if (groupGeneratorNode == null)
                    {
                        continue;
                    }

                    m_immediateUIGroups.Add(groupGeneratorNode.UIGroup);
                }
            }

            return(m_readOnlyImmediateUIGroups);
        }
Пример #21
0
        public bool ReverseCalculateIndex()
        {
            // index need to be 0, as I will use the value from the index once I backtracked all the way to the root.
            GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper(this.CurrentNode, 0, 0);

            while ((nodeHelper.CurrentNode.Previous != null) || (nodeHelper.CurrentNode.Parent != null))
            {
                if (!nodeHelper.MoveToPrevious())
                {
                    nodeHelper.MoveToParent();
                }
            }

            m_index           = Math.Abs(nodeHelper.Index);
            m_sourceDataIndex = Math.Abs(nodeHelper.SourceDataIndex);
            return(true);
        }
Пример #22
0
        public static GeneratorNode EvaluateChain(GeneratorNode chainStart, out int totalChildCount, out int chainLength)
        {
            //if we insert a chain of nodes, this GeneratorNodeHelper will help us
            GeneratorNodeHelper newHelper = new GeneratorNodeHelper(chainStart, 0, 0);

            //first determine the total number of childs from this "node"
            totalChildCount = 0;
            chainLength     = 0;

            do
            {
                totalChildCount += newHelper.CurrentNode.ItemCount;
                chainLength++;
            }while(newHelper.MoveToNext());

            //then, since we moved at the end of the "chain"
            return(newHelper.CurrentNode);
        }
Пример #23
0
        private static State FindNodeLocation(GeneratorNode node)
        {
            var state = new State(node, 0, 0);

            while (true)
            {
                GeneratorNodeHelper.MoveToFirst(ref state);

                if (!GeneratorNodeHelper.MoveToParent(ref state))
                {
                    break;
                }
            }

            Debug.Assert(state.Index <= 0);
            Debug.Assert(state.DataIndex <= 0);

            return(new State(node, -state.Index, -state.DataIndex));
        }
Пример #24
0
        private static bool FindNode(ref State state, GeneratorNode node)
        {
            if (state.Node == node)
            {
                return(true);
            }

            var from      = state;
            var fromLevel = state.Node.Level;
            var to        = new State(node, 0, 0);
            var toLevel   = node.Level;

            while (from.Node != to.Node)
            {
                if (fromLevel > toLevel)
                {
                    if (!GeneratorNodeHelper.MoveToParent(ref from))
                    {
                        return(false);
                    }

                    fromLevel = from.Node.Level;
                }
                else
                {
                    if (!GeneratorNodeHelper.MoveToPrevious(ref to))
                    {
                        if (!GeneratorNodeHelper.MoveToParent(ref to))
                        {
                            return(false);
                        }

                        toLevel = to.Node.Level;
                    }
                }
            }

            state.Node      = node;
            state.Index     = from.Index - to.Index;
            state.DataIndex = from.DataIndex - to.DataIndex;

            return(true);
        }
Пример #25
0
        public bool MoveBackward()
        {
            bool recurseGroup = true;
            GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper(m_currentNode, m_index, m_sourceDataIndex);

            do
            {
                GroupGeneratorNode groupNode = nodeHelper.CurrentNode as GroupGeneratorNode;

                if ((groupNode == null) && (nodeHelper.CurrentNode != m_currentNode) && (nodeHelper.CurrentNode.ItemCount != 0))
                {
                    m_currentNode     = nodeHelper.CurrentNode;
                    m_index           = nodeHelper.Index;
                    m_sourceDataIndex = nodeHelper.SourceDataIndex;
                    return(true);
                }

                if ((recurseGroup) && (nodeHelper.MoveToChild()))
                {
                    nodeHelper.MoveToEnd();
                    continue;
                }

                recurseGroup = true;

                if (nodeHelper.MoveToPrevious())
                {
                    continue;
                }


                if (nodeHelper.MoveToParent())
                {
                    recurseGroup = false;
                    continue;
                }

                break;
            }while(true);

            return(false);
        }
    internal static HeadersFootersGeneratorNode GetSameLevelFirstHeaderNode( GroupGeneratorNode generatorNode )
    {
      HeadersFootersGeneratorNode headerGeneratorNode = null;

      GroupGeneratorNode parentGroupGeneratorNode = generatorNode.Parent as GroupGeneratorNode;

      if( parentGroupGeneratorNode == null )
      {
        GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( generatorNode, 0, 0 ); //index is not important

        if( nodeHelper.MoveToFirst() )
          headerGeneratorNode = nodeHelper.CurrentNode as HeadersFootersGeneratorNode;
      }
      else
      {
        headerGeneratorNode = parentGroupGeneratorNode.Child as HeadersFootersGeneratorNode;
      }

      return headerGeneratorNode;
    }
Пример #27
0
        internal bool InsertBefore(GeneratorNode insert)
        {
            if (insert == null)
            {
                throw new DataGridInternalException("GeneratorNode is null");
            }

            var insertionCount = default(int);
            var chainLength    = default(int);
            var insertLast     = GeneratorNodeHelper.EvaluateChain(insert, out insertionCount, out chainLength);

            var previousNode = m_state.Node.Previous;

            if (previousNode != null)
            {
                previousNode.Next = insert;
            }

            insert.Previous       = previousNode;
            m_state.Node.Previous = insertLast;
            insertLast.Next       = m_state.Node;

            var parentGroup = insert.Parent as GroupGeneratorNode;

            if (parentGroup != null)
            {
                if (parentGroup.Child == m_state.Node)
                {
                    parentGroup.Child = insert;
                }
            }

            // Move the current to the first item inserted.
            // No need to update the indexes since they will still be with the correct value.
            m_state.Node = insert;

            this.EnsureState();

            return(true);
        }
Пример #28
0
        private static bool FindNodeForIndex(ref State state, int index)
        {
            var current = state;

            // The algo is a single drill-down... for optimized performance..
            // It first tries to go horizontally in the tree and then drills-down if it can.
            while (true)
            {
                var itemCount = current.Node.ItemCount;

                // Verify if the current node contains ( directly or its children ) the required index
                if (index < current.Index + itemCount)
                {
                    // A Group node is the only node that can have children is by definition empty... let's dig deeper.
                    // If we cannot travel deeper, then this node is the closest we get.
                    if (!GeneratorNodeHelper.MoveToChild(ref current))
                    {
                        break;
                    }
                }
                else if ((index == current.Index) && (itemCount != 0))
                {
                    break;
                }
                else
                {
                    if (!GeneratorNodeHelper.MoveToNext(ref current))
                    {
                        return(false);
                    }
                }
            }

            state.Node      = current.Node;
            state.Index     = current.Index;
            state.DataIndex = current.DataIndex;

            return(true);
        }
        internal static HeadersFootersGeneratorNode GetSameLevelFirstHeaderNode(GroupGeneratorNode generatorNode)
        {
            HeadersFootersGeneratorNode headerGeneratorNode = null;

            GroupGeneratorNode parentGroupGeneratorNode = generatorNode.Parent as GroupGeneratorNode;

            if (parentGroupGeneratorNode == null)
            {
                GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper(generatorNode, 0, 0); //index is not important

                if (nodeHelper.MoveToFirst())
                {
                    headerGeneratorNode = nodeHelper.CurrentNode as HeadersFootersGeneratorNode;
                }
            }
            else
            {
                headerGeneratorNode = parentGroupGeneratorNode.Child as HeadersFootersGeneratorNode;
            }

            return(headerGeneratorNode);
        }
Пример #30
0
        public bool InsertBefore(GeneratorNode insert)
        {
            if (insert == null)
            {
                throw new DataGridInternalException();
            }

            int           insertionCount;
            int           chainLength;
            GeneratorNode insertLast = GeneratorNodeHelper.EvaluateChain(insert, out insertionCount, out chainLength);
            GeneratorNode previous   = m_currentNode.Previous;

            if (previous != null)
            {
                previous.Next = insert;
            }

            insert.Previous        = previous;
            m_currentNode.Previous = insertLast;
            insertLast.Next        = m_currentNode;

            GroupGeneratorNode parentGroup = insert.Parent as GroupGeneratorNode;

            if (parentGroup != null)
            {
                if (parentGroup.Child == m_currentNode)
                {
                    parentGroup.Child = insert;
                }
            }

            // Move the current to the first item inserted.
            // No need to change m_index, m_sourceDataIndex since they will still be with the correct value.
            m_currentNode = insert;
            return(true);
        }
Пример #31
0
        internal object FindIndex(int index)
        {
            var current = m_state;

            while (true)
            {
                if (index < current.Index + current.Node.ItemCount)
                {
                    var itemsNode = current.Node as CollectionGeneratorNode;
                    if (itemsNode != null)
                    {
                        m_state = current;

                        this.EnsureState();

                        return(itemsNode.GetAt(index - current.Index));
                    }

                    if (!GeneratorNodeHelper.MoveToChild(ref current))
                    {
                        break;
                    }
                }
                else
                {
                    // If we reach this point, it's because the item we are looking for is not in this node... Try to access the child.
                    // No need to check for childs, since the condition above would catch it (childs part of ItemCount).
                    if (!GeneratorNodeHelper.MoveToFollowing(ref current))
                    {
                        break;
                    }
                }
            }

            return(null);
        }
    private bool TryGetParentGroupFromItemHelper( object item, out CollectionViewGroup collectionViewGroup )
    {
      //This helper method is used to simplify previous code flow of the TryGetParentGroupFromItem method.
      collectionViewGroup = null;

      //-----------------------------------------------
      //1 - First check is to see of the item is a CVG.
      //-----------------------------------------------
      CollectionViewGroup groupItem = item as CollectionViewGroup;
      if( groupItem != null )
      {
        Group group = this.GetGroupFromCollectionViewGroup( groupItem );
        if( group != null )
        {
          GroupGeneratorNode groupGeneratorNode = group.GeneratorNode;
          if( groupGeneratorNode.Parent == null )
          {
            //no parent for speficied item.
            return true;
          }
          //if the nodeHelper was able to locate the content, use the nodeHelper's CurrentNode as the node for the item.
          collectionViewGroup = ( ( GroupGeneratorNode )groupGeneratorNode.Parent ).CollectionViewGroup;
          return true;
        }

        //item is a CVG, but is not present in the generator!
        return false;
      }

      //-----------------------------------------------
      //2 - Second check is to see if the item is already in the generated list
      //-----------------------------------------------

      //item might be in the "generated" list... much quicker to find-out if it is!
      //note: if the item belongs to a detail, then it will be excluded from the "fast" algo.
      int itemGenPosIndex = this.FindFirstGeneratedIndexForLocalItem( item );
      if( itemGenPosIndex != -1 )
      {
        //item was generated and was not from a DetailGeneratorNode
        if( m_genPosToNode[ itemGenPosIndex ].Parent == null )
        {
          //no parent for speficied item.
          return true;
        }
        collectionViewGroup = ( ( GroupGeneratorNode )m_genPosToNode[ itemGenPosIndex ].Parent ).CollectionViewGroup;
        return true;
      }

      //-----------------------------------------------
      //3 - Third check is to check of the item is a GroupHeaderFooterItem
      //-----------------------------------------------
      if( item.GetType() == typeof( GroupHeaderFooterItem ) )
      {
        GroupHeaderFooterItem groupHeaderFooterItem = ( GroupHeaderFooterItem )item;
        CollectionViewGroup parentGroup = groupHeaderFooterItem.Group;

        if( this.GetGroupFromCollectionViewGroup( parentGroup ) != null )
        {
          //since the goal is the find the parentGroup from the item passed (which is a GroupHeader or GroupFooter), then the Group
          //is what I am looking for.
          collectionViewGroup = parentGroup;

          return true;
        }

        //Item was a GroupHeaderFooterItem but was not part of the genreator!
        return false;
      }

      //-----------------------------------------------
      //4 - Final Check
      //-----------------------------------------------

      //if the item was not generated, then try to find the item as is within the generator's content  
      GeneratorNodeHelper finalNodeHelper = new GeneratorNodeHelper( m_startNode, 0, 0 );
      if( finalNodeHelper.AbsoluteFindItem( item ) )
      {
        //item was not generated but was part of this generator
        if( finalNodeHelper.CurrentNode.Parent == null )
        {
          //no parent for speficied item.
          return true;
        }
        collectionViewGroup = ( ( GroupGeneratorNode )finalNodeHelper.CurrentNode.Parent ).CollectionViewGroup;
        return true;
      }
      else
      {
        return false;
      }

      //Default function behavior, if nobody else returned, then the function did not work as intended... as each "block" is supposed to return a value...
      throw new DataGridInternalException();
    }
    private void OnGeneratorNodeGroupsCollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
    {
      if( this.Status == GeneratorStatus.GeneratingContainers )
      {
        throw new InvalidOperationException( "Cannot perform this operation while the generator is busy generating items" );
      }

      GroupGeneratorNode node = sender as GroupGeneratorNode;

      if( node != null )
      {
        switch( e.Action )
        {
          case NotifyCollectionChangedAction.Add:
            int addCount;
            GeneratorNode addNode = this.HandleParentGroupAddition( node, out addCount, e );

            if( node.IsComputedExpanded )
            {
              GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( addNode, 0, 0 );//index not important, will reserve find it.
              nodeHelper.ReverseCalculateIndex();

              this.IncrementCurrentGenerationCount();

              GeneratorPosition genPos = this.GeneratorPositionFromIndex( nodeHelper.Index );

              this.SendAddEvent( genPos, nodeHelper.Index, addCount );
            }
            break;
          case NotifyCollectionChangedAction.Move:
            if( node.Child == null )
            {
              throw new DataGridInternalException();
            }
            else
            {
              Debug.Assert( e.OldStartingIndex != e.NewStartingIndex, "An attempt was made to move a group to the same location." );
#if LOG
              Log.Assert( this, e.OldStartingIndex != e.NewStartingIndex, "An attempt was made to move a group to the same location." );
#endif

              this.HandleSameLevelGroupMove( node.Child, e );
            }
            break;
          case NotifyCollectionChangedAction.Remove:
            if( node.Child == null )
            {
              throw new DataGridInternalException();
            }
            else
            {
              int remCount;
              int generatedRemCount;
              int removeIndex;
              List<DependencyObject> removedContainers = new List<DependencyObject>();
              GeneratorPosition remPos = this.HandleParentGroupRemove( node, out remCount, out generatedRemCount, out removeIndex, e, removedContainers );

              if( node.IsComputedExpanded )
              {
                this.IncrementCurrentGenerationCount();

                this.SendRemoveEvent( remPos, removeIndex, remCount, generatedRemCount, removedContainers );
              }

            }
            break;
          case NotifyCollectionChangedAction.Replace:
            //if( node.Child == null )
            //{
            //  throw new DataGridInternalException();
            //}
            //else
            //{
            //  this.HandleGroupReplace( node.Child, e );
            //}

            //m_currentGeneratorContentGeneration++;
            throw new DataGridInternalException();
          //break;
          case NotifyCollectionChangedAction.Reset:
            //m_currentGeneratorContentGeneration++;
            throw new DataGridInternalException();
          default:
            throw new DataGridInternalException();

        }
      }

    }
    private int FindGlobalIndexForDetailNode( DetailGeneratorNode detailNode )
    {
      int retval = -1;

      //first thing, loop through the details for the 
      foreach( KeyValuePair<object, List<DetailGeneratorNode>> masterItemToDetails in m_masterToDetails )
      {
        //in each master to details entry, try to find the detail node passed
        int detailIndex = masterItemToDetails.Value.IndexOf( detailNode );
        if( detailIndex != -1 )
        {
          //if the desired detailNode is present for this master item... then evaluate it...
          int detailNodeOffset = 0;
          for( int i = 0; i < detailIndex; i++ )
          {
            detailNodeOffset += masterItemToDetails.Value[ i ].ItemCount - 1;
          }

          int index = m_genPosToItem.IndexOf( masterItemToDetails.Key );
          int masterItemIndex;

          if( index > -1 )
          {
            masterItemIndex = m_genPosToIndex[ index ];
          }
          else
          {
            GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( m_startNode, 0, 0 );
            masterItemIndex = nodeHelper.FindItem( masterItemToDetails.Key );
          }

          if( masterItemIndex == -1 )
            throw new DataGridInternalException();

          retval = detailNodeOffset + masterItemIndex + detailIndex + 1;

          //leave the top level loop
          break;
        }
      }

      return retval;
    }
    private void HandleDetailMoveRemove( object masterItem, DetailGeneratorNode detailNode, CustomGeneratorChangedEventArgs e )
    {
      GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( m_startNode, 0, 0 );
      int masterIndex = nodeHelper.FindItem( masterItem );

      //If the masterItem is part of an ItemsGeneratorNode which is below a collapsed group, masterIndex will be -1
      if( masterIndex == -1 )
      {
        //in that case, I need to determine the appropriate masterNode another way
        nodeHelper = new GeneratorNodeHelper( m_startNode, 0, 0 );
        if( !nodeHelper.Contains( masterItem ) )
          throw new DataGridInternalException();
      }

      ItemsGeneratorNode masterNode = nodeHelper.CurrentNode as ItemsGeneratorNode;

      Debug.Assert( masterNode != null, "masterNode != null" );
#if LOG
      Log.Assert( this, masterNode != null, "masterNode != null" );
#endif

      int globalIndex = -1;
      GeneratorPosition convertedGeneratorPosition = ( masterIndex != -1 ) ? this.ConvertDetailGeneratorPosition( e.OldPosition, masterItem, detailNode, out globalIndex ) : new GeneratorPosition( -1, 1 );

      if( masterIndex != -1 )
      {
        this.RemoveDetailContainers( convertedGeneratorPosition, e.ItemUICount );
      }

      if( e.Action == NotifyCollectionChangedAction.Remove )
      {
        masterNode.AdjustItemCount( -e.ItemCount );

        detailNode.UpdateItemCount();
      }

      this.IncrementCurrentGenerationCount();

      if( masterIndex != -1 )
      {
        this.SendRemoveEvent( convertedGeneratorPosition, globalIndex, e.ItemCount, e.ItemUICount, e.RemovedContainers );
      }
    }
    public int GetGroupIndex( Group group )
    {
      if( group == null )
        throw new ArgumentNullException( "group" );

      this.EnsureNodeTreeCreated();

      GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( m_startNode, 0, 0 );

      if( nodeHelper.FindGroup( group.CollectionViewGroup ) )
        return nodeHelper.Index;

      //the item was not found in this generator's content... check all the detail generators
      foreach( KeyValuePair<object, List<DetailGeneratorNode>> itemToDetails in m_masterToDetails )
      {
        foreach( DetailGeneratorNode detailNode in itemToDetails.Value )
        {
          int groupIndex = detailNode.DetailGenerator.GetGroupIndex( group );

          if( groupIndex > -1 )
            return groupIndex + this.FindGlobalIndexForDetailNode( detailNode );
        }
      }

      return -1;
    }
    public Group GetGroupFromItem( object item )
    {
      Group retval = null;

      GeneratorNode nodeForItem = null;

      this.EnsureNodeTreeCreated();

      //item might be in the "generated" list... much quicker to find-out if it is!
      int itemGenPosIndex = this.FindFirstGeneratedIndexForLocalItem( item );
      if( itemGenPosIndex != -1 )
      {
        //item is generated...
        nodeForItem = m_genPosToNode[ itemGenPosIndex ];
      }
      else
      {
        //only try to find the item is the generator has some content...
        if( m_startNode != null )
        {
          GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( m_startNode, 0, 0 );
          if( nodeHelper.Contains( item ) ) //NOTE: this will only return items directly contained in this generator (not from details )
          {
            //if the nodeHelper was able to locate the content, use the nodeHelper's CurrentNode as the node for the item.
            nodeForItem = nodeHelper.CurrentNode;
          }
          else
          {
            throw new InvalidOperationException( "An attempt was made to retrieve the group of an item that does not belong to the generator." );
          }
        }
      }

      if( nodeForItem != null )
      {
        GroupGeneratorNode parentGroup = nodeForItem.Parent as GroupGeneratorNode;
        if( parentGroup != null )
        {
          retval = parentGroup.UIGroup;
        }
      }

      return retval;
    }
    private GeneratorNode HandleParentGroupAddition( GeneratorNode parent, out int countAdded, NotifyCollectionChangedEventArgs e )
    {
      GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( parent, 0, 0 ); //do not care about index (for now).

      //start by moving to the first child... of the node (GroupHeaders node, most probably).
      if( !nodeHelper.MoveToChild( false ) ) //case 120137: false parameter is to prevent skipping over a collapsed node (item count 0 )
      {
        //could not advance to the child item so there is no items to be removed...

        throw new DataGridInternalException();
      }

      return this.HandleSameLevelGroupAddition( nodeHelper.CurrentNode, out countAdded, e );
    }
    private void HandleItemRemoveMoveReplace( ItemsGeneratorNode node, NotifyCollectionChangedEventArgs e )
    {
      GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( node, 0, 0 ); //index not important for now.
      nodeHelper.ReverseCalculateIndex();

      node.AdjustItemCount( -e.OldItems.Count );
      node.AdjustLeafCount( -e.OldItems.Count );

      int nodeStartIndex = e.OldStartingIndex;
      int nodeEndIndex = nodeStartIndex + e.OldItems.Count - 1;
      int detailCountToRemove = CustomItemContainerGenerator.ComputeDetailsCount( node, nodeStartIndex, nodeEndIndex );
      int detailCountBeforeRemovedItems = 0;
      if( nodeStartIndex > 0 )
      {
        detailCountBeforeRemovedItems = CustomItemContainerGenerator.ComputeDetailsCount( node, 0, nodeStartIndex - 1 );
      }

      int startIndex = nodeHelper.Index + e.OldStartingIndex + detailCountBeforeRemovedItems;
      int endIndex = startIndex + detailCountToRemove + e.OldItems.Count - 1;

      int removeCount = e.OldItems.Count + detailCountToRemove;
      int replaceCount = ( e.Action == NotifyCollectionChangedAction.Replace ) ? e.NewItems.Count : 0;

      // *** RemoveDetails must be done before GeneratorPositionFromIndex, since GeneratorPositionFromIndex will indirectly do a RemapFloatingDetails
      // *** that will cause the index to already be rectified and make a double rectification to occurs.

      //Remove the details from the ItemsGeneratorNode and re-index the other details appropriatly.
      this.RemoveDetails( node, nodeStartIndex, nodeEndIndex, replaceCount );

      GeneratorPosition removeGenPos = this.GeneratorPositionFromIndex( startIndex );

      //Try to remap the old item for detail remapping (will do nothing if item has no details )
      foreach( object oldItem in e.OldItems )
      {
        this.QueueDetailItemForRemapping( oldItem );
      }

      //if the node is totally expanded
      if( node.IsComputedExpanded )
      {
        List<DependencyObject> removedContainers = new List<DependencyObject>();
        int genRemCount = this.RemoveGeneratedItems( startIndex, endIndex, removedContainers );

        this.IncrementCurrentGenerationCount();

        this.SendRemoveEvent( removeGenPos, startIndex, removeCount, genRemCount, removedContainers );
      }

      //then, based on the action that was performed (move, replace or remove)
      switch( e.Action )
      {
        case NotifyCollectionChangedAction.Move:
          this.OffsetDetails( node, e.NewStartingIndex, e.NewItems.Count );
          this.HandleItemMoveRemoveReplaceHelper( node, e, nodeHelper.Index );
          break;

        case NotifyCollectionChangedAction.Replace:
          this.HandleItemMoveRemoveReplaceHelper( node, e, nodeHelper.Index );
          break;

        case NotifyCollectionChangedAction.Remove:
          // Do nothing!
          break;

        case NotifyCollectionChangedAction.Add:
        case NotifyCollectionChangedAction.Reset:
        default:
          throw new DataGridInternalException();
      }

    }
    private void HandleSameLevelGroupMove( GeneratorNode node, NotifyCollectionChangedEventArgs e )
    {
      GroupGeneratorNode parentGroup = node.Parent as GroupGeneratorNode;

      //Start a NodeHelper on the first child of the node where the move occured.
      GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( node, 0, 0 );
      nodeHelper.ReverseCalculateIndex(); //determine index of the node.

      //Advance to the first "Group" node (skip the GroupHEaders)
      while( !( nodeHelper.CurrentNode is GroupGeneratorNode ) )
      {
        if( !nodeHelper.MoveToNext() )
          throw new DataGridInternalException();
      }

      //then move up to the removal start point.
      if( !nodeHelper.MoveToNextBy( e.OldStartingIndex ) )
      {
        throw new DataGridInternalException();
      }

      //remember the current node as the start point of the move (will be used when "extracting the chain")
      GeneratorNode startNode = nodeHelper.CurrentNode;
      //also remember the index of the node, to calculate range of elements to remove (containers )
      int startIndex = nodeHelper.Index;

      //then, cumulate the total number of items in the groups concerned
      int totalCountRemoved = 0;

      node = this.ProcessGroupRemoval( startNode, e.OldItems.Count, false, out totalCountRemoved );

      //send a message to the panel to remove the visual elements concerned 
      GeneratorPosition removeGenPos = this.GeneratorPositionFromIndex( startIndex );

      List<DependencyObject> removedContainers = new List<DependencyObject>();
      int genCountRemoved = this.RemoveGeneratedItems( startIndex, startIndex + totalCountRemoved - 1, removedContainers );

      this.SendRemoveEvent( removeGenPos, startIndex, totalCountRemoved, genCountRemoved, removedContainers );

      //reset the node parameter for the "re-addition"
      node = ( parentGroup != null ) ? parentGroup.Child : m_firstItem;

      if( node == null )
        throw new DataGridInternalException();

      //Once the chain was pulled out, re-insert it at the appropriate location.
      nodeHelper = new GeneratorNodeHelper( node, 0, 0 ); //do not care about the index for what I need

      //Advance to the first "Group" node (skip the GroupHEaders)
      while( !( nodeHelper.CurrentNode is GroupGeneratorNode ) )
      {
        if( !nodeHelper.MoveToNext() )
          throw new DataGridInternalException();
      }

      bool insertBefore = nodeHelper.MoveToNextBy( e.NewStartingIndex );

      if( insertBefore )
      {
        if( nodeHelper.CurrentNode == m_firstItem )
        {
          if( m_startNode == m_firstItem )
          {
            m_startNode = startNode;
          }

          m_firstItem = startNode;
        }

        //reinsert the chain at the specified location.
        nodeHelper.InsertBefore( startNode );
      }
      else
      {
        nodeHelper.InsertAfter( startNode );
      }

      //and finally, call to increment the generation count for the generator content
      this.IncrementCurrentGenerationCount();
    }
    private void OnGeneratorNodeExpansionStateChanged( object sender, ExpansionStateChangedEventArgs e )
    {
      //throw an error is the Generator is actually busy generating!
      if( this.Status == GeneratorStatus.GeneratingContainers )
      {
        throw new InvalidOperationException( "Cannot perform this operation while the generator is busy generating items" );
      }

      GeneratorNode node = sender as GeneratorNode;

      Debug.Assert( node != null, "node != null" );
#if LOG
      Log.Assert( this, node != null, "node != null" );
#endif

      if( node == null )
        return;

      GroupGeneratorNode changedNode = node.Parent as GroupGeneratorNode;

      Debug.Assert( changedNode != null, "changedNode != null" ); //should never be null, as the "node" is supposed to be the child node of this one.
#if LOG
      Log.Assert( this, changedNode != null, "changedNode != null" ); //should never be null, as the "node" is supposed to be the child node of this one.
#endif

      //Determine if the changedNode is "below" a collapsed group (because if so, I don't need any sort of notification or removal ).
      GroupGeneratorNode parentGroupNode = changedNode.Parent as GroupGeneratorNode;
      if( ( parentGroupNode != null ) && ( !parentGroupNode.IsComputedExpanded ) )
        return;

      GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( node, 0, 0 );
      nodeHelper.ReverseCalculateIndex();

      //if the node was "Collapsed"
      if( !e.NewExpansionState )
      {
        int removeCount = e.Count;
        int startIndex = nodeHelper.Index + e.IndexOffset;

        GeneratorPosition removeGenPos = this.GeneratorPositionFromIndex( startIndex );

        List<DependencyObject> removedContainers = new List<DependencyObject>();
        //remove the Generated items between the appropriate indexes
        int removeUICount = this.RemoveGeneratedItems( startIndex, startIndex + removeCount - 1, removedContainers );

        if( removeCount > 0 )
        {
          //send the event so the panel can remove the group elements
          this.SendRemoveEvent( removeGenPos, startIndex, removeCount, removeUICount, removedContainers );
        }
      }
      //if the node was "Expanded" 
      else
      {
        int addCount = e.Count;
        int startIndex = nodeHelper.Index + e.IndexOffset;
        GeneratorPosition addGenPos = this.GeneratorPositionFromIndex( startIndex );

        if( addCount > 0 )
        {
          //send the event so the panel can add the group elements
          this.SendAddEvent( addGenPos, startIndex, addCount );
        }
      }
    }
    private void OnGroupsChanged( object sender, NotifyCollectionChangedEventArgs e )
    {
      // Avoid re-entrance when processing a global reset
      if( m_isProcessingGlobalResetOrRemovingAllGeneratedItemsDisposableCount > 0 )
        return;

      if( this.Status == GeneratorStatus.GeneratingContainers )
      {
        throw new InvalidOperationException( "Cannot perform this operation while the generator is busy generating items" );
      }

      //this fonction is only used to process the content of the DataGridControl.Items.Groups collection...
      // for the CollectionChanged event of branch groups ( IsBottomLevel = false ), refer to the 
      // OnBranchGroupsChanged fonction

      switch( e.Action )
      {
        case NotifyCollectionChangedAction.Add:
          int addCount = e.NewItems.Count;

          GeneratorPosition genPos = new GeneratorPosition( -1, 1 ); //this would map to the first item in the list if not generated.

          int addIndex = -1;

          //if the first item is empty, do not do anything, the structure will be generated when the generator is started!
          if( m_firstItem != null )
          {
            //The only moment where the m_firstItem is null is typically when a reset occured...
            //other moments is when there are 0 items (in which case, the.

            GeneratorNode addNode = this.HandleSameLevelGroupAddition( m_firstItem, out addCount, e );

            this.IncrementCurrentGenerationCount();

            GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( addNode, 0, 0 );//index not important, will reserve find it.
            nodeHelper.ReverseCalculateIndex();
            addIndex = nodeHelper.Index;

            genPos = this.GeneratorPositionFromIndex( addIndex );
          }

          this.SendAddEvent( genPos, addIndex, addCount );

          break;
        case NotifyCollectionChangedAction.Move:
          if( m_firstItem != null )
          {
            if( !( m_firstItem is GroupGeneratorNode ) )
            {
              throw new DataGridInternalException();
            }

            Debug.Assert( e.OldStartingIndex != e.NewStartingIndex, "An attempt was made to move a group to the same location." );
#if LOG
            Log.Assert( this, e.OldStartingIndex != e.NewStartingIndex, "An attempt was made to move a group to the same location." );
#endif

            this.HandleSameLevelGroupMove( m_firstItem, e );
          }
          break;
        case NotifyCollectionChangedAction.Remove:
          if( m_firstItem != null )
          {
            if( !( m_firstItem is GroupGeneratorNode ) )
            {
              throw new DataGridInternalException();
            }

            int remCount;
            int generatedRemCount;
            int removeIndex;
            List<DependencyObject> removedContainers = new List<DependencyObject>();
            GeneratorPosition remPos = this.HandleSameLevelGroupRemove( m_firstItem, out remCount, out generatedRemCount, out removeIndex, e, removedContainers );

            //there is no need to check if the parent node is expanded or not... since the first level of group cannot be collapsed.

            this.IncrementCurrentGenerationCount();

            this.SendRemoveEvent( remPos, removeIndex, remCount, generatedRemCount, removedContainers );
          }

          break;
        case NotifyCollectionChangedAction.Replace:
          throw new NotSupportedException( "Replace not supported at the moment on groups!!!" );
        //break;
        case NotifyCollectionChangedAction.Reset:
          //I'm forced to handle it specifically since the Panel will AUTOMATICALLY clear its children
          this.HandleGlobalItemsReset();
          break;
        default:
          throw new DataGridInternalException();
      }

    }
    void ICustomItemContainerGenerator.SetCurrentIndex( int newCurrentIndex )
    {
      // This method enables the possibility for the "ItemsHost" panels to set the current item of the 
      // DataGridControl. This is required for scenarios such as the CardflowItemsHost.
      this.EnsureNodeTreeCreated();

      GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( m_startNode, 0, 0 );

      //First, locate the item within the Generator.
      object newCurrentItem = nodeHelper.FindIndex( newCurrentIndex );

      if( newCurrentItem == null )
        throw new InvalidOperationException( "An attempt was made to access an item at an index that does not correspond to an item." );

      //Then, if the item is within an ItemsNode, check if it belongs to a detail
      ItemsGeneratorNode itemsNode = nodeHelper.CurrentNode as ItemsGeneratorNode;
      if( itemsNode != null )
      {
        int masterIndex;
        int detailIndex;
        int detailNodeIndex;

        DetailGeneratorNode detailNode = itemsNode.GetDetailNodeForIndex( newCurrentIndex - nodeHelper.Index, out masterIndex, out detailIndex, out detailNodeIndex );
        //If it belongs to a detail
        if( detailNode != null )
        {
          //call recursively the SetCurrentIndex method on the detail generator to ensure that if the item
          //belongs to a sub-generator of the detail generator, then the appropriate DataGridContext will get invoked.
          ICustomItemContainerGenerator detailGenerator = detailNode.DetailGenerator;
          detailGenerator.SetCurrentIndex( detailIndex );
          return;
        }
      }

      //If the item is not within an ItemsNode or not within a detail
      //then set it current within its context.
      m_dataGridContext.SetCurrentItemCore( newCurrentItem, false, false );
    }
    internal int GetStickyFooterCountForIndex(
      int index,
      bool areFootersSticky,
      bool areGroupFootersSticky )
    {
      int stickyFooterCount = 0;

      GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( m_startNode, 0, 0 );

      if( !nodeHelper.FindNodeForIndex( index ) )
      {
        // Unable to find the node, no sticky header for this node
        return 0;
      }

      GeneratorNode indexNode = nodeHelper.CurrentNode;

      ItemsGeneratorNode itemsGeneratorNode = indexNode as ItemsGeneratorNode;

      // We found an ItemsGeneratorNode
      if( itemsGeneratorNode != null )
      {
        GeneratorNode innerNode = itemsGeneratorNode.GetDetailNodeForIndex( index - nodeHelper.Index );

        DetailGeneratorNode detailNode = innerNode as DetailGeneratorNode;

        if( detailNode != null )
        {
          int detailFirstRealizedIndex = this.FindGlobalIndexForDetailNode( detailNode );
          int correctedIndex = index - detailFirstRealizedIndex;

          Debug.Assert( correctedIndex >= 0, "correctedIndex >= 0 .. 2" );
#if LOG
          Log.Assert( this, correctedIndex >= 0, "correctedIndex >= 0 .. 2" );
#endif

          stickyFooterCount +=
            detailNode.DetailGenerator.GetStickyFooterCountForIndex( correctedIndex,
                                                                     areFootersSticky,
                                                                     areGroupFootersSticky );
        }
      }

      // We want to find the HeaderFooterGeneratorNode for the container 
      // node. This is to find the footers for the container.
      nodeHelper.MoveToEnd();
      HeadersFootersGeneratorNode footersNode = nodeHelper.CurrentNode as HeadersFootersGeneratorNode;

      // There is no footers to generate if the item count of the node is 0.
      if( footersNode.ItemCount > 0 )
      {
        if( ( ( footersNode.Parent == null ) && ( areFootersSticky ) )
          || ( ( footersNode.Parent is GroupGeneratorNode ) && ( areGroupFootersSticky ) ) )
        {
          // We are generating sticky footers for a container that is already 
          // part of the footers, we need to handle it differently.
          stickyFooterCount += this.GetStickyFooterCountForNode( footersNode,
                                                                 nodeHelper.Index,
                                                                 index,
                                                                 ( footersNode != indexNode ) );
        }
      }

      // We must also find the bottom most footers for our level of detail and, if they need to be sticky,
      // we will generate the containers and add them the to list.
      HeadersFootersGeneratorNode bottomFootersNode = this.GetDetailFootersNode( nodeHelper );

      if( ( bottomFootersNode != null )
        && ( bottomFootersNode != footersNode )
        && ( bottomFootersNode.ItemCount > 0 )
        && ( areFootersSticky ) )
      {
        stickyFooterCount += this.GetStickyFooterCountForNode( bottomFootersNode, nodeHelper.Index );
      }

      return stickyFooterCount;
    }
    private GeneratorNode HandleSameLevelGroupAddition( GeneratorNode firstChild, out int countAdded, NotifyCollectionChangedEventArgs e )
    {

      Debug.Assert( ( ( firstChild.Parent == null ) || ( firstChild.Parent is GroupGeneratorNode ) ), "parent of the node should be a GroupGeneratorNode" );
#if LOG
      Log.Assert( this, ( ( firstChild.Parent == null ) || ( firstChild.Parent is GroupGeneratorNode ) ), "parent of the node should be a GroupGeneratorNode" );
#endif

      GeneratorNode newNodeChain = this.CreateGroupListFromCollection( e.NewItems, firstChild.Parent );

      countAdded = 0;
      if( newNodeChain != null )
      {
        int chainLength;
        GeneratorNodeHelper.EvaluateChain( newNodeChain, out countAdded, out chainLength );

        GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( firstChild, 0, 0 ); //do not care about index.

        //Advance to the first "Group" node (skip the GroupHEaders)
        while( !( nodeHelper.CurrentNode is GroupGeneratorNode ) )
        {
          if( !nodeHelper.MoveToNext() )
          {
            //if there are no items and no groups in the Group Node, then we will never find a GroupGeneratorNode...
            //However, the structure of the group/headers/footers/group headers/groups footers makes it so 
            //that inserting before the last item (footer/group footer) will place the group at the appropriate location.
            break;
          }
        }

        bool insertAfter = false;
        //If there is 0 group in the parent group, then this loop will exit without executing the control block once...
        for( int i = 0; i < e.NewStartingIndex; i++ )
        {
          if( !nodeHelper.MoveToNext() )
          {
            insertAfter = true;
          }
        }

        //if we are inserting past the end of the linked list level.
        if( insertAfter )
        {
          nodeHelper.InsertAfter( newNodeChain );
        }
        else
        {
          //we are inserting in the middle of the list
          nodeHelper.InsertBefore( newNodeChain );
        }


        //If the insertion point is the beginning, check that the node pointers are updated properly
        if( ( e.NewStartingIndex == 0 ) && ( ( firstChild.Parent == null ) ) )
        {
          if( m_startNode == m_firstItem )
          {
            m_startNode = newNodeChain;
          }
          m_firstItem = newNodeChain;
        }

      }

      return newNodeChain;
    }
    private void UpdateHeaders( IList headers )
    {

      //if there was no header prior this udpate.
      if( m_firstHeader == null )
      {
        //create the node(s) that would contain the headers
        m_firstHeader = this.CreateHeaders( headers );

        int count;
        int chainLength;
        GeneratorNodeHelper.EvaluateChain( m_firstHeader, out count, out chainLength );

        //if there was items present in the linked list.
        if( m_startNode != null )
        {
          //headers are automatically inserted at the beginning
          GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( m_startNode, 0, 0 );
          nodeHelper.InsertBefore( m_firstHeader );
        }
        else
        {
#if LOG
          Log.WriteLine( this, "UpdateHeaders - new tree" );
#endif
        }

        //set the start node as the first header node.
        m_startNode = m_firstHeader;
      }

      //If the m_firstHeader is not NULL, then there is nothing to do, since the Header node contains an  observable collection
      //which we monitor otherwise.
    }
    private void HandleItemAddition( GeneratorNode node, NotifyCollectionChangedEventArgs e )
    {
      GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( node, 0, 0 ); //index not important for now.

      node.AdjustItemCount( e.NewItems.Count );

      ItemsGeneratorNode itemsNode = node as ItemsGeneratorNode;
      if( itemsNode != null )
      {
        itemsNode.AdjustLeafCount( e.NewItems.Count );
        this.OffsetDetails( itemsNode, e.NewStartingIndex, e.NewItems.Count );
      }

      //if the node is totally expanded
      if( node.IsComputedExpanded )
      {
        nodeHelper.ReverseCalculateIndex();

        //invalidate the indexes
        this.IncrementCurrentGenerationCount();

        int startIndex = nodeHelper.Index + e.NewStartingIndex;
        GeneratorPosition addGenPos = this.GeneratorPositionFromIndex( startIndex );

        //and send notification message
        this.SendAddEvent( addGenPos, startIndex, e.NewItems.Count );
      }
    }
    private void UpdateFooters( IList footers )
    {

      //if there was no header prior this udpate.
      if( m_firstFooter == null )
      {
        //create the node(s) that would contain the footers
        m_firstFooter = this.CreateFooters( footers );

        //if there are no footers, then the m_firstFooter will remain null
        if( m_firstFooter != null )
        {
          int count;
          int chainLength;
          GeneratorNodeHelper.EvaluateChain( m_firstFooter, out count, out chainLength );

          //if there was items present in the linked list.
          if( m_startNode != null )
          {
            //since we called ClearFooters earlier, I can just go at the end of the list of items
            GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( m_startNode, 0, 0 );

            nodeHelper.MoveToEnd();
            nodeHelper.InsertAfter( m_firstFooter );
          }
          else
          {
            m_startNode = m_firstFooter;
#if LOG
            Log.WriteLine( this, "UpdateFooters - new tree" );
#endif
          }
        }
      }

      //If the m_firstHeader is not NULL, then there is nothing to do, since the Header node contains an  observable collection
      //which we monitor otherwise.
    }
    public object ItemFromIndex( int index )
    {
      object retval = null;

      this.EnsureNodeTreeCreated();

      GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( m_startNode, 0, 0 );

      retval = nodeHelper.FindIndex( index );

      return retval;
    }
    private GeneratorNode SetupInitialItemsNodes( out int addCount )
    {
      //TODO ( case 117275 ): Check the amount of time this is executed for a single grid being populated!
      GeneratorNode newItemNode = null;

      addCount = 0;

      this.RefreshGroupsCollection();

      //this function should only be called when the m_firstItem is null.
      if( m_groupsCollection != null )
      {
        newItemNode = this.CreateGroupListFromCollection( m_groupsCollection, null );
      }
      else
      {
        newItemNode = this.CreateStandaloneItemsNode();
      }

      //if the node is non-null, then that's because there was items in the collection
      if( newItemNode != null )
      {
        m_firstItem = newItemNode;

        int chainLength;
        GeneratorNodeHelper.EvaluateChain( m_firstItem, out addCount, out chainLength );

        //find the appropriate point to inject the Items nodes...
        if( m_startNode != null )
        {
          //if there is a footer node, then the insertion point is just before the footer node (if there is anything before!)
          if( m_firstFooter != null )
          {
            GeneratorNode originalPrevious = m_firstFooter.Previous;
            GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( m_firstFooter, 0, 0 ); //do not care about index!

            nodeHelper.InsertBefore( m_firstItem );

            if( originalPrevious == null ) //that means that the first footer is the first item
            {
              m_startNode = newItemNode;
            }
          }
          else if( m_firstHeader != null ) //if there is no footer but some headers, add it at the end.
          {
            GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( m_firstHeader, 0, 0 ); //do not care about index!

            nodeHelper.MoveToEnd();
            nodeHelper.InsertAfter( m_firstItem );
          }
          else
          {
            throw new DataGridInternalException(); //this case should not be possible: no header, no footers but there is a startNode
          }
        }
        else
        {
          m_startNode = m_firstItem;
#if LOG
          Log.WriteLine( this, "SetupInitialItemsNodes - new tree" );
#endif
        }
      }

      return newItemNode;
    }
    private void HandleHeaderFooterRemove( HeadersFootersGeneratorNode node, NotifyCollectionChangedEventArgs e )
    {
      bool itemsRemoved = false;

      node.AdjustItemCount( -e.OldItems.Count );

      //if the node is totally expanded
      if( node.IsComputedExpanded )
      {
        GroupGeneratorNode parentGroup = node.Parent as GroupGeneratorNode;

        int removeGenPosIndex;
        int removeIndex;

        foreach( object item in e.OldItems )
        {
          object realItem = ( parentGroup != null )
                              ? new GroupHeaderFooterItem( parentGroup.CollectionViewGroup, item )
                              : item;


          removeGenPosIndex = m_genPosToItem.IndexOf( realItem );
          GeneratorPosition removeGenPos;
          // If the value is -1, it means the Header/Footer was not realized when the remove occured.
          if( removeGenPosIndex != -1 )
          {
            removeIndex = m_genPosToIndex[ removeGenPosIndex ];
            removeGenPos = new GeneratorPosition( removeGenPosIndex, 0 );
          }
          else
          {
            //Since there is no way to get the item's index from the list of generated items, then
            //compute it based on the node's index and the event args parameters.

            GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( node, 0, 0 );
            nodeHelper.ReverseCalculateIndex();

            removeIndex = nodeHelper.Index + e.OldStartingIndex;

            removeGenPos = this.GeneratorPositionFromIndex( removeIndex );
          }

          List<DependencyObject> removedContainers = new List<DependencyObject>();
          this.RemoveGeneratedItems( node, realItem, removedContainers );

          this.SendRemoveEvent( removeGenPos, removeIndex, 1, removedContainers.Count, removedContainers );
          itemsRemoved = true;
        }

        this.IncrementCurrentGenerationCount( itemsRemoved );
      }
    }
    private GeneratorNode CreateGroupListFromCollection( IList collection, GeneratorNode parentNode )
    {
      GeneratorNode rootNode = null;
      GeneratorNode previousNode = null;
      GroupGeneratorNode actualNode = null;

      GeneratorNode childNode = null;
      int level = ( parentNode == null ) ? 0 : parentNode.Level + 1;
      GroupConfiguration groupConfig;
      bool initiallyExpanded;

      ObservableCollection<GroupDescription> groupDescriptions = DataGridContext.GetGroupDescriptionsHelper( m_collectionView );
      GroupConfigurationSelector groupConfigurationSelector = m_dataGridContext.GroupConfigurationSelector;


      foreach( CollectionViewGroup group in collection )
      {
        groupConfig = GroupConfiguration.GetGroupConfiguration( m_dataGridContext, groupDescriptions, groupConfigurationSelector, level, group );

        Debug.Assert( groupConfig != null, "groupConfig != null" );
#if LOG
        Log.Assert( this, groupConfig != null, "groupConfig != null" );
#endif

        if( groupConfig.UseDefaultHeadersFooters )
          groupConfig.AddDefaultHeadersFooters();

        initiallyExpanded = groupConfig.InitiallyExpanded;

        actualNode = ( GroupGeneratorNode )this.NodeFactory.CreateGroupGeneratorNode( group, parentNode, previousNode, null, groupConfig );
        m_groupNodeMappingCache.Add( group, actualNode );

        if( rootNode == null )
        {
          rootNode = actualNode;
        }

        previousNode = actualNode;

        actualNode.UIGroup = new Group( actualNode, group, m_dataGridContext.GroupLevelDescriptions, m_dataGridContext );

        //Independently if the Group is the bottom level or not, we need to setup GroupHeaders
        childNode = this.SetupGroupHeaders( groupConfig, actualNode );
        actualNode.Child = childNode;

        GeneratorNodeHelper childNodeHelper = new GeneratorNodeHelper( childNode, 0, 0 ); //do not care about index.
        childNodeHelper.MoveToEnd(); //extensibility, just in case SetupGroupHeaders() ever return a node list.


        IList<object> subItems = group.GetItems();

        //if the node newly created is not the bottom level
        if( !group.IsBottomLevel )
        {
          if( ( subItems != null ) && ( subItems.Count > 0 ) )
          {
            GeneratorNode subGroupsNode = this.CreateGroupListFromCollection( subItems as IList, actualNode );
            if( subGroupsNode != null )
            {
              childNodeHelper.InsertAfter( subGroupsNode );
            }
          }
        }
        else
        {
          //this is the bottom level, create an Items node
          GeneratorNode itemsNode = this.NodeFactory.CreateItemsGeneratorNode( subItems as IList, actualNode, null, null, this );
          if( itemsNode != null )
          {
            childNodeHelper.InsertAfter( itemsNode );
          }
        }

        childNodeHelper.InsertAfter( this.SetupGroupFooters( groupConfig, actualNode ) );
      }

      return rootNode;
    }
    private void UpdateGenPosToIndexList()
    {
#if LOG
      int count = m_genPosToIndex.Count;
      int previousIndex = -1;

      for( int i = 0; i < count; i++ )
      {
        int tempIndex = m_genPosToIndex[ i ];

        if( tempIndex >= previousIndex )
        {
          previousIndex = tempIndex;
        }
        else
        {
          Debug.Assert( false, "### none sequential index detected (before)" );
          Log.Assert( this, false, "### none sequential index detected (before)" );
          this.WriteStateInLog();
          break;
        }
      }
#endif
      //after the modification to have the item count stored "locally" in the DetailGeneratorNodes,
      //it becomes important to have the nodes updated when the layout of the items changes.
      if( m_masterToDetails.Count > 0 )
      {
        foreach( KeyValuePair<object, List<DetailGeneratorNode>> masterToDetails in m_masterToDetails )
        {
          foreach( DetailGeneratorNode detailNode in masterToDetails.Value )
          {
            detailNode.UpdateItemCount();
          }
        }
      }

      if( m_startNode != null )
      {
        GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( m_startNode, 0, 0 ); //index is 0, since i'm beginning at the start 
        //(and I want to reindex the whole list)

        DetailGeneratorNode cachedDetailNode = null;
        int cachedDetailNodeIndex = -1;
        int previousDetailIndex = -1;

        //loop through the realized items. By design they are present sequentially in the linked list, so I do not need to reset the GeneratorNodeHelper
        for( int i = 0; i < m_genPosToItem.Count; i++ )
        {
          object item = m_genPosToItem[ i ];

          DetailGeneratorNode detailNode = m_genPosToNode[ i ] as DetailGeneratorNode;
          if( detailNode == null )
          {
            //find the Item 
            int tmpIndex = nodeHelper.FindItem( item );
            if( tmpIndex != -1 )
            {
              //set the Index (new) for the item
              ItemsGeneratorNode itemsNode = nodeHelper.CurrentNode as ItemsGeneratorNode;
              if( itemsNode != null )
              {
                tmpIndex = nodeHelper.Index + itemsNode.IndexOf( item );
              }

              m_genPosToIndex[ i ] = tmpIndex;
            }
            else
            {
#if LOG
              if( item == null )
              {
                Log.WriteLine( this, "# UpdateGenPosToIndexList - item not found Dnull" );
              }
              else
              {
                Log.WriteLine( this, "# UpdateGenPosToIndexList - Item not found D" + item.GetHashCode() );
              }
#endif
              //a possible fix for this is to set the index of the "not found" element to the same index as previous item in the generated list...
              m_genPosToIndex[ i ] = ( i > 0 ) ? m_genPosToIndex[ i - 1 ] : 0;

              //item is not in the linked list... there is a problem, throw.
              //throw new DataGridInternalException();

            }
          }
          else //else is detailNode != null
          {
            if( cachedDetailNode != detailNode )
            {
              cachedDetailNodeIndex = this.FindGlobalIndexForDetailNode( detailNode );
              cachedDetailNode = detailNode;
              previousDetailIndex = -1;
            }
            int detailIndex = detailNode.DetailGenerator.IndexFromRealizedItem( item, previousDetailIndex + 1, out previousDetailIndex );
            m_genPosToIndex[ i ] = cachedDetailNodeIndex + detailIndex;
          }
        } //end for()
      }

#if LOG
      count = m_genPosToIndex.Count;
      previousIndex = -1;

      for( int i = 0; i < count; i++ )
      {
        int tempIndex = m_genPosToIndex[ i ];

        if( tempIndex >= previousIndex )
        {
          previousIndex = tempIndex;
        }
        else
        {
          Debug.Assert( false, "### none sequential index detected (after)" );
          Log.Assert( this, false, "### none sequential index detected (after)" );
          this.WriteStateInLog();
          break;
        }
      }
#endif
    }
    private GeneratorPosition HandleParentGroupRemove( GeneratorNode parent, out int countRemoved, out int genCountRemoved, out int removeIndex, NotifyCollectionChangedEventArgs e, IList<DependencyObject> removedContainers )
    {
      GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( parent, 0, 0 ); //do not care about index (for now).

      // start by moving to the first child... of the node (GroupHeaders node, most probably).
      // false parameter is to prevent skipping over a collapsed node (item count 0 )
      if( !nodeHelper.MoveToChild( false ) )
      {
        //could not advance to the child item so there is no items to be removed...
        throw new DataGridInternalException();
      }

      return this.HandleSameLevelGroupRemove( nodeHelper.CurrentNode, out countRemoved, out genCountRemoved, out removeIndex, e, removedContainers );
    }
    private void StartGenerator( GeneratorPosition startPos, GeneratorDirection direction )
    {
      if( this.Status == GeneratorStatus.GeneratingContainers )
      {
        throw new InvalidOperationException( "Cannot perform this operation while the generator is busy generating items" );
      }

      //set the GeneratorStatus to "Generating"
      m_generatorStatus = GeneratorStatus.GeneratingContainers;

      //Initialize the Direction
      m_generatorDirection = direction;

      //retrieve the Index for the GeneratorPosition retrieved
      m_generatorCurrentGlobalIndex = this.IndexFromGeneratorPosition( startPos );

      // case 117460: throw an exception if the GeneratorPosition is bad, but not if the itemcount is 0 
      //(and generator position maps to index 0 ).
      int itemCount = this.ItemCount;
      if( ( m_generatorCurrentGlobalIndex < 0 ) || ( ( itemCount > 0 ) && ( m_generatorCurrentGlobalIndex >= itemCount ) ) )
        throw new ArgumentOutOfRangeException( "startPos", "The specified start position is outside the range of the Generator content." );

      //case 117460: if the item count is 0, return without doing any check, GenerateNext will never process any content in that case.
      //Since the GeneratorNodeHelper is never created.
      if( itemCount == 0 )
        return;

      //and create a node helper that will assist us during the Generation process...
      m_generatorNodeHelper = new GeneratorNodeHelper( m_startNode, 0, 0 ); // start index is always 0

      //position the GeneratorNodeHelper to the appropriate node
      if( !m_generatorNodeHelper.FindNodeForIndex( m_generatorCurrentGlobalIndex ) ) //find index?!?!
      {
        //there was a problem moving the Node helper... 
        throw new DataGridInternalException();
      }

      //Calculate the offset
      m_generatorCurrentOffset = m_generatorCurrentGlobalIndex - m_generatorNodeHelper.Index;

      ItemsGeneratorNode itemsNode = m_generatorNodeHelper.CurrentNode as ItemsGeneratorNode;
      if( itemsNode != null )
      {
        m_generatorCurrentDetail = itemsNode.GetDetailNodeForIndex( m_generatorCurrentOffset, out m_generatorCurrentOffset, out m_generatorCurrentDetailIndex, out m_generatorCurrentDetailNodeIndex );
      }

      if( m_generatorCurrentDetail != null )
      {
        m_generatorCurrentDetailDisposable = ( ( IItemContainerGenerator )m_generatorCurrentDetail.DetailGenerator ).StartAt( m_generatorCurrentDetail.DetailGenerator.GeneratorPositionFromIndex( m_generatorCurrentDetailIndex ), direction, true );
      }
    }
    private GeneratorPosition HandleSameLevelGroupRemove( GeneratorNode firstChild, out int countRemoved, out int genCountRemoved, out int removeIndex, NotifyCollectionChangedEventArgs e, IList<DependencyObject> removedContainers )
    {
      GeneratorPosition retval;

      countRemoved = 0;
      genCountRemoved = 0;

      GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( firstChild, 0, 0 );
      nodeHelper.ReverseCalculateIndex();

      //Advance to the first "Group" node (skip the GroupHEaders)
      while( !( nodeHelper.CurrentNode is GroupGeneratorNode ) )
      {
        if( !nodeHelper.MoveToNext() )
          throw new DataGridInternalException();
      }

      //then move up to the removal start point.
      if( !nodeHelper.MoveToNextBy( e.OldStartingIndex ) )
      {
        throw new DataGridInternalException();
      }

      GroupGeneratorNode startNode = nodeHelper.CurrentNode as GroupGeneratorNode;
      removeIndex = -1;

      //Only fetch the index if the group itself is not "collapsed" or under a collapsed group already
      if( ( startNode.IsExpanded == startNode.IsComputedExpanded ) && ( startNode.ItemCount > 0 ) )
      {
        removeIndex = nodeHelper.Index;
        retval = this.GeneratorPositionFromIndex( removeIndex );
      }
      else
      {
        retval = new GeneratorPosition( -1, 1 );
      }

      //retrieve the generator position for the first item to remove.

      this.ProcessGroupRemoval( startNode, e.OldItems.Count, true, out countRemoved );

      //Clean the chain "isolated" previously
      this.NodeFactory.CleanGeneratorNodeTree( startNode );

      if( removeIndex != -1 )
      {
        //remove the appropriate 
        genCountRemoved = this.RemoveGeneratedItems( removeIndex, removeIndex + countRemoved - 1, removedContainers );
      }

      return retval;
    }
    private void HandleDetailReset( object masterItem, DetailGeneratorNode detailNode )
    {
      GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( m_startNode, 0, 0 );
      int masterIndex = nodeHelper.FindItem( masterItem );

      // -1 means either taht the master item is below a collapsed group node, or that the item does not exists, validate.
      if( masterIndex == -1 )
      {
        nodeHelper = new GeneratorNodeHelper( m_startNode, 0, 0 );
        if( !nodeHelper.Contains( masterItem ) )
          throw new DataGridInternalException();
      }

      ItemsGeneratorNode masterNode = nodeHelper.CurrentNode as ItemsGeneratorNode;

      Debug.Assert( masterNode != null, "masterNode != null" );
#if LOG
      Log.Assert( this, masterNode != null, "masterNode != null" );
#endif

      //start index will be ignored later on if the masterIndex is -1!!
      int startIndex = nodeHelper.Index + masterNode.IndexOf( masterItem ) + 1; //details start a master index + 1

      List<DetailGeneratorNode> detailsForMaster = null;

      //edge case, it is possible to receive a Reset from Floating details!
      if( masterNode.Details == null )
      {
        //check for floating details, if not present, throw, this is an abnormal case.
        if( !m_floatingDetails.Contains( masterItem ) )
        {
          throw new DataGridInternalException();
        }
      }
      else
      {
        masterNode.Details.TryGetValue( masterNode.Items.IndexOf( masterItem ), out detailsForMaster );

        Debug.Assert( detailsForMaster != null, "detailsForMaster != null" );
#if LOG
        Log.Assert( this, detailsForMaster != null, "detailsForMaster != null" );
#endif
      }

      if( detailsForMaster != null )
      {
        //this is required to ensure that if the details that resets is not the first one, the index is calculated appropriatly.
        foreach( DetailGeneratorNode node in detailsForMaster )
        {
          if( node == detailNode )
          {
            break;
          }
          else
          {
            startIndex += node.ItemCount;
          }
        }

        //if there were 'items' in the detail node, process the remove of them
        int oldDetailCount = detailNode.ItemCount;
        if( oldDetailCount > 0 )
        {
          int endIndex = startIndex + oldDetailCount - 1; //last detail index

          GeneratorPosition removeGenPos = ( masterIndex != -1 )
            ? this.GeneratorPositionFromIndex( startIndex )
            : new GeneratorPosition( -1, 1 );

          int genRemCount = 0;

          List<DependencyObject> removedContainers = new List<DependencyObject>();

          //this has no uses if the masterIndex is -1 ( collapsed master item )
          if( masterIndex != -1 )
          {
            genRemCount = this.RemoveGeneratedItems( startIndex, endIndex, removedContainers );
          }

          masterNode.AdjustItemCount( -oldDetailCount );

          this.IncrementCurrentGenerationCount();

          //this has no uses if the masterIndex is -1 ( collapsed master item )
          if( masterIndex != -1 )
          {
            this.SendRemoveEvent( removeGenPos, masterIndex + 1, oldDetailCount, genRemCount, removedContainers );
          }
        }

        detailNode.UpdateItemCount();

        int newDetailCount = detailNode.ItemCount;
        if( newDetailCount > 0 )
        {
          GeneratorPosition addGenPos = new GeneratorPosition( -1, 1 );

          //this has no uses if the masterIndex is -1 ( collapsed master item )
          if( masterIndex != -1 )
          {
            addGenPos = this.GeneratorPositionFromIndex( startIndex );
          }

          masterNode.AdjustItemCount( newDetailCount );

          this.IncrementCurrentGenerationCount();

          //this has no uses if the masterIndex is -1 ( collapsed master item )
          if( masterIndex != -1 )
          {
            this.SendAddEvent( addGenPos, masterIndex + 1, newDetailCount );
          }
        }
      }
    }
    private GroupGeneratorNode ProcessGroupRemoval(
      GeneratorNode startNode,
      int removeCount,
      bool updateGroupNodeMappingCache,
      out int countRemoved )
    {

      Debug.Assert( removeCount != 0, "remove count cannot be 0" );
#if LOG
      Log.Assert( this, removeCount != 0, "remove count cannot be 0" );
#endif

      GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( startNode, 0, 0 );//index not important.
      GroupGeneratorNode parentGroup = startNode.Parent as GroupGeneratorNode;
      int i = 0;

      countRemoved = 0;

      do
      {
        GroupGeneratorNode group = nodeHelper.CurrentNode as GroupGeneratorNode;

        if( updateGroupNodeMappingCache )
        {
          m_groupNodeMappingCache.Remove( group.CollectionViewGroup );
        }

        Debug.Assert( group != null, "node to be removed must be a GroupGeneratorNode" );
#if LOG
        Log.Assert( this, group != null, "node to be removed must be a GroupGeneratorNode" );
#endif

        //add the total number of child to the count of items removed.
        countRemoved += group.ItemCount;

        i++;

        if( i < removeCount )
        {
          if( !nodeHelper.MoveToNext() )
          {
            //could not advance to the last item to be removed...

            throw new DataGridInternalException();
          }
        }
      }
      while( i < removeCount );

      //disconnect the node chain to be removed from the linked list.
      GeneratorNode previous = startNode.Previous;
      GeneratorNode next = nodeHelper.CurrentNode.Next;

      if( next != null )
      {
        next.Previous = previous;
      }

      if( previous != null )
      {
        previous.Next = next;
      }

      //if the first node removed was the first child of its parent
      if( ( parentGroup != null ) && ( parentGroup.Child == startNode ) )
      {
        //set the next in line after the chain to be removed as the first child
        parentGroup.Child = next;
      }

      //break the link between the chain and its same-level siblings.
      nodeHelper.CurrentNode.Next = null;
      startNode.Previous = null;

      //Here, I need a special handling case... If I remove the first group node, I need to set a new firstItem
      if( startNode == m_firstItem )
      {
        if( next != m_firstFooter )
        {
          m_firstItem = next;
        }
        else
        {
          m_firstItem = null;
        }

        if( m_startNode == startNode )
        {
          m_startNode = next;
        }
      }

      Debug.Assert( nodeHelper.CurrentNode is GroupGeneratorNode, "last node is not a GroupGeneratorNode" );
#if LOG
      Log.Assert( this, nodeHelper.CurrentNode is GroupGeneratorNode, "last node is not a GroupGeneratorNode" );
#endif

      return ( GroupGeneratorNode )nodeHelper.CurrentNode;
    }
    void IDataGridContextVisitable.AcceptVisitor( int minIndex, int maxIndex, IDataGridContextVisitor visitor, DataGridContextVisitorType visitorType, bool visitDetails, out bool visitWasStopped )
    {
      visitWasStopped = false;

      if( m_startNode == null )
        return;

      GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( m_startNode, 0, 0 );
      nodeHelper.ProcessVisit( m_dataGridContext, minIndex, maxIndex, visitor, visitorType, visitDetails, out visitWasStopped );
    }
    public int IndexFromItem( object item )
    {
      int retval = -1;

      if( item != null )
      {
        this.EnsureNodeTreeCreated();

        if( m_startNode != null )
        {
          //if item is generated
          int generatedIndex = this.FindFirstGeneratedIndexForLocalItem( item );
          if( generatedIndex != -1 )
          {
            retval = m_genPosToIndex[ generatedIndex ];
          }
          //if the item is not generated
          else
          {
            GeneratorNodeHelper nodeHelper = new GeneratorNodeHelper( m_startNode, 0, 0 );
            //Note: Under that specific case, I do not want to search through collapsed group nodes... Effectivelly, an item "below" a collapsed group node 
            // Have no index as per the "item to index" interface of the generator. 
            retval = nodeHelper.FindItem( item );
          }
        }
      }

      return retval;
    }