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 | |
---|
19 | using System; |
---|
20 | using System.ComponentModel; |
---|
21 | using System.Diagnostics; |
---|
22 | using System.Linq; |
---|
23 | using System.Runtime.InteropServices; |
---|
24 | using System.Windows; |
---|
25 | using System.Windows.Documents; |
---|
26 | using System.Windows.Input; |
---|
27 | using System.Windows.Media.TextFormatting; |
---|
28 | using System.Windows.Threading; |
---|
29 | using ICSharpCode.AvalonEdit.Document; |
---|
30 | using ICSharpCode.AvalonEdit.Rendering; |
---|
31 | using ICSharpCode.AvalonEdit.Utils; |
---|
32 | #if NREFACTORY |
---|
33 | using ICSharpCode.NRefactory.Editor; |
---|
34 | #endif |
---|
35 | |
---|
36 | namespace 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 | } |
---|