OSDN Git Service

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