OSDN Git Service

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