|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
//
//
// Description: AnnotationComponent that visualizes highlights
//
using System;
using MS.Internal;
using System.Diagnostics;
using System.Collections;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Shapes;
using MS.Internal.Text;
using System.Xml;
using System.IO;
using System.Windows.Annotations;
using System.Reflection;
using System.Windows.Annotations.Storage;
using System.Windows.Controls;
using MS.Internal.Annotations.Anchoring;
using MS.Utility;
namespace MS.Internal.Annotations.Component
{
// Highlight rendering for the Annotation highlight and sticky note anchor.
// TBD
internal class HighlightComponent : Canvas, IAnnotationComponent, IHighlightRange
{
#region Constructor
/// <summary>
/// Creates a new instance of the HighlightComponent
/// </summary>
public HighlightComponent()
{
}
/// <summary>
/// Creates a new instance of the HighlightComponent with
/// nondefault priority and type;
/// </summary>
/// <param name="priority">component priority</param>
/// <param name="highlightContent">if true - highlight only content of tables, figures and floaters</param>
/// <param name="type">component type</param>
public HighlightComponent(int priority, bool highlightContent, XmlQualifiedName type)
: base()
{
ArgumentNullException.ThrowIfNull(type);
_priority = priority;
_type = type;
_highlightContent = highlightContent;
}
#endregion Constructor
#region Public Properties
/// <summary>
/// Return a copy of the list of IAttachedAnnotations held by this component
/// </summary>
public IList AttachedAnnotations
{
get
{
ArrayList list = new ArrayList();
if (_attachedAnnotation != null)
{
list.Add(_attachedAnnotation);
}
return list;
}
}
/// <summary>
/// Sets and gets the context this annotation component is hosted in.
/// </summary>
/// <value>Context this annotation component is hosted in</value>
public PresentationContext PresentationContext
{
get
{
return _presentationContext;
}
set
{
_presentationContext = value;
}
}
/// <summary>
/// Sets and gets the Z-order of this component. NOP -
/// Highlight does not have Z-order
/// </summary>
/// <value>Context this annotation component is hosted in</value>
public int ZOrder
{
get
{
return -1;
}
set
{
}
}
/// <summary>
/// The annotation type name
/// </summary>
public static XmlQualifiedName TypeName
{
get
{
return _name;
}
}
/// <summary>
/// gets and sets the default backgroud color for the highlight
/// </summary>
public Color DefaultBackground
{
get
{
return _defaultBackroundColor;
}
set
{
_defaultBackroundColor = value;
}
}
/// <summary>
/// gets and sets the default foregroud color for the highlight
/// </summary>
public Color DefaultActiveBackground
{
get
{
return _defaultActiveBackgroundColor;
}
set
{
_defaultActiveBackgroundColor = value;
}
}
/// <summary>
/// Highlight color
/// </summary>
public Brush HighlightBrush
{
set
{
SetValue(HighlightComponent.HighlightBrushProperty, value);
}
}
//those properies are exposed to allow external components like StickyNote to synchronize their color
// with Highlight colors
public static DependencyProperty HighlightBrushProperty = DependencyProperty.Register("HighlightBrushProperty", typeof(Brush), typeof(HighlightComponent));
/// <summary>
/// Returns the one element the annotation component is attached to.
/// </summary>
/// <value></value>
public UIElement AnnotatedElement
{
get
{
return _attachedAnnotation != null ? (_attachedAnnotation.Parent as UIElement) : null;
}
}
/// <summary>
/// When the value is set to true - the AnnotatedElement content has changed -
/// save the value and invalidate the visual, so we can recalculate the geometry.
/// The value is set to false when the geometry is synced with the content
/// </summary>
public bool IsDirty
{
get
{
return _isDirty;
}
set
{
_isDirty = value;
if (value)
InvalidateChildren();
}
}
#endregion Public Properties
#region Public Methods
/// <summary>
/// The HighlightComponent uses built-in highlight rendering, so no transformation is needed
/// </summary>
/// <param name="transform">Transform to the AnnotatedElement.</param>
/// <returns>Transform to the annotation component</returns>
public GeneralTransform GetDesiredTransform(GeneralTransform transform)
{
return transform;
}
/// <summary>
/// Add an attached annotation to the component. The attached anchor will be used to add
/// a highlight to the appropriate TextContainer
/// </summary>
/// <param name="attachedAnnotation">The attached annotation to be added to the component</param>
public void AddAttachedAnnotation(IAttachedAnnotation attachedAnnotation)
{
if (_attachedAnnotation != null)
{
throw new ArgumentException(SR.MoreThanOneAttachedAnnotation);
}
//fire trace event
EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordAnnotation, EventTrace.Event.AddAttachedHighlightBegin);
//check input data and retrieve the TextContainer
ITextContainer textContainer = CheckInputData(attachedAnnotation);
TextAnchor textAnchor = attachedAnnotation.AttachedAnchor as TextAnchor;
//Get highlight Colors from the cargo. For undefined Colors the default values are used
GetColors(attachedAnnotation.Annotation, out _background, out _selectedBackground);
_range = textAnchor;
Invariant.Assert(textContainer.Highlights != null, "textContainer.Highlights is null");
//get or create AnnotationHighlightLayer in the textContainer
AnnotationHighlightLayer highlightLayer = textContainer.Highlights.GetLayer(typeof(HighlightComponent)) as AnnotationHighlightLayer;
if (highlightLayer == null)
{
highlightLayer = new AnnotationHighlightLayer();
textContainer.Highlights.AddLayer(highlightLayer);
}
//save the attached annotation
_attachedAnnotation = attachedAnnotation;
//register for cargo changes
_attachedAnnotation.Annotation.CargoChanged += new AnnotationResourceChangedEventHandler(OnAnnotationUpdated);
//add this highlight range
highlightLayer.AddRange(this);
HighlightBrush = new SolidColorBrush(_background);
IsHitTestVisible = false;
//fire trace event
EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordAnnotation, EventTrace.Event.AddAttachedHighlightEnd);
}
/// <summary>
/// Remove an attached annotation from the component
/// </summary>
/// <param name="attachedAnnotation">The attached annotation to be removed from the component</param>
public void RemoveAttachedAnnotation(IAttachedAnnotation attachedAnnotation)
{
ArgumentNullException.ThrowIfNull(attachedAnnotation);
if (attachedAnnotation != _attachedAnnotation)
{
throw new ArgumentException(SR.InvalidAttachedAnnotation, "attachedAnnotation");
}
Invariant.Assert(_range != null, "null highlight range");
//fire trace event
EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordAnnotation, EventTrace.Event.RemoveAttachedHighlightBegin);
//check input data and retrieve the TextContainer
ITextContainer textContainer = CheckInputData(attachedAnnotation);
Invariant.Assert(textContainer.Highlights != null, "textContainer.Highlights is null");
//get AnnotationHighlightLayer in the textContainer
AnnotationHighlightLayer highlightLayer = textContainer.Highlights.GetLayer(typeof(HighlightComponent)) as AnnotationHighlightLayer;
Invariant.Assert(highlightLayer != null, "AnnotationHighlightLayer is not initialized");
//unregister of cargo changes
_attachedAnnotation.Annotation.CargoChanged -= new AnnotationResourceChangedEventHandler(OnAnnotationUpdated);
highlightLayer.RemoveRange(this);
//highlight is removed - remove the attached annotation and the data
_attachedAnnotation = null;
//fire trace event
EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordAnnotation, EventTrace.Event.RemoveAttachedHighlightEnd);
}
/// <summary>
/// Modify an attached annotation that is held by the component
/// </summary>
/// <param name="attachedAnnotation">The attached annotation after modification</param>
/// <param name="previousAttachedAnchor">The attached anchor previously associated with the attached annotation.</param>
/// <param name="previousAttachmentLevel">The previous attachment level of the attached annotation.</param>
public void ModifyAttachedAnnotation(IAttachedAnnotation attachedAnnotation, object previousAttachedAnchor, AttachmentLevel previousAttachmentLevel)
{
throw new NotSupportedException(SR.NotSupported);
}
/// <summary>
/// Sets highlight color to active/inactive
/// <param name="active">true - activate, false = deactivate</param>
/// </summary>
public void Activate(bool active)
{
//return if the state is unchanged
if (_active == active)
return;
//get the highlight layer
if (_attachedAnnotation == null)
{
throw new InvalidOperationException(SR.NoAttachedAnnotationToModify);
}
TextAnchor textAnchor = _attachedAnnotation.AttachedAnchor as TextAnchor;
Invariant.Assert(textAnchor != null, "AttachedAnchor is not a text anchor");
//this should be in a fixed or flow textcontainer
ITextContainer textContainer = textAnchor.Start.TextContainer;
Invariant.Assert(textContainer != null, "TextAnchor does not belong to a TextContainer");
//get AnnotationHighlightLayer in the textContainer
AnnotationHighlightLayer highlightLayer = textContainer.Highlights.GetLayer(typeof(HighlightComponent)) as AnnotationHighlightLayer;
Invariant.Assert(highlightLayer != null, "AnnotationHighlightLayer is not initialized");
highlightLayer.ActivateRange(this, active);
_active = active;
if (active)
HighlightBrush = new SolidColorBrush(_selectedBackground);
else
HighlightBrush = new SolidColorBrush(_background);
}
#endregion Public Methods
//------------------------------------------------------
//
// IHighlightRange implementation
//
//------------------------------------------------------
#region IHighlightRange implementation
#region Internal Methods
/// <summary>
/// Adds a new shape to the children of the corresponding visual
/// </summary>
/// <param name="child">the new child</param>
void IHighlightRange.AddChild(Shape child)
{
Children.Add(child);
}
/// <summary>
/// Removes a shape from the children of the corresponding visual
/// </summary>
/// <param name="child">the new child</param>
void IHighlightRange.RemoveChild(Shape child)
{
Children.Remove(child);
}
#endregion Internal Methods
//------------------------------------------------------
//
// Internal properties
//
//------------------------------------------------------
#region Internal Properties
/// <summary>
/// Highlight Background color
/// </summary>
Color IHighlightRange.Background
{
get
{
return _background;
}
}
/// <summary>
/// Highlight color if highlight is active
/// </summary>
Color IHighlightRange.SelectedBackground
{
get
{
return _selectedBackground;
}
}
/// <summary>
/// Highlight TextSegment
/// </summary>
TextAnchor IHighlightRange.Range
{
get
{
return _range;
}
}
/// <summary>
/// Highlight priority
/// </summary>
int IHighlightRange.Priority
{
get
{
return _priority;
}
}
/// <summary>
/// if true - highlight only the content of tables, figures and floaters
/// </summary>
bool IHighlightRange.HighlightContent
{
get
{
return _highlightContent;
}
}
#endregion Internal Properties
#endregion IHighlightRange implementation
#region Internal Methods
internal bool IsSelected(ITextRange selection)
{
ArgumentNullException.ThrowIfNull(selection);
Invariant.Assert(_attachedAnnotation != null, "No _attachedAnnotation");
// For activation based on the selection state, we need to use anchors that
// span virtualized content (i.e., content that isn't visible). For that we
// grab the 'fullly resolved' anchors instead of the anchors used by the
// framework for all other purposes.
TextAnchor fullAnchor = _attachedAnnotation.FullyAttachedAnchor as TextAnchor;
//Debug.Assert(fullAnchor != null, "null TextAnchor");
if (fullAnchor == null)
return false;
return fullAnchor.IsOverlapping(selection.TextSegments);
}
/// <summary>
/// Looks for Colors from the Annotation's cargo. If corresponding Color is present
/// set it to the input parameter. Otherwise leave the parameter intact.
/// </summary>
/// <param name="annot">The Annotation</param>
/// <param name="backgroundColor">background Color</param>
/// <param name="activeBackgroundColor">background Color for active highlight</param>
internal static void GetCargoColors(Annotation annot, ref Nullable<Color> backgroundColor, ref Nullable<Color> activeBackgroundColor)
{
Invariant.Assert(annot != null, "annotation is null");
ICollection<AnnotationResource> cargos = annot.Cargos;
if (cargos != null)
{
foreach (AnnotationResource cargo in cargos)
{
if (cargo.Name == HighlightResourceName)
{
ICollection contents = cargo.Contents;
foreach (XmlElement content in contents)
{
if ((content.LocalName == ColorsContentName) &&
(content.NamespaceURI == AnnotationXmlConstants.Namespaces.BaseSchemaNamespace))
{
if (content.Attributes[BackgroundAttributeName] != null)
backgroundColor = GetColor(content.Attributes[BackgroundAttributeName].Value);
if (content.Attributes[ActiveBackgroundAttributeName] != null)
activeBackgroundColor = GetColor(content.Attributes[ActiveBackgroundAttributeName].Value);
}
}
}
}
}
}
#endregion Internal Methods
#region Private Methods
/// <summary>
/// Checks if this attachedAnnotation data - AttachedAnchor and Annotation
/// </summary>
/// <param name="attachedAnnotation">The AttachedAnnotation</param>
/// <returns>The AttachedAnchor TextContainer</returns>
private ITextContainer CheckInputData(IAttachedAnnotation attachedAnnotation)
{
ArgumentNullException.ThrowIfNull(attachedAnnotation);
TextAnchor textAnchor = attachedAnnotation.AttachedAnchor as TextAnchor;
if (textAnchor == null)
{
throw new ArgumentException(SR.InvalidAttachedAnchor, "attachedAnnotation");
}
//this should be in a fixed or flow textcontainer
ITextContainer textContainer = textAnchor.Start.TextContainer;
Invariant.Assert(textContainer != null, "TextAnchor does not belong to a TextContainer");
if (attachedAnnotation.Annotation == null)
{
throw new ArgumentException(SR.AnnotationIsNull, "attachedAnnotation");
}
//check annotation type
if (!_type.Equals(attachedAnnotation.Annotation.AnnotationType))
{
throw new ArgumentException(SR.Format(SR.NotHighlightAnnotationType, attachedAnnotation.Annotation.AnnotationType.ToString()), "attachedAnnotation");
}
return textContainer;
}
/// <summary>
/// Converts a string to Color object
/// </summary>
/// <param name="color">Color string</param>
/// <returns>Color object</returns>
private static Color GetColor(string color)
{
return (Color)ColorConverter.ConvertFromString(color);
}
/// <summary>
/// Gets the Colors from the Annotation's cargo. If corresponding Color is not present
/// a default value is used.
/// </summary>
/// <param name="annot">The Annotation</param>
/// <param name="backgroundColor">background Color</param>
/// <param name="activeBackgroundColor">background Color for active highlight</param>
private void GetColors(Annotation annot, out Color backgroundColor, out Color activeBackgroundColor)
{
Nullable<Color> tempBackgroundColor = _defaultBackroundColor;
Nullable<Color> tempActiveBackgroundColor = _defaultActiveBackgroundColor;
GetCargoColors(annot, ref tempBackgroundColor, ref tempActiveBackgroundColor);
backgroundColor = (Color)tempBackgroundColor;
activeBackgroundColor = (Color)tempActiveBackgroundColor;
}
/// <summary>
/// Called when the Annotation cargo changes
/// </summary>
/// <param name="sender">the sender</param>
/// <param name="args">event arguments</param>
private void OnAnnotationUpdated(object sender, AnnotationResourceChangedEventArgs args)
{
Invariant.Assert(_attachedAnnotation != null && _attachedAnnotation.Annotation == args.Annotation, "_attachedAnnotation is different than the input one");
Invariant.Assert(_range != null, "The highlight range is null");
//get text container
TextAnchor textAnchor = _attachedAnnotation.AttachedAnchor as TextAnchor;
Invariant.Assert(textAnchor != null, "wrong anchor type of the saved attached annotation");
//this should be in a fixed or flow textcontainer
ITextContainer textContainer = textAnchor.Start.TextContainer;
Invariant.Assert(textContainer != null, "TextAnchor does not belong to a TextContainer");
//Get highlight Colors from the cargo and update the highlight layer
Color background, activeBackground;
GetColors(args.Annotation, out background, out activeBackground);
if (!_background.Equals(background) ||
!_selectedBackground.Equals(activeBackground))
{
//modify the highlight
Invariant.Assert(textContainer.Highlights != null, "textContainer.Highlights is null");
//get AnnotationHighlightLayer in the textContainer
AnnotationHighlightLayer highlightLayer = textContainer.Highlights.GetLayer(typeof(HighlightComponent)) as AnnotationHighlightLayer;
if (highlightLayer == null)
{
throw new InvalidDataException(SR.MissingAnnotationHighlightLayer);
}
//change the colors and invalidate
_background = background;
_selectedBackground = activeBackground;
highlightLayer.ModifiedRange(this);
}
}
/// <summary>
/// Invalidating the measure on all the children (which are Shapes)
/// causes them to recalculate their desired geometry
/// </summary>
private void InvalidateChildren()
{
// Invalidating the measure on all the children (which are Shapes)
// causes them to recalculate their desired geometry
foreach (Visual child in Children)
{
Shape uiChild = child as Shape;
Invariant.Assert(uiChild != null, "HighlightComponent has non-Shape children.");
uiChild.InvalidateMeasure();
}
//reset the Dirty flag
IsDirty = false;
}
#endregion Private Methods
#region Public Fields
//resource and content names
public const string HighlightResourceName = "Highlight";
public const string ColorsContentName = "Colors";
public const string BackgroundAttributeName = "Background";
public const string ActiveBackgroundAttributeName = "ActiveBackground";
#endregion Public Fields
//------------------------------------------------------
//
// Private fields
//
//------------------------------------------------------
#region Private Fields
private Color _background;
private Color _selectedBackground;
private TextAnchor _range; //the entire range for this highlight
private IAttachedAnnotation _attachedAnnotation;
private PresentationContext _presentationContext;
private static readonly XmlQualifiedName _name = new XmlQualifiedName("Highlight", AnnotationXmlConstants.Namespaces.BaseSchemaNamespace);
private XmlQualifiedName _type = _name;
private int _priority = 0; //used for highlights Z-order. The segment owners are ordered with highest
// priority first
private bool _highlightContent = true; //highlight only the content of tables, figures and floaters
private bool _active = false; //saves the highlight state - active or not
private bool _isDirty = true; //shows if the annotation is in sync with the content of the AnnotatedElement
//default color colors
private Color _defaultBackroundColor = (Color)ColorConverter.ConvertFromString("#33FFFF00");
private Color _defaultActiveBackgroundColor = (Color)ColorConverter.ConvertFromString("#339ACD32");
#endregion Private Fields
}
}
|