OSDN Git Service

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