OSDN Git Service

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