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