Code Coverage Statistics for Source File

c:\Tools\SD3\src\Libraries\ICSharpCode.TextEditor\Project\Src\Document\Selection\SelectionManager.cs

Sequence Point Coverage
N/A
0 of 0
Branch Coverage
N/A
0 of 0
Lines
456
Highlight: Uncovered Code Covered Code
L V Source
1
// <file>
2
//     <copyright see="prj:///doc/copyright.txt"/>
3
//     <license see="prj:///doc/license.txt"/>
4
//     <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
5
//     <version>$Revision: 2679 $</version>
6
// </file>
7
8
using System;
9
using System.Collections.Generic;
10
using System.Drawing;
11
using System.Text;
12
13
namespace ICSharpCode.TextEditor.Document
14
{
15
	/// <summary>
16
	/// This class manages the selections in a document.
17
	/// </summary>
18
	public class SelectionManager : IDisposable
19
	{
20
		TextLocation selectionStart;
21
		
22
		internal TextLocation SelectionStart {
23
			get { return selectionStart; }
24
			set {
25
				DefaultDocument.ValidatePosition(document, value);
26
				selectionStart = value;
27
			}
28
		}
29
		IDocument document;
30
		TextArea textArea;
31
		internal SelectFrom selectFrom = new SelectFrom();
32
33
		internal List<ISelection> selectionCollection = new List<ISelection>();
34
		
35
		/// <value>
36
		/// A collection containing all selections.
37
		/// </value>
38
		public List<ISelection> SelectionCollection {
39
			get {
40
				return selectionCollection;
41
			}
42
		}
43
		
44
		/// <value>
45
		/// true if the <see cref="SelectionCollection"/> is not empty, false otherwise.
46
		/// </value>
47
		public bool HasSomethingSelected {
48
			get {
49
				return selectionCollection.Count > 0;
50
			}
51
		}
52
		
53
		public bool SelectionIsReadonly {
54
			get {
55
				if (document.ReadOnly)
56
					return true;
57
				if (document.TextEditorProperties.UseCustomLine) {
58
					foreach (ISelection sel in selectionCollection) {
59
						if (document.CustomLineManager.IsReadOnly(sel, false))
60
							return true;
61
					}
62
				}
63
				return false;
64
			}
65
		}
66
		
67
		/// <value>
68
		/// The text that is currently selected.
69
		/// </value>
70
		public string SelectedText {
71
			get {
72
				StringBuilder builder = new StringBuilder();
73
				
74
//				PriorityQueue queue = new PriorityQueue();
75
				
76
				foreach (ISelection s in selectionCollection) {
77
					builder.Append(s.SelectedText);
78
//					queue.Insert(-s.Offset, s);
79
				}
80
				
81
//				while (queue.Count > 0) {
82
//					ISelection s = ((ISelection)queue.Remove());
83
//					builder.Append(s.SelectedText);
84
//				}
85
				
86
				return builder.ToString();
87
			}
88
		}
89
		
90
		/// <summary>
91
		/// Creates a new instance of <see cref="SelectionManager"/>
92
		/// </summary>
93
		public SelectionManager(IDocument document)
94
		{
95
			this.document = document;
96
			document.DocumentChanged += new DocumentEventHandler(DocumentChanged);
97
		}
98
99
		/// <summary>
100
		/// Creates a new instance of <see cref="SelectionManager"/>
101
		/// </summary>
102
		public SelectionManager(IDocument document, TextArea textArea)
103
		{
104
			this.document = document;
105
			this.textArea = textArea;
106
			document.DocumentChanged += new DocumentEventHandler(DocumentChanged);
107
		}
108
109
		public void Dispose()
110
		{
111
			if (this.document != null) {
112
				document.DocumentChanged -= new DocumentEventHandler(DocumentChanged);
113
				this.document = null;
114
			}
115
		}
116
		
117
		void DocumentChanged(object sender, DocumentEventArgs e)
118
		{
119
			if (e.Text == null) {
120
				Remove(e.Offset, e.Length);
121
			} else {
122
				if (e.Length < 0) {
123
					Insert(e.Offset, e.Text);
124
				} else {
125
					Replace(e.Offset, e.Length, e.Text);
126
				}
127
			}
128
		}
129
		
130
		/// <remarks>
131
		/// Clears the selection and sets a new selection
132
		/// using the given <see cref="ISelection"/> object.
133
		/// </remarks>
134
		public void SetSelection(ISelection selection)
135
		{
136
//			autoClearSelection = false;
137
			if (selection != null) {
138
				if (SelectionCollection.Count == 1 &&
139
				    selection.StartPosition == SelectionCollection[0].StartPosition &&
140
				    selection.EndPosition == SelectionCollection[0].EndPosition ) {
141
					return;
142
				}
143
				ClearWithoutUpdate();
144
				selectionCollection.Add(selection);
145
				document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.LinesBetween, selection.StartPosition.Y, selection.EndPosition.Y));
146
				document.CommitUpdate();
147
				OnSelectionChanged(EventArgs.Empty);
148
			} else {
149
				ClearSelection();
150
			}
151
		}
152
		
153
		public void SetSelection(TextLocation startPosition, TextLocation endPosition)
154
		{
155
			SetSelection(new DefaultSelection(document, startPosition, endPosition));
156
		}
157
		
158
		public bool GreaterEqPos(TextLocation p1, TextLocation p2)
159
		{
160
			return p1.Y > p2.Y || p1.Y == p2.Y && p1.X >= p2.X;
161
		}
162
		
163
		public void ExtendSelection(TextLocation oldPosition, TextLocation newPosition)
164
		{
165
			// where oldposition is where the cursor was,
166
			// and newposition is where it has ended up from a click (both zero based)
167
168
			if (oldPosition == newPosition)
169
			{
170
				return;
171
			}
172
173
			TextLocation min;
174
			TextLocation max;
175
			int oldnewX = newPosition.X;
176
			bool  oldIsGreater = GreaterEqPos(oldPosition, newPosition);
177
			if (oldIsGreater) {
178
				min = newPosition;
179
				max = oldPosition;
180
			} else {
181
				min = oldPosition;
182
				max = newPosition;
183
			}
184
185
			if (min == max) {
186
				return;
187
			}
188
189
			if (!HasSomethingSelected)
190
			{
191
				SetSelection(new DefaultSelection(document, min, max));
192
				// initialise selectFrom for a cursor selection
193
				if (selectFrom.where == WhereFrom.None)
194
					SelectionStart = oldPosition; //textArea.Caret.Position;
195
				return;
196
			}
197
198
			ISelection selection = this.selectionCollection[0];
199
200
			if (min == max) {
201
				//selection.StartPosition = newPosition;
202
				return;
203
			} else {
204
				// changed selection via gutter
205
				if (selectFrom.where == WhereFrom.Gutter)
206
				{
207
					// selection new position is always at the left edge for gutter selections
208
					newPosition.X = 0;
209
				}
210
211
				if (GreaterEqPos(newPosition, SelectionStart)) // selecting forward
212
				{
213
					selection.StartPosition = SelectionStart;
214
					// this handles last line selection
215
					if (selectFrom.where == WhereFrom.Gutter ) //&& newPosition.Y != oldPosition.Y)
216
						selection.EndPosition = new TextLocation(textArea.Caret.Column, textArea.Caret.Line);
217
					else {
218
						newPosition.X = oldnewX;
219
						selection.EndPosition = newPosition;
220
					}
221
				} else { // selecting back
222
					if (selectFrom.where == WhereFrom.Gutter && selectFrom.first == WhereFrom.Gutter)
223
					{ // gutter selection
224
						selection.EndPosition = NextValidPosition(SelectionStart.Y);
225
					} else { // internal text selection
226
						selection.EndPosition = SelectionStart; //selection.StartPosition;
227
					}
228
					selection.StartPosition = newPosition;
229
				}
230
			}
231
232
			document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.LinesBetween, min.Y, max.Y));
233
			document.CommitUpdate();
234
			OnSelectionChanged(EventArgs.Empty);
235
		}
236
237
		// retrieve the next available line
238
		// - checks that there are more lines available after the current one
239
		// - if there are then the next line is returned
240
		// - if there are NOT then the last position on the given line is returned
241
		public TextLocation NextValidPosition(int line)
242
		{
243
			if (line < document.TotalNumberOfLines - 1)
244
				return new TextLocation(0, line + 1);
245
			else
246
				return new TextLocation(document.GetLineSegment(document.TotalNumberOfLines - 1).Length + 1, line);
247
		}
248
249
		void ClearWithoutUpdate()
250
		{
251
			while (selectionCollection.Count > 0) {
252
				ISelection selection = selectionCollection[selectionCollection.Count - 1];
253
				selectionCollection.RemoveAt(selectionCollection.Count - 1);
254
				document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.LinesBetween, selection.StartPosition.Y, selection.EndPosition.Y));
255
				OnSelectionChanged(EventArgs.Empty);
256
			}
257
		}
258
		/// <remarks>
259
		/// Clears the selection.
260
		/// </remarks>
261
		public void ClearSelection()
262
		{
263
			Point mousepos;
264
			mousepos = textArea.mousepos;
265
			// this is the most logical place to reset selection starting
266
			// positions because it is always called before a new selection
267
			selectFrom.first = selectFrom.where;
268
			TextLocation newSelectionStart = textArea.TextView.GetLogicalPosition(mousepos.X - textArea.TextView.DrawingPosition.X, mousepos.Y - textArea.TextView.DrawingPosition.Y);
269
			if (selectFrom.where == WhereFrom.Gutter) {
270
				newSelectionStart.X = 0;
271
//				selectionStart.Y = -1;
272
			}
273
			if (newSelectionStart.Line >= document.TotalNumberOfLines) {
274
				newSelectionStart.Line = document.TotalNumberOfLines-1;
275
				newSelectionStart.Column = document.GetLineSegment(document.TotalNumberOfLines-1).Length;
276
			}
277
			this.SelectionStart = newSelectionStart;
278
279
			ClearWithoutUpdate();
280
			document.CommitUpdate();
281
		}
282
		
283
		/// <remarks>
284
		/// Removes the selected text from the buffer and clears
285
		/// the selection.
286
		/// </remarks>
287
		public void RemoveSelectedText()
288
		{
289
			List<int> lines = new List<int>();
290
			int offset = -1;
291
			bool oneLine = true;
292
//			PriorityQueue queue = new PriorityQueue();
293
			foreach (ISelection s in selectionCollection) {
294
//				ISelection s = ((ISelection)queue.Remove());
295
				if (oneLine) {
296
					int lineBegin = s.StartPosition.Y;
297
					if (lineBegin != s.EndPosition.Y) {
298
						oneLine = false;
299
					} else {
300
						lines.Add(lineBegin);
301
					}
302
				}
303
				offset = s.Offset;
304
				document.Remove(s.Offset, s.Length);
305
306
//				queue.Insert(-s.Offset, s);
307
			}
308
			ClearSelection();
309
			if (offset >= 0) {
310
				//             TODO:
311
//				document.Caret.Offset = offset;
312
			}
313
			if (offset != -1) {
314
				if (oneLine) {
315
					foreach (int i in lines) {
316
						document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.SingleLine, i));
317
					}
318
				} else {
319
					document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
320
				}
321
				document.CommitUpdate();
322
			}
323
		}
324
		
325
		
326
		bool SelectionsOverlap(ISelection s1, ISelection s2)
327
		{
328
			return (s1.Offset <= s2.Offset && s2.Offset <= s1.Offset + s1.Length)                         ||
329
				(s1.Offset <= s2.Offset + s2.Length && s2.Offset + s2.Length <= s1.Offset + s1.Length) ||
330
				(s1.Offset >= s2.Offset && s1.Offset + s1.Length <= s2.Offset + s2.Length);
331
		}
332
		
333
		/// <remarks>
334
		/// Returns true if the given offset points to a section which is
335
		/// selected.
336
		/// </remarks>
337
		public bool IsSelected(int offset)
338
		{
339
			return GetSelectionAt(offset) != null;
340
		}
341
342
		/// <remarks>
343
		/// Returns a <see cref="ISelection"/> object giving the selection in which
344
		/// the offset points to.
345
		/// </remarks>
346
		/// <returns>
347
		/// <code>null</code> if the offset doesn't point to a selection
348
		/// </returns>
349
		public ISelection GetSelectionAt(int offset)
350
		{
351
			foreach (ISelection s in selectionCollection) {
352
				if (s.ContainsOffset(offset)) {
353
					return s;
354
				}
355
			}
356
			return null;
357
		}
358
		
359
		/// <remarks>
360
		/// Used internally, do not call.
361
		/// </remarks>
362
		internal void Insert(int offset, string text)
363
		{
364
//			foreach (ISelection selection in SelectionCollection) {
365
//				if (selection.Offset > offset) {
366
//					selection.Offset += text.Length;
367
//				} else if (selection.Offset + selection.Length > offset) {
368
//					selection.Length += text.Length;
369
//				}
370
//			}
371
		}
372
		
373
		/// <remarks>
374
		/// Used internally, do not call.
375
		/// </remarks>
376
		internal void Remove(int offset, int length)
377
		{
378
//			foreach (ISelection selection in selectionCollection) {
379
//				if (selection.Offset > offset) {
380
//					selection.Offset -= length;
381
//				} else if (selection.Offset + selection.Length > offset) {
382
//					selection.Length -= length;
383
//				}
384
//			}
385
		}
386
		
387
		/// <remarks>
388
		/// Used internally, do not call.
389
		/// </remarks>
390
		internal void Replace(int offset, int length, string text)
391
		{
392
//			foreach (ISelection selection in selectionCollection) {
393
//				if (selection.Offset > offset) {
394
//					selection.Offset = selection.Offset - length + text.Length;
395
//				} else if (selection.Offset + selection.Length > offset) {
396
//					selection.Length = selection.Length - length + text.Length;
397
//				}
398
//			}
399
		}
400
		
401
		public ColumnRange GetSelectionAtLine(int lineNumber)
402
		{
403
			foreach (ISelection selection in selectionCollection) {
404
				int startLine = selection.StartPosition.Y;
405
				int endLine   = selection.EndPosition.Y;
406
				if (startLine < lineNumber && lineNumber < endLine) {
407
					return ColumnRange.WholeColumn;
408
				}
409
				
410
				if (startLine == lineNumber) {
411
					LineSegment line = document.GetLineSegment(startLine);
412
					int startColumn = selection.StartPosition.X;
413
					int endColumn   = endLine == lineNumber ? selection.EndPosition.X : line.Length + 1;
414
					return new ColumnRange(startColumn, endColumn);
415
				}
416
				
417
				if (endLine == lineNumber) {
418
					int endColumn   = selection.EndPosition.X;
419
					return new ColumnRange(0, endColumn);
420
				}
421
			}
422
			
423
			return ColumnRange.NoColumn;
424
		}
425
		
426
		public void FireSelectionChanged()
427
		{
428
			OnSelectionChanged(EventArgs.Empty);
429
		}
430
		protected virtual void OnSelectionChanged(EventArgs e)
431
		{
432
			if (SelectionChanged != null) {
433
				SelectionChanged(this, e);
434
			}
435
		}
436
		
437
		public event EventHandler SelectionChanged;
438
	}
439
440
	// selection initiated from...
441
	internal class SelectFrom {
442
		public int where = WhereFrom.None; // last selection initiator
443
		public int first = WhereFrom.None; // first selection initiator
444
445
		public SelectFrom()
446
		{
447
		}
448
	}
449
450
	// selection initiated from type...
451
	internal class WhereFrom {
452
		public const int None = 0;
453
		public const int Gutter = 1;
454
		public const int TArea = 2;
455
	}
456
}