Skip to content

Commit 11603da

Browse files
committed
Add TextArea.MouseSelectionMode property for starting/canceling mouse selection.
1 parent e5913bb commit 11603da

4 files changed

Lines changed: 140 additions & 66 deletions

File tree

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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+
namespace ICSharpCode.AvalonEdit.Editing
20+
{
21+
/// <summary>
22+
/// Enumeration of possible states of mouse selection.
23+
/// </summary>
24+
public enum MouseSelectionMode
25+
{
26+
/// <summary>
27+
/// no selection (no mouse button down)
28+
/// </summary>
29+
None,
30+
/// <summary>
31+
/// left mouse button down on selection, might be normal click
32+
/// or might be drag'n'drop
33+
/// </summary>
34+
PossibleDragStart,
35+
/// <summary>
36+
/// dragging text
37+
/// </summary>
38+
Drag,
39+
/// <summary>
40+
/// normal selection (click+drag)
41+
/// </summary>
42+
Normal,
43+
/// <summary>
44+
/// whole-word selection (double click+drag or ctrl+click+drag)
45+
/// </summary>
46+
WholeWord,
47+
/// <summary>
48+
/// whole-line selection (triple click+drag)
49+
/// </summary>
50+
WholeLine,
51+
/// <summary>
52+
/// rectangular selection (alt+click+drag)
53+
/// </summary>
54+
Rectangular
55+
}
56+
}

ICSharpCode.AvalonEdit/Editing/SelectionMouseHandler.cs

Lines changed: 51 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -40,49 +40,14 @@ namespace ICSharpCode.AvalonEdit.Editing
4040
/// </summary>
4141
sealed class SelectionMouseHandler : ITextAreaInputHandler
4242
{
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-
7843
readonly TextArea textArea;
7944

80-
SelectionMode mode;
45+
MouseSelectionMode mode;
8146
AnchorSegment startWord;
8247
Point possibleDragStartMousePos;
8348

8449
#region Constructor + Attach + Detach
85-
public SelectionMouseHandler(TextArea textArea)
50+
internal SelectionMouseHandler(TextArea textArea)
8651
{
8752
if (textArea == null)
8853
throw new ArgumentNullException("textArea");
@@ -101,15 +66,15 @@ private static void OnLostMouseCapture(object sender, MouseEventArgs e)
10166
{
10267
SelectionMouseHandler handler = textArea.DefaultInputHandler.MouseSelection as SelectionMouseHandler;
10368
if (handler != null)
104-
handler.mode = SelectionMode.None;
69+
handler.mode = MouseSelectionMode.None;
10570
}
10671
}
10772

108-
public TextArea TextArea {
73+
TextArea ITextAreaInputHandler.TextArea {
10974
get { return textArea; }
11075
}
11176

112-
public void Attach()
77+
void ITextAreaInputHandler.Attach()
11378
{
11479
textArea.MouseLeftButtonDown += textArea_MouseLeftButtonDown;
11580
textArea.MouseMove += textArea_MouseMove;
@@ -123,9 +88,9 @@ public void Attach()
12388
}
12489
}
12590

126-
public void Detach()
91+
void ITextAreaInputHandler.Detach()
12792
{
128-
mode = SelectionMode.None;
93+
mode = MouseSelectionMode.None;
12994
textArea.MouseLeftButtonDown -= textArea_MouseLeftButtonDown;
13095
textArea.MouseMove -= textArea_MouseMove;
13196
textArea.MouseLeftButtonUp -= textArea_MouseLeftButtonUp;
@@ -239,7 +204,7 @@ void textArea_Drop(object sender, DragEventArgs e)
239204
e.Effects = effect;
240205
if (effect != DragDropEffects.None) {
241206
int start = textArea.Caret.Offset;
242-
if (mode == SelectionMode.Drag && textArea.Selection.Contains(start)) {
207+
if (mode == MouseSelectionMode.Drag && textArea.Selection.Contains(start)) {
243208
Debug.WriteLine("Drop: did not drop: drop target is inside selection");
244209
e.Effects = DragDropEffects.None;
245210
} else {
@@ -324,7 +289,7 @@ void textArea_QueryContinueDrag(object sender, QueryContinueDragEventArgs e)
324289
void StartDrag()
325290
{
326291
// prevent nested StartDrag calls
327-
mode = SelectionMode.Drag;
292+
mode = MouseSelectionMode.Drag;
328293

329294
// mouse capture and Drag'n'Drop doesn't mix
330295
textArea.ReleaseMouseCapture();
@@ -391,7 +356,7 @@ void StartDrag()
391356
void textArea_QueryCursor(object sender, QueryCursorEventArgs e)
392357
{
393358
if (!e.Handled) {
394-
if (mode != SelectionMode.None) {
359+
if (mode != MouseSelectionMode.None) {
395360
// during selection, use IBeam cursor even outside the text area
396361
e.Cursor = Cursors.IBeam;
397362
e.Handled = true;
@@ -418,7 +383,7 @@ void textArea_QueryCursor(object sender, QueryCursorEventArgs e)
418383
#region LeftButtonDown
419384
void textArea_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
420385
{
421-
mode = SelectionMode.None;
386+
mode = MouseSelectionMode.None;
422387
if (!e.Handled && e.ChangedButton == MouseButton.Left) {
423388
ModifierKeys modifiers = Keyboard.Modifiers;
424389
bool shift = (modifiers & ModifierKeys.Shift) == ModifierKeys.Shift;
@@ -428,7 +393,7 @@ void textArea_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
428393
int offset = GetOffsetFromMousePosition(e, out visualColumn, out isAtEndOfLine);
429394
if (textArea.Selection.Contains(offset)) {
430395
if (textArea.CaptureMouse()) {
431-
mode = SelectionMode.PossibleDragStart;
396+
mode = MouseSelectionMode.PossibleDragStart;
432397
possibleDragStartMousePos = e.GetPosition(textArea);
433398
}
434399
e.Handled = true;
@@ -445,26 +410,26 @@ void textArea_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
445410
}
446411
if (textArea.CaptureMouse()) {
447412
if ((modifiers & ModifierKeys.Alt) == ModifierKeys.Alt && textArea.Options.EnableRectangularSelection) {
448-
mode = SelectionMode.Rectangular;
413+
mode = MouseSelectionMode.Rectangular;
449414
if (shift && textArea.Selection is RectangleSelection) {
450415
textArea.Selection = textArea.Selection.StartSelectionOrSetEndpoint(oldPosition, textArea.Caret.Position);
451416
}
452417
} else if (e.ClickCount == 1 && ((modifiers & ModifierKeys.Control) == 0)) {
453-
mode = SelectionMode.Normal;
418+
mode = MouseSelectionMode.Normal;
454419
if (shift && !(textArea.Selection is RectangleSelection)) {
455420
textArea.Selection = textArea.Selection.StartSelectionOrSetEndpoint(oldPosition, textArea.Caret.Position);
456421
}
457422
} else {
458423
SimpleSegment startWord;
459424
if (e.ClickCount == 3) {
460-
mode = SelectionMode.WholeLine;
425+
mode = MouseSelectionMode.WholeLine;
461426
startWord = GetLineAtMousePosition(e);
462427
} else {
463-
mode = SelectionMode.WholeWord;
428+
mode = MouseSelectionMode.WholeWord;
464429
startWord = GetWordAtMousePosition(e);
465430
}
466431
if (startWord == SimpleSegment.Invalid) {
467-
mode = SelectionMode.None;
432+
mode = MouseSelectionMode.None;
468433
textArea.ReleaseMouseCapture();
469434
return;
470435
}
@@ -484,6 +449,28 @@ void textArea_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
484449
}
485450
e.Handled = true;
486451
}
452+
453+
public MouseSelectionMode MouseSelectionMode
454+
{
455+
get { return mode; }
456+
set {
457+
if (mode == value)
458+
return;
459+
if (value == MouseSelectionMode.None) {
460+
mode = MouseSelectionMode.None;
461+
textArea.ReleaseMouseCapture();
462+
} else if (textArea.CaptureMouse()) {
463+
switch (value) {
464+
case MouseSelectionMode.Normal:
465+
case MouseSelectionMode.Rectangular:
466+
mode = value;
467+
break;
468+
default:
469+
throw new NotImplementedException("Programmatically starting mouse selection is only supported for normal and rectangular selections.");
470+
}
471+
}
472+
}
473+
}
487474
#endregion
488475

489476
#region Mouse Position <-> Text coordinates
@@ -585,15 +572,15 @@ void textArea_MouseMove(object sender, MouseEventArgs e)
585572
{
586573
if (e.Handled)
587574
return;
588-
if (mode == SelectionMode.Normal || mode == SelectionMode.WholeWord || mode == SelectionMode.WholeLine || mode == SelectionMode.Rectangular) {
575+
if (mode == MouseSelectionMode.Normal || mode == MouseSelectionMode.WholeWord || mode == MouseSelectionMode.WholeLine || mode == MouseSelectionMode.Rectangular) {
589576
e.Handled = true;
590577
if (textArea.TextView.VisualLinesValid) {
591578
// If the visual lines are not valid, don't extend the selection.
592579
// Extending the selection forces a VisualLine refresh, and it is sufficient
593580
// to do that on MouseUp, we don't have to do it every MouseMove.
594581
ExtendSelectionToMouse(e);
595582
}
596-
} else if (mode == SelectionMode.PossibleDragStart) {
583+
} else if (mode == MouseSelectionMode.PossibleDragStart) {
597584
e.Handled = true;
598585
Vector mouseMovement = e.GetPosition(textArea) - possibleDragStartMousePos;
599586
if (Math.Abs(mouseMovement.X) > SystemParameters.MinimumHorizontalDragDistance
@@ -616,7 +603,7 @@ void SetCaretOffsetToMousePosition(MouseEventArgs e, ISegment allowedSegment)
616603
int visualColumn;
617604
bool isAtEndOfLine;
618605
int offset;
619-
if (mode == SelectionMode.Rectangular) {
606+
if (mode == MouseSelectionMode.Rectangular) {
620607
offset = GetOffsetFromMousePositionFirstTextLineOnly(e.GetPosition(textArea.TextView), out visualColumn);
621608
isAtEndOfLine = true;
622609
} else {
@@ -634,16 +621,16 @@ void SetCaretOffsetToMousePosition(MouseEventArgs e, ISegment allowedSegment)
634621
void ExtendSelectionToMouse(MouseEventArgs e)
635622
{
636623
TextViewPosition oldPosition = textArea.Caret.Position;
637-
if (mode == SelectionMode.Normal || mode == SelectionMode.Rectangular) {
624+
if (mode == MouseSelectionMode.Normal || mode == MouseSelectionMode.Rectangular) {
638625
SetCaretOffsetToMousePosition(e);
639-
if (mode == SelectionMode.Normal && textArea.Selection is RectangleSelection)
626+
if (mode == MouseSelectionMode.Normal && textArea.Selection is RectangleSelection)
640627
textArea.Selection = new SimpleSelection(textArea, oldPosition, textArea.Caret.Position);
641-
else if (mode == SelectionMode.Rectangular && !(textArea.Selection is RectangleSelection))
628+
else if (mode == MouseSelectionMode.Rectangular && !(textArea.Selection is RectangleSelection))
642629
textArea.Selection = new RectangleSelection(textArea, oldPosition, textArea.Caret.Position);
643630
else
644631
textArea.Selection = textArea.Selection.StartSelectionOrSetEndpoint(oldPosition, textArea.Caret.Position);
645-
} else if (mode == SelectionMode.WholeWord || mode == SelectionMode.WholeLine) {
646-
var newWord = (mode == SelectionMode.WholeLine) ? GetLineAtMousePosition(e) : GetWordAtMousePosition(e);
632+
} else if (mode == MouseSelectionMode.WholeWord || mode == MouseSelectionMode.WholeLine) {
633+
var newWord = (mode == MouseSelectionMode.WholeLine) ? GetLineAtMousePosition(e) : GetWordAtMousePosition(e);
647634
if (newWord != SimpleSegment.Invalid) {
648635
textArea.Selection = Selection.Create(textArea,
649636
Math.Min(newWord.Offset, startWord.Offset),
@@ -662,17 +649,17 @@ void ExtendSelectionToMouse(MouseEventArgs e)
662649
#region MouseLeftButtonUp
663650
void textArea_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
664651
{
665-
if (mode == SelectionMode.None || e.Handled)
652+
if (mode == MouseSelectionMode.None || e.Handled)
666653
return;
667654
e.Handled = true;
668-
if (mode == SelectionMode.PossibleDragStart) {
655+
if (mode == MouseSelectionMode.PossibleDragStart) {
669656
// -> this was not a drag start (mouse didn't move after mousedown)
670657
SetCaretOffsetToMousePosition(e);
671658
textArea.ClearSelection();
672-
} else if (mode == SelectionMode.Normal || mode == SelectionMode.WholeWord || mode == SelectionMode.WholeLine || mode == SelectionMode.Rectangular) {
659+
} else if (mode == MouseSelectionMode.Normal || mode == MouseSelectionMode.WholeWord || mode == MouseSelectionMode.WholeLine || mode == MouseSelectionMode.Rectangular) {
673660
ExtendSelectionToMouse(e);
674661
}
675-
mode = SelectionMode.None;
662+
mode = MouseSelectionMode.None;
676663
textArea.ReleaseMouseCapture();
677664
}
678665
#endregion

ICSharpCode.AvalonEdit/Editing/TextArea.cs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,6 @@ public override void OnApplyTemplate()
397397
/// <summary>
398398
/// Gets/Sets the selection in this text area.
399399
/// </summary>
400-
401400
public Selection Selection {
402401
get { return selection; }
403402
set {
@@ -505,8 +504,39 @@ public double SelectionCornerRadius {
505504
get { return (double)GetValue(SelectionCornerRadiusProperty); }
506505
set { SetValue(SelectionCornerRadiusProperty, value); }
507506
}
507+
508+
/// <summary>
509+
/// Gets/Sets the active mouse selection mode.
510+
///
511+
/// Setting this property to MouseSelectionMode.None will cancel mouse selection
512+
/// and release mouse capture.
513+
///
514+
/// Setting this property to another value will acquire mouse capture and
515+
/// activate the mouse selection mode.
516+
/// If mouse capture cannot be acquired, MouseSelectionMode will stay unchanged.
517+
///
518+
/// Currently, the setter only supports the values <c>None</c>, <c>Normal</c>
519+
/// and <c>Rectangular</c>.
520+
/// </summary>
521+
public MouseSelectionMode MouseSelectionMode
522+
{
523+
get {
524+
var mouseHandler = DefaultInputHandler.MouseSelection as SelectionMouseHandler;
525+
if (mouseHandler != null) {
526+
return mouseHandler.MouseSelectionMode;
527+
} else {
528+
return MouseSelectionMode.None;
529+
}
530+
}
531+
set {
532+
var mouseHandler = DefaultInputHandler.MouseSelection as SelectionMouseHandler;
533+
if (mouseHandler != null) {
534+
mouseHandler.MouseSelectionMode = value;
535+
}
536+
}
537+
}
508538
#endregion
509-
539+
510540
#region Force caret to stay inside selection
511541
bool ensureSelectionValidRequested;
512542
int allowCaretOutsideSelection;

ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@
176176
<DependentUpon>Selection.cs</DependentUpon>
177177
</Compile>
178178
<Compile Include="Editing\ImeNativeWrapper.cs" />
179+
<Compile Include="Editing\MouseSelectionMode.cs" />
179180
<Compile Include="Editing\SelectionSegment.cs" />
180181
<Compile Include="Editing\ImeSupport.cs" />
181182
<Compile Include="Editing\TextAreaAutomationPeer.cs" />

0 commit comments

Comments
 (0)