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