OSDN Git Service

ReplaceAll2()が大文字小文字を区別しない状況下だと遅くなるので最適化した
[fooeditengine/FooEditEngine.git] / Common / Controller.cs
1 /*\r
2  * Copyright (C) 2013 FooProject\r
3  * * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by\r
4  * the Free Software Foundation; either version 3 of the License, or (at your option) any later version.\r
5 \r
6  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of \r
7  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\r
8 \r
9 You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.\r
10  */\r
11 using System;\r
12 using System.Globalization;\r
13 using System.Text;\r
14 using System.Linq;\r
15 using System.Diagnostics;\r
16 using System.Text.RegularExpressions;\r
17 #if WINFORM\r
18 using System.Drawing;\r
19 #endif\r
20 \r
21 namespace FooEditEngine\r
22 {\r
23     internal enum MoveFlow\r
24     {\r
25         Horizontical,\r
26         Vertical,\r
27     }\r
28     internal enum ScrollDirection\r
29     {\r
30         Up,\r
31         Down,\r
32         Left,\r
33         Right,\r
34     }\r
35     /// <summary>\r
36     /// ユーザー側からの処理を担当するクラス。一部を除き、こちらで行われた操作はアンドゥの対象になります\r
37     /// </summary>\r
38     internal sealed class Controller\r
39     {\r
40         EditView View;\r
41         Document Document;\r
42         int AnchorIndex;\r
43         \r
44         public Controller(Document doc, EditView view)\r
45         {\r
46             this.Document = doc;\r
47             this.Document.Update += new DocumentUpdateEventHandler(Document_Update);\r
48             this.View = view;\r
49             this.View.render.ChangedRightToLeft += render_ChangedRightToLeft;\r
50             this.View.render.ChangedRenderResource += render_ChangedRenderResource;\r
51             this.View.PerformLayouted += View_LineBreakChanged;\r
52             this.View.PageBoundChanged += View_PageBoundChanged;\r
53             this.Document.Clear();\r
54             this.CaretMoved += new EventHandler((s, e) => { });\r
55         }\r
56 \r
57         /// <summary>\r
58         /// キャレット移動時に通知される\r
59         /// </summary>\r
60         public event EventHandler CaretMoved;\r
61 \r
62 \r
63         /// <summary>\r
64         /// 矩形選択モードなら真を返し、そうでない場合は偽を返す\r
65         /// </summary>\r
66         public bool RectSelection\r
67         {\r
68             get;\r
69             set;\r
70         }\r
71 \r
72         /// <summary>\r
73         /// 選択範囲の開始位置\r
74         /// </summary>\r
75         /// <remarks>SelectionLengthが0の場合、キャレット位置を表します</remarks>\r
76         public int SelectionStart\r
77         {\r
78             get\r
79             {\r
80                 if (this.View.Selections.Count == 0)\r
81                     return this.AnchorIndex;\r
82                 else\r
83                     return this.View.Selections.First().start;\r
84             }\r
85         }\r
86 \r
87         /// <summary>\r
88         /// 選択範囲の長さ\r
89         /// </summary>\r
90         /// <remarks>矩形選択モードの場合、選択範囲の文字数ではなく、開始位置から終了位置までの長さとなります</remarks>\r
91         public int SelectionLength\r
92         {\r
93             get\r
94             {\r
95                 if (this.View.Selections.Count == 0)\r
96                     return 0;\r
97                 Selection last = this.View.Selections.Last();\r
98                 return last.start + last.length - this.SelectionStart;\r
99             }\r
100         }\r
101 \r
102         /// <summary>\r
103         /// 選択範囲内の文字列を返す\r
104         /// </summary>\r
105         /// <remarks>\r
106         /// 未選択状態で代入したときは追加され、そうでない場合は選択範囲の文字列と置き換えられます。\r
107         /// </remarks>\r
108         public string SelectedText\r
109         {\r
110             get\r
111             {\r
112                 if (this.View.LayoutLines.Count == 0 || this.View.Selections.Count == 0)\r
113                     return null;\r
114                 string str;\r
115                 if (this.RectSelection)\r
116                     str = GetTextFromRectangleSelectArea(this.View.Selections);\r
117                 else\r
118                     str = GetTextFromLineSelectArea(this.View.Selections);\r
119                 return str.Replace(Document.NewLine.ToString(), Environment.NewLine);\r
120             }\r
121             set\r
122             {\r
123                 if (this.Document.FireUpdateEvent == false)\r
124                     throw new InvalidOperationException("");\r
125                 if (value == null)\r
126                     return;\r
127                 this.RepleaceSelectionArea(this.View.Selections, value.Replace(Environment.NewLine,Document.NewLine.ToString()));\r
128             }\r
129         }\r
130 \r
131         /// <summary>\r
132         /// 選択範囲が逆転しているかどうかを判定する\r
133         /// </summary>\r
134         /// <returns>逆転しているなら真を返す</returns>\r
135         public bool IsReverseSelect()\r
136         {\r
137             int index = this.View.LayoutLines.GetIndexFromTextPoint(this.View.CaretPostion);\r
138             return index < this.AnchorIndex;\r
139         }\r
140 \r
141         /// <summary>\r
142         /// 指定された範囲を選択する\r
143         /// </summary>\r
144         /// <param name="start"></param>\r
145         /// <param name="length"></param>\r
146         /// <remarks>RectSelectionの値によって動作が変わります。真の場合は矩形選択モードに、そうでない場合は行ごとに選択されます</remarks>\r
147         public void Select(int start, int length)\r
148         {\r
149             if (this.Document.FireUpdateEvent == false)\r
150                 throw new InvalidOperationException("");\r
151             if (start < 0 || start + length < 0 || start + length > this.Document.Length)\r
152                 throw new ArgumentOutOfRangeException("startかendが指定できる範囲を超えてます");\r
153             this.View.Selections.Clear();\r
154             if (length < 0)\r
155             {\r
156                 int oldStart = start;\r
157                 start += length;\r
158                 length = oldStart - start;\r
159             }\r
160             if (this.RectSelection && length != 0)\r
161             {\r
162                 TextPoint startTextPoint = this.View.GetLayoutLineFromIndex(start);\r
163                 TextPoint endTextPoint = this.View.GetLayoutLineFromIndex(start + length);\r
164                 this.SelectByRectangle(new TextRectangle(startTextPoint, endTextPoint));\r
165                 if (startTextPoint.col == endTextPoint.col)\r
166                     this.View.InsertPoint = new SelectCollection(this.View.Selections);\r
167                 else\r
168                     this.View.InsertPoint = null;\r
169             }\r
170             else\r
171             {\r
172                 this.View.Selections.Add(Selection.Create(start, length));\r
173                 this.View.InsertPoint = null;\r
174             }\r
175         }\r
176 \r
177         public void Select(TextPoint tp, int width, int height)\r
178         {\r
179             if (this.Document.FireUpdateEvent == false || !this.RectSelection)\r
180                 throw new InvalidOperationException("");\r
181             TextPoint end = tp;\r
182 \r
183             end.row = tp.row + height;\r
184             end.col = tp.col + width;\r
185             \r
186             if (end.row > this.View.LayoutLines.Count - 1)\r
187                 throw new ArgumentOutOfRangeException("");\r
188             \r
189             this.View.Selections.Clear();\r
190             \r
191             this.SelectByRectangle(new TextRectangle(tp,end));\r
192             \r
193             if (width == 0)\r
194                 this.View.InsertPoint = new SelectCollection(this.View.Selections);\r
195             else\r
196                 this.View.InsertPoint = null;\r
197         }\r
198 \r
199         private void SelectByRectangle(TextRectangle rect)\r
200         {\r
201             if (this.Document.FireUpdateEvent == false)\r
202                 throw new InvalidOperationException("");\r
203             if (rect.TopLeft <= rect.BottomRight)\r
204             {\r
205                 for (int i = rect.TopLeft.row; i <= rect.BottomLeft.row; i++)\r
206                 {\r
207                     int length = this.View.LayoutLines.GetLengthFromLineNumber(i);\r
208                     int leftCol = rect.TopLeft.col, rightCol = rect.TopRight.col, lastCol = length;\r
209                     if(length > 0 && this.View.LayoutLines[i][length - 1] == Document.NewLine)\r
210                         lastCol =  length - 1;\r
211                     if (lastCol < 0)\r
212                         lastCol = 0;\r
213                     if (rect.TopLeft.col > lastCol)\r
214                         leftCol = lastCol;\r
215                     if (rect.TopRight.col > lastCol)\r
216                         rightCol = lastCol;\r
217 \r
218                     int StartIndex = this.View.LayoutLines.GetIndexFromTextPoint(new TextPoint(i, leftCol));\r
219                     int EndIndex = this.View.LayoutLines.GetIndexFromTextPoint(new TextPoint(i, rightCol));\r
220 \r
221                     Selection sel;\r
222                     sel = Selection.Create(StartIndex, EndIndex - StartIndex);\r
223 \r
224                     this.View.Selections.Add(sel);\r
225                 }\r
226             }\r
227         }\r
228 \r
229         /// <summary>\r
230         /// 単語単位で選択する\r
231         /// </summary>\r
232         /// <param name="index">探索を開始するインデックス</param>\r
233         public void SelectWord(int index)\r
234         {\r
235             if (this.Document.FireUpdateEvent == false)\r
236                 throw new InvalidOperationException("");\r
237 \r
238             if (this.Document.Length <= 0 || index >= this.Document.Length)\r
239                 return;\r
240 \r
241             Document str = this.Document;\r
242 \r
243             int start = index;\r
244             while (start > 0 && !Util.IsWordSeparator(str[start]))\r
245                 start--;\r
246 \r
247             if (Util.IsWordSeparator(str[start]))\r
248                 start++;\r
249 \r
250             int end = index;\r
251             while (end < this.Document.Length && !Util.IsWordSeparator(str[end]))\r
252                 end++;\r
253 \r
254             this.Select(start, end - start);\r
255         }\r
256 \r
257         /// <summary>\r
258         /// 選択範囲内のUTF32コードポイントを文字列に変換します\r
259         /// </summary>\r
260         /// <returns>成功した場合は真。そうでない場合は偽を返す</returns>\r
261         public bool ConvertToChar()\r
262         {\r
263             if (this.SelectionLength == 0 || this.RectSelection)\r
264                 return false;\r
265             string str = this.Document.ToString(this.SelectionStart, this.SelectionLength);\r
266             string[] codes = str.Split(new char[] { ' ' },StringSplitOptions.RemoveEmptyEntries);\r
267             StringBuilder result = new StringBuilder();\r
268             foreach (string code in codes)\r
269             {\r
270                 int utf32_code;\r
271                 if (code[0] != 'U')\r
272                     return false;\r
273                 if (Int32.TryParse(code.TrimStart('U'),NumberStyles.HexNumber,null, out utf32_code))\r
274                     result.Append(Char.ConvertFromUtf32(utf32_code));\r
275                 else\r
276                     return false;\r
277             }\r
278             this.Document.Replace(this.SelectionStart, this.SelectionLength, result.ToString());\r
279             return true;\r
280         }\r
281 \r
282         /// <summary>\r
283         /// 選択文字列をUTF32のコードポイントに変換します\r
284         /// </summary>\r
285         public void ConvertToCodePoint()\r
286         {\r
287             if (this.SelectionLength == 0 || this.RectSelection)\r
288                 return;\r
289             string str = this.Document.ToString(this.SelectionStart, this.SelectionLength);\r
290             StringInfo info = new StringInfo(str);\r
291             StringBuilder result = new StringBuilder();\r
292             for (int i = 0; i < str.Length;)\r
293             {\r
294                 int utf32_code = Char.ConvertToUtf32(str, i); \r
295                 result.Append("U" + Convert.ToString(utf32_code,16));\r
296                 result.Append(' ');\r
297                 if(Char.IsHighSurrogate(str[i]))\r
298                     i += 2;\r
299                 else\r
300                     i++;\r
301             }\r
302             this.Document.Replace(this.SelectionStart, this.SelectionLength, result.ToString());\r
303         }\r
304 \r
305         /// <summary>\r
306         /// 選択を解除する\r
307         /// </summary>\r
308         public void DeSelectAll()\r
309         {\r
310             if (this.Document.FireUpdateEvent == false)\r
311                 throw new InvalidOperationException("");\r
312 \r
313             this.View.Selections.Clear();\r
314         }\r
315 \r
316         /// <summary>\r
317         /// 任意のマーカーかどうか\r
318         /// </summary>\r
319         /// <param name="tp"></param>\r
320         /// <param name="type"></param>\r
321         /// <returns>真ならマーカーがある</returns>\r
322         public bool IsMarker(TextPoint tp,HilightType type)\r
323         {\r
324             if (this.Document.FireUpdateEvent == false)\r
325                 throw new InvalidOperationException("");\r
326             int index = this.View.LayoutLines.GetIndexFromTextPoint(tp);\r
327             return this.IsMarker(index, type);\r
328         }\r
329 \r
330         /// <summary>\r
331         /// 任意のマーカーかどうか判定する\r
332         /// </summary>\r
333         /// <param name="index"></param>\r
334         /// <param name="type"></param>\r
335         /// <returns>真ならマーカーがある</returns>\r
336         public bool IsMarker(int index, HilightType type)\r
337         {\r
338             foreach (Marker m in this.Document.GetMarkers(index))\r
339             {\r
340                 if (m.hilight == type)\r
341                     return true;\r
342             }\r
343             return false;\r
344         }\r
345 \r
346         /// <summary>\r
347         /// キャレット位置を再調整する\r
348         /// </summary>\r
349         public void AdjustCaret()\r
350         {\r
351             int row = this.View.CaretPostion.row;\r
352             if (row > this.View.LayoutLines.Count - 1)\r
353                 row = this.View.LayoutLines.Count - 1;\r
354             int col = this.View.CaretPostion.col;\r
355             if (col > 0 && col > this.View.LayoutLines[row].Length)\r
356                 col = this.View.LayoutLines[row].Length;\r
357             this.JumpCaret(row, col);\r
358         }\r
359 \r
360         /// <summary>\r
361         /// キャレットを指定した位置に移動させる\r
362         /// </summary>\r
363         /// <param name="index"></param>\r
364         /// <param name="autoExpand">折り畳みを展開するなら真</param>\r
365         public void JumpCaret(int index,bool autoExpand = true)\r
366         {\r
367             if (index < 0 || index > this.Document.Length)\r
368                 throw new ArgumentOutOfRangeException("indexが設定できる範囲を超えています");\r
369             TextPoint tp = this.View.GetLayoutLineFromIndex(index);\r
370 \r
371             this.JumpCaret(tp.row, tp.col,autoExpand);\r
372          }\r
373 \r
374         /// <summary>\r
375         /// キャレットを指定した位置に移動させる\r
376         /// </summary>\r
377         /// <param name="row"></param>\r
378         /// <param name="col"></param>\r
379         /// <param name="autoExpand">折り畳みを展開するなら真</param>\r
380         public void JumpCaret(int row, int col, bool autoExpand = true)\r
381         {\r
382             if (this.Document.FireUpdateEvent == false)\r
383                 throw new InvalidOperationException("");\r
384 \r
385             this.View.JumpCaret(row, col,autoExpand);\r
386 \r
387             this.View.AdjustCaretAndSrc();\r
388 \r
389             this.SelectWithMoveCaret(false);\r
390 \r
391             this.CaretMoved(this, null);\r
392         }\r
393 \r
394         /// <summary>\r
395         /// ドキュメントの先頭に移動する\r
396         /// </summary>\r
397         /// <param name="isSelected"></param>\r
398         public void JumpToHead(bool isSelected)\r
399         {\r
400             if (this.View.TryScroll(0, 0))\r
401                 return;\r
402             this.View.JumpCaret(0, 0);\r
403             this.View.AdjustCaretAndSrc();\r
404             this.SelectWithMoveCaret(isSelected);\r
405             this.CaretMoved(this, null);\r
406         }\r
407 \r
408         /// <summary>\r
409         /// ドキュメントの終わりにに移動する\r
410         /// </summary>\r
411         /// <param name="isSelected"></param>\r
412         public void JumpToEnd(bool isSelected)\r
413         {\r
414             int srcRow = this.View.LayoutLines.Count - this.View.LineCountOnScreen - 1;\r
415             if(srcRow < 0)\r
416                 srcRow = 0;\r
417             if (this.View.TryScroll(0, srcRow))\r
418                 return;\r
419             this.View.JumpCaret(this.View.LayoutLines.Count - 1, 0);\r
420             this.View.AdjustCaretAndSrc();\r
421             this.SelectWithMoveCaret(isSelected);\r
422             this.CaretMoved(this, null);\r
423         }\r
424 \r
425         /// <summary>\r
426         /// スクロールする\r
427         /// </summary>\r
428         /// <param name="dir">方向を指定する</param>\r
429         /// <param name="delta">スクロールする量。ScrollDirectionの値がUpやDownなら行数。LeftやRightならピクセル単位の値となる</param>\r
430         /// <param name="isSelected">選択状態にするなら真</param>\r
431         /// <param name="withCaret">同時にキャレットを移動させるなら真</param>\r
432         public void Scroll(ScrollDirection dir, int delta, bool isSelected,bool withCaret)\r
433         {\r
434             if (this.Document.FireUpdateEvent == false)\r
435                 throw new InvalidOperationException("");\r
436             int toRow = this.View.Src.Row;\r
437             double toX = this.View.Src.X;\r
438             switch (dir)\r
439             {\r
440                 case ScrollDirection.Up:\r
441                     toRow = Math.Max(0, this.View.Src.Row - delta);\r
442                     toRow = this.View.AdjustRow(toRow, false);\r
443                     break;\r
444                 case ScrollDirection.Down:\r
445                     toRow = Math.Min(this.View.Src.Row + delta, this.View.LayoutLines.Count - 1);\r
446                     toRow = this.View.AdjustRow(toRow, true);\r
447                     break;\r
448                 case ScrollDirection.Left:\r
449                     toX -= delta;\r
450                     break;\r
451                 case ScrollDirection.Right:\r
452                     toX += delta;\r
453                     break;\r
454                 default:\r
455                     throw new ArgumentOutOfRangeException();\r
456             }\r
457             this.Scroll(toX, toRow, isSelected, withCaret);\r
458         }\r
459 \r
460         /// <summary>\r
461         /// スクロールする\r
462         /// </summary>\r
463         /// <param name="toX">スクロール先の座標</param>\r
464         /// <param name="toRow">スクロール先の行</param>\r
465         /// <param name="isSelected">選択状態にするなら真</param>\r
466         /// <param name="withCaret">同時にキャレットを移動させるなら真</param>\r
467         public void Scroll(double toX, int toRow, bool isSelected, bool withCaret)\r
468         {\r
469             if (withCaret)\r
470             {\r
471                 this.View.Scroll(toX, toRow);\r
472                 this.View.JumpCaret(toRow, 0);\r
473                 this.View.AdjustCaretAndSrc();\r
474                 this.SelectWithMoveCaret(isSelected);\r
475                 this.CaretMoved(this, null);\r
476             }\r
477             else\r
478             {\r
479                 this.View.Scroll(toX, toRow);\r
480                 this.View.HideCaret = true;\r
481                 this.View.HideLineMarker = true;\r
482             }\r
483         }\r
484 \r
485         /// <summary>\r
486         /// キャレットを桁方向に移動させる\r
487         /// </summary>\r
488         /// <returns>移動できない場合は真を返す</returns>\r
489         /// <param name="realLength">負の値なら左側へ、そうでないなら右側へ移動する</param>\r
490         /// <param name="isSelected">選択範囲とするなら真。そうでないなら偽</param>\r
491         /// <param name="alignWord">単語単位で移動するなら真。そうでないなら偽</param>\r
492         public void MoveCaretHorizontical(int realLength, bool isSelected,bool alignWord = false)\r
493         {\r
494             for (int i = Math.Abs(realLength); i > 0; i--)\r
495             {\r
496                 bool MoveFlow = realLength > 0;\r
497                 if (this.View.render.RightToLeft)\r
498                     MoveFlow = !MoveFlow;\r
499                 this.MoveCaretHorizontical(MoveFlow);\r
500 \r
501                 if (alignWord)\r
502                     this.AlignNearestWord(MoveFlow);\r
503             }\r
504             this.View.AdjustCaretAndSrc(AdjustFlow.Col);\r
505             this.SelectWithMoveCaret(isSelected);\r
506             this.CaretMoved(this, null);\r
507         }\r
508 \r
509         void AlignNearestWord(bool MoveFlow)\r
510         {\r
511             string str = this.View.LayoutLines[this.View.CaretPostion.row];\r
512             while (this.View.CaretPostion.col > 0 &&\r
513                 this.View.CaretPostion.col < str.Length &&\r
514                 str[this.View.CaretPostion.col] != Document.NewLine)\r
515             {\r
516                 if (!Util.IsWordSeparator(str[this.View.CaretPostion.col]))\r
517                 {\r
518                     this.MoveCaretHorizontical(MoveFlow);\r
519                 }\r
520                 else\r
521                 {\r
522                     if(MoveFlow)\r
523                         this.MoveCaretHorizontical(MoveFlow);\r
524                     break;\r
525                 }\r
526             }\r
527         }\r
528 \r
529         /// <summary>\r
530         /// キャレットを行方向に移動させる\r
531         /// </summary>\r
532         /// <returns>再描写する必要があるなら真を返す</returns>\r
533         /// <param name="deltarow">移動量</param>\r
534         /// <param name="isSelected"></param>\r
535         public void MoveCaretVertical(int deltarow,bool isSelected)\r
536         {\r
537             for (int i = Math.Abs(deltarow); i > 0; i--)\r
538                 this.MoveCaretVertical(deltarow > 0);\r
539             this.View.AdjustCaretAndSrc(AdjustFlow.Both);\r
540             this.SelectWithMoveCaret(isSelected);\r
541             this.CaretMoved(this, null);\r
542         }\r
543 \r
544         /// <summary>\r
545         /// キャレット位置の文字を一文字削除する\r
546         /// </summary>\r
547         public void DoDeleteAction()\r
548         {\r
549             if (this.SelectionLength != 0)\r
550             {\r
551                 this.SelectedText = "";\r
552                 return;\r
553             }\r
554             \r
555             if (this.Document.FireUpdateEvent == false)\r
556                 throw new InvalidOperationException("");\r
557 \r
558             TextPoint CaretPostion = this.View.CaretPostion;\r
559             int index = this.View.GetIndexFromLayoutLine(CaretPostion);\r
560 \r
561             if (index == this.Document.Length)\r
562                 return;\r
563 \r
564             int lineHeadIndex = this.View.LayoutLines.GetIndexFromLineNumber(CaretPostion.row);\r
565             int next = this.View.LayoutLines.GetLayout(CaretPostion.row).AlignIndexToNearestCluster(CaretPostion.col, AlignDirection.Forward) + lineHeadIndex;\r
566 \r
567             if (this.Document[index] == Document.NewLine)\r
568                 next = index + 1;\r
569 \r
570             this.Document.Replace(index, next - index, "");\r
571         }\r
572 \r
573         /// <summary>\r
574         /// キャレット位置の文字を一文字削除し、キャレット位置を後ろにずらす\r
575         /// </summary>\r
576         public void DoBackSpaceAction()\r
577         {\r
578             if (this.View.InsertPoint != null)\r
579             {\r
580                 this.ReplaceBeforeSelectionArea(this.View.Selections, 1, "");\r
581                 return;\r
582             }\r
583             else if (this.SelectionLength > 0)\r
584             {\r
585                 this.SelectedText = "";\r
586                 return;\r
587             }\r
588 \r
589             if (this.Document.FireUpdateEvent == false)\r
590                 throw new InvalidOperationException("");\r
591 \r
592             TextPoint CurrentPostion = this.View.CaretPostion;\r
593 \r
594             if (CurrentPostion.row == 0 && CurrentPostion.col == 0)\r
595                 return;\r
596 \r
597             int oldIndex = this.View.GetIndexFromLayoutLine(CurrentPostion);\r
598 \r
599             int newCol, newIndex;\r
600             if (CurrentPostion.col > 0)\r
601             {\r
602                 newCol = this.View.LayoutLines.GetLayout(CurrentPostion.row).AlignIndexToNearestCluster(CurrentPostion.col - 1, AlignDirection.Back);\r
603                 newIndex = this.View.GetIndexFromLayoutLine(new TextPoint(CurrentPostion.row, newCol));\r
604             }\r
605             else\r
606             {\r
607                 newIndex = this.View.GetIndexFromLayoutLine(CurrentPostion);\r
608                 newIndex--;\r
609             }\r
610 \r
611             this.Document.Replace(newIndex, oldIndex - newIndex, "");\r
612         }\r
613 \r
614         /// <summary>\r
615         /// キャレット位置で行を分割する\r
616         /// </summary>\r
617         public void DoEnterAction()\r
618         {            \r
619             this.DoInputChar('\n');\r
620         }\r
621 \r
622         /// <summary>\r
623         /// キャレット位置に文字を入力し、その分だけキャレットを進める。isInsertModeの値により動作が変わります\r
624         /// </summary>\r
625         /// <param name="ch"></param>\r
626         public void DoInputChar(char ch)\r
627         {\r
628             this.DoInputString(ch.ToString());\r
629         }\r
630 \r
631         /// <summary>\r
632         /// キャレット位置に文字列を挿入し、その分だけキャレットを進める。isInsertModeの値により動作が変わります\r
633         /// </summary>\r
634         /// <param name="str"></param>\r
635         /// <param name="fromTip"></param>\r
636         public void DoInputString(string str,bool fromTip = false)\r
637         {\r
638             if (this.View.InsertPoint != null)\r
639             {\r
640                 this.ReplaceBeforeSelectionArea(this.View.Selections, 0, str);\r
641                 return;\r
642             }\r
643             else if (this.SelectionLength != 0)\r
644             {\r
645                 this.RepleaceSelectionArea(this.View.Selections, str, fromTip);\r
646                 return;\r
647             }\r
648 \r
649             if (this.Document.FireUpdateEvent == false)\r
650                 throw new InvalidOperationException("");\r
651 \r
652             int index = this.View.GetIndexFromLayoutLine(this.View.CaretPostion);\r
653             int length = 0;\r
654             TextPoint CaretPos = this.View.CaretPostion;\r
655             if (this.View.InsertMode == false && index < this.Document.Length && this.Document[index] != Document.NewLine)\r
656             {\r
657                 string lineString = this.View.LayoutLines[CaretPos.row];\r
658                 int end = this.View.LayoutLines.GetLayout(CaretPos.row).AlignIndexToNearestCluster(CaretPos.col + str.Length - 1, AlignDirection.Forward);\r
659                 if (end > lineString.Length - 1)\r
660                     end = lineString.Length - 1;\r
661                 end += this.View.LayoutLines.GetIndexFromLineNumber(CaretPos.row);\r
662                 length = end - index;\r
663             }\r
664             if (str == Document.NewLine.ToString())\r
665             {\r
666                 int lineHeadIndex = this.View.LayoutLines.GetIndexFromLineNumber(CaretPos.row);\r
667                 int lineLength = this.View.LayoutLines.GetLengthFromLineNumber(CaretPos.row);\r
668                 FoldingItem foldingData = this.View.LayoutLines.FoldingCollection.GetFarestHiddenFoldingData(lineHeadIndex, lineLength);\r
669                 if (foldingData != null && !foldingData.Expand && index > foldingData.Start && index <= foldingData.End)\r
670                     index = foldingData.End + 1;\r
671             }\r
672             this.Document.Replace(index, length, str);\r
673         }\r
674 \r
675         /// <summary>\r
676         /// キャレットの移動に合わせて選択する\r
677         /// </summary>\r
678         /// <param name="isSelected">選択状態にするかどうか</param>\r
679         /// <remarks>\r
680         /// キャレットを移動後、このメソッドを呼び出さない場合、Select()メソッドは正常に機能しません\r
681         /// </remarks>\r
682         void SelectWithMoveCaret(bool isSelected)\r
683         {\r
684             if (this.View.CaretPostion.col < 0 || this.View.CaretPostion.row < 0)\r
685                 return;\r
686 \r
687             if (this.Document.FireUpdateEvent == false)\r
688                 throw new InvalidOperationException("");\r
689 \r
690             int CaretPostion = this.View.GetIndexFromLayoutLine(this.View.CaretPostion);\r
691             \r
692             SelectCollection Selections = this.View.Selections;\r
693             if (isSelected)\r
694                 this.Select(this.AnchorIndex, CaretPostion - this.AnchorIndex);\r
695             else\r
696             {\r
697                 this.View.Selections.Clear();\r
698                 this.AnchorIndex = CaretPostion;\r
699                 this.View.InsertPoint = null;\r
700             }\r
701         }\r
702 \r
703         /// <summary>\r
704         /// JumpCaretで移動した位置からキャレットを移動し、選択状態にする\r
705         /// </summary>\r
706         /// <param name="tp"></param>\r
707         public void MoveCaretAndSelect(TextPoint tp)\r
708         {\r
709             int CaretPostion = this.View.GetIndexFromLayoutLine(tp);\r
710             this.Select(this.AnchorIndex, CaretPostion - this.AnchorIndex);\r
711             this.View.JumpCaret(tp.row, tp.col);\r
712             this.View.AdjustCaretAndSrc();\r
713         }\r
714 \r
715         public void MoveSelectBefore(TextPoint tp)\r
716         {\r
717             int NewAnchorIndex;\r
718             int SelectionLength;\r
719             if (this.IsReverseSelect())\r
720             {\r
721                 NewAnchorIndex = this.View.GetIndexFromLayoutLine(tp);\r
722                 SelectionLength = this.SelectionLength + NewAnchorIndex - this.AnchorIndex;\r
723                 this.Select(this.SelectionStart, SelectionLength);\r
724             }\r
725             else\r
726             {\r
727                 NewAnchorIndex = this.View.GetIndexFromLayoutLine(tp);\r
728                 SelectionLength = this.SelectionLength + this.AnchorIndex - NewAnchorIndex;\r
729                 this.Select(NewAnchorIndex, SelectionLength);\r
730             }\r
731             this.AnchorIndex = NewAnchorIndex;\r
732         }\r
733 \r
734         /// <summary>\r
735         /// キャレット位置を既定の位置に戻す\r
736         /// </summary>\r
737         public void ResetCaretPostion()\r
738         {\r
739             this.JumpCaret(0);\r
740         }\r
741 \r
742         /// <summary>\r
743         /// 行単位で移動後のキャレット位置を取得する\r
744         /// </summary>\r
745         /// <param name="count">移動量</param>\r
746         /// <param name="current">現在のキャレット位置</param>\r
747         /// <returns>移動後のキャレット位置</returns>\r
748         public TextPoint GetTextPointAfterMoveLine(int count, TextPoint current)\r
749         {\r
750             int row = current.row + count;\r
751 \r
752             if (row < 0)\r
753                 row = 0;\r
754             else if (row >= this.View.LayoutLines.Count)\r
755                 row = this.View.LayoutLines.Count - 1;\r
756 \r
757             row = this.View.AdjustRow(row, count > 0);\r
758 \r
759             double colpos = this.View.GetColPostionFromIndex(current.row, current.col);\r
760             int col = this.View.GetIndexFromColPostion(row, colpos);\r
761 \r
762             return new TextPoint(row, col);\r
763         }\r
764 \r
765         /// <summary>\r
766         /// 選択文字列のインデントを一つ増やす\r
767         /// </summary>\r
768         public void UpIndent()\r
769         {\r
770             if (this.RectSelection || this.SelectionLength == 0)\r
771                 return;\r
772             int selectionStart = this.SelectionStart;\r
773             string text = this.InsertLineHead(GetTextFromLineSelectArea(this.View.Selections), "\t");\r
774             this.RepleaceSelectionArea(this.View.Selections,text);\r
775             this.Select(selectionStart, text.Length);\r
776         }\r
777 \r
778         /// <summary>\r
779         /// 選択文字列のインデントを一つ減らす\r
780         /// </summary>\r
781         public void DownIndent()\r
782         {\r
783             if (this.RectSelection || this.SelectionLength == 0)\r
784                 return;\r
785             int selectionStart = this.SelectionStart;\r
786             string text = this.RemoveLineHead(GetTextFromLineSelectArea(this.View.Selections), "\t");\r
787             this.RepleaceSelectionArea(this.View.Selections, text);\r
788             this.Select(selectionStart, text.Length);\r
789         }\r
790 \r
791         string InsertLineHead(string s, string str)\r
792         {\r
793             string[] lines = s.Split(new string[] { Document.NewLine.ToString() }, StringSplitOptions.RemoveEmptyEntries);\r
794             StringBuilder output = new StringBuilder();\r
795             for (int i = 0; i < lines.Length; i++)\r
796                 output.Append(str + lines[i] + Document.NewLine);\r
797             return output.ToString();\r
798         }\r
799 \r
800         public string RemoveLineHead(string s, string str)\r
801         {\r
802             string[] lines = s.Split(new string[] { Document.NewLine.ToString() }, StringSplitOptions.RemoveEmptyEntries);\r
803             StringBuilder output = new StringBuilder();\r
804             for (int i = 0; i < lines.Length; i++)\r
805                 if (lines[i].StartsWith(str))\r
806                     output.Append(lines[i].Substring(1) + Document.NewLine);\r
807                 else\r
808                     output.Append(lines[i] + Document.NewLine);\r
809             return output.ToString();\r
810         }\r
811 \r
812         /// <summary>\r
813         /// キャレットを一文字移動させる\r
814         /// </summary>\r
815         /// <param name="isMoveNext">真なら1文字すすめ、そうでなければ戻す</param>\r
816         /// <remarks>このメソッドを呼び出した後でScrollToCaretメソッドとSelectWithMoveCaretメソッドを呼び出す必要があります</remarks>\r
817         void MoveCaretHorizontical(bool isMoveNext)\r
818         {\r
819             if (this.Document.FireUpdateEvent == false)\r
820                 throw new InvalidOperationException("");\r
821             int delta = isMoveNext ? 0 : -1;\r
822             int prevcol = this.View.CaretPostion.col;\r
823             int col = this.View.CaretPostion.col + delta;\r
824             string lineString = this.View.LayoutLines[this.View.CaretPostion.row];\r
825             if (col < 0 || this.View.CaretPostion.row >= this.View.LayoutLines.Count)\r
826             {\r
827                 if (this.View.CaretPostion.row == 0)\r
828                 {\r
829                     col = 0;\r
830                     return;\r
831                 }\r
832                 this.MoveCaretVertical(false);\r
833                 this.View.AdjustCaretAndSrc(AdjustFlow.Row);  //この段階で調整しないとスクロールされない\r
834                 col = this.View.LayoutLines.GetLengthFromLineNumber(this.View.CaretPostion.row) - 1;  //最終行以外はすべて改行コードが付くはず\r
835             }\r
836             else if (col >= lineString.Length || lineString[col] == Document.NewLine)\r
837             {\r
838                 if (this.View.CaretPostion.row < this.View.LayoutLines.Count - 1)\r
839                 {\r
840                     this.MoveCaretVertical(true);\r
841                     this.View.AdjustCaretAndSrc(AdjustFlow.Row);  //この段階で調整しないとスクロールされない\r
842                     col = 0;\r
843                 }\r
844             }\r
845             else\r
846             {\r
847                 AlignDirection direction = isMoveNext ? AlignDirection.Forward : AlignDirection.Back;\r
848                 col = this.View.LayoutLines.GetLayout(this.View.CaretPostion.row).AlignIndexToNearestCluster(col, direction);\r
849             }\r
850 \r
851             this.View.JumpCaret(this.View.CaretPostion.row, col,false);\r
852         }\r
853 \r
854         /// <summary>\r
855         /// キャレットを行方向に移動させる\r
856         /// </summary>\r
857         /// <param name="isMoveNext">プラス方向に移動するなら真</param>\r
858         /// <remarks>このメソッドを呼び出した後でScrollToCaretメソッドとSelectWithMoveCaretメソッドを呼び出す必要があります</remarks>\r
859         void MoveCaretVertical(bool isMoveNext)\r
860         {\r
861             if (this.Document.FireUpdateEvent == false)\r
862                 throw new InvalidOperationException("");\r
863 \r
864             TextPoint nextPoint = this.GetTextPointAfterMoveLine(isMoveNext ? 1 : -1, this.View.CaretPostion);\r
865             \r
866             this.View.JumpCaret(nextPoint.row, nextPoint.col,false);\r
867         }\r
868 \r
869         private void ReplaceBeforeSelectionArea(SelectCollection Selections, int removeLength, string insertStr)\r
870         {\r
871             if (removeLength == 0 && insertStr.Length == 0)\r
872                 return;\r
873 \r
874             if (this.RectSelection == false || this.Document.FireUpdateEvent == false)\r
875                 throw new InvalidOperationException();\r
876 \r
877             SelectCollection temp = this.View.InsertPoint;\r
878             int selectStart = temp.First().start;\r
879             int selectEnd = temp.Last().start + temp.Last().length;\r
880 \r
881             //ドキュメント操作後に行うとうまくいかないので、あらかじめ取得しておく\r
882             TextPoint start = this.View.LayoutLines.GetTextPointFromIndex(selectStart);\r
883             TextPoint end = this.View.LayoutLines.GetTextPointFromIndex(selectEnd);\r
884 \r
885             bool reverse = temp.First().start > temp.Last().start;\r
886 \r
887             int lineHeadIndex = this.View.LayoutLines.GetIndexFromLineNumber(this.View.LayoutLines.GetLineNumberFromIndex(selectStart));\r
888             if (selectStart - removeLength < lineHeadIndex)\r
889                 return;\r
890 \r
891             this.Document.UndoManager.BeginUndoGroup();\r
892             this.Document.FireUpdateEvent = false;\r
893 \r
894             if (reverse)\r
895             {\r
896                 for (int i = 0; i < temp.Count; i++)\r
897                 {\r
898                     this.ReplaceBeforeSelection(temp[i], removeLength, insertStr);\r
899                 }\r
900             }\r
901             else\r
902             {\r
903                 for (int i = temp.Count - 1; i >= 0; i--)\r
904                 {\r
905                     this.ReplaceBeforeSelection(temp[i], removeLength, insertStr);\r
906                 }\r
907             }\r
908 \r
909             this.Document.FireUpdateEvent = true;\r
910             this.Document.UndoManager.EndUndoGroup();\r
911 \r
912             int delta = insertStr.Length - removeLength;\r
913             start.col += delta;\r
914             end.col += delta;\r
915 \r
916             if (reverse)\r
917                 this.JumpCaret(start.row, start.col);\r
918             else\r
919                 this.JumpCaret(end.row, end.col);\r
920             \r
921             this.Select(start, 0, end.row - start.row);\r
922         }\r
923 \r
924         private void ReplaceBeforeSelection(Selection sel, int removeLength, string insertStr)\r
925         {\r
926             sel = Util.NormalizeIMaker<Selection>(sel);\r
927             this.Document.Replace(sel.start - removeLength, removeLength, insertStr);\r
928         }\r
929 \r
930         private void RepleaceSelectionArea(SelectCollection Selections, string value,bool updateInsertPoint = false)\r
931         {\r
932             if (value == null)\r
933                 return;\r
934 \r
935             if (this.RectSelection == false)\r
936             {\r
937                 Selection sel = Selection.Create(this.AnchorIndex, 0);\r
938                 if (Selections.Count > 0)\r
939                     sel = Util.NormalizeIMaker<Selection>(this.View.Selections.First());\r
940 \r
941                 this.Document.Replace(sel.start, sel.length, value);\r
942                 return;\r
943             }\r
944 \r
945             if (this.Document.FireUpdateEvent == false)\r
946                 throw new InvalidOperationException("");\r
947 \r
948             int StartIndex = this.SelectionStart;\r
949 \r
950             SelectCollection newInsertPoint = new SelectCollection();\r
951 \r
952             if (this.SelectionLength == 0)\r
953             {\r
954                 int i;\r
955 \r
956                 this.Document.UndoManager.BeginUndoGroup();\r
957 \r
958                 this.Document.FireUpdateEvent = false;\r
959 \r
960                 string[] line = value.Split(new string[] { Document.NewLine.ToString() }, StringSplitOptions.RemoveEmptyEntries);\r
961 \r
962                 TextPoint Current = this.View.GetLayoutLineFromIndex(this.SelectionStart);\r
963 \r
964                 for (i = 0; i < line.Length && Current.row < this.View.LayoutLines.Count; i++, Current.row++)\r
965                 {\r
966                     if (Current.col > this.View.LayoutLines[Current.row].Length)\r
967                         Current.col = this.View.LayoutLines[Current.row].Length;\r
968                     StartIndex = this.View.GetIndexFromLayoutLine(Current);\r
969                     this.Document.Replace(StartIndex, 0, line[i]);\r
970                     StartIndex += line[i].Length;\r
971                 }\r
972 \r
973                 for (; i < line.Length; i++)\r
974                 {\r
975                     StartIndex = this.Document.Length;\r
976                     string str = Document.NewLine + line[i];\r
977                     this.Document.Replace(StartIndex, 0, str);\r
978                     StartIndex += str.Length;\r
979                 }\r
980 \r
981                 this.Document.FireUpdateEvent = true;\r
982 \r
983                 this.Document.UndoManager.EndUndoGroup();\r
984             }\r
985             else\r
986             {\r
987                 SelectCollection temp = new SelectCollection(this.View.Selections); //コピーしないとReplaceCommandを呼び出した段階で書き換えられてしまう\r
988 \r
989                 this.Document.UndoManager.BeginUndoGroup();\r
990 \r
991                 this.Document.FireUpdateEvent = false;\r
992 \r
993                 if (temp.First().start < temp.Last().start)\r
994                 {\r
995                     for (int i = temp.Count - 1; i >= 0; i--)\r
996                     {\r
997                         Selection sel = Util.NormalizeIMaker<Selection>(temp[i]);\r
998 \r
999                         StartIndex = sel.start;\r
1000 \r
1001                         this.Document.Replace(sel.start, sel.length, value);\r
1002 \r
1003                         newInsertPoint.Add(Selection.Create(sel.start + (value.Length - sel.length) * i,0));\r
1004                     }\r
1005                 }\r
1006                 else\r
1007                 {\r
1008                     for (int i = 0; i < temp.Count; i++)\r
1009                     {\r
1010                         Selection sel = Util.NormalizeIMaker<Selection>(temp[i]);\r
1011 \r
1012                         StartIndex = sel.start;\r
1013 \r
1014                         this.Document.Replace(sel.start, sel.length, value);\r
1015 \r
1016                         newInsertPoint.Add(Selection.Create(sel.start + (value.Length - sel.length) * i, 0));\r
1017                     }\r
1018                 }\r
1019 \r
1020                 this.Document.FireUpdateEvent = true;\r
1021 \r
1022                 this.Document.UndoManager.EndUndoGroup();\r
1023             }\r
1024             this.JumpCaret(StartIndex);\r
1025             if (updateInsertPoint && newInsertPoint.Count > 0)\r
1026                 this.View.InsertPoint = newInsertPoint;\r
1027         }\r
1028 \r
1029         private string GetTextFromLineSelectArea(SelectCollection Selections)\r
1030         {\r
1031             Selection sel = Util.NormalizeIMaker<Selection>(Selections.First());\r
1032 \r
1033             string str = this.Document.ToString(sel.start, sel.length);\r
1034 \r
1035             return str;\r
1036         }\r
1037 \r
1038         string GetTextFromRectangleSelectArea(SelectCollection Selections)\r
1039         {\r
1040             StringBuilder temp = new StringBuilder();\r
1041             if (Selections.First().start < Selections.Last().start)\r
1042             {\r
1043                 for (int i = 0; i < this.View.Selections.Count; i++)\r
1044                 {\r
1045                     Selection sel = Util.NormalizeIMaker<Selection>(Selections[i]);\r
1046 \r
1047                     string str = this.Document.ToString(sel.start, sel.length);\r
1048                     if (str.IndexOf(Environment.NewLine) == -1)\r
1049                         temp.AppendLine(str);\r
1050                     else\r
1051                         temp.Append(str);\r
1052                 }\r
1053             }\r
1054             else\r
1055             {\r
1056                 for (int i = this.View.Selections.Count - 1; i >= 0; i--)\r
1057                 {\r
1058                     Selection sel = Util.NormalizeIMaker<Selection>(Selections[i]);\r
1059 \r
1060                     string str = this.Document.ToString(sel.start, sel.length).Replace(Document.NewLine.ToString(), Environment.NewLine);\r
1061                     if (str.IndexOf(Environment.NewLine) == -1)\r
1062                         temp.AppendLine(str);\r
1063                     else\r
1064                         temp.Append(str);\r
1065                 }\r
1066             }\r
1067             return temp.ToString();\r
1068         }\r
1069 \r
1070         void View_LineBreakChanged(object sender, EventArgs e)\r
1071         {\r
1072             this.DeSelectAll();\r
1073             this.AdjustCaret();\r
1074         }\r
1075 \r
1076         void View_PageBoundChanged(object sender, EventArgs e)\r
1077         {\r
1078             if (this.View.LineBreak == LineBreakMethod.PageBound && this.View.PageBound.Width - this.View.LineBreakingMarginWidth > 0)\r
1079                 this.View.PerfomLayouts();\r
1080             this.AdjustCaret();\r
1081         }\r
1082 \r
1083         void render_ChangedRenderResource(object sender, ChangedRenderRsourceEventArgs e)\r
1084         {\r
1085             if (e.type == ResourceType.Font)\r
1086             {\r
1087                 if (this.View.LineBreak == LineBreakMethod.PageBound)\r
1088                     this.View.PerfomLayouts();\r
1089                 this.AdjustCaret();\r
1090             }\r
1091             if (e.type == ResourceType.InlineChar)\r
1092             {\r
1093                 int oldLineCountOnScreen = this.View.LineCountOnScreen;\r
1094                 this.View.CalculateLineCountOnScreen();\r
1095                 if(this.View.LineCountOnScreen != oldLineCountOnScreen)\r
1096                     this.AdjustCaret();\r
1097             }\r
1098         }\r
1099 \r
1100         void render_ChangedRightToLeft(object sender, EventArgs e)\r
1101         {\r
1102             this.AdjustCaret();\r
1103         }\r
1104 \r
1105         void Document_Update(object sender, DocumentUpdateEventArgs e)\r
1106         {\r
1107             switch (e.type)\r
1108             {\r
1109                 case UpdateType.Replace:\r
1110                     if(e.startIndex < this.Document.Length && this.Document[e.startIndex] == Document.NewLine)\r
1111                         this.View.CalculateLineCountOnScreen();\r
1112                     this.JumpCaret(e.startIndex + e.insertLength,true);\r
1113                     break;\r
1114                 case UpdateType.Clear:\r
1115                     this.View.TryScroll(0, 0);\r
1116                     break;\r
1117             }\r
1118         }\r
1119     }\r
1120 }\r