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