Journal.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ Net / Net / 3.5.50727.3053 / DEVDIV / depot / DevDiv / releases / Orcas / SP / wpf / src / Framework / System / Windows / Navigation / Journal.cs / 1 / Journal.cs

                            //---------------------------------------------------------------------------------------------- 
//
// 
//      Copyright (C) Microsoft Corporation.    All rights reserved.
//  
//
// Description: 
//      Implements the Avalon Journal Object 
//
//      The WCP application journal enables users to retrace their steps backward 
//      and forward in a linear navigation sequence. Whether a navigation application
//      is hosted in the browser or in a standalone NavigationWindow, each navigation
//      is persisted in the journal, and can be revisited in a linear sequence by
//      using the Forward and Back buttons. An application can have multiple 
//      NavigationWindows. Each NavigationWindow has its own Journal.
// 
//      The Windows Client Platform will also provide some value adds over the 
//      current journaling behavior. Developers will be able to add their own journal entries,
//      and to remove entries from the journal (within their own application). 
//
// History:
//  07/30/2001:  t-ypchen        Created
//  06/11/2003:  kusumav         Matched spec at http://avalon/app/Journalling/Journaling.doc 
//
// Copyright (C) 2003 by Microsoft Corporation.  All rights reserved. 
// 
//---------------------------------------------------------------------------------------------
using System; 
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.Serialization; 
using System.Windows.Threading;
using System.Security; 
using System.Security.Permissions; 

using MS.Internal; 
using MS.Internal.AppModel;
using MS.Utility;
using System.ComponentModel;
 
// Since we disable PreSharp warnings in this file, we first need to disable warnings about unknown message numbers and unknown pragmas.
#pragma warning disable 1634, 1691 
 
namespace System.Windows.Navigation
{ 

    /// 
    /// Journal object is provided for each NavigationWindow for linear
    /// navigations in history. Developers can also add or remove entries 
    /// from the journal.
    ///  
    /// http://avalon/app/Journalling/Journaling.doc 
    [Serializable]
    internal sealed class Journal : ISerializable 
    {
        /// 
        /// Construct a new Journal instance.
        ///  
        internal Journal()
        { 
            _Initialize(); 
        }
 
        [SecurityPermissionAttribute(SecurityAction.LinkDemand, SerializationFormatter = true)]
        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("_journalEntryList", _journalEntryList); 
            info.AddValue("_currentEntryIndex", _currentEntryIndex);
            info.AddValue("_journalEntryId", _journalEntryId); 
        } 

        ///  
        /// Ctor for ISerializable implementation
        /// 
        private Journal(SerializationInfo info, StreamingContext context)
        { 
            _Initialize();
            _journalEntryList = (List)info.GetValue("_journalEntryList", typeof(List)); 
            _currentEntryIndex = info.GetInt32("_currentEntryIndex"); 
            _uncommittedCurrentIndex = _currentEntryIndex;
            _journalEntryId = info.GetInt32("_journalEntryId"); 
        }

        //-----------------------------------------------------
        // 
        //  Internal Properties
        // 
        //----------------------------------------------------- 
        #region Internal Properties
 
        #region Operator Overloads
        /// 
        /// Gets the journal entry at the specified index.
        ///  
        /// The zero-based index of the journal entry to get or set.
        /// The journal entry at the specified index. 
        // 

 



 
        internal JournalEntry this[int index]
        { 
            get 
            {
                return _journalEntryList[index]; 
            }
        }
        #endregion Operator Overloads
 
        // Total number of entries in the journal including non-navigable entries
        internal int TotalCount 
        { 
            get
            { 
                return _journalEntryList.Count;
            }
        }
 
        /// 
        /// Current index - could be in the middle of the list when in history 
        /// navigation. Else will be at the end of the list, a new entry will be 
        /// added at this index for normal navigations
        ///  
        internal int CurrentIndex
        {
            get
            { 
                return _currentEntryIndex;
            } 
        } 

        ///  
        /// Get the current journal entry.
        /// 
        internal JournalEntry CurrentEntry
        { 
            get
            { 
                if (_currentEntryIndex >= 0 && _currentEntryIndex < TotalCount) 
                {
                    return _journalEntryList[_currentEntryIndex]; 
                }
                else
                {
                    return null; 
                }
            } 
        } 

        internal bool HasUncommittedNavigation 
        {
            get { return _uncommittedCurrentIndex != _currentEntryIndex; }
        }
 
        /// 
        /// The getter for the BackStack 
        ///  
        /// Gets the BackStack
        internal JournalEntryStack BackStack 
        {
            get
            {
                return _backStack; 
            }
        } 
 
        /// 
        /// The getter for the ForwardStack 
        /// 
        /// Gets the ForwardStack
        internal JournalEntryStack ForwardStack
        { 
            get
            { 
                return _forwardStack; 
            }
        } 

        /// 
        /// Check if there are journal entries for going back.
        ///  
        internal bool CanGoBack
        { 
            get 
            {
                return GetGoBackEntry() != null; 
            }
        }

        ///  
        /// Check if there are journal entries for going forward.
        ///  
        internal bool CanGoForward 
        {
            get 
            {
                int index;
                GetGoForwardEntryIndex(out index);
                return index != -1; 
            }
        } 
 
        /// 
        /// Returns a journal version used to invalidate old enumerators after journal data changes 
        /// 
        /// Current journal version
        internal int Version
        { 
            get
            { 
                return _version; 
            }
        } 

        internal JournalEntryFilter Filter
        {
            get { return _filter; } 
            set
            { 
                _filter = value; 
                BackStack.Filter = _filter;
                ForwardStack.Filter = _filter; 
            }
        }

        #endregion Internal Properties 

        //------------------------------------------------------ 
        // 
        //  Internal Events
        // 
        //-----------------------------------------------------

        #region Internal Events
        ///  
        /// Raised when the contents of the BackStack or ForwardStack changes.
        /// Note that this doesn't always mean CanGoBack/CanGoForward has changed. 
        ///  
        internal event EventHandler BackForwardStateChange
        { 
            add { _backForwardStateChange += value; }
            remove { _backForwardStateChange -= value; }
        }
 
        [NonSerialized()]
        EventHandler _backForwardStateChange; 
        #endregion 

        //------------------------------------------------------ 
        //
        //  Internal Methods
        //
        //------------------------------------------------------ 

        #region Internal Methods 
 
        /// 
        /// Remove the top JournalEntry from back entry 
        /// 
        // Not a true "remove"
        internal JournalEntry RemoveBackEntry()
        { 
            Debug.Assert(ValidateIndexes());
            int index = _currentEntryIndex; // start from current but do not change it 
            do 
            {
                if (--index < 0) 
                {
                    return null;
                }
            } while (IsNavigable(_journalEntryList[index]) == false); 
            JournalEntry removedEntry = RemoveEntryInternal(index);
            Debug.Assert(ValidateIndexes()); 
            UpdateView(); 
            return removedEntry;
        } 

        /// 
        /// Ensures current data about the current page is stored in the journal.
        /// This either updates an existing entry or adds a new one. 
        /// 
        ///  
        internal void UpdateCurrentEntry(JournalEntry journalEntry) 
        {
            if (journalEntry == null) 
            {
                throw new ArgumentNullException("journalEntry");
            }
            Debug.Assert(journalEntry.ContentId != 0); 
            Debug.Assert(!(journalEntry.IsAlive() && journalEntry.JEGroupState.JournalDataStreams != null),
                "Keep-alive content state should not be serialized."); 
 
            if (_currentEntryIndex > -1 && _currentEntryIndex < TotalCount)
            { 
                // update existing entry using the old entry's index.
                // Note: the new entry can be for a different NavigationService.
                JournalEntry oldEntry = _journalEntryList[_currentEntryIndex];
                journalEntry.Id = oldEntry.Id; 
                _journalEntryList[_currentEntryIndex] = journalEntry;
            } 
            else 
            {
                // add new entry to the front 
                journalEntry.Id = ++_journalEntryId;
                _journalEntryList.Add(journalEntry);
            }
            _version++; 

            // If the next navigation is not #fragment or CustomContentState, this entry should be 
            // remembered as the "exit" entry for the group, so when navigating back to the same 
            // page, it will be shown. (It is not necessarily the last one in the group.)
            // Journal filtering will hide all other entries while at another page (different 
            // NavigationService.Content object).
            journalEntry.JEGroupState.GroupExitEntry = journalEntry;
        }
 
        internal void RecordNewNavigation()
        { 
            Invariant.Assert(ValidateIndexes()); 
            Debug.Assert(_uncommittedCurrentIndex == _currentEntryIndex,
                "This method should be called only in steady state."); 

            // moves _currentEntryIndex forward
            // clear forward entries if necessary
 
            _currentEntryIndex++;
            _uncommittedCurrentIndex = _currentEntryIndex; 
 
            if (!ClearForwardStack())
            { 
                // If ClearForwardStack() didn't change the journal, UpdateView() needs to be
                // called here to enable the Back button.
                UpdateView();
            } 
        }
 
        internal bool ClearForwardStack() 
        {
            Debug.Assert(ValidateIndexes()); 

            if (_currentEntryIndex >= TotalCount)
                return false; // nothing to do
 
            if(_uncommittedCurrentIndex > _currentEntryIndex)
                throw new InvalidOperationException(SR.Get(SRID.InvalidOperation_CannotClearFwdStack)); 
 
            _journalEntryList.RemoveRange(_currentEntryIndex, _journalEntryList.Count - _currentEntryIndex);
            UpdateView(); 
            return true;
        }

        internal void CommitJournalNavigation(JournalEntry navigated) 
        {
            NavigateTo(navigated); 
        } 

        internal void AbortJournalNavigation() 
        {
            _uncommittedCurrentIndex = _currentEntryIndex;
            UpdateView();
        } 

        ///  
        /// Get the previous journal entry without changing any indexes. 
        /// 
        /// Null if we cannot go back, otherwise the journal entry on the top of the back stack 
        internal JournalEntry BeginBackNavigation()
        {
            Invariant.Assert(ValidateIndexes());
 
            int index;
            JournalEntry journalEntry = GetGoBackEntry(out index); 
            if (journalEntry == null) 
                throw new InvalidOperationException(SR.Get(SRID.NoBackEntry));
            _uncommittedCurrentIndex = index; 
            UpdateView();
            if (_uncommittedCurrentIndex == _currentEntryIndex)
                return null; // See BeginForwardNavigation() for explanation of this special case.
            return journalEntry; 
        }
 
        internal JournalEntry BeginForwardNavigation() 
        {
            Invariant.Assert(ValidateIndexes()); 

            int fwdEntryIndex;

            GetGoForwardEntryIndex(out fwdEntryIndex); 
            if (fwdEntryIndex == -1)
                throw new InvalidOperationException(SR.Get(SRID.NoForwardEntry)); 
 
            _uncommittedCurrentIndex = fwdEntryIndex;
            UpdateView(); 

            if (fwdEntryIndex == _currentEntryIndex)
            {
                // this is a special case where the user BeginBackNavigation() was called but not allowed to finish 
                // before BeginForwardNavigation() was called.
                // Note that _uncommittedCurrentIndex may be less than _currentEntryIndex-1 at this 
                // point. That's because there might be non-navigable entries between the two indexes... 
                // Returning null indicates to the caller that it should stop any current navigation
                // and remain at the current page. If reloading of the current page were allowed, 
                // its controls' state would be lost.
                return null;
            }
 
            return _journalEntryList[fwdEntryIndex];
        } 
 
        /// 
        /// For jump navigation this determines if it is a backwards or forwards navigation 
        /// 
        internal NavigationMode GetNavigationMode(JournalEntry entry)
        {
            int index = _journalEntryList.IndexOf(entry); 

            if (index <= _currentEntryIndex) 
            { 
                // If index = _currentEntryIndex it means the application is being navigated back to
                // in the browser.  The browser has just loaded the journal and is restoring the 
                // current page.  This would also work if we chose "forward" but it must be one of the
                // two so that NavigationService will complete the navigation with CommitJournalNavigation()
                return NavigationMode.Back;
            } 
            else
            { 
                return NavigationMode.Forward; 
            }
        } 

        internal void NavigateTo(JournalEntry target)
        {
            Debug.Assert(IsNavigable(target), "target must be navigable"); 
            Debug.Assert(ValidateIndexes());
 
            int index = _journalEntryList.IndexOf(target); 

            // When navigating back to a page which contains a previously navigated frame a 
            // saved journal entry is replayed to restore the frame’s location, in many cases
            // this entry is not in the journal.
            if (index > -1)
            { 
                _currentEntryIndex = index;
                _uncommittedCurrentIndex = _currentEntryIndex; 
                UpdateView(); 
            }
        } 

        internal int FindIndexForEntryWithId(int id)
        {
            // Search the list 
            for (int i = 0; i < TotalCount; i++)
            { 
                if (this[i].Id == id) 
                {
                    return i; 
                }
            }

            // Didn't find it 
            return -1;
        } 
 
        // This is only called from ApplicationProxyInternal.GetSaveHistoryBytes when
        // we are persisting the entire journal; we only do that when we're quitting. 
        // [new] Also when navigating a Frame that has its own journal.
        //

 
        internal void PruneKeepAliveEntries()
        { 
            for (int i = TotalCount - 1; i >= 0; --i) 
            {
                JournalEntry je = _journalEntryList[i]; 
                if (je.IsAlive())
                {
                    RemoveEntryInternal(i);
                } 
                else
                { 
                    Debug.Assert(je.GetType().IsSerializable); 
                    // There can be keep-alive JEs creates for child frames.
                    DataStreams jds = je.JEGroupState.JournalDataStreams; 
                    if (jds != null)
                    {
                        jds.PrepareForSerialization();
                    } 

                    if (je.RootViewerState != null) 
                    { 
                        je.RootViewerState.PrepareForSerialization();
                    } 
                }
            }
        }
 
        ///  The caller is responsible for calling UpdateView(). 
        internal JournalEntry RemoveEntryInternal(int index) 
        { 
            Debug.Assert(index < TotalCount && index >= 0, "Invalid index passed to RemoveEntryInternal");
            Debug.Assert(_uncommittedCurrentIndex == _currentEntryIndex, 
                "This method should be called only in steady state.");

            JournalEntry theEntry = _journalEntryList[index];
            Debug.Assert(theEntry != null, "Journal list state is messed up"); 

            // Increase version always, see note above the data member declaration 
            _version++; 

            _journalEntryList.RemoveAt(index); 
            if (_currentEntryIndex > index)
            {
                _currentEntryIndex--;
            } 
            if (_uncommittedCurrentIndex > index)
            { 
                _uncommittedCurrentIndex--; 
            }
 
            return theEntry;
        }

        internal void RemoveEntries(Guid navSvcId) 
        {
            for (int i = TotalCount - 1; i >= 0; i--) 
            { 
                // The entry at _currentEntryIndex is just a placeholder. It should not be deleted.
                // Otherwise, the following entry (first one in the "forward stack") will get overwritten 
                // if a Back navigation occurs next.
                if (i != _currentEntryIndex)
                {
                    JournalEntry entry = _journalEntryList[i]; 
                    if (entry.NavigationServiceId == navSvcId)
                    { 
                        RemoveEntryInternal(i); 
                    }
                } 
            }

            UpdateView();
        } 

        //[IsKeepAlive() moved to NavigationService.IsContentKeepAlive()] 
 
        internal void UpdateView()
        { 
            BackStack.OnCollectionChanged();
            ForwardStack.OnCollectionChanged();
            if (_backForwardStateChange != null)
            { 
                _backForwardStateChange(this, EventArgs.Empty);
            } 
        } 

        ///  Returns the entry the GoBack command would navigate to; null/-1 if can't go back.  
        internal JournalEntry GetGoBackEntry(out int index)
        {
            for (index = _uncommittedCurrentIndex - 1; index >= 0; index--)
            { 
                JournalEntry je = _journalEntryList[index];
                if (IsNavigable(je)) 
                { 
                    return je;
                } 
            }
            return null; // and index=-1
        }
        internal JournalEntry GetGoBackEntry() 
        {
            int unused; 
            return GetGoBackEntry(out unused); 
        }
 
        /// 
        /// Returns the index of the entry the GoForward command would navigate to; -1 if can't
        /// go forward.
        ///  
        /// 
        /// This funtion is not symmetric to GetGoBackEntry() becaue of the special case when 
        /// _currentEntryIndex=TotalCount and _uncommittedCurrentIndex=TotalCount-1. Then there is 
        /// no JournalEntry object to return (but fwd navigation is allowed--to the current page).
        ///  
        internal void GetGoForwardEntryIndex(out int index)
        {
            Debug.Assert(ValidateIndexes());
 
            // Special case: _uncommittedCurrentIndex=_currentEntryIndex=TotalCount-1.
            // Then we can't go fwd. But if _currentEntryIndex=TotalCount, we can. 
            // See also the special case in BeginForwardNavigation(). 
            index = _uncommittedCurrentIndex;
            do { 
                index++;
                if (index == _currentEntryIndex)
                {
                    return; 
                }
                if (index >= TotalCount) 
                { 
                    index = -1;
                    return; 
                }
            } while (!IsNavigable(_journalEntryList[index]));
        }
 
        #endregion Internal Methods
 
        //----------------------------------------------------- 
        //
        //  Private Methods 
        //
        //------------------------------------------------------

        #region Private Methods 

        ///  Checks that the internal indices are not out of range. If an index is equal 
        /// to TotalCount, it is valid, but there is no JournalEntry created yet (for the current page). 
        /// 
        private bool ValidateIndexes() 
        {
            return _currentEntryIndex >= 0 && _currentEntryIndex <= TotalCount
                && _uncommittedCurrentIndex >= 0 && _uncommittedCurrentIndex <= TotalCount;
        } 

        private void _Initialize() 
        { 
            _backStack = new JournalEntryBackStack(this);
            _forwardStack = new JournalEntryForwardStack(this); 
        }

        internal bool IsNavigable(JournalEntry entry)
        { 
            if (entry == null)
                return false; 
            // Fallback to entry.IsNavigable if the Filter hasn't been specified 
            return (Filter != null) ? Filter(entry) : entry.IsNavigable();
        } 
        #endregion

        //-----------------------------------------------------
        // 
        //  Private Fields
        // 
        //----------------------------------------------------- 

        #region Private Fields 

        private JournalEntryFilter  _filter;

        JournalEntryBackStack       _backStack; 
        JournalEntryForwardStack    _forwardStack;
 
        // This is where we get the id we assign to all JournalEntries. 
        // It will be incremented each time.
        // This is stored in WINDOWDATA structure of the browser's travellog. Trident uses it to 
        // identify frame windows and decide if a frame journal entry is invocable or not. We use it
        // to identify the JournalEntry which has the NavigationService Guid to identify navigable frame
        // entries in the current context. When the travelentry is invoked we use this id to find
        // the JournalEntry to navigate it to. Since we don't explicitly remove the entry from the browser's 
        // travellog when it is removed from the internal Avalon journal, we need to keep this id
        // unique so we can respond correctly to the CanInvokeEntry calls from the browser. As such 
        // this id needs to be serialized so we can continue to assign unique numbers to each journal entry 
        // if we navigate away and back to the avalon app in the journal
        // 
        // ISSUE: Multiple browser applications activated in the same browser window (incl. multiple
        // instances of the same app) need to also use unique ids. Otherwise they can get mixed up.
        // This can also happen when opening a new window from the current one (Ctrl+N). Then the
        // TravelLog is copied. 
        //   Unfortunately, IE does not distinguish the entries of multiple instances of the same
        // DocObject when making calls on ITravelLogClient. It gives us only a DWORD for the id 
        // ('dwWindowID'). Attempts to ensure a globally unique instance id across all PresentationHost 
        // instances proved impractical due to restricted access rights. The solution here is to use
        // the system tick count as an initial value and keep incrementing it. This should be good 
        // enough in all normal usage scenarios.
        private int _journalEntryId = MS.Win32.SafeNativeMethods.GetTickCount();

        private List _journalEntryList = new List(); 
        private int _currentEntryIndex = 0;
 
        // This index is used to support the case where the back/forward is called multiple times 
        // without letting the first navigation finish loading.  For example if the page is at 'C'
        // and the back stack contains 'b','a' and Back() is called twice the user should end up at 
        // 'a'. Navigation to 'b' starts but is canceled before it finishes when navigation to 'a' begins.
        private int _uncommittedCurrentIndex = 0;

        // Incremented everytime a journal entry is added/removed/updated. The enumerator 
        // operation will then be invalidated since the list it was enumerating over has now
        // changed. This is the standard implementation used by the .Net ArrayList enumerator too. 
        // We could optimize for the case for when the changes happen at an index greater than 
        // the enumerator index (enumerator would do this check against a lastDirtyIndex that the
        // journal would maintain). But this will be bad if we decided to implement ICollection later 
        // since we would then export the Count which would need to be invalidated as well.
        // Also if the enumerator user maintains some kind of count of entries he is interested in
        // then the index/count would be invalidated.
        private int _version; 

        #endregion 
    } 

    internal delegate bool JournalEntryFilter(JournalEntry entry); 
}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
//---------------------------------------------------------------------------------------------- 
//
// 
//      Copyright (C) Microsoft Corporation.    All rights reserved.
//  
//
// Description: 
//      Implements the Avalon Journal Object 
//
//      The WCP application journal enables users to retrace their steps backward 
//      and forward in a linear navigation sequence. Whether a navigation application
//      is hosted in the browser or in a standalone NavigationWindow, each navigation
//      is persisted in the journal, and can be revisited in a linear sequence by
//      using the Forward and Back buttons. An application can have multiple 
//      NavigationWindows. Each NavigationWindow has its own Journal.
// 
//      The Windows Client Platform will also provide some value adds over the 
//      current journaling behavior. Developers will be able to add their own journal entries,
//      and to remove entries from the journal (within their own application). 
//
// History:
//  07/30/2001:  t-ypchen        Created
//  06/11/2003:  kusumav         Matched spec at http://avalon/app/Journalling/Journaling.doc 
//
// Copyright (C) 2003 by Microsoft Corporation.  All rights reserved. 
// 
//---------------------------------------------------------------------------------------------
using System; 
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.Serialization; 
using System.Windows.Threading;
using System.Security; 
using System.Security.Permissions; 

using MS.Internal; 
using MS.Internal.AppModel;
using MS.Utility;
using System.ComponentModel;
 
// Since we disable PreSharp warnings in this file, we first need to disable warnings about unknown message numbers and unknown pragmas.
#pragma warning disable 1634, 1691 
 
namespace System.Windows.Navigation
{ 

    /// 
    /// Journal object is provided for each NavigationWindow for linear
    /// navigations in history. Developers can also add or remove entries 
    /// from the journal.
    ///  
    /// http://avalon/app/Journalling/Journaling.doc 
    [Serializable]
    internal sealed class Journal : ISerializable 
    {
        /// 
        /// Construct a new Journal instance.
        ///  
        internal Journal()
        { 
            _Initialize(); 
        }
 
        [SecurityPermissionAttribute(SecurityAction.LinkDemand, SerializationFormatter = true)]
        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("_journalEntryList", _journalEntryList); 
            info.AddValue("_currentEntryIndex", _currentEntryIndex);
            info.AddValue("_journalEntryId", _journalEntryId); 
        } 

        ///  
        /// Ctor for ISerializable implementation
        /// 
        private Journal(SerializationInfo info, StreamingContext context)
        { 
            _Initialize();
            _journalEntryList = (List)info.GetValue("_journalEntryList", typeof(List)); 
            _currentEntryIndex = info.GetInt32("_currentEntryIndex"); 
            _uncommittedCurrentIndex = _currentEntryIndex;
            _journalEntryId = info.GetInt32("_journalEntryId"); 
        }

        //-----------------------------------------------------
        // 
        //  Internal Properties
        // 
        //----------------------------------------------------- 
        #region Internal Properties
 
        #region Operator Overloads
        /// 
        /// Gets the journal entry at the specified index.
        ///  
        /// The zero-based index of the journal entry to get or set.
        /// The journal entry at the specified index. 
        // 

 



 
        internal JournalEntry this[int index]
        { 
            get 
            {
                return _journalEntryList[index]; 
            }
        }
        #endregion Operator Overloads
 
        // Total number of entries in the journal including non-navigable entries
        internal int TotalCount 
        { 
            get
            { 
                return _journalEntryList.Count;
            }
        }
 
        /// 
        /// Current index - could be in the middle of the list when in history 
        /// navigation. Else will be at the end of the list, a new entry will be 
        /// added at this index for normal navigations
        ///  
        internal int CurrentIndex
        {
            get
            { 
                return _currentEntryIndex;
            } 
        } 

        ///  
        /// Get the current journal entry.
        /// 
        internal JournalEntry CurrentEntry
        { 
            get
            { 
                if (_currentEntryIndex >= 0 && _currentEntryIndex < TotalCount) 
                {
                    return _journalEntryList[_currentEntryIndex]; 
                }
                else
                {
                    return null; 
                }
            } 
        } 

        internal bool HasUncommittedNavigation 
        {
            get { return _uncommittedCurrentIndex != _currentEntryIndex; }
        }
 
        /// 
        /// The getter for the BackStack 
        ///  
        /// Gets the BackStack
        internal JournalEntryStack BackStack 
        {
            get
            {
                return _backStack; 
            }
        } 
 
        /// 
        /// The getter for the ForwardStack 
        /// 
        /// Gets the ForwardStack
        internal JournalEntryStack ForwardStack
        { 
            get
            { 
                return _forwardStack; 
            }
        } 

        /// 
        /// Check if there are journal entries for going back.
        ///  
        internal bool CanGoBack
        { 
            get 
            {
                return GetGoBackEntry() != null; 
            }
        }

        ///  
        /// Check if there are journal entries for going forward.
        ///  
        internal bool CanGoForward 
        {
            get 
            {
                int index;
                GetGoForwardEntryIndex(out index);
                return index != -1; 
            }
        } 
 
        /// 
        /// Returns a journal version used to invalidate old enumerators after journal data changes 
        /// 
        /// Current journal version
        internal int Version
        { 
            get
            { 
                return _version; 
            }
        } 

        internal JournalEntryFilter Filter
        {
            get { return _filter; } 
            set
            { 
                _filter = value; 
                BackStack.Filter = _filter;
                ForwardStack.Filter = _filter; 
            }
        }

        #endregion Internal Properties 

        //------------------------------------------------------ 
        // 
        //  Internal Events
        // 
        //-----------------------------------------------------

        #region Internal Events
        ///  
        /// Raised when the contents of the BackStack or ForwardStack changes.
        /// Note that this doesn't always mean CanGoBack/CanGoForward has changed. 
        ///  
        internal event EventHandler BackForwardStateChange
        { 
            add { _backForwardStateChange += value; }
            remove { _backForwardStateChange -= value; }
        }
 
        [NonSerialized()]
        EventHandler _backForwardStateChange; 
        #endregion 

        //------------------------------------------------------ 
        //
        //  Internal Methods
        //
        //------------------------------------------------------ 

        #region Internal Methods 
 
        /// 
        /// Remove the top JournalEntry from back entry 
        /// 
        // Not a true "remove"
        internal JournalEntry RemoveBackEntry()
        { 
            Debug.Assert(ValidateIndexes());
            int index = _currentEntryIndex; // start from current but do not change it 
            do 
            {
                if (--index < 0) 
                {
                    return null;
                }
            } while (IsNavigable(_journalEntryList[index]) == false); 
            JournalEntry removedEntry = RemoveEntryInternal(index);
            Debug.Assert(ValidateIndexes()); 
            UpdateView(); 
            return removedEntry;
        } 

        /// 
        /// Ensures current data about the current page is stored in the journal.
        /// This either updates an existing entry or adds a new one. 
        /// 
        ///  
        internal void UpdateCurrentEntry(JournalEntry journalEntry) 
        {
            if (journalEntry == null) 
            {
                throw new ArgumentNullException("journalEntry");
            }
            Debug.Assert(journalEntry.ContentId != 0); 
            Debug.Assert(!(journalEntry.IsAlive() && journalEntry.JEGroupState.JournalDataStreams != null),
                "Keep-alive content state should not be serialized."); 
 
            if (_currentEntryIndex > -1 && _currentEntryIndex < TotalCount)
            { 
                // update existing entry using the old entry's index.
                // Note: the new entry can be for a different NavigationService.
                JournalEntry oldEntry = _journalEntryList[_currentEntryIndex];
                journalEntry.Id = oldEntry.Id; 
                _journalEntryList[_currentEntryIndex] = journalEntry;
            } 
            else 
            {
                // add new entry to the front 
                journalEntry.Id = ++_journalEntryId;
                _journalEntryList.Add(journalEntry);
            }
            _version++; 

            // If the next navigation is not #fragment or CustomContentState, this entry should be 
            // remembered as the "exit" entry for the group, so when navigating back to the same 
            // page, it will be shown. (It is not necessarily the last one in the group.)
            // Journal filtering will hide all other entries while at another page (different 
            // NavigationService.Content object).
            journalEntry.JEGroupState.GroupExitEntry = journalEntry;
        }
 
        internal void RecordNewNavigation()
        { 
            Invariant.Assert(ValidateIndexes()); 
            Debug.Assert(_uncommittedCurrentIndex == _currentEntryIndex,
                "This method should be called only in steady state."); 

            // moves _currentEntryIndex forward
            // clear forward entries if necessary
 
            _currentEntryIndex++;
            _uncommittedCurrentIndex = _currentEntryIndex; 
 
            if (!ClearForwardStack())
            { 
                // If ClearForwardStack() didn't change the journal, UpdateView() needs to be
                // called here to enable the Back button.
                UpdateView();
            } 
        }
 
        internal bool ClearForwardStack() 
        {
            Debug.Assert(ValidateIndexes()); 

            if (_currentEntryIndex >= TotalCount)
                return false; // nothing to do
 
            if(_uncommittedCurrentIndex > _currentEntryIndex)
                throw new InvalidOperationException(SR.Get(SRID.InvalidOperation_CannotClearFwdStack)); 
 
            _journalEntryList.RemoveRange(_currentEntryIndex, _journalEntryList.Count - _currentEntryIndex);
            UpdateView(); 
            return true;
        }

        internal void CommitJournalNavigation(JournalEntry navigated) 
        {
            NavigateTo(navigated); 
        } 

        internal void AbortJournalNavigation() 
        {
            _uncommittedCurrentIndex = _currentEntryIndex;
            UpdateView();
        } 

        ///  
        /// Get the previous journal entry without changing any indexes. 
        /// 
        /// Null if we cannot go back, otherwise the journal entry on the top of the back stack 
        internal JournalEntry BeginBackNavigation()
        {
            Invariant.Assert(ValidateIndexes());
 
            int index;
            JournalEntry journalEntry = GetGoBackEntry(out index); 
            if (journalEntry == null) 
                throw new InvalidOperationException(SR.Get(SRID.NoBackEntry));
            _uncommittedCurrentIndex = index; 
            UpdateView();
            if (_uncommittedCurrentIndex == _currentEntryIndex)
                return null; // See BeginForwardNavigation() for explanation of this special case.
            return journalEntry; 
        }
 
        internal JournalEntry BeginForwardNavigation() 
        {
            Invariant.Assert(ValidateIndexes()); 

            int fwdEntryIndex;

            GetGoForwardEntryIndex(out fwdEntryIndex); 
            if (fwdEntryIndex == -1)
                throw new InvalidOperationException(SR.Get(SRID.NoForwardEntry)); 
 
            _uncommittedCurrentIndex = fwdEntryIndex;
            UpdateView(); 

            if (fwdEntryIndex == _currentEntryIndex)
            {
                // this is a special case where the user BeginBackNavigation() was called but not allowed to finish 
                // before BeginForwardNavigation() was called.
                // Note that _uncommittedCurrentIndex may be less than _currentEntryIndex-1 at this 
                // point. That's because there might be non-navigable entries between the two indexes... 
                // Returning null indicates to the caller that it should stop any current navigation
                // and remain at the current page. If reloading of the current page were allowed, 
                // its controls' state would be lost.
                return null;
            }
 
            return _journalEntryList[fwdEntryIndex];
        } 
 
        /// 
        /// For jump navigation this determines if it is a backwards or forwards navigation 
        /// 
        internal NavigationMode GetNavigationMode(JournalEntry entry)
        {
            int index = _journalEntryList.IndexOf(entry); 

            if (index <= _currentEntryIndex) 
            { 
                // If index = _currentEntryIndex it means the application is being navigated back to
                // in the browser.  The browser has just loaded the journal and is restoring the 
                // current page.  This would also work if we chose "forward" but it must be one of the
                // two so that NavigationService will complete the navigation with CommitJournalNavigation()
                return NavigationMode.Back;
            } 
            else
            { 
                return NavigationMode.Forward; 
            }
        } 

        internal void NavigateTo(JournalEntry target)
        {
            Debug.Assert(IsNavigable(target), "target must be navigable"); 
            Debug.Assert(ValidateIndexes());
 
            int index = _journalEntryList.IndexOf(target); 

            // When navigating back to a page which contains a previously navigated frame a 
            // saved journal entry is replayed to restore the frame’s location, in many cases
            // this entry is not in the journal.
            if (index > -1)
            { 
                _currentEntryIndex = index;
                _uncommittedCurrentIndex = _currentEntryIndex; 
                UpdateView(); 
            }
        } 

        internal int FindIndexForEntryWithId(int id)
        {
            // Search the list 
            for (int i = 0; i < TotalCount; i++)
            { 
                if (this[i].Id == id) 
                {
                    return i; 
                }
            }

            // Didn't find it 
            return -1;
        } 
 
        // This is only called from ApplicationProxyInternal.GetSaveHistoryBytes when
        // we are persisting the entire journal; we only do that when we're quitting. 
        // [new] Also when navigating a Frame that has its own journal.
        //

 
        internal void PruneKeepAliveEntries()
        { 
            for (int i = TotalCount - 1; i >= 0; --i) 
            {
                JournalEntry je = _journalEntryList[i]; 
                if (je.IsAlive())
                {
                    RemoveEntryInternal(i);
                } 
                else
                { 
                    Debug.Assert(je.GetType().IsSerializable); 
                    // There can be keep-alive JEs creates for child frames.
                    DataStreams jds = je.JEGroupState.JournalDataStreams; 
                    if (jds != null)
                    {
                        jds.PrepareForSerialization();
                    } 

                    if (je.RootViewerState != null) 
                    { 
                        je.RootViewerState.PrepareForSerialization();
                    } 
                }
            }
        }
 
        ///  The caller is responsible for calling UpdateView(). 
        internal JournalEntry RemoveEntryInternal(int index) 
        { 
            Debug.Assert(index < TotalCount && index >= 0, "Invalid index passed to RemoveEntryInternal");
            Debug.Assert(_uncommittedCurrentIndex == _currentEntryIndex, 
                "This method should be called only in steady state.");

            JournalEntry theEntry = _journalEntryList[index];
            Debug.Assert(theEntry != null, "Journal list state is messed up"); 

            // Increase version always, see note above the data member declaration 
            _version++; 

            _journalEntryList.RemoveAt(index); 
            if (_currentEntryIndex > index)
            {
                _currentEntryIndex--;
            } 
            if (_uncommittedCurrentIndex > index)
            { 
                _uncommittedCurrentIndex--; 
            }
 
            return theEntry;
        }

        internal void RemoveEntries(Guid navSvcId) 
        {
            for (int i = TotalCount - 1; i >= 0; i--) 
            { 
                // The entry at _currentEntryIndex is just a placeholder. It should not be deleted.
                // Otherwise, the following entry (first one in the "forward stack") will get overwritten 
                // if a Back navigation occurs next.
                if (i != _currentEntryIndex)
                {
                    JournalEntry entry = _journalEntryList[i]; 
                    if (entry.NavigationServiceId == navSvcId)
                    { 
                        RemoveEntryInternal(i); 
                    }
                } 
            }

            UpdateView();
        } 

        //[IsKeepAlive() moved to NavigationService.IsContentKeepAlive()] 
 
        internal void UpdateView()
        { 
            BackStack.OnCollectionChanged();
            ForwardStack.OnCollectionChanged();
            if (_backForwardStateChange != null)
            { 
                _backForwardStateChange(this, EventArgs.Empty);
            } 
        } 

        ///  Returns the entry the GoBack command would navigate to; null/-1 if can't go back.  
        internal JournalEntry GetGoBackEntry(out int index)
        {
            for (index = _uncommittedCurrentIndex - 1; index >= 0; index--)
            { 
                JournalEntry je = _journalEntryList[index];
                if (IsNavigable(je)) 
                { 
                    return je;
                } 
            }
            return null; // and index=-1
        }
        internal JournalEntry GetGoBackEntry() 
        {
            int unused; 
            return GetGoBackEntry(out unused); 
        }
 
        /// 
        /// Returns the index of the entry the GoForward command would navigate to; -1 if can't
        /// go forward.
        ///  
        /// 
        /// This funtion is not symmetric to GetGoBackEntry() becaue of the special case when 
        /// _currentEntryIndex=TotalCount and _uncommittedCurrentIndex=TotalCount-1. Then there is 
        /// no JournalEntry object to return (but fwd navigation is allowed--to the current page).
        ///  
        internal void GetGoForwardEntryIndex(out int index)
        {
            Debug.Assert(ValidateIndexes());
 
            // Special case: _uncommittedCurrentIndex=_currentEntryIndex=TotalCount-1.
            // Then we can't go fwd. But if _currentEntryIndex=TotalCount, we can. 
            // See also the special case in BeginForwardNavigation(). 
            index = _uncommittedCurrentIndex;
            do { 
                index++;
                if (index == _currentEntryIndex)
                {
                    return; 
                }
                if (index >= TotalCount) 
                { 
                    index = -1;
                    return; 
                }
            } while (!IsNavigable(_journalEntryList[index]));
        }
 
        #endregion Internal Methods
 
        //----------------------------------------------------- 
        //
        //  Private Methods 
        //
        //------------------------------------------------------

        #region Private Methods 

        ///  Checks that the internal indices are not out of range. If an index is equal 
        /// to TotalCount, it is valid, but there is no JournalEntry created yet (for the current page). 
        /// 
        private bool ValidateIndexes() 
        {
            return _currentEntryIndex >= 0 && _currentEntryIndex <= TotalCount
                && _uncommittedCurrentIndex >= 0 && _uncommittedCurrentIndex <= TotalCount;
        } 

        private void _Initialize() 
        { 
            _backStack = new JournalEntryBackStack(this);
            _forwardStack = new JournalEntryForwardStack(this); 
        }

        internal bool IsNavigable(JournalEntry entry)
        { 
            if (entry == null)
                return false; 
            // Fallback to entry.IsNavigable if the Filter hasn't been specified 
            return (Filter != null) ? Filter(entry) : entry.IsNavigable();
        } 
        #endregion

        //-----------------------------------------------------
        // 
        //  Private Fields
        // 
        //----------------------------------------------------- 

        #region Private Fields 

        private JournalEntryFilter  _filter;

        JournalEntryBackStack       _backStack; 
        JournalEntryForwardStack    _forwardStack;
 
        // This is where we get the id we assign to all JournalEntries. 
        // It will be incremented each time.
        // This is stored in WINDOWDATA structure of the browser's travellog. Trident uses it to 
        // identify frame windows and decide if a frame journal entry is invocable or not. We use it
        // to identify the JournalEntry which has the NavigationService Guid to identify navigable frame
        // entries in the current context. When the travelentry is invoked we use this id to find
        // the JournalEntry to navigate it to. Since we don't explicitly remove the entry from the browser's 
        // travellog when it is removed from the internal Avalon journal, we need to keep this id
        // unique so we can respond correctly to the CanInvokeEntry calls from the browser. As such 
        // this id needs to be serialized so we can continue to assign unique numbers to each journal entry 
        // if we navigate away and back to the avalon app in the journal
        // 
        // ISSUE: Multiple browser applications activated in the same browser window (incl. multiple
        // instances of the same app) need to also use unique ids. Otherwise they can get mixed up.
        // This can also happen when opening a new window from the current one (Ctrl+N). Then the
        // TravelLog is copied. 
        //   Unfortunately, IE does not distinguish the entries of multiple instances of the same
        // DocObject when making calls on ITravelLogClient. It gives us only a DWORD for the id 
        // ('dwWindowID'). Attempts to ensure a globally unique instance id across all PresentationHost 
        // instances proved impractical due to restricted access rights. The solution here is to use
        // the system tick count as an initial value and keep incrementing it. This should be good 
        // enough in all normal usage scenarios.
        private int _journalEntryId = MS.Win32.SafeNativeMethods.GetTickCount();

        private List _journalEntryList = new List(); 
        private int _currentEntryIndex = 0;
 
        // This index is used to support the case where the back/forward is called multiple times 
        // without letting the first navigation finish loading.  For example if the page is at 'C'
        // and the back stack contains 'b','a' and Back() is called twice the user should end up at 
        // 'a'. Navigation to 'b' starts but is canceled before it finishes when navigation to 'a' begins.
        private int _uncommittedCurrentIndex = 0;

        // Incremented everytime a journal entry is added/removed/updated. The enumerator 
        // operation will then be invalidated since the list it was enumerating over has now
        // changed. This is the standard implementation used by the .Net ArrayList enumerator too. 
        // We could optimize for the case for when the changes happen at an index greater than 
        // the enumerator index (enumerator would do this check against a lastDirtyIndex that the
        // journal would maintain). But this will be bad if we decided to implement ICollection later 
        // since we would then export the Count which would need to be invalidated as well.
        // Also if the enumerator user maintains some kind of count of entries he is interested in
        // then the index/count would be invalidated.
        private int _version; 

        #endregion 
    } 

    internal delegate bool JournalEntryFilter(JournalEntry entry); 
}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.

                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK