#pragma warning disable 1634, 1691
//----------------------------------------------------------------------------
//
// Copyright(C) Microsoft Corporation. All rights reserved.
//
//
// Description:
// AnnotationService provides DynamicProperties and API for configuring
// and invoking the annotation framework.
//
// History:
// 11/25/2002 rruiz: Created AnnotationService.cs
// 08/08/2003 magedz: Port to WCP tree
// 11/11/2003 magedz: restructure, and add event handling as per service spec
// 2004-04-13 axelk: added DP for Chooser and code changes to integrate annotation component manager.
// 12/12/2005 rruiz: Made most APIs internal but left them in their places
// because we intend to make them public in V2. Added the
// new limited public APIs for V2.
// 05/12/2005: rruiz: Simplified the creation/enable/disable model. Moved create/delete methods to
// helper class.
//
//---------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Threading;
using System.IO;
using System.Windows;
using System.Windows.Annotations.Storage;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Markup;
using System.Xml;
using MS.Internal;
using MS.Internal.Annotations;
using MS.Internal.Annotations.Anchoring;
using MS.Internal.Annotations.Component;
using MS.Internal.Documents;
using MS.Utility;
using System.Reflection; // for BindingFlags
using System.Globalization; // for CultureInfo
using System.Threading;
namespace System.Windows.Annotations
{
///
/// AnnotationService represents a single configuration of the annotation sub-system.
/// AnnotationService is scoped to the DependencyObject it was created on.
///
public sealed class AnnotationService : DispatcherObject
{
//-----------------------------------------------------
//
// Constructors
//
//-----------------------------------------------------
#region Constructors
///
/// Static constructor registers command bindings on DocumentViewerBase for all annotation commands.
///
static AnnotationService()
{
CommandManager.RegisterClassCommandBinding(typeof(DocumentViewerBase), new CommandBinding(CreateHighlightCommand, AnnotationHelper.OnCreateHighlightCommand, AnnotationHelper.OnQueryCreateHighlightCommand));
CommandManager.RegisterClassCommandBinding(typeof(DocumentViewerBase), new CommandBinding(CreateTextStickyNoteCommand, AnnotationHelper.OnCreateTextStickyNoteCommand, AnnotationHelper.OnQueryCreateTextStickyNoteCommand));
CommandManager.RegisterClassCommandBinding(typeof(DocumentViewerBase), new CommandBinding(CreateInkStickyNoteCommand, AnnotationHelper.OnCreateInkStickyNoteCommand, AnnotationHelper.OnQueryCreateInkStickyNoteCommand));
CommandManager.RegisterClassCommandBinding(typeof(DocumentViewerBase), new CommandBinding(ClearHighlightsCommand, AnnotationHelper.OnClearHighlightsCommand, AnnotationHelper.OnQueryClearHighlightsCommand));
CommandManager.RegisterClassCommandBinding(typeof(DocumentViewerBase), new CommandBinding(DeleteStickyNotesCommand, AnnotationHelper.OnDeleteStickyNotesCommand, AnnotationHelper.OnQueryDeleteStickyNotesCommand));
CommandManager.RegisterClassCommandBinding(typeof(DocumentViewerBase), new CommandBinding(DeleteAnnotationsCommand, AnnotationHelper.OnDeleteAnnotationsCommand, AnnotationHelper.OnQueryDeleteAnnotationsCommand));
CommandManager.RegisterClassCommandBinding(typeof(FlowDocumentScrollViewer), new CommandBinding(CreateHighlightCommand, AnnotationHelper.OnCreateHighlightCommand, AnnotationHelper.OnQueryCreateHighlightCommand));
CommandManager.RegisterClassCommandBinding(typeof(FlowDocumentScrollViewer), new CommandBinding(CreateTextStickyNoteCommand, AnnotationHelper.OnCreateTextStickyNoteCommand, AnnotationHelper.OnQueryCreateTextStickyNoteCommand));
CommandManager.RegisterClassCommandBinding(typeof(FlowDocumentScrollViewer), new CommandBinding(CreateInkStickyNoteCommand, AnnotationHelper.OnCreateInkStickyNoteCommand, AnnotationHelper.OnQueryCreateInkStickyNoteCommand));
CommandManager.RegisterClassCommandBinding(typeof(FlowDocumentScrollViewer), new CommandBinding(ClearHighlightsCommand, AnnotationHelper.OnClearHighlightsCommand, AnnotationHelper.OnQueryClearHighlightsCommand));
CommandManager.RegisterClassCommandBinding(typeof(FlowDocumentScrollViewer), new CommandBinding(DeleteStickyNotesCommand, AnnotationHelper.OnDeleteStickyNotesCommand, AnnotationHelper.OnQueryDeleteStickyNotesCommand));
CommandManager.RegisterClassCommandBinding(typeof(FlowDocumentScrollViewer), new CommandBinding(DeleteAnnotationsCommand, AnnotationHelper.OnDeleteAnnotationsCommand, AnnotationHelper.OnQueryDeleteAnnotationsCommand));
CommandManager.RegisterClassCommandBinding(typeof(FlowDocumentReader), new CommandBinding(CreateHighlightCommand, AnnotationHelper.OnCreateHighlightCommand, AnnotationHelper.OnQueryCreateHighlightCommand));
CommandManager.RegisterClassCommandBinding(typeof(FlowDocumentReader), new CommandBinding(CreateTextStickyNoteCommand, AnnotationHelper.OnCreateTextStickyNoteCommand, AnnotationHelper.OnQueryCreateTextStickyNoteCommand));
CommandManager.RegisterClassCommandBinding(typeof(FlowDocumentReader), new CommandBinding(CreateInkStickyNoteCommand, AnnotationHelper.OnCreateInkStickyNoteCommand, AnnotationHelper.OnQueryCreateInkStickyNoteCommand));
CommandManager.RegisterClassCommandBinding(typeof(FlowDocumentReader), new CommandBinding(ClearHighlightsCommand, AnnotationHelper.OnClearHighlightsCommand, AnnotationHelper.OnQueryClearHighlightsCommand));
CommandManager.RegisterClassCommandBinding(typeof(FlowDocumentReader), new CommandBinding(DeleteStickyNotesCommand, AnnotationHelper.OnDeleteStickyNotesCommand, AnnotationHelper.OnQueryDeleteStickyNotesCommand));
CommandManager.RegisterClassCommandBinding(typeof(FlowDocumentReader), new CommandBinding(DeleteAnnotationsCommand, AnnotationHelper.OnDeleteAnnotationsCommand, AnnotationHelper.OnQueryDeleteAnnotationsCommand));
}
///
/// Creates an instance of the AnnotationService focused on a particular
/// DocumentViewerBase.
///
/// the viewer this service will operate on
/// viewer is null
public AnnotationService(DocumentViewerBase viewer)
{
if (viewer == null)
throw new ArgumentNullException("viewer");
Initialize(viewer);
}
///
/// Creates an instance of the AnnotationService focused on a particular
/// FlowDocumentScrollViewer.
///
/// the viewer this service will operate on
/// viewer is null
public AnnotationService(FlowDocumentScrollViewer viewer)
{
if (viewer == null)
throw new ArgumentNullException("viewer");
Initialize(viewer);
}
///
/// Creates an instance of the AnnotationService focused on a particular
/// FlowDocumentReader.
///
/// the viewer this service will operate on
/// viewer is null
public AnnotationService(FlowDocumentReader viewer)
{
if (viewer == null)
throw new ArgumentNullException("viewer");
Initialize(viewer);
}
///
/// Creates an instance of the AnnotationService focused on a particular
/// tree node.
///
/// the tree node this service will operate on
/// root is null
/// element is not a FrameworkElement or FrameworkContentElement
internal AnnotationService(DependencyObject root)
{
if (root == null)
throw new ArgumentNullException("root");
if (!(root is FrameworkElement || root is FrameworkContentElement))
throw new ArgumentException(SR.Get(SRID.ParameterMustBeLogicalNode), "root");
Initialize(root);
}
#endregion Constructors
//------------------------------------------------------
//
// Public Methods
//
//-----------------------------------------------------
#region Public Methods
///
/// Enables the service with the given store.
///
/// store to use for retreiving and persisting annotations
/// store is null
/// DocumentViewerBase has content which is neither
/// FlowDocument or FixedDocument; only those two are supported
/// this service or another service is already
/// enabled for the DocumentViewerBase
public void Enable(AnnotationStore annotationStore)
{
if (annotationStore == null)
throw new ArgumentNullException("annotationStore");
VerifyAccess();
if (_isEnabled)
throw new InvalidOperationException(SR.Get(SRID.AnnotationServiceIsAlreadyEnabled));
// Make sure there isn't a service above or below us
VerifyServiceConfiguration(_root);
// Post a background work item to load existing annotations. Do this early in the
// Enable method in case any later code causes an indirect call to LoadAnnotations,
// this cached operation will make that LoadAnnotations call a no-op.
_asyncLoadOperation = _root.Dispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(LoadAnnotationsAsync), this);
// Enable the store and set it on the tree
_isEnabled = true;
_root.SetValue(AnnotationService.ServiceProperty, this);
_store = annotationStore;
// If our root is a DocumentViewerBase we need to setup some special processors.
DocumentViewerBase viewer = _root as DocumentViewerBase;
if (viewer != null)
{
bool isFixed = viewer.Document is FixedDocument || viewer.Document is FixedDocumentSequence;
bool isFlow = !isFixed && viewer.Document is FlowDocument;
if (isFixed || isFlow || viewer.Document == null)
{
// Take care of some special processors here
if (isFixed)
{
_locatorManager.RegisterSelectionProcessor(new FixedTextSelectionProcessor(), typeof(TextRange));
_locatorManager.RegisterSelectionProcessor(new FixedTextSelectionProcessor(), typeof(TextAnchor));
}
else if (isFlow)
{
_locatorManager.RegisterSelectionProcessor(new TextSelectionProcessor(), typeof(TextRange));
_locatorManager.RegisterSelectionProcessor(new TextSelectionProcessor(), typeof(TextAnchor));
_locatorManager.RegisterSelectionProcessor(new TextViewSelectionProcessor(), typeof(DocumentViewerBase));
}
}
else
{
throw new InvalidOperationException(SR.Get(SRID.OnlyFlowFixedSupported));
}
}
// Don't register on the store until the service is ready to accept events.
// Register for content change and anchor change events - we'll need to attach/detach
// annotations as they are added/removed from the store or their anchors change
annotationStore.StoreContentChanged += new StoreContentChangedEventHandler(OnStoreContentChanged);
annotationStore.AnchorChanged += new AnnotationResourceChangedEventHandler(OnAnchorChanged);
}
///
/// Disables annotations on the DocumentViewerBase. Any visible annotations will
/// disappear. Service can be enabled again by calling Enable.
///
/// service isn't enabled
public void Disable()
{
VerifyAccess();
if (!_isEnabled)
throw new InvalidOperationException(SR.Get(SRID.AnnotationServiceNotEnabled));
// If it hasn't been run yet, abort the pending operation. Still need to
// unregister and unload annotations. They may have been loaded due to a
// store event.
if (_asyncLoadOperation != null)
{
_asyncLoadOperation.Abort();
_asyncLoadOperation = null;
}
// Unregister for changes to the store - add/deletes/anchor changes - before
// unloading annotations. We don't want any events between unloading and unregistering.
try
{
_store.StoreContentChanged -= new StoreContentChangedEventHandler(OnStoreContentChanged);
_store.AnchorChanged -= new AnnotationResourceChangedEventHandler(OnAnchorChanged);
}
finally
{
IDocumentPaginatorSource document;
DocumentViewerBase viewer;
GetViewerAndDocument(_root, out viewer, out document);
if (viewer != null)
{
// If our root is a DocumentViewerBase or Reader in pageView mode -
// we need to unregister for changes
// to its collection of DocumentPageViews.
UnregisterOnDocumentViewer(viewer);
}
else if (document != null)
{
// If working with a FlowDocumentScrollViewer or a FlowDocumentReader
// in Scroll mode, we need to unregister for TextViewUpdated events
ITextView textView = GetTextView(document);
// ITextView may have gone away already
if (textView != null)
{
textView.Updated -= OnContentChanged;
}
}
// Unload annotations
this.UnloadAnnotations();
// This must be cleared no matter what else fails
_isEnabled = false;
_root.ClearValue(AnnotationService.ServiceProperty);
}
}
///
/// Returns the AnnotationService enabled for the viewer. If no service is
/// enabled for the viewer, returns null.
///
/// viewer to check for an enabled AnnotationService
/// the AnnotationService instance for this viewer, null if a service
/// is not enabled for this viewer
/// viewer is null
public static AnnotationService GetService(DocumentViewerBase viewer)
{
if (viewer == null)
throw new ArgumentNullException("viewer");
return viewer.GetValue(AnnotationService.ServiceProperty) as AnnotationService;
}
///
/// Returns the AnnotationService enabled for the reader. If no service is
/// enabled for the reader, returns null.
///
/// reader to check for an enabled AnnotationService
/// the AnnotationService instance for this reader, null if a service
/// is not enabled for this reader
/// reader is null
public static AnnotationService GetService(FlowDocumentReader reader)
{
if (reader == null)
throw new ArgumentNullException("reader");
return reader.GetValue(AnnotationService.ServiceProperty) as AnnotationService;
}
///
/// Returns the AnnotationService enabled for the viewer. If no service is
/// enabled for the viewer, returns null.
///
/// viewer to check for an enabled AnnotationService
/// the AnnotationService instance for this viewer, null if a service
/// is not enabled for this viewer
/// viewer is null
public static AnnotationService GetService(FlowDocumentScrollViewer viewer)
{
if (viewer == null)
throw new ArgumentNullException("viewer");
return viewer.GetValue(AnnotationService.ServiceProperty) as AnnotationService;
}
#endregion Public Methods
//------------------------------------------------------
//
// Internal Methods
//
//------------------------------------------------------
#region Internal Methods
///
/// Loads all annotations that are found for the subtree rooted at element.
/// For each annotation found and resolved in the subtree, the annotation service
/// will fire an AttachedAnnotationChanged event with action AttachedAnnotation.Added.
/// A call to GetAttachedAnnotations() after LoadAnnotations(element) will include the newly
/// added AttachedAnnotations.
///
/// root of the subtree where annotations are to be loaded
/// element is null
/// service is not enabled
/// element is not a FrameworkElement or FrameworkContentElement
internal void LoadAnnotations(DependencyObject element)
{
// If a LoadAnnotations call has happened before our initial asynchronous
// load has happened, we should just ignore this call
if (_asyncLoadOperation != null)
return;
if (element == null)
throw new ArgumentNullException("element");
if (!(element is FrameworkElement || element is FrameworkContentElement))
throw new ArgumentException(SR.Get(SRID.ParameterMustBeLogicalNode), "element");
VerifyAccess();
if (!_isEnabled)
throw new InvalidOperationException(SR.Get(SRID.AnnotationServiceNotEnabled));
//fire start trace event
EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordAnnotation, EventTrace.Event.LoadAnnotationsBegin);
IList attachedAnnotations = LocatorManager.ProcessSubTree(element);
LoadAnnotationsFromList(attachedAnnotations);
//fire end trace event
EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordAnnotation, EventTrace.Event.LoadAnnotationsEnd);
}
///
/// Unloads all the annotations for the subtree rooted at element.
/// For each attached annotation unloaded, the annotation service will fire an
/// AttachedAnnotationChanged event with AttachedAnnotationAction.Deleted.
/// A call to GetAttachedAnnotations() will return a list that excludes the just
/// removed AttachedAnnotations.
/// Note: Multiple calls to UnloadAnnotations() at the same element are idempotent
///
/// root of the subtree where annotations are to be unloaded
/// element is null
/// service is not enabled
/// element is not a FrameworkElement or FrameworkContentElement
internal void UnloadAnnotations(DependencyObject element)
{
if (element == null)
throw new ArgumentNullException("element");
if (!(element is FrameworkElement || element is FrameworkContentElement))
throw new ArgumentException(SR.Get(SRID.ParameterMustBeLogicalNode), "element");
VerifyAccess();
if (!_isEnabled)
throw new InvalidOperationException(SR.Get(SRID.AnnotationServiceNotEnabled));
// Short circuit search for annotations if the service has no attached annotations
if (_annotationMap.IsEmpty)
return;
// We have to clear all the attached annotations underneath this DependencyObject
IList attachedAnnotations = GetAllAttachedAnnotationsFor(element);
UnloadAnnotationsFromList(attachedAnnotations);
}
///
/// Unloads all attached annotations at the moment. Does not walk the tree - this is
/// invoked when there is no garantee that all annotation parents are still attached
/// to the visual tree
///
private void UnloadAnnotations()
{
IList attachedAnnotations = _annotationMap.GetAllAttachedAnnotations();
UnloadAnnotationsFromList(attachedAnnotations);
}
///
/// Returns all attached annotations that the service knows about in no particular order.
/// Note: The order of the attached annotations in the list is not significant. Do not
/// write code that relies on the order.
///
/// a list of IAttachedAnnotations
/// service is not enabled
internal IList GetAttachedAnnotations()
{
VerifyAccess();
if (!_isEnabled)
throw new InvalidOperationException(SR.Get(SRID.AnnotationServiceNotEnabled));
return _annotationMap.GetAllAttachedAnnotations();
}
#endregion Internal Methods
//-----------------------------------------------------
//
// Public Operators
//
//------------------------------------------------------
//-----------------------------------------------------
//
// Public Properties
//
//-----------------------------------------------------
#region Public Properties
///
/// Command to create Highlight annotation for the current selection. (selection > 0)
/// User can pass the color as command parameter. Default yellow color is used otherwise.
///
public static readonly RoutedUICommand CreateHighlightCommand = new RoutedUICommand(SR.Get(SRID.CreateHighlight), "CreateHighlight", typeof(AnnotationService), null);
///
/// Command to create Text StickyNote annotation for the current selection. (selection > 0)
///
public static readonly RoutedUICommand CreateTextStickyNoteCommand = new RoutedUICommand(SR.Get(SRID.CreateTextNote), "CreateTextStickyNote", typeof(AnnotationService), null);
///
/// Command to create Ink StickyNote annotation for the current selection. (selection > 0)
///
public static readonly RoutedUICommand CreateInkStickyNoteCommand = new RoutedUICommand(SR.Get(SRID.CreateInkNote), "CreateInkStickyNote", typeof(AnnotationService), null);
///
/// Command to clear highlight(s) for the current selection.
///
public static readonly RoutedUICommand ClearHighlightsCommand = new RoutedUICommand(SR.Get(SRID.ClearHighlight), "ClearHighlights", typeof(AnnotationService), null);
///
/// Command to delete all Ink and Text StickyNote annotation(s) that are included in a selection.
///
public static readonly RoutedUICommand DeleteStickyNotesCommand = new RoutedUICommand(SR.Get(SRID.DeleteNotes), "DeleteStickyNotes", typeof(AnnotationService), null);
///
/// Command to delete all Ink+Text StickyNote and highlight annotation(s) that are included in a selection.
///
public static readonly RoutedUICommand DeleteAnnotationsCommand = new RoutedUICommand(SR.Get(SRID.DeleteAnnotations), "DeleteAnnotations", typeof(AnnotationService), null);
///
/// Returns whether or not the annotation service is enabled.
///
public bool IsEnabled
{
get
{
return _isEnabled;
}
}
///
/// Soon to be public. Returns the AnnotationStore this service uses.
///
public AnnotationStore Store
{
get
{
return _store;
}
}
#endregion Public Properties
//-----------------------------------------------------
//
// Public Dependency Properties
//
//------------------------------------------------------
#region Public Dependency Properties
///
/// Returns the AnnotationService instance for this object. If the service is not
/// enabled for this object, returns null.
///
/// object from which to read the attached property
/// the AnnotationService instance for this object, null if the service
/// is not enabled for this object
/// d is null
internal static AnnotationService GetService(DependencyObject d)
{
if (d == null)
throw new ArgumentNullException("d");
return d.GetValue(AnnotationService.ServiceProperty) as AnnotationService;
}
///
/// The chooser property holds a AnnotationComponentChooser which determines what annotation components are used
/// for a given attached annotation for the subtree the DP is on. If this DP is not set, a default chooser will return the appropriate
/// out of box annotation component. If the DP is set to AnnotationComponentChooser.None, no annotation components will be choosen
/// for this subtree, in essence disabling the mechanism for the subtree.
///
#pragma warning suppress 7009
internal static readonly DependencyProperty ChooserProperty = DependencyProperty.RegisterAttached("Chooser", typeof(AnnotationComponentChooser), typeof(AnnotationService), new FrameworkPropertyMetadata(new AnnotationComponentChooser(), FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.OverridesInheritanceBehavior));
///
/// Return the AnnotationComponentChooser set in the Chooser DP.
///
/// Object from which to get the DP.
/// AnnotationComponentChooser to use for this subtree
internal static AnnotationComponentChooser GetChooser(DependencyObject d)
{
if (d == null)
throw new ArgumentNullException("d");
return (AnnotationComponentChooser)d.GetValue(ChooserProperty);
}
///
/// DependencyProperty used to specify the processor to use for a
/// given subtree. When set on a node, all nodes below it will be
/// processed with the specified processor, unless overriden. Setting
/// this property after annotations have been loaded will have no effect
/// on existing annotations. If you want to change how the tree is
/// processed, you should set this property and call LoadAnnotations/
/// UnloadAnnotations on the service.
///
#pragma warning suppress 7009
internal static readonly DependencyProperty SubTreeProcessorIdProperty = LocatorManager.SubTreeProcessorIdProperty.AddOwner(typeof(AnnotationService));
///
/// Sets the value of the SubTreeProcessorId attached property
/// of the LocatorManager class.
///
/// object to which to write the attached property
/// the DP value to set
/// d is null
internal static void SetSubTreeProcessorId(DependencyObject d, String id)
{
if (d == null)
throw new ArgumentNullException("d");
if (id == null)
throw new ArgumentNullException("id");
//d will check the context if needed
d.SetValue(SubTreeProcessorIdProperty, id);
}
///
/// Gets the value of the SubTreeProcessorId attached property
/// of the LocatorManager class.
///
/// the object from which to read the attached property
/// the value of the SubTreeProcessorId attached property
/// d is null
internal static String GetSubTreeProcessorId(DependencyObject d)
{
if (d == null)
throw new ArgumentNullException("d");
//d will check the context if needed
return d.GetValue(SubTreeProcessorIdProperty) as String;
}
///
/// Used to specify a unique id for the data represented by a
/// logical tree node. Attach this property to the element with a
/// unique value.
///
#pragma warning suppress 7009
internal static readonly DependencyProperty DataIdProperty = DataIdProcessor.DataIdProperty.AddOwner(typeof(AnnotationService));
///
/// Sets the value of the DataId attached property
/// of the LocatorManager class.
///
/// element to which to write the attached property
/// the value to set
/// d is null
internal static void SetDataId(DependencyObject d, String id)
{
if (d == null)
throw new ArgumentNullException("d");
if (id == null)
throw new ArgumentNullException("id");
//d will check the context if needed
d.SetValue(DataIdProperty, id);
}
///
/// Gets the value of the DataId attached property
/// of the LocatorManager class.
///
/// the object from which to read the attached property
/// the value of the DataId attached property
/// d is null
internal static String GetDataId(DependencyObject d)
{
if (d == null)
throw new ArgumentNullException("d");
//d will check the context if needed
return d.GetValue(DataIdProperty) as String;
}
#endregion Public Dependency Properties
//-----------------------------------------------------
//
// Public Events
//
//------------------------------------------------------
#region Public Events
///
/// event that notifies the registered objects, typically the annotation panel, with the Add/Delete/Modify
/// changes to the AttachedAnnotations
///
internal event AttachedAnnotationChangedEventHandler AttachedAnnotationChanged;
#endregion Public Events
//------------------------------------------------------
//
// Internal Properties
//
//-----------------------------------------------------
#region Internal Properties
///
/// Gets the LocatorManager instance.
///
internal LocatorManager LocatorManager
{
get
{
return _locatorManager;
}
}
///
/// The node the service is enabled on. This used by the LocatorManager
/// when resolving a locator that must be resolved from the 'top' of the
/// annotatable tree.
///
internal DependencyObject Root
{
get
{
return _root;
}
}
#endregion Internal Properties
//------------------------------------------------------
//
// Internal Fields
//
//-----------------------------------------------------
#region Internal Fields
///
/// ServiceProperty for the AnnotationService. Used to retrieve the service object
/// in order to make direct calls such as CreateAnnotation. Kept internal because
/// PathNode uses it to short-circuit the path building when there is a service set.
///
#pragma warning suppress 7009
internal static readonly DependencyProperty ServiceProperty = DependencyProperty.RegisterAttached("Service", typeof(AnnotationService), typeof(AnnotationService), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.OverridesInheritanceBehavior));
#endregion Internal Fields
//-----------------------------------------------------
//
// Private Methods
//
//-----------------------------------------------------
#region Private Methods
///
/// Initializes the service on this tree node.
///
/// the tree node the service is being created on
private void Initialize(DependencyObject root)
{
Invariant.Assert(root != null, "Parameter 'root' is null.");
// Root of the subtree this service will operate on
_root = root;
// Object that resolves and generates locators
_locatorManager = new LocatorManager();
// sets up dependency for AnnotationComponentManager
_annotationComponentManager = new AnnotationComponentManager(this);
//set known component types priorities
AdornerPresentationContext.SetTypeZLevel(typeof(StickyNoteControl), 0);
AdornerPresentationContext.SetTypeZLevel(typeof(MarkedHighlightComponent), 1);
AdornerPresentationContext.SetTypeZLevel(typeof(HighlightComponent), 1);
//set ZRanges for the ZLevels
AdornerPresentationContext.SetZLevelRange(0, Int32.MaxValue / 2 + 1, Int32.MaxValue);
AdornerPresentationContext.SetZLevelRange(1, 0, 0);
}
///
/// Queue work item - put on the queue when the service is first enabled.
/// This method causes the service's annotations to be loaded.
/// If the service is disabled before this work item is run, this work item
/// does nothing.
///
/// the service being enabled
/// Always returns null
private object LoadAnnotationsAsync(object obj)
{
Invariant.Assert(_isEnabled, "Service was disabled before attach operation executed.");
// Operation is now being executed so we can clear the cached operation
_asyncLoadOperation = null;
IDocumentPaginatorSource document = null;
DocumentViewerBase documentViewerBase;
GetViewerAndDocument(Root, out documentViewerBase, out document);
if (documentViewerBase != null)
{
// If our root is a DocumentViewerBase - we need to register for changes
// to its collection of DocumentPageViews. This allows us to unload/load
// annotations as the DPVs change. Prevents us from loading an annotation
// because of a store event and not unloading it becuase we havent' registered
// on the DPV yet.
RegisterOnDocumentViewer(documentViewerBase);
}
else if (document != null)
{
// If working with a FlowDocumentScrollViewer or FlowDocumentReader
// in Scroll mode, we must register on ITextView to
// get notified about relayout changes. For the ScrollViewer OnTextViewUpdated
// happens synchronously which is not true for the FDPV - we register OnPageConnected
// for each page there.
ITextView textView = GetTextView(document);
if (textView != null)
{
textView.Updated += OnContentChanged;
}
}
//if there are too many visible annotations the application will freeze if we load them
// synchronously - so do it in batches
IList attachedAnnotations = LocatorManager.ProcessSubTree(_root);
LoadAnnotationsFromListAsync(attachedAnnotations);
return null;
}
///
/// Queue work item - put on the queue when the service is first enabled.
/// This method causes the service's annotations to be loaded.
/// If the service is disabled before this work item is run, this work item
/// does nothing.
///
/// list of annotations to be loaded
/// Always returns null
private object LoadAnnotationsFromListAsync(object obj)
{
//if we are executed from the Dispather queue _asyncLoadFromListOperation
// will point to us. Set it to null, to avoid Abort
_asyncLoadFromListOperation = null;
List annotations = obj as List;
if (annotations == null)
return null;
if (annotations.Count > _maxAnnotationsBatch)
{
//put the overhead in a new array
List leftover = new List(annotations.Count);
leftover = annotations.GetRange(_maxAnnotationsBatch, annotations.Count - _maxAnnotationsBatch);
//put another item in the queue for the rest
_asyncLoadFromListOperation = _root.Dispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(LoadAnnotationsFromListAsync), leftover);
//trim the annotations to process list
annotations.RemoveRange(_maxAnnotationsBatch, annotations.Count - _maxAnnotationsBatch);
}
LoadAnnotationsFromList(annotations);
return null;
}
///
/// Check if the attachment level of the two attached annotations is equal, if the
/// two anchoes are TextAnchors and are equal
///
/// first attached annotation
/// second attached annotation
/// true if the attachment level is equal and the two anchors are equal TextAnchors
private bool AttachedAnchorsEqual(IAttachedAnnotation firstAttachedAnnotation, IAttachedAnnotation secondAttachedAnnotation)
{
//the annotation exists, but we do not know if the attached anchor has
//changed, so we we always modify it
object oldAttachedAnchor = firstAttachedAnnotation.AttachedAnchor;
// WorkItem 41404 - Until we have a design that lets us get around
// anchor specifics in the service we need this here.
// If the attachment levels are the same, we want to test to see if the
// anchors are the same as well - if they are we do nothing
if (firstAttachedAnnotation.AttachmentLevel == secondAttachedAnnotation.AttachmentLevel)
{
// If new anchor is text anchor - compare it to old anchor to
// detect if any changes actually happened.
TextAnchor newAnchor = secondAttachedAnnotation.AttachedAnchor as TextAnchor;
if (newAnchor != null)
{
if (newAnchor.Equals(oldAttachedAnchor))
return true;
}
}
return false;
}
///
/// Loads all annotations that are found for the subtree rooted at element.
/// For each annotation found and resolved in the subtree, the annotation service
/// will fire an AttachedAnnotationChanged event with action AttachedAnnotation.Added.
/// A call to GetAttachedAnnotations() after LoadAnnotations(element) will include the newly
/// added AttachedAnnotations.
///
/// list of attachedAnnotations to be processed
/// element is null
/// service is not enabled
/// element is not a FrameworkElement or FrameworkContentElement
private void LoadAnnotationsFromList(IList attachedAnnotations)
{
Invariant.Assert(attachedAnnotations != null, "null attachedAnnotations list");
List eventsToFire = new List(attachedAnnotations.Count);
IAttachedAnnotation matchingAnnotation = null;
foreach (IAttachedAnnotation attachedAnnotation in attachedAnnotations)
{
Invariant.Assert((attachedAnnotation != null) && (attachedAnnotation.Annotation != null), "invalid attached annotation");
matchingAnnotation = FindAnnotationInList(attachedAnnotation, _annotationMap.GetAttachedAnnotations(attachedAnnotation.Annotation.Id));
if (matchingAnnotation != null)
{
//the annotation exists, but we do not know if the attached anchor has
//changed, so we we always modify it
object oldAttachedAnchor = matchingAnnotation.AttachedAnchor;
AttachmentLevel oldAttachmentLevel = matchingAnnotation.AttachmentLevel;
if (attachedAnnotation.AttachmentLevel != AttachmentLevel.Unresolved && attachedAnnotation.AttachmentLevel != AttachmentLevel.Incomplete)
{
if (AttachedAnchorsEqual(matchingAnnotation, attachedAnnotation))
continue;
((AttachedAnnotation)matchingAnnotation).Update(attachedAnnotation.AttachedAnchor, attachedAnnotation.AttachmentLevel, null);
FullyResolveAnchor(matchingAnnotation);
eventsToFire.Add(AttachedAnnotationChangedEventArgs.Modified(matchingAnnotation,
oldAttachedAnchor, oldAttachmentLevel));
}
// It went from being attached to being partially attached. Since we don't
// support attaching partially resolved anchors we remove it here
else
{
DoRemoveAttachedAnnotation(attachedAnnotation);
eventsToFire.Add(AttachedAnnotationChangedEventArgs.Unloaded(attachedAnnotation));
}
}
else
{
if (attachedAnnotation.AttachmentLevel != AttachmentLevel.Unresolved && attachedAnnotation.AttachmentLevel != AttachmentLevel.Incomplete)
{
DoAddAttachedAnnotation(attachedAnnotation);
eventsToFire.Add(AttachedAnnotationChangedEventArgs.Loaded(attachedAnnotation));
}
}
}
FireEvents(eventsToFire);
}
///
/// Unloads all the annotations from the list
///
/// list of annotations
private void UnloadAnnotationsFromList(IList attachedAnnotations)
{
Invariant.Assert(attachedAnnotations != null, "null attachedAnnotations list");
List eventsToFire = new List(attachedAnnotations.Count);
foreach (IAttachedAnnotation attachedAnnotation in attachedAnnotations)
{
DoRemoveAttachedAnnotation(attachedAnnotation);
eventsToFire.Add(AttachedAnnotationChangedEventArgs.Unloaded(attachedAnnotation));
}
FireEvents(eventsToFire);
}
///
/// LayoutUpdate event handler
///
/// event sender (not used)
/// event arguments (not used)
private void OnLayoutUpdated(object sender, EventArgs args)
{
// Unregister for the event
UIElement root = _root as UIElement;
if (root != null)
root.LayoutUpdated -= OnLayoutUpdated;
UpdateAnnotations();
}
///
/// Updates the set of attached annotations. Unneeded annotations are unloaded
/// the annotations that were there and must stay are invalidated, the annotations that
/// must be added are added in batches asinchronously to avoid freezing of the application
///
private void UpdateAnnotations()
{
// If a UpdateAnnotations call has happened before our initial asynchronous
// load has finished, we should just ignore this call
if (_asyncLoadOperation != null)
return;
//the service must be Enabled
if (!_isEnabled)
return;
IList attachedAnnotations = null;
IList dirtyAnnotations = new List();
//first get the list of annotations to be
attachedAnnotations = LocatorManager.ProcessSubTree(_root);
//now make list of annotations to remove
List existingAnnotations = _annotationMap.GetAllAttachedAnnotations();
for (int i = existingAnnotations.Count - 1; i >= 0; i--)
{
IAttachedAnnotation match = FindAnnotationInList(existingAnnotations[i], attachedAnnotations);
if ((match != null) && AttachedAnchorsEqual(match, existingAnnotations[i]))
{
//we do not need to load the matching one
attachedAnnotations.Remove(match);
dirtyAnnotations.Add(existingAnnotations[i]);
//we do not need to unload the existing one too
existingAnnotations.RemoveAt(i);
}
}
//now every thing that is left in existingAnnotations must be removed
if ((existingAnnotations != null) && (existingAnnotations.Count > 0))
{
UnloadAnnotationsFromList(existingAnnotations);
}
//invalidate the annotations that are left
IList processedElements = new List();
foreach (IAttachedAnnotation annotation in dirtyAnnotations)
{
UIElement parent = annotation.Parent as UIElement;
if ((parent != null) && !processedElements.Contains(parent))
{
processedElements.Add(parent);
InvalidateAdorners(parent);
}
}
if (_asyncLoadFromListOperation != null)
{
//stop this one - we will set a new one in the queue
_asyncLoadFromListOperation.Abort();
_asyncLoadFromListOperation = null;
}
if ((attachedAnnotations != null) && (attachedAnnotations.Count > 0))
LoadAnnotationsFromListAsync(attachedAnnotations);
}
///
/// Mark all AnnotationAdorners that Annotate this element as Dirty
/// annotated element
///
private static void InvalidateAdorners(UIElement element)
{
AdornerLayer layer = AdornerLayer.GetAdornerLayer(element);
if (layer != null)
{
//mark the components that adorn the same element as dirty
Adorner[] adorners = layer.GetAdorners(element);
if (adorners != null)
{
for (int i = 0; i < adorners.Length; i++)
{
AnnotationAdorner adorner = adorners[i] as AnnotationAdorner;
if (adorner != null)
{
Invariant.Assert(adorner.AnnotationComponent != null, "AnnotationAdorner component undefined");
adorner.AnnotationComponent.IsDirty = true;
}
}
layer.Update(element);
}
}
}
///
/// Verify that no other annotation services are attached above, on or below root
///
/// the proposed root for a new AnnotationService being enabled
/// Other Instance of AnnotationService Is Already Set
static private void VerifyServiceConfiguration(DependencyObject root)
{
Invariant.Assert(root != null, "Parameter 'root' is null.");
// First make sure there isn't a service above us
if (AnnotationService.GetService(root) != null)
throw new InvalidOperationException(SR.Get(SRID.AnnotationServiceAlreadyExists));
// Now check the tree below us for an existing service
DescendentsWalker