OSDN Git Service

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