OSDN Git Service

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