OSDN Git Service

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