PageRenderTime 82ms CodeModel.GetById 16ms app.highlight 59ms RepoModel.GetById 2ms app.codeStats 0ms

/AvalonEdit/ICSharpCode.AvalonEdit/Editing/SelectionMouseHandler.cs

http://github.com/icsharpcode/ILSpy
C# | 676 lines | 557 code | 53 blank | 66 comment | 182 complexity | 74785dd42c340dd81dba214c3ff65cdf MD5 | raw file
  1// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
  2// 
  3// Permission is hereby granted, free of charge, to any person obtaining a copy of this
  4// software and associated documentation files (the "Software"), to deal in the Software
  5// without restriction, including without limitation the rights to use, copy, modify, merge,
  6// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
  7// to whom the Software is furnished to do so, subject to the following conditions:
  8// 
  9// The above copyright notice and this permission notice shall be included in all copies or
 10// substantial portions of the Software.
 11// 
 12// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 13// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 14// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
 15// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 16// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 17// DEALINGS IN THE SOFTWARE.
 18
 19using System;
 20using System.ComponentModel;
 21using System.Diagnostics;
 22using System.Linq;
 23using System.Runtime.InteropServices;
 24using System.Windows;
 25using System.Windows.Documents;
 26using System.Windows.Input;
 27using System.Windows.Media.TextFormatting;
 28using System.Windows.Threading;
 29using ICSharpCode.AvalonEdit.Document;
 30using ICSharpCode.AvalonEdit.Rendering;
 31using ICSharpCode.AvalonEdit.Utils;
 32#if NREFACTORY
 33using ICSharpCode.NRefactory.Editor;
 34#endif
 35
 36namespace ICSharpCode.AvalonEdit.Editing
 37{
 38	/// <summary>
 39	/// Handles selection of text using the mouse.
 40	/// </summary>
 41	sealed class SelectionMouseHandler : ITextAreaInputHandler
 42	{
 43		#region enum SelectionMode
 44		enum SelectionMode
 45		{
 46			/// <summary>
 47			/// no selection (no mouse button down)
 48			/// </summary>
 49			None,
 50			/// <summary>
 51			/// left mouse button down on selection, might be normal click
 52			/// or might be drag'n'drop
 53			/// </summary>
 54			PossibleDragStart,
 55			/// <summary>
 56			/// dragging text
 57			/// </summary>
 58			Drag,
 59			/// <summary>
 60			/// normal selection (click+drag)
 61			/// </summary>
 62			Normal,
 63			/// <summary>
 64			/// whole-word selection (double click+drag or ctrl+click+drag)
 65			/// </summary>
 66			WholeWord,
 67			/// <summary>
 68			/// whole-line selection (triple click+drag)
 69			/// </summary>
 70			WholeLine,
 71			/// <summary>
 72			/// rectangular selection (alt+click+drag)
 73			/// </summary>
 74			Rectangular
 75		}
 76		#endregion
 77		
 78		readonly TextArea textArea;
 79		
 80		SelectionMode mode;
 81		AnchorSegment startWord;
 82		Point possibleDragStartMousePos;
 83		
 84		#region Constructor + Attach + Detach
 85		public SelectionMouseHandler(TextArea textArea)
 86		{
 87			if (textArea == null)
 88				throw new ArgumentNullException("textArea");
 89			this.textArea = textArea;
 90		}
 91		
 92		public TextArea TextArea {
 93			get { return textArea; }
 94		}
 95		
 96		public void Attach()
 97		{
 98			textArea.MouseLeftButtonDown += textArea_MouseLeftButtonDown;
 99			textArea.MouseMove += textArea_MouseMove;
100			textArea.MouseLeftButtonUp += textArea_MouseLeftButtonUp;
101			textArea.QueryCursor += textArea_QueryCursor;
102			textArea.OptionChanged += textArea_OptionChanged;
103			
104			enableTextDragDrop = textArea.Options.EnableTextDragDrop;
105			if (enableTextDragDrop) {
106				AttachDragDrop();
107			}
108		}
109		
110		public void Detach()
111		{
112			mode = SelectionMode.None;
113			textArea.MouseLeftButtonDown -= textArea_MouseLeftButtonDown;
114			textArea.MouseMove -= textArea_MouseMove;
115			textArea.MouseLeftButtonUp -= textArea_MouseLeftButtonUp;
116			textArea.QueryCursor -= textArea_QueryCursor;
117			textArea.OptionChanged -= textArea_OptionChanged;
118			if (enableTextDragDrop) {
119				DetachDragDrop();
120			}
121		}
122		
123		void AttachDragDrop()
124		{
125			textArea.AllowDrop = true;
126			textArea.GiveFeedback += textArea_GiveFeedback;
127			textArea.QueryContinueDrag += textArea_QueryContinueDrag;
128			textArea.DragEnter += textArea_DragEnter;
129			textArea.DragOver += textArea_DragOver;
130			textArea.DragLeave += textArea_DragLeave;
131			textArea.Drop += textArea_Drop;
132		}
133		
134		void DetachDragDrop()
135		{
136			textArea.AllowDrop = false;
137			textArea.GiveFeedback -= textArea_GiveFeedback;
138			textArea.QueryContinueDrag -= textArea_QueryContinueDrag;
139			textArea.DragEnter -= textArea_DragEnter;
140			textArea.DragOver -= textArea_DragOver;
141			textArea.DragLeave -= textArea_DragLeave;
142			textArea.Drop -= textArea_Drop;
143		}
144		
145		bool enableTextDragDrop;
146		
147		void textArea_OptionChanged(object sender, PropertyChangedEventArgs e)
148		{
149			bool newEnableTextDragDrop = textArea.Options.EnableTextDragDrop;
150			if (newEnableTextDragDrop != enableTextDragDrop) {
151				enableTextDragDrop = newEnableTextDragDrop;
152				if (newEnableTextDragDrop)
153					AttachDragDrop();
154				else
155					DetachDragDrop();
156			}
157		}
158		#endregion
159		
160		#region Dropping text
161		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
162		void textArea_DragEnter(object sender, DragEventArgs e)
163		{
164			try {
165				e.Effects = GetEffect(e);
166				textArea.Caret.Show();
167			} catch (Exception ex) {
168				OnDragException(ex);
169			}
170		}
171
172		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
173		void textArea_DragOver(object sender, DragEventArgs e)
174		{
175			try {
176				e.Effects = GetEffect(e);
177			} catch (Exception ex) {
178				OnDragException(ex);
179			}
180		}
181		
182		DragDropEffects GetEffect(DragEventArgs e)
183		{
184			if (e.Data.GetDataPresent(DataFormats.UnicodeText, true)) {
185				e.Handled = true;
186				int visualColumn;
187				bool isAtEndOfLine;
188				int offset = GetOffsetFromMousePosition(e.GetPosition(textArea.TextView), out visualColumn, out isAtEndOfLine);
189				if (offset >= 0) {
190					textArea.Caret.Position = new TextViewPosition(textArea.Document.GetLocation(offset), visualColumn) { IsAtEndOfLine = isAtEndOfLine };
191					textArea.Caret.DesiredXPos = double.NaN;
192					if (textArea.ReadOnlySectionProvider.CanInsert(offset)) {
193						if ((e.AllowedEffects & DragDropEffects.Move) == DragDropEffects.Move
194						    && (e.KeyStates & DragDropKeyStates.ControlKey) != DragDropKeyStates.ControlKey)
195						{
196							return DragDropEffects.Move;
197						} else {
198							return e.AllowedEffects & DragDropEffects.Copy;
199						}
200					}
201				}
202			}
203			return DragDropEffects.None;
204		}
205		
206		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
207		void textArea_DragLeave(object sender, DragEventArgs e)
208		{
209			try {
210				e.Handled = true;
211				if (!textArea.IsKeyboardFocusWithin)
212					textArea.Caret.Hide();
213			} catch (Exception ex) {
214				OnDragException(ex);
215			}
216		}
217		
218		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
219		void textArea_Drop(object sender, DragEventArgs e)
220		{
221			try {
222				DragDropEffects effect = GetEffect(e);
223				e.Effects = effect;
224				if (effect != DragDropEffects.None) {
225					string text = e.Data.GetData(DataFormats.UnicodeText, true) as string;
226					if (text != null) {
227						int start = textArea.Caret.Offset;
228						if (mode == SelectionMode.Drag && textArea.Selection.Contains(start)) {
229							Debug.WriteLine("Drop: did not drop: drop target is inside selection");
230							e.Effects = DragDropEffects.None;
231						} else {
232							Debug.WriteLine("Drop: insert at " + start);
233							
234							bool rectangular = e.Data.GetDataPresent(RectangleSelection.RectangularSelectionDataType);
235							
236							string newLine = TextUtilities.GetNewLineFromDocument(textArea.Document, textArea.Caret.Line);
237							text = TextUtilities.NormalizeNewLines(text, newLine);
238							
239							string pasteFormat;
240							// fill the suggested DataFormat used for the paste action:
241							if (rectangular)
242								pasteFormat = RectangleSelection.RectangularSelectionDataType;
243							else
244								pasteFormat = DataFormats.UnicodeText;
245							
246							var pastingEventArgs = new DataObjectPastingEventArgs(e.Data, true, pasteFormat);
247							textArea.RaiseEvent(pastingEventArgs);
248							if (pastingEventArgs.CommandCancelled)
249								return;
250							
251							// DataObject.PastingEvent handlers might have changed the format to apply.
252							rectangular = pastingEventArgs.FormatToApply == RectangleSelection.RectangularSelectionDataType;
253							
254							// Mark the undo group with the currentDragDescriptor, if the drag
255							// is originating from the same control. This allows combining
256							// the undo groups when text is moved.
257							textArea.Document.UndoStack.StartUndoGroup(this.currentDragDescriptor);
258							try {
259								if (rectangular && RectangleSelection.PerformRectangularPaste(textArea, textArea.Caret.Position, text, true)) {
260									
261								} else {
262									textArea.Document.Insert(start, text);
263									textArea.Selection = Selection.Create(textArea, start, start + text.Length);
264								}
265							} finally {
266								textArea.Document.UndoStack.EndUndoGroup();
267							}
268						}
269						e.Handled = true;
270					}
271				}
272			} catch (Exception ex) {
273				OnDragException(ex);
274			}
275		}
276		
277		void OnDragException(Exception ex)
278		{
279			// WPF swallows exceptions during drag'n'drop or reports them incorrectly, so
280			// we re-throw them later to allow the application's unhandled exception handler
281			// to catch them
282			textArea.Dispatcher.BeginInvoke(
283				DispatcherPriority.Send,
284				new Action(delegate {
285				           	throw new DragDropException("Exception during drag'n'drop", ex);
286				           }));
287		}
288		
289		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
290		void textArea_GiveFeedback(object sender, GiveFeedbackEventArgs e)
291		{
292			try {
293				e.UseDefaultCursors = true;
294				e.Handled = true;
295			} catch (Exception ex) {
296				OnDragException(ex);
297			}
298		}
299		
300		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
301		void textArea_QueryContinueDrag(object sender, QueryContinueDragEventArgs e)
302		{
303			try {
304				if (e.EscapePressed) {
305					e.Action = DragAction.Cancel;
306				} else if ((e.KeyStates & DragDropKeyStates.LeftMouseButton) != DragDropKeyStates.LeftMouseButton) {
307					e.Action = DragAction.Drop;
308				} else {
309					e.Action = DragAction.Continue;
310				}
311				e.Handled = true;
312			} catch (Exception ex) {
313				OnDragException(ex);
314			}
315		}
316		#endregion
317		
318		#region Start Drag
319		object currentDragDescriptor;
320		
321		void StartDrag()
322		{
323			// prevent nested StartDrag calls
324			mode = SelectionMode.Drag;
325			
326			// mouse capture and Drag'n'Drop doesn't mix
327			textArea.ReleaseMouseCapture();
328			
329			DataObject dataObject = textArea.Selection.CreateDataObject(textArea);
330			
331			DragDropEffects allowedEffects = DragDropEffects.All;
332			var deleteOnMove = textArea.Selection.Segments.Select(s => new AnchorSegment(textArea.Document, s)).ToList();
333			foreach (ISegment s in deleteOnMove) {
334				ISegment[] result = textArea.GetDeletableSegments(s);
335				if (result.Length != 1 || result[0].Offset != s.Offset || result[0].EndOffset != s.EndOffset) {
336					allowedEffects &= ~DragDropEffects.Move;
337				}
338			}
339			
340			var copyingEventArgs = new DataObjectCopyingEventArgs(dataObject, true);
341			textArea.RaiseEvent(copyingEventArgs);
342			if (copyingEventArgs.CommandCancelled)
343				return;
344			
345			object dragDescriptor = new object();
346			this.currentDragDescriptor = dragDescriptor;
347			
348			DragDropEffects resultEffect;
349			using (textArea.AllowCaretOutsideSelection()) {
350				var oldCaretPosition = textArea.Caret.Position;
351				try {
352					Debug.WriteLine("DoDragDrop with allowedEffects=" + allowedEffects);
353					resultEffect = DragDrop.DoDragDrop(textArea, dataObject, allowedEffects);
354					Debug.WriteLine("DoDragDrop done, resultEffect=" + resultEffect);
355				} catch (COMException ex) {
356					// ignore COM errors - don't crash on badly implemented drop targets
357					Debug.WriteLine("DoDragDrop failed: " + ex.ToString());
358					return;
359				}
360				if (resultEffect == DragDropEffects.None) {
361					// reset caret if drag was aborted
362					textArea.Caret.Position = oldCaretPosition;
363				}
364			}
365			
366			this.currentDragDescriptor = null;
367			
368			if (deleteOnMove != null && resultEffect == DragDropEffects.Move && (allowedEffects & DragDropEffects.Move) == DragDropEffects.Move) {
369				bool draggedInsideSingleDocument = (dragDescriptor == textArea.Document.UndoStack.LastGroupDescriptor);
370				if (draggedInsideSingleDocument)
371					textArea.Document.UndoStack.StartContinuedUndoGroup(null);
372				textArea.Document.BeginUpdate();
373				try {
374					foreach (ISegment s in deleteOnMove) {
375						textArea.Document.Remove(s.Offset, s.Length);
376					}
377				} finally {
378					textArea.Document.EndUpdate();
379					if (draggedInsideSingleDocument)
380						textArea.Document.UndoStack.EndUndoGroup();
381				}
382			}
383		}
384		#endregion
385		
386		#region QueryCursor
387		// provide the IBeam Cursor for the text area
388		void textArea_QueryCursor(object sender, QueryCursorEventArgs e)
389		{
390			if (!e.Handled) {
391				if (mode != SelectionMode.None || !enableTextDragDrop) {
392					e.Cursor = Cursors.IBeam;
393					e.Handled = true;
394				} else if (textArea.TextView.VisualLinesValid) {
395					// Only query the cursor if the visual lines are valid.
396					// If they are invalid, the cursor will get re-queried when the visual lines
397					// get refreshed.
398					Point p = e.GetPosition(textArea.TextView);
399					if (p.X >= 0 && p.Y >= 0 && p.X <= textArea.TextView.ActualWidth && p.Y <= textArea.TextView.ActualHeight) {
400						int visualColumn;
401						bool isAtEndOfLine;
402						int offset = GetOffsetFromMousePosition(e, out visualColumn, out isAtEndOfLine);
403						if (textArea.Selection.Contains(offset))
404							e.Cursor = Cursors.Arrow;
405						else
406							e.Cursor = Cursors.IBeam;
407						e.Handled = true;
408					}
409				}
410			}
411		}
412		#endregion
413		
414		#region LeftButtonDown
415		void textArea_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
416		{
417			mode = SelectionMode.None;
418			if (!e.Handled && e.ChangedButton == MouseButton.Left) {
419				ModifierKeys modifiers = Keyboard.Modifiers;
420				bool shift = (modifiers & ModifierKeys.Shift) == ModifierKeys.Shift;
421				if (enableTextDragDrop && e.ClickCount == 1 && !shift) {
422					int visualColumn;
423					bool isAtEndOfLine;
424					int offset = GetOffsetFromMousePosition(e, out visualColumn, out isAtEndOfLine);
425					if (textArea.Selection.Contains(offset)) {
426						if (textArea.CaptureMouse()) {
427							mode = SelectionMode.PossibleDragStart;
428							possibleDragStartMousePos = e.GetPosition(textArea);
429						}
430						e.Handled = true;
431						return;
432					}
433				}
434				
435				var oldPosition = textArea.Caret.Position;
436				SetCaretOffsetToMousePosition(e);
437				
438				
439				if (!shift) {
440					textArea.ClearSelection();
441				}
442				if (textArea.CaptureMouse()) {
443					if ((modifiers & ModifierKeys.Alt) == ModifierKeys.Alt && textArea.Options.EnableRectangularSelection) {
444						mode = SelectionMode.Rectangular;
445						if (shift && textArea.Selection is RectangleSelection) {
446							textArea.Selection = textArea.Selection.StartSelectionOrSetEndpoint(oldPosition, textArea.Caret.Position);
447						}
448					} else if (e.ClickCount == 1 && ((modifiers & ModifierKeys.Control) == 0)) {
449						mode = SelectionMode.Normal;
450						if (shift && !(textArea.Selection is RectangleSelection)) {
451							textArea.Selection = textArea.Selection.StartSelectionOrSetEndpoint(oldPosition, textArea.Caret.Position);
452						}
453					} else {
454						SimpleSegment startWord;
455						if (e.ClickCount == 3) {
456							mode = SelectionMode.WholeLine;
457							startWord = GetLineAtMousePosition(e);
458						} else {
459							mode = SelectionMode.WholeWord;
460							startWord = GetWordAtMousePosition(e);
461						}
462						if (startWord == SimpleSegment.Invalid) {
463							mode = SelectionMode.None;
464							textArea.ReleaseMouseCapture();
465							return;
466						}
467						if (shift && !textArea.Selection.IsEmpty) {
468							if (startWord.Offset < textArea.Selection.SurroundingSegment.Offset) {
469								textArea.Selection = textArea.Selection.SetEndpoint(new TextViewPosition(textArea.Document.GetLocation(startWord.Offset)));
470							} else if (startWord.EndOffset > textArea.Selection.SurroundingSegment.EndOffset) {
471								textArea.Selection = textArea.Selection.SetEndpoint(new TextViewPosition(textArea.Document.GetLocation(startWord.EndOffset)));
472							}
473							this.startWord = new AnchorSegment(textArea.Document, textArea.Selection.SurroundingSegment);
474						} else {
475							textArea.Selection = Selection.Create(textArea, startWord.Offset, startWord.EndOffset);
476							this.startWord = new AnchorSegment(textArea.Document, startWord.Offset, startWord.Length);
477						}
478					}
479				}
480			}
481			e.Handled = true;
482		}
483		#endregion
484		
485		#region Mouse Position <-> Text coordinates
486		SimpleSegment GetWordAtMousePosition(MouseEventArgs e)
487		{
488			TextView textView = textArea.TextView;
489			if (textView == null) return SimpleSegment.Invalid;
490			Point pos = e.GetPosition(textView);
491			if (pos.Y < 0)
492				pos.Y = 0;
493			if (pos.Y > textView.ActualHeight)
494				pos.Y = textView.ActualHeight;
495			pos += textView.ScrollOffset;
496			VisualLine line = textView.GetVisualLineFromVisualTop(pos.Y);
497			if (line != null) {
498				int visualColumn = line.GetVisualColumn(pos, textArea.Selection.EnableVirtualSpace);
499				int wordStartVC = line.GetNextCaretPosition(visualColumn + 1, LogicalDirection.Backward, CaretPositioningMode.WordStartOrSymbol, textArea.Selection.EnableVirtualSpace);
500				if (wordStartVC == -1)
501					wordStartVC = 0;
502				int wordEndVC = line.GetNextCaretPosition(wordStartVC, LogicalDirection.Forward, CaretPositioningMode.WordBorderOrSymbol, textArea.Selection.EnableVirtualSpace);
503				if (wordEndVC == -1)
504					wordEndVC = line.VisualLength;
505				int relOffset = line.FirstDocumentLine.Offset;
506				int wordStartOffset = line.GetRelativeOffset(wordStartVC) + relOffset;
507				int wordEndOffset = line.GetRelativeOffset(wordEndVC) + relOffset;
508				return new SimpleSegment(wordStartOffset, wordEndOffset - wordStartOffset);
509			} else {
510				return SimpleSegment.Invalid;
511			}
512		}
513		
514		SimpleSegment GetLineAtMousePosition(MouseEventArgs e)
515		{
516			TextView textView = textArea.TextView;
517			if (textView == null) return SimpleSegment.Invalid;
518			Point pos = e.GetPosition(textView);
519			if (pos.Y < 0)
520				pos.Y = 0;
521			if (pos.Y > textView.ActualHeight)
522				pos.Y = textView.ActualHeight;
523			pos += textView.ScrollOffset;
524			VisualLine line = textView.GetVisualLineFromVisualTop(pos.Y);
525			if (line != null) {
526				return new SimpleSegment(line.StartOffset, line.LastDocumentLine.EndOffset - line.StartOffset);
527			} else {
528				return SimpleSegment.Invalid;
529			}
530		}
531		
532		int GetOffsetFromMousePosition(MouseEventArgs e, out int visualColumn, out bool isAtEndOfLine)
533		{
534			return GetOffsetFromMousePosition(e.GetPosition(textArea.TextView), out visualColumn, out isAtEndOfLine);
535		}
536		
537		int GetOffsetFromMousePosition(Point positionRelativeToTextView, out int visualColumn, out bool isAtEndOfLine)
538		{
539			visualColumn = 0;
540			TextView textView = textArea.TextView;
541			Point pos = positionRelativeToTextView;
542			if (pos.Y < 0)
543				pos.Y = 0;
544			if (pos.Y > textView.ActualHeight)
545				pos.Y = textView.ActualHeight;
546			pos += textView.ScrollOffset;
547			if (pos.Y > textView.DocumentHeight)
548				pos.Y = textView.DocumentHeight - ExtensionMethods.Epsilon;
549			VisualLine line = textView.GetVisualLineFromVisualTop(pos.Y);
550			if (line != null) {
551				visualColumn = line.GetVisualColumn(pos, textArea.Selection.EnableVirtualSpace, out isAtEndOfLine);
552				return line.GetRelativeOffset(visualColumn) + line.FirstDocumentLine.Offset;
553			}
554			isAtEndOfLine = false;
555			return -1;
556		}
557		
558		int GetOffsetFromMousePositionFirstTextLineOnly(Point positionRelativeToTextView, out int visualColumn)
559		{
560			visualColumn = 0;
561			TextView textView = textArea.TextView;
562			Point pos = positionRelativeToTextView;
563			if (pos.Y < 0)
564				pos.Y = 0;
565			if (pos.Y > textView.ActualHeight)
566				pos.Y = textView.ActualHeight;
567			pos += textView.ScrollOffset;
568			if (pos.Y > textView.DocumentHeight)
569				pos.Y = textView.DocumentHeight - ExtensionMethods.Epsilon;
570			VisualLine line = textView.GetVisualLineFromVisualTop(pos.Y);
571			if (line != null) {
572				visualColumn = line.GetVisualColumn(line.TextLines.First(), pos.X, textArea.Selection.EnableVirtualSpace);
573				return line.GetRelativeOffset(visualColumn) + line.FirstDocumentLine.Offset;
574			}
575			return -1;
576		}
577		#endregion
578		
579		#region MouseMove
580		void textArea_MouseMove(object sender, MouseEventArgs e)
581		{
582			if (e.Handled)
583				return;
584			if (mode == SelectionMode.Normal || mode == SelectionMode.WholeWord || mode == SelectionMode.WholeLine || mode == SelectionMode.Rectangular) {
585				e.Handled = true;
586				if (textArea.TextView.VisualLinesValid) {
587					// If the visual lines are not valid, don't extend the selection.
588					// Extending the selection forces a VisualLine refresh, and it is sufficient
589					// to do that on MouseUp, we don't have to do it every MouseMove.
590					ExtendSelectionToMouse(e);
591				}
592			} else if (mode == SelectionMode.PossibleDragStart) {
593				e.Handled = true;
594				Vector mouseMovement = e.GetPosition(textArea) - possibleDragStartMousePos;
595				if (Math.Abs(mouseMovement.X) > SystemParameters.MinimumHorizontalDragDistance
596				    || Math.Abs(mouseMovement.Y) > SystemParameters.MinimumVerticalDragDistance)
597				{
598					StartDrag();
599				}
600			}
601		}
602		#endregion
603		
604		#region ExtendSelection
605		void SetCaretOffsetToMousePosition(MouseEventArgs e)
606		{
607			SetCaretOffsetToMousePosition(e, null);
608		}
609		
610		void SetCaretOffsetToMousePosition(MouseEventArgs e, ISegment allowedSegment)
611		{
612			int visualColumn;
613			bool isAtEndOfLine;
614			int offset;
615			if (mode == SelectionMode.Rectangular) {
616				offset = GetOffsetFromMousePositionFirstTextLineOnly(e.GetPosition(textArea.TextView), out visualColumn);
617				isAtEndOfLine = true;
618			} else {
619				offset = GetOffsetFromMousePosition(e, out visualColumn, out isAtEndOfLine);
620			}
621			if (allowedSegment != null) {
622				offset = offset.CoerceValue(allowedSegment.Offset, allowedSegment.EndOffset);
623			}
624			if (offset >= 0) {
625				textArea.Caret.Position = new TextViewPosition(textArea.Document.GetLocation(offset), visualColumn) { IsAtEndOfLine = isAtEndOfLine };
626				textArea.Caret.DesiredXPos = double.NaN;
627			}
628		}
629		
630		void ExtendSelectionToMouse(MouseEventArgs e)
631		{
632			TextViewPosition oldPosition = textArea.Caret.Position;
633			if (mode == SelectionMode.Normal || mode == SelectionMode.Rectangular) {
634				SetCaretOffsetToMousePosition(e);
635				if (mode == SelectionMode.Normal && textArea.Selection is RectangleSelection)
636					textArea.Selection = new SimpleSelection(textArea, oldPosition, textArea.Caret.Position);
637				else if (mode == SelectionMode.Rectangular && !(textArea.Selection is RectangleSelection))
638					textArea.Selection = new RectangleSelection(textArea, oldPosition, textArea.Caret.Position);
639				else
640					textArea.Selection = textArea.Selection.StartSelectionOrSetEndpoint(oldPosition, textArea.Caret.Position);
641			} else if (mode == SelectionMode.WholeWord || mode == SelectionMode.WholeLine) {
642				var newWord = (mode == SelectionMode.WholeLine) ? GetLineAtMousePosition(e) : GetWordAtMousePosition(e);
643				if (newWord != SimpleSegment.Invalid) {
644					textArea.Selection = Selection.Create(textArea,
645					                                      Math.Min(newWord.Offset, startWord.Offset),
646					                                      Math.Max(newWord.EndOffset, startWord.EndOffset));
647					// Set caret offset, but limit the caret to stay inside the selection.
648					// in whole-word selection, it's otherwise possible that we get the caret outside the
649					// selection - but the TextArea doesn't like that and will reset the selection, causing
650					// flickering.
651					SetCaretOffsetToMousePosition(e, textArea.Selection.SurroundingSegment);
652				}
653			}
654			textArea.Caret.BringCaretToView(5.0);
655		}
656		#endregion
657		
658		#region MouseLeftButtonUp
659		void textArea_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
660		{
661			if (mode == SelectionMode.None || e.Handled)
662				return;
663			e.Handled = true;
664			if (mode == SelectionMode.PossibleDragStart) {
665				// -> this was not a drag start (mouse didn't move after mousedown)
666				SetCaretOffsetToMousePosition(e);
667				textArea.ClearSelection();
668			} else if (mode == SelectionMode.Normal || mode == SelectionMode.WholeWord || mode == SelectionMode.WholeLine || mode == SelectionMode.Rectangular) {
669				ExtendSelectionToMouse(e);
670			}
671			mode = SelectionMode.None;
672			textArea.ReleaseMouseCapture();
673		}
674		#endregion
675	}
676}