Browse Source

Allow creating rectangle selections using the keyboard (Alt+Shift+Left/Up/Right/Down)

pull/24/head
Daniel Grunwald 13 years ago
parent
commit
6ee01cfdee
  1. 35
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/CaretNavigationCommandHandler.cs
  2. 8
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/EmptySelection.cs
  3. 149
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/RectangleSelection.cs
  4. 10
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/Selection.cs
  5. 14
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/SimpleSelection.cs

35
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/CaretNavigationCommandHandler.cs

@ -4,6 +4,7 @@ @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
@ -41,21 +42,28 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -41,21 +42,28 @@ namespace ICSharpCode.AvalonEdit.Editing
const ModifierKeys None = ModifierKeys.None;
const ModifierKeys Ctrl = ModifierKeys.Control;
const ModifierKeys Shift = ModifierKeys.Shift;
const ModifierKeys Alt = ModifierKeys.Alt;
AddBinding(EditingCommands.MoveLeftByCharacter, None, Key.Left, OnMoveCaret(CaretMovementType.CharLeft));
AddBinding(EditingCommands.SelectLeftByCharacter, Shift, Key.Left, OnMoveCaretExtendSelection(CaretMovementType.CharLeft));
AddBinding(RectangleSelection.BoxSelectLeftByCharacter, Alt | Shift, Key.Left, OnMoveCaretBoxSelection(CaretMovementType.CharLeft));
AddBinding(EditingCommands.MoveRightByCharacter, None, Key.Right, OnMoveCaret(CaretMovementType.CharRight));
AddBinding(EditingCommands.SelectRightByCharacter, Shift, Key.Right, OnMoveCaretExtendSelection(CaretMovementType.CharRight));
AddBinding(RectangleSelection.BoxSelectRightByCharacter, Alt | Shift, Key.Right, OnMoveCaretBoxSelection(CaretMovementType.CharRight));
AddBinding(EditingCommands.MoveLeftByWord, Ctrl, Key.Left, OnMoveCaret(CaretMovementType.WordLeft));
AddBinding(EditingCommands.SelectLeftByWord, Ctrl | Shift, Key.Left, OnMoveCaretExtendSelection(CaretMovementType.WordLeft));
AddBinding(RectangleSelection.BoxSelectLeftByWord, Ctrl | Alt | Shift, Key.Left, OnMoveCaretBoxSelection(CaretMovementType.WordLeft));
AddBinding(EditingCommands.MoveRightByWord, Ctrl, Key.Right, OnMoveCaret(CaretMovementType.WordRight));
AddBinding(EditingCommands.SelectRightByWord, Ctrl | Shift, Key.Right, OnMoveCaretExtendSelection(CaretMovementType.WordRight));
AddBinding(RectangleSelection.BoxSelectRightByWord, Ctrl | Alt | Shift, Key.Right, OnMoveCaretBoxSelection(CaretMovementType.WordRight));
AddBinding(EditingCommands.MoveUpByLine, None, Key.Up, OnMoveCaret(CaretMovementType.LineUp));
AddBinding(EditingCommands.SelectUpByLine, Shift, Key.Up, OnMoveCaretExtendSelection(CaretMovementType.LineUp));
AddBinding(RectangleSelection.BoxSelectUpByLine, Alt | Shift, Key.Up, OnMoveCaretBoxSelection(CaretMovementType.LineUp));
AddBinding(EditingCommands.MoveDownByLine, None, Key.Down, OnMoveCaret(CaretMovementType.LineDown));
AddBinding(EditingCommands.SelectDownByLine, Shift, Key.Down, OnMoveCaretExtendSelection(CaretMovementType.LineDown));
AddBinding(RectangleSelection.BoxSelectDownByLine, Alt | Shift, Key.Down, OnMoveCaretBoxSelection(CaretMovementType.LineDown));
AddBinding(EditingCommands.MoveDownByPage, None, Key.PageDown, OnMoveCaret(CaretMovementType.PageDown));
AddBinding(EditingCommands.SelectDownByPage, Shift, Key.PageDown, OnMoveCaretExtendSelection(CaretMovementType.PageDown));
@ -64,8 +72,10 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -64,8 +72,10 @@ namespace ICSharpCode.AvalonEdit.Editing
AddBinding(EditingCommands.MoveToLineStart, None, Key.Home, OnMoveCaret(CaretMovementType.LineStart));
AddBinding(EditingCommands.SelectToLineStart, Shift, Key.Home, OnMoveCaretExtendSelection(CaretMovementType.LineStart));
AddBinding(RectangleSelection.BoxSelectToLineStart, Alt | Shift, Key.Home, OnMoveCaretBoxSelection(CaretMovementType.LineStart));
AddBinding(EditingCommands.MoveToLineEnd, None, Key.End, OnMoveCaret(CaretMovementType.LineEnd));
AddBinding(EditingCommands.SelectToLineEnd, Shift, Key.End, OnMoveCaretExtendSelection(CaretMovementType.LineEnd));
AddBinding(RectangleSelection.BoxSelectToLineEnd, Alt | Shift, Key.End, OnMoveCaretBoxSelection(CaretMovementType.LineEnd));
AddBinding(EditingCommands.MoveToDocumentStart, Ctrl, Key.Home, OnMoveCaret(CaretMovementType.DocumentStart));
AddBinding(EditingCommands.SelectToDocumentStart, Ctrl | Shift, Key.Home, OnMoveCaretExtendSelection(CaretMovementType.DocumentStart));
@ -135,6 +145,31 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -135,6 +145,31 @@ namespace ICSharpCode.AvalonEdit.Editing
};
}
static ExecutedRoutedEventHandler OnMoveCaretBoxSelection(CaretMovementType direction)
{
return (target, args) => {
TextArea textArea = GetTextArea(target);
if (textArea != null && textArea.Document != null) {
args.Handled = true;
// First, convert the selection into a rectangle selection
// (this is required so that virtual space gets enabled for the caret movement)
if (textArea.Options.EnableRectangularSelection && !(textArea.Selection is RectangleSelection)) {
if (textArea.Selection.IsEmpty) {
textArea.Selection = new RectangleSelection(textArea, textArea.Caret.Position, textArea.Caret.Position);
} else {
// Convert normal selection to rectangle selection
textArea.Selection = new RectangleSelection(textArea, textArea.Selection.StartPosition, textArea.Caret.Position);
}
}
// Now move the caret and extend the selection
TextViewPosition oldPosition = textArea.Caret.Position;
MoveCaret(textArea, direction);
textArea.Selection = textArea.Selection.StartSelectionOrSetEndpoint(oldPosition, textArea.Caret.Position);
textArea.Caret.BringCaretToView();
}
};
}
#region Caret movement
static void MoveCaret(TextArea textArea, CaretMovementType direction)
{

8
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/EmptySelection.cs

@ -20,6 +20,14 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -20,6 +20,14 @@ namespace ICSharpCode.AvalonEdit.Editing
return this;
}
public override TextViewPosition StartPosition {
get { return new TextViewPosition(TextLocation.Empty); }
}
public override TextViewPosition EndPosition {
get { return new TextViewPosition(TextLocation.Empty); }
}
public override ISegment SurroundingSegment {
get { return null; }
}

149
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/RectangleSelection.cs

@ -7,8 +7,9 @@ using System.IO; @@ -7,8 +7,9 @@ using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media.TextFormatting;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Rendering;
using ICSharpCode.AvalonEdit.Utils;
@ -20,20 +21,70 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -20,20 +21,70 @@ namespace ICSharpCode.AvalonEdit.Editing
/// </summary>
public sealed class RectangleSelection : Selection
{
#region Commands
/// <summary>
/// Expands the selection left by one character, creating a rectangular selection.
/// Key gesture: Alt+Shift+Left
/// </summary>
public static readonly RoutedUICommand BoxSelectLeftByCharacter = Command("BoxSelectLeftByCharacter");
/// <summary>
/// Expands the selection right by one character, creating a rectangular selection.
/// Key gesture: Alt+Shift+Right
/// </summary>
public static readonly RoutedUICommand BoxSelectRightByCharacter = Command("BoxSelectRightByCharacter");
/// <summary>
/// Expands the selection left by one word, creating a rectangular selection.
/// Key gesture: Ctrl+Alt+Shift+Left
/// </summary>
public static readonly RoutedUICommand BoxSelectLeftByWord = Command("BoxSelectLeftByWord");
/// <summary>
/// Expands the selection left by one word, creating a rectangular selection.
/// Key gesture: Ctrl+Alt+Shift+Right
/// </summary>
public static readonly RoutedUICommand BoxSelectRightByWord = Command("BoxSelectRightByWord");
/// <summary>
/// Expands the selection up by one line, creating a rectangular selection.
/// Key gesture: Alt+Shift+Up
/// </summary>
public static readonly RoutedUICommand BoxSelectUpByLine = Command("BoxSelectUpByLine");
/// <summary>
/// Expands the selection up by one line, creating a rectangular selection.
/// Key gesture: Alt+Shift+Down
/// </summary>
public static readonly RoutedUICommand BoxSelectDownByLine = Command("BoxSelectDownByLine");
/// <summary>
/// Expands the selection to the start of the line, creating a rectangular selection.
/// Key gesture: Alt+Shift+Home
/// </summary>
public static readonly RoutedUICommand BoxSelectToLineStart = Command("BoxSelectToLineStart");
/// <summary>
/// Expands the selection to the end of the line, creating a rectangular selection.
/// Key gesture: Alt+Shift+End
/// </summary>
public static readonly RoutedUICommand BoxSelectToLineEnd = Command("BoxSelectToLineEnd");
static RoutedUICommand Command(string name)
{
return new RoutedUICommand(name, name, typeof(RectangleSelection));
}
#endregion
TextDocument document;
readonly int startLine, endLine;
readonly double startXPos, endXPos;
readonly int topLeftOffset, bottomRightOffset;
readonly TextViewPosition start, end;
readonly List<SelectionSegment> segments = new List<SelectionSegment>();
void InitDocument()
{
document = textArea.Document;
if (document == null)
throw ThrowUtil.NoDocumentAssigned();
}
#region Constructors
/// <summary>
/// Creates a new rectangular selection.
/// </summary>
@ -48,6 +99,9 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -48,6 +99,9 @@ namespace ICSharpCode.AvalonEdit.Editing
CalculateSegments();
this.topLeftOffset = this.segments.First().StartOffset;
this.bottomRightOffset = this.segments.Last().EndOffset;
this.start = start;
this.end = end;
}
private RectangleSelection(TextArea textArea, int startLine, double startXPos, TextViewPosition end)
@ -61,6 +115,9 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -61,6 +115,9 @@ namespace ICSharpCode.AvalonEdit.Editing
CalculateSegments();
this.topLeftOffset = this.segments.First().StartOffset;
this.bottomRightOffset = this.segments.Last().EndOffset;
this.start = GetStart();
this.end = end;
}
private RectangleSelection(TextArea textArea, TextViewPosition start, int endLine, double endXPos)
@ -74,6 +131,16 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -74,6 +131,16 @@ namespace ICSharpCode.AvalonEdit.Editing
CalculateSegments();
this.topLeftOffset = this.segments.First().StartOffset;
this.bottomRightOffset = this.segments.Last().EndOffset;
this.start = start;
this.end = GetEnd();
}
void InitDocument()
{
document = textArea.Document;
if (document == null)
throw ThrowUtil.NoDocumentAssigned();
}
static double GetXPos(TextArea textArea, TextViewPosition pos)
@ -85,11 +152,43 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -85,11 +152,43 @@ namespace ICSharpCode.AvalonEdit.Editing
return visualLine.GetTextLineVisualXPosition(textLine, vc);
}
int GetVisualColumnFromXPos(int line, double xPos)
void CalculateSegments()
{
var vl = textArea.TextView.GetOrConstructVisualLine(textArea.Document.GetLineByNumber(line));
return vl.GetVisualColumn(new Point(xPos, 0), true);
DocumentLine nextLine = document.GetLineByNumber(Math.Min(startLine, endLine));
do {
VisualLine vl = textArea.TextView.GetOrConstructVisualLine(nextLine);
int startVC = vl.GetVisualColumn(new Point(startXPos, 0), true);
int endVC = vl.GetVisualColumn(new Point(endXPos, 0), true);
int baseOffset = vl.FirstDocumentLine.Offset;
int startOffset = baseOffset + vl.GetRelativeOffset(startVC);
int endOffset = baseOffset + vl.GetRelativeOffset(endVC);
segments.Add(new SelectionSegment(startOffset, startVC, endOffset, endVC));
nextLine = vl.LastDocumentLine.NextLine;
} while (nextLine != null && nextLine.LineNumber <= Math.Max(startLine, endLine));
}
TextViewPosition GetStart()
{
SelectionSegment segment = (startLine < endLine ? segments.First() : segments.Last());
if (startXPos < endXPos) {
return new TextViewPosition(document.GetLocation(segment.StartOffset), segment.StartVisualColumn);
} else {
return new TextViewPosition(document.GetLocation(segment.EndOffset), segment.EndVisualColumn);
}
}
TextViewPosition GetEnd()
{
SelectionSegment segment = (startLine < endLine ? segments.Last() : segments.First());
if (startXPos < endXPos) {
return new TextViewPosition(document.GetLocation(segment.EndOffset), segment.EndVisualColumn);
} else {
return new TextViewPosition(document.GetLocation(segment.StartOffset), segment.StartVisualColumn);
}
}
#endregion
/// <inheritdoc/>
public override string GetText()
@ -133,22 +232,14 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -133,22 +232,14 @@ namespace ICSharpCode.AvalonEdit.Editing
get { return segments; }
}
/// <inheritdoc/>
public override TextViewPosition StartPosition {
get { return start; }
}
void CalculateSegments()
{
DocumentLine nextLine = document.GetLineByNumber(Math.Min(startLine, endLine));
do {
VisualLine vl = textArea.TextView.GetOrConstructVisualLine(nextLine);
int startVC = vl.GetVisualColumn(new Point(startXPos, 0), true);
int endVC = vl.GetVisualColumn(new Point(endXPos, 0), true);
int baseOffset = vl.FirstDocumentLine.Offset;
int startOffset = baseOffset + vl.GetRelativeOffset(startVC);
int endOffset = baseOffset + vl.GetRelativeOffset(endVC);
segments.Add(new SelectionSegment(startOffset, startVC, endOffset, endVC));
nextLine = vl.LastDocumentLine.NextLine;
} while (nextLine != null && nextLine.LineNumber <= Math.Max(startLine, endLine));
/// <inheritdoc/>
public override TextViewPosition EndPosition {
get { return end; }
}
/// <inheritdoc/>
@ -173,6 +264,12 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -173,6 +264,12 @@ namespace ICSharpCode.AvalonEdit.Editing
return new RectangleSelection(textArea, startLine, startXPos, endPosition);
}
int GetVisualColumnFromXPos(int line, double xPos)
{
var vl = textArea.TextView.GetOrConstructVisualLine(textArea.Document.GetLineByNumber(line));
return vl.GetVisualColumn(new Point(xPos, 0), true);
}
/// <inheritdoc/>
public override Selection UpdateOnDocumentChange(DocumentChangeEventArgs e)
{

10
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/Selection.cs

@ -63,6 +63,16 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -63,6 +63,16 @@ namespace ICSharpCode.AvalonEdit.Editing
this.textArea = textArea;
}
/// <summary>
/// Gets the start position of the selection.
/// </summary>
public abstract TextViewPosition StartPosition { get; }
/// <summary>
/// Gets the end position of the selection.
/// </summary>
public abstract TextViewPosition EndPosition { get; }
/// <summary>
/// Gets the selected text segments.
/// </summary>

14
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/SimpleSelection.cs

@ -71,18 +71,12 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -71,18 +71,12 @@ namespace ICSharpCode.AvalonEdit.Editing
}
}
/// <summary>
/// Gets the start offset.
/// </summary>
public int StartOffset {
get { return startOffset; }
public override TextViewPosition StartPosition {
get { return start; }
}
/// <summary>
/// Gets the end offset.
/// </summary>
public int EndOffset {
get { return endOffset; }
public override TextViewPosition EndPosition {
get { return end; }
}
/// <inheritdoc/>

Loading…
Cancel
Save