Free cookie consent management tool by TermsFeed Policy Generator

source: branches/2877_HiveImprovements/HeuristicLab.ExtLibs/HeuristicLab.AvalonEdit/5.0.1/AvalonEdit-5.0.1/Editing/RectangleSelection.cs @ 16755

Last change on this file since 16755 was 11700, checked in by jkarder, 10 years ago

#2077: created branch and added first version

File size: 15.8 KB
Line 
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.Collections.Generic;
21using System.IO;
22using System.Linq;
23using System.Text;
24using System.Windows;
25using System.Windows.Documents;
26using System.Windows.Input;
27using System.Windows.Media.TextFormatting;
28using ICSharpCode.AvalonEdit.Document;
29using ICSharpCode.AvalonEdit.Rendering;
30using ICSharpCode.AvalonEdit.Utils;
31#if NREFACTORY
32using ICSharpCode.NRefactory;
33using ICSharpCode.NRefactory.Editor;
34#endif
35
36namespace ICSharpCode.AvalonEdit.Editing
37{
38  /// <summary>
39  /// Rectangular selection ("box selection").
40  /// </summary>
41  public sealed class RectangleSelection : Selection
42  {
43    #region Commands
44    /// <summary>
45    /// Expands the selection left by one character, creating a rectangular selection.
46    /// Key gesture: Alt+Shift+Left
47    /// </summary>
48    public static readonly RoutedUICommand BoxSelectLeftByCharacter = Command("BoxSelectLeftByCharacter");
49   
50    /// <summary>
51    /// Expands the selection right by one character, creating a rectangular selection.
52    /// Key gesture: Alt+Shift+Right
53    /// </summary>
54    public static readonly RoutedUICommand BoxSelectRightByCharacter = Command("BoxSelectRightByCharacter");
55   
56    /// <summary>
57    /// Expands the selection left by one word, creating a rectangular selection.
58    /// Key gesture: Ctrl+Alt+Shift+Left
59    /// </summary>
60    public static readonly RoutedUICommand BoxSelectLeftByWord = Command("BoxSelectLeftByWord");
61   
62    /// <summary>
63    /// Expands the selection left by one word, creating a rectangular selection.
64    /// Key gesture: Ctrl+Alt+Shift+Right
65    /// </summary>
66    public static readonly RoutedUICommand BoxSelectRightByWord = Command("BoxSelectRightByWord");
67   
68    /// <summary>
69    /// Expands the selection up by one line, creating a rectangular selection.
70    /// Key gesture: Alt+Shift+Up
71    /// </summary>
72    public static readonly RoutedUICommand BoxSelectUpByLine = Command("BoxSelectUpByLine");
73   
74    /// <summary>
75    /// Expands the selection up by one line, creating a rectangular selection.
76    /// Key gesture: Alt+Shift+Down
77    /// </summary>
78    public static readonly RoutedUICommand BoxSelectDownByLine = Command("BoxSelectDownByLine");
79   
80    /// <summary>
81    /// Expands the selection to the start of the line, creating a rectangular selection.
82    /// Key gesture: Alt+Shift+Home
83    /// </summary>
84    public static readonly RoutedUICommand BoxSelectToLineStart = Command("BoxSelectToLineStart");
85   
86    /// <summary>
87    /// Expands the selection to the end of the line, creating a rectangular selection.
88    /// Key gesture: Alt+Shift+End
89    /// </summary>
90    public static readonly RoutedUICommand BoxSelectToLineEnd = Command("BoxSelectToLineEnd");
91   
92    static RoutedUICommand Command(string name)
93    {
94      return new RoutedUICommand(name, name, typeof(RectangleSelection));
95    }
96    #endregion
97   
98    TextDocument document;
99    readonly int startLine, endLine;
100    readonly double startXPos, endXPos;
101    readonly int topLeftOffset, bottomRightOffset;
102    readonly TextViewPosition start, end;
103   
104    readonly List<SelectionSegment> segments = new List<SelectionSegment>();
105   
106    #region Constructors
107    /// <summary>
108    /// Creates a new rectangular selection.
109    /// </summary>
110    public RectangleSelection(TextArea textArea, TextViewPosition start, TextViewPosition end)
111      : base(textArea)
112    {
113      InitDocument();
114      this.startLine = start.Line;
115      this.endLine = end.Line;
116      this.startXPos = GetXPos(textArea, start);
117      this.endXPos = GetXPos(textArea, end);
118      CalculateSegments();
119      this.topLeftOffset = this.segments.First().StartOffset;
120      this.bottomRightOffset = this.segments.Last().EndOffset;
121     
122      this.start = start;
123      this.end = end;
124    }
125   
126    private RectangleSelection(TextArea textArea, int startLine, double startXPos, TextViewPosition end)
127      : base(textArea)
128    {
129      InitDocument();
130      this.startLine = startLine;
131      this.endLine = end.Line;
132      this.startXPos = startXPos;
133      this.endXPos = GetXPos(textArea, end);
134      CalculateSegments();
135      this.topLeftOffset = this.segments.First().StartOffset;
136      this.bottomRightOffset = this.segments.Last().EndOffset;
137     
138      this.start = GetStart();
139      this.end = end;
140    }
141   
142    private RectangleSelection(TextArea textArea, TextViewPosition start, int endLine, double endXPos)
143      : base(textArea)
144    {
145      InitDocument();
146      this.startLine = start.Line;
147      this.endLine = endLine;
148      this.startXPos = GetXPos(textArea, start);
149      this.endXPos = endXPos;
150      CalculateSegments();
151      this.topLeftOffset = this.segments.First().StartOffset;
152      this.bottomRightOffset = this.segments.Last().EndOffset;
153     
154      this.start = start;
155      this.end = GetEnd();
156    }
157   
158    void InitDocument()
159    {
160      document = textArea.Document;
161      if (document == null)
162        throw ThrowUtil.NoDocumentAssigned();
163    }
164   
165    static double GetXPos(TextArea textArea, TextViewPosition pos)
166    {
167      DocumentLine documentLine = textArea.Document.GetLineByNumber(pos.Line);
168      VisualLine visualLine = textArea.TextView.GetOrConstructVisualLine(documentLine);
169      int vc = visualLine.ValidateVisualColumn(pos, true);
170      TextLine textLine = visualLine.GetTextLine(vc, pos.IsAtEndOfLine);
171      return visualLine.GetTextLineVisualXPosition(textLine, vc);
172    }
173   
174    void CalculateSegments()
175    {
176      DocumentLine nextLine = document.GetLineByNumber(Math.Min(startLine, endLine));
177      do {
178        VisualLine vl = textArea.TextView.GetOrConstructVisualLine(nextLine);
179        int startVC = vl.GetVisualColumn(new Point(startXPos, 0), true);
180        int endVC = vl.GetVisualColumn(new Point(endXPos, 0), true);
181       
182        int baseOffset = vl.FirstDocumentLine.Offset;
183        int startOffset = baseOffset + vl.GetRelativeOffset(startVC);
184        int endOffset = baseOffset + vl.GetRelativeOffset(endVC);
185        segments.Add(new SelectionSegment(startOffset, startVC, endOffset, endVC));
186       
187        nextLine = vl.LastDocumentLine.NextLine;
188      } while (nextLine != null && nextLine.LineNumber <= Math.Max(startLine, endLine));
189    }
190   
191    TextViewPosition GetStart()
192    {
193      SelectionSegment segment = (startLine < endLine ? segments.First() : segments.Last());
194      if (startXPos < endXPos) {
195        return new TextViewPosition(document.GetLocation(segment.StartOffset), segment.StartVisualColumn);
196      } else {
197        return new TextViewPosition(document.GetLocation(segment.EndOffset), segment.EndVisualColumn);
198      }
199    }
200   
201    TextViewPosition GetEnd()
202    {
203      SelectionSegment segment = (startLine < endLine ? segments.Last() : segments.First());
204      if (startXPos < endXPos) {
205        return new TextViewPosition(document.GetLocation(segment.EndOffset), segment.EndVisualColumn);
206      } else {
207        return new TextViewPosition(document.GetLocation(segment.StartOffset), segment.StartVisualColumn);
208      }
209    }
210    #endregion
211   
212    /// <inheritdoc/>
213    public override string GetText()
214    {
215      StringBuilder b = new StringBuilder();
216      foreach (ISegment s in this.Segments) {
217        if (b.Length > 0)
218          b.AppendLine();
219        b.Append(document.GetText(s));
220      }
221      return b.ToString();
222    }
223   
224    /// <inheritdoc/>
225    public override Selection StartSelectionOrSetEndpoint(TextViewPosition startPosition, TextViewPosition endPosition)
226    {
227      return SetEndpoint(endPosition);
228    }
229   
230    /// <inheritdoc/>
231    public override int Length {
232      get {
233        return this.Segments.Sum(s => s.Length);
234      }
235    }
236   
237    /// <inheritdoc/>
238    public override bool EnableVirtualSpace {
239      get { return true; }
240    }
241   
242    /// <inheritdoc/>
243    public override ISegment SurroundingSegment {
244      get {
245        return new SimpleSegment(topLeftOffset, bottomRightOffset - topLeftOffset);
246      }
247    }
248   
249    /// <inheritdoc/>
250    public override IEnumerable<SelectionSegment> Segments {
251      get { return segments; }
252    }
253   
254    /// <inheritdoc/>
255    public override TextViewPosition StartPosition {
256      get { return start; }
257    }
258   
259    /// <inheritdoc/>
260    public override TextViewPosition EndPosition {
261      get { return end; }
262    }
263   
264    /// <inheritdoc/>
265    public override bool Equals(object obj)
266    {
267      RectangleSelection r = obj as RectangleSelection;
268      return r != null && r.textArea == this.textArea
269        && r.topLeftOffset == this.topLeftOffset && r.bottomRightOffset == this.bottomRightOffset
270        && r.startLine == this.startLine && r.endLine == this.endLine
271        && r.startXPos == this.startXPos && r.endXPos == this.endXPos;
272    }
273   
274    /// <inheritdoc/>
275    public override int GetHashCode()
276    {
277      return topLeftOffset ^ bottomRightOffset;
278    }
279   
280    /// <inheritdoc/>
281    public override Selection SetEndpoint(TextViewPosition endPosition)
282    {
283      return new RectangleSelection(textArea, startLine, startXPos, endPosition);
284    }
285   
286    int GetVisualColumnFromXPos(int line, double xPos)
287    {
288      var vl = textArea.TextView.GetOrConstructVisualLine(textArea.Document.GetLineByNumber(line));
289      return vl.GetVisualColumn(new Point(xPos, 0), true);
290    }
291   
292    /// <inheritdoc/>
293    public override Selection UpdateOnDocumentChange(DocumentChangeEventArgs e)
294    {
295      TextLocation newStartLocation = textArea.Document.GetLocation(e.GetNewOffset(topLeftOffset, AnchorMovementType.AfterInsertion));
296      TextLocation newEndLocation = textArea.Document.GetLocation(e.GetNewOffset(bottomRightOffset, AnchorMovementType.BeforeInsertion));
297     
298      return new RectangleSelection(textArea,
299                                    new TextViewPosition(newStartLocation, GetVisualColumnFromXPos(newStartLocation.Line, startXPos)),
300                                    new TextViewPosition(newEndLocation, GetVisualColumnFromXPos(newEndLocation.Line, endXPos)));
301    }
302   
303    /// <inheritdoc/>
304    public override void ReplaceSelectionWithText(string newText)
305    {
306      if (newText == null)
307        throw new ArgumentNullException("newText");
308      using (textArea.Document.RunUpdate()) {
309        TextViewPosition start = new TextViewPosition(document.GetLocation(topLeftOffset), GetVisualColumnFromXPos(startLine, startXPos));
310        TextViewPosition end = new TextViewPosition(document.GetLocation(bottomRightOffset), GetVisualColumnFromXPos(endLine, endXPos));
311        int insertionLength;
312        int totalInsertionLength = 0;
313        int firstInsertionLength = 0;
314        int editOffset = Math.Min(topLeftOffset, bottomRightOffset);
315        TextViewPosition pos;
316        if (NewLineFinder.NextNewLine(newText, 0) == SimpleSegment.Invalid) {
317          // insert same text into every line
318          foreach (SelectionSegment lineSegment in this.Segments.Reverse()) {
319            ReplaceSingleLineText(textArea, lineSegment, newText, out insertionLength);
320            totalInsertionLength += insertionLength;
321            firstInsertionLength = insertionLength;
322          }
323         
324          int newEndOffset = editOffset + totalInsertionLength;
325          pos = new TextViewPosition(document.GetLocation(editOffset + firstInsertionLength));
326         
327          textArea.Selection = new RectangleSelection(textArea, pos, Math.Max(startLine, endLine), GetXPos(textArea, pos));
328        } else {
329          string[] lines = newText.Split(NewLineFinder.NewlineStrings, segments.Count, StringSplitOptions.None);
330          int line = Math.Min(startLine, endLine);
331          for (int i = lines.Length - 1; i >= 0; i--) {
332            ReplaceSingleLineText(textArea, segments[i], lines[i], out insertionLength);
333            firstInsertionLength = insertionLength;
334          }
335          pos = new TextViewPosition(document.GetLocation(editOffset + firstInsertionLength));
336          textArea.ClearSelection();
337        }
338        textArea.Caret.Position = textArea.TextView.GetPosition(new Point(GetXPos(textArea, pos), textArea.TextView.GetVisualTopByDocumentLine(Math.Max(startLine, endLine)))).GetValueOrDefault();
339      }
340    }
341   
342    void ReplaceSingleLineText(TextArea textArea, SelectionSegment lineSegment, string newText, out int insertionLength)
343    {
344      if (lineSegment.Length == 0) {
345        if (newText.Length > 0 && textArea.ReadOnlySectionProvider.CanInsert(lineSegment.StartOffset)) {
346          newText = AddSpacesIfRequired(newText, new TextViewPosition(document.GetLocation(lineSegment.StartOffset), lineSegment.StartVisualColumn), new TextViewPosition(document.GetLocation(lineSegment.EndOffset), lineSegment.EndVisualColumn));
347          textArea.Document.Insert(lineSegment.StartOffset, newText);
348        }
349      } else {
350        ISegment[] segmentsToDelete = textArea.GetDeletableSegments(lineSegment);
351        for (int i = segmentsToDelete.Length - 1; i >= 0; i--) {
352          if (i == segmentsToDelete.Length - 1) {
353            if (segmentsToDelete[i].Offset == SurroundingSegment.Offset && segmentsToDelete[i].Length == SurroundingSegment.Length) {
354              newText = AddSpacesIfRequired(newText, new TextViewPosition(document.GetLocation(lineSegment.StartOffset), lineSegment.StartVisualColumn), new TextViewPosition(document.GetLocation(lineSegment.EndOffset), lineSegment.EndVisualColumn));
355            }
356            textArea.Document.Replace(segmentsToDelete[i], newText);
357          } else {
358            textArea.Document.Remove(segmentsToDelete[i]);
359          }
360        }
361      }
362      insertionLength = newText.Length;
363    }
364   
365    /// <summary>
366    /// Performs a rectangular paste operation.
367    /// </summary>
368    public static bool PerformRectangularPaste(TextArea textArea, TextViewPosition startPosition, string text, bool selectInsertedText)
369    {
370      if (textArea == null)
371        throw new ArgumentNullException("textArea");
372      if (text == null)
373        throw new ArgumentNullException("text");
374      int newLineCount = text.Count(c => c == '\n'); // TODO might not work in all cases, but single \r line endings are really rare today.
375      TextLocation endLocation = new TextLocation(startPosition.Line + newLineCount, startPosition.Column);
376      if (endLocation.Line <= textArea.Document.LineCount) {
377        int endOffset = textArea.Document.GetOffset(endLocation);
378        if (textArea.Selection.EnableVirtualSpace || textArea.Document.GetLocation(endOffset) == endLocation) {
379          RectangleSelection rsel = new RectangleSelection(textArea, startPosition, endLocation.Line, GetXPos(textArea, startPosition));
380          rsel.ReplaceSelectionWithText(text);
381          if (selectInsertedText && textArea.Selection is RectangleSelection) {
382            RectangleSelection sel = (RectangleSelection)textArea.Selection;
383            textArea.Selection = new RectangleSelection(textArea, startPosition, sel.endLine, sel.endXPos);
384          }
385          return true;
386        }
387      }
388      return false;
389    }
390   
391    /// <summary>
392    /// Gets the name of the entry in the DataObject that signals rectangle selections.
393    /// </summary>
394    public const string RectangularSelectionDataType = "AvalonEditRectangularSelection";
395   
396    /// <inheritdoc/>
397    public override System.Windows.DataObject CreateDataObject(TextArea textArea)
398    {
399      var data = base.CreateDataObject(textArea);
400     
401      if (EditingCommandHandler.ConfirmDataFormat(textArea, data, RectangularSelectionDataType)) {
402        MemoryStream isRectangle = new MemoryStream(1);
403        isRectangle.WriteByte(1);
404        data.SetData(RectangularSelectionDataType, isRectangle, false);
405      }
406      return data;
407    }
408   
409    /// <inheritdoc/>
410    public override string ToString()
411    {
412      // It's possible that ToString() gets called on old (invalid) selections, e.g. for "change from... to..." debug message
413      // make sure we don't crash even when the desired locations don't exist anymore.
414      return string.Format("[RectangleSelection {0} {1} {2} to {3} {4} {5}]", startLine, topLeftOffset, startXPos, endLine, bottomRightOffset, endXPos);
415    }
416  }
417}
Note: See TracBrowser for help on using the repository browser.