OSDN Git Service

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