context: // <file>
  //     <copyright see="prj:///doc/copyright.txt"/>
  //     <license see="prj:///doc/license.txt"/>
  //     <author name="Daniel Grunwald"/>
  //     <version>$Revision: 5584 $</version>
  // </file>
  
  using System;
  using System.Collections.Generic;
  using System.Collections.ObjectModel;
  using System.ComponentModel;
  using System.ComponentModel.Design;
  using System.Diagnostics;
  using System.Globalization;
  using System.Linq;
  using System.Windows;
  using System.Windows.Controls;
  using System.Windows.Controls.Primitives;
  using System.Windows.Input;
  using System.Windows.Media;
  using System.Windows.Media.TextFormatting;
  using System.Windows.Threading;
  
  using ICSharpCode.AvalonEdit.Document;
  using ICSharpCode.AvalonEdit.Utils;
  
  namespace ICSharpCode.AvalonEdit.Rendering
  {
  	/// <summary>
  	/// A virtualizing panel producing+showing <see cref="VisualLine"/>s for a <see cref="TextDocument"/>.
  	/// 
  	/// This is the heart of the text editor, this class controls the text rendering process.
  	/// 
  	/// Taken as a standalone control, it's a text viewer without any editing capability.
  	/// </summary>
  	[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable",
  	                                                 Justification = "The user usually doesn't work with TextView but with TextEditor; and nulling the Document property is sufficient to dispose everything.")]
  	public class TextView : FrameworkElement, IScrollInfo, IWeakEventListener, ITextEditorComponent, IServiceProvider
  	{
  		#region Constructor
  		static TextView()
  		{
  			ClipToBoundsProperty.OverrideMetadata(typeof(TextView), new FrameworkPropertyMetadata(Boxes.True));
  			FocusableProperty.OverrideMetadata(typeof(TextView), new FrameworkPropertyMetadata(Boxes.False));
  		}
  		
  		/// <summary>
  		/// Creates a new TextView instance.
  		/// </summary>
  		public TextView()
  		{
  			services.AddService(typeof(TextView), this);
  			textLayer = new TextLayer(this);
  			elementGenerators = new ObserveAddRemoveCollection<VisualLineElementGenerator>(ElementGenerator_Added, ElementGenerator_Removed);
  			lineTransformers = new ObserveAddRemoveCollection<IVisualLineTransformer>(LineTransformer_Added, LineTransformer_Removed);
  			backgroundRenderers = new ObserveAddRemoveCollection<IBackgroundRenderer>(BackgroundRenderer_Added, BackgroundRenderer_Removed);
  			this.Options = new TextEditorOptions();
  			Debug.Assert(singleCharacterElementGenerator != null); // assert that the option change created the builtin element generators
  			
  			layers = new UIElementCollection(this, this);
  			InsertLayer(textLayer, KnownLayer.Text, LayerInsertionPosition.Replace);
  		}
  		#endregion
  		
  		#region Document Property
  		/// <summary>
  		/// Document property.
  		/// </summary>
  		public static readonly DependencyProperty DocumentProperty =
  			DependencyProperty.Register("Document", typeof(TextDocument), typeof(TextView),
  			                            new FrameworkPropertyMetadata(OnDocumentChanged));
  		
  		TextDocument document;
  		HeightTree heightTree;
  		
  		/// <summary>
  		/// Gets/Sets the document displayed by the text editor.
  		/// </summary>
  		public TextDocument Document {
  			get { return (TextDocument)GetValue(DocumentProperty); }
  			set { SetValue(DocumentProperty, value); }
  		}
  		
  		static void OnDocumentChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
  		{
  			((TextView)dp).OnDocumentChanged((TextDocument)e.OldValue, (TextDocument)e.NewValue);
  		}
  		
  		internal double FontSize {
  			get {
  				return (double)GetValue(TextBlock.FontSizeProperty);
  			}
  		}
  		
  		/// <summary>
  		/// Occurs when the document property has changed.
  		/// </summary>
  		public event EventHandler DocumentChanged;
  		
  		void OnDocumentChanged(TextDocument oldValue, TextDocument newValue)
  		{
  			if (oldValue != null) {
  				heightTree.Dispose();
  				heightTree = null;
  				formatter.Dispose();
  				formatter = null;
  				TextDocumentWeakEventManager.Changing.RemoveListener(oldValue, this);
  			}
  			this.document = newValue;
  			ClearScrollData();
  			ClearVisualLines();
  			if (newValue != null) {
  				TextDocumentWeakEventManager.Changing.AddListener(newValue, this);
  				heightTree = new HeightTree(newValue, FontSize + 3);
  				formatter = TextFormatterFactory.Create(this);
  			}
  			InvalidateMeasure(DispatcherPriority.Normal);
  			if (DocumentChanged != null)
  				DocumentChanged(this, EventArgs.Empty);
  		}
  		
  		/// <summary>
  		/// Recreates the text formatter that is used internally
  		/// by calling <see cref="TextFormatterFactory.Create"/>.
  		/// </summary>
  		void RecreateTextFormatter()
  		{
  			if (formatter != null) {
  				formatter.Dispose();
  				formatter = TextFormatterFactory.Create(this);
  				Redraw();
  			}
  		}
  		
  		/// <inheritdoc cref="IWeakEventListener.ReceiveWeakEvent"/>
  		protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
  		{
  			if (managerType == typeof(TextDocumentWeakEventManager.Changing)) {
  				// 'TODO: put redraw into background so that other input events can be handled before the redraw.
  				// Unfortunately the "easy" approach (just use DispatcherPriority.Background) here makes the editor twice as slow because
  				// the caret position change forces an immediate redraw, and the text input then forces a background redraw.
  				// When fixing this, make sure performance on the SharpDevelop "type text in C# comment" stress test doesn't get significantly worse.
  				DocumentChangeEventArgs change = (DocumentChangeEventArgs)e;
  				Redraw(change.Offset, change.RemovalLength, DispatcherPriority.Normal);
  				return true;
  			} else if (managerType == typeof(PropertyChangedWeakEventManager)) {
  				OnOptionChanged((PropertyChangedEventArgs)e);
  				return true;
  			}
  			return false;
  		}
  		
  		bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
  		{
  			return ReceiveWeakEvent(managerType, sender, e);
  		}
  		#endregion
  		
  		#region Options property
  		/// <summary>
  		/// Options property.
  		/// </summary>
  		public static readonly DependencyProperty OptionsProperty =
  			DependencyProperty.Register("Options", typeof(TextEditorOptions), typeof(TextView),
  			                            new FrameworkPropertyMetadata(OnOptionsChanged));
  		
  		/// <summary>
  		/// Gets/Sets the document displayed by the text editor.
  		/// </summary>
  		public TextEditorOptions Options {
  			get { return (TextEditorOptions)GetValue(OptionsProperty); }
  			set { SetValue(OptionsProperty, value); }
  		}
  		
  		/// <summary>
  		/// Occurs when a text editor option has changed.
  		/// </summary>
  		public event PropertyChangedEventHandler OptionChanged;
  		
  		/// <summary>
  		/// Raises the <see cref="OptionChanged"/> event.
  		/// </summary>
  		protected virtual void OnOptionChanged(PropertyChangedEventArgs e)
  		{
  			if (OptionChanged != null) {
  				OptionChanged(this, e);
  			}
  			UpdateBuiltinElementGeneratorsFromOptions();
  			Redraw();
  		}
  		
  		static void OnOptionsChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
  		{
  			((TextView)dp).OnOptionsChanged((TextEditorOptions)e.OldValue, (TextEditorOptions)e.NewValue);
  		}
  		
  		void OnOptionsChanged(TextEditorOptions oldValue, TextEditorOptions newValue)
  		{
  			if (oldValue != null) {
  				PropertyChangedWeakEventManager.RemoveListener(oldValue, this);
  			}
  			if (newValue != null) {
  				PropertyChangedWeakEventManager.AddListener(newValue, this);
  			}
  			OnOptionChanged(new PropertyChangedEventArgs(null));
  		}
  		#endregion
  		
  		#region ElementGenerators+LineTransformers Properties
  		readonly ObserveAddRemoveCollection<VisualLineElementGenerator> elementGenerators;
  		
  		/// <summary>
  		/// Gets a collection where element generators can be registered.
  		/// </summary>
  		public IList<VisualLineElementGenerator> ElementGenerators {
  			get { return elementGenerators; }
  		}
  		
  		void ElementGenerator_Added(VisualLineElementGenerator generator)
  		{
  			ConnectToTextView(generator);
  			Redraw();
  		}
  		
  		void ElementGenerator_Removed(VisualLineElementGenerator generator)
  		{
  			DisconnectFromTextView(generator);
  			Redraw();
  		}
  		
  		readonly ObserveAddRemoveCollection<IVisualLineTransformer> lineTransformers;
  		
  		/// <summary>
  		/// Gets a collection where line transformers can be registered.
  		/// </summary>
  		public IList<IVisualLineTransformer> LineTransformers {
  			get { return lineTransformers; }
  		}
  		
  		void LineTransformer_Added(IVisualLineTransformer lineTransformer)
  		{
  			ConnectToTextView(lineTransformer);
  			Redraw();
  		}
  		
  		void LineTransformer_Removed(IVisualLineTransformer lineTransformer)
  		{
  			DisconnectFromTextView(lineTransformer);
  			Redraw();
  		}
  		#endregion
  		
  		#region Builtin ElementGenerators
  		NewLineElementGenerator newLineElementGenerator;
  		SingleCharacterElementGenerator singleCharacterElementGenerator;
  		LinkElementGenerator linkElementGenerator;
  		MailLinkElementGenerator mailLinkElementGenerator;
  		
  		void UpdateBuiltinElementGeneratorsFromOptions()
  		{
  			TextEditorOptions options = this.Options;
  			
  			AddRemoveDefaultElementGeneratorOnDemand(ref newLineElementGenerator, options.ShowEndOfLine);
  			AddRemoveDefaultElementGeneratorOnDemand(ref singleCharacterElementGenerator, options.ShowBoxForControlCharacters || options.ShowSpaces || options.ShowTabs);
  			AddRemoveDefaultElementGeneratorOnDemand(ref linkElementGenerator, options.EnableHyperlinks);
  			AddRemoveDefaultElementGeneratorOnDemand(ref mailLinkElementGenerator, options.EnableEmailHyperlinks);
  		}
  		
  		void AddRemoveDefaultElementGeneratorOnDemand<T>(ref T generator, bool demand)
  			where T : VisualLineElementGenerator, IBuiltinElementGenerator, new()
  		{
  			bool hasGenerator = generator != null;
  			if (hasGenerator != demand) {
  				if (demand) {
  					generator = new T();
  					this.ElementGenerators.Add(generator);
  				} else {
  					this.ElementGenerators.Remove(generator);
  					generator = null;
  				}
  			}
  			if (generator != null)
  				generator.FetchOptions(this.Options);
  		}
  		#endregion
  		
  		#region Layers
  		internal readonly TextLayer textLayer;
  		readonly UIElementCollection layers;
  		
  		/// <summary>
  		/// Gets the list of layers displayed in the text view.
  		/// </summary>
  		public UIElementCollection Layers {
  			get { return layers; }
  		}
  		
  		/// <summary>
  		/// Inserts a new layer at a position specified relative to an existing layer.
  		/// </summary>
  		/// <param name="layer">The new layer to insert.</param>
  		/// <param name="referencedLayer">The existing layer</param>
  		/// <param name="position">Specifies whether the layer is inserted above,below, or replaces the referenced layer</param>
  		public void InsertLayer(UIElement layer, KnownLayer referencedLayer, LayerInsertionPosition position)
  		{
  
gt: 			if (layer == null)