2 * Copyright (C) 2013 FooProject
3 * * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
6 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
7 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
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/>.
12 using System.Globalization;
15 using System.Diagnostics;
16 using System.Text.RegularExpressions;
21 namespace FooEditEngine
45 internal enum ScrollDirection
56 public enum IndentMode
69 /// ユーザー側からの処理を担当するクラス。一部を除き、こちらで行われた操作はアンドゥの対象になります
71 internal sealed class Controller
76 public Controller(Document doc, EditView view)
80 this.View.render.ChangedRenderResource += render_ChangedRenderResource;
81 this.View.PageBoundChanged += View_PageBoundChanged;
82 //this.Document.Clear();
85 public Document Document
89 return this._Document;
93 //メモリリークを防ぐためにパンドラーを除く
94 if (this._Document != null)
96 this._Document.Update -= Document_Update;
97 this._Document.StatusUpdate -= Document_StatusChanged;
98 this._Document.SelectionChanged -= Document_SelectionChanged;
99 this._Document.PerformLayouted -= View_LineBreakChanged;
100 this._Document.CaretChanged -= _Document_CaretChanged;
103 this._Document = value;
105 this._Document.Update += new DocumentUpdateEventHandler(Document_Update);
106 this._Document.StatusUpdate += Document_StatusChanged;
107 this._Document.SelectionChanged += Document_SelectionChanged;
108 this._Document.PerformLayouted += View_LineBreakChanged;
109 this._Document.CaretChanged += _Document_CaretChanged;
113 private void _Document_CaretChanged(object sender, EventArgs e)
115 TextPoint pos = this.Document.CaretPostion;
116 this.JumpCaret(pos.row, pos.col);
119 private void Document_SelectionChanged(object sender, EventArgs e)
121 if (this.IsReverseSelect())
123 if (this.Document.SelectGrippers.BottomRight.Enabled)
124 this.Document.SelectGrippers.BottomRight.MoveByIndex(this.View, this.SelectionStart);
125 if (this.Document.SelectGrippers.BottomLeft.Enabled)
126 this.Document.SelectGrippers.BottomLeft.MoveByIndex(this.View, this.SelectionStart + this.SelectionLength);
130 if (this.Document.SelectGrippers.BottomLeft.Enabled)
131 this.Document.SelectGrippers.BottomLeft.MoveByIndex(this.View, this.SelectionStart);
132 if (this.Document.SelectGrippers.BottomRight.Enabled)
133 this.Document.SelectGrippers.BottomRight.MoveByIndex(this.View, this.SelectionStart + this.SelectionLength);
137 void Document_StatusChanged(object sender,EventArgs e)
143 /// 矩形選択モードなら真を返し、そうでない場合は偽を返す
145 public bool RectSelection
147 get { return this.Document.RectSelection; }
148 set { this.Document.RectSelection = value; }
154 public IndentMode IndentMode
156 get { return this.Document.IndentMode; }
157 set { this.Document.IndentMode = value; }
163 /// <remarks>SelectionLengthが0の場合、キャレット位置を表します</remarks>
164 public int SelectionStart
168 if (this.View.Selections.Count == 0)
169 return this.Document.AnchorIndex;
171 return this.View.Selections.First().start;
178 /// <remarks>矩形選択モードの場合、選択範囲の文字数ではなく、開始位置から終了位置までの長さとなります</remarks>
179 public int SelectionLength
183 if (this.View.Selections.Count == 0)
185 Selection last = this.View.Selections.Last();
186 return last.start + last.length - this.SelectionStart;
194 /// 未選択状態で代入したときは追加され、そうでない場合は選択範囲の文字列と置き換えられます。
196 public string SelectedText
200 if (this.View.LayoutLines.Count == 0 || this.View.Selections.Count == 0)
202 if (this.RectSelection)
203 return GetTextFromRectangleSelectArea(this.View.Selections);
205 return GetTextFromLineSelectArea(this.View.Selections).Replace(Document.NewLine.ToString(), Environment.NewLine);
209 if (this.Document.FireUpdateEvent == false)
210 throw new InvalidOperationException("");
213 this.RepleaceSelectionArea(this.View.Selections, value.Replace(Environment.NewLine,Document.NewLine.ToString()));
218 /// 選択範囲が逆転しているかどうかを判定する
220 /// <returns>逆転しているなら真を返す</returns>
221 public bool IsReverseSelect()
223 int index = this.View.LayoutLines.GetIndexFromTextPoint(this.Document.CaretPostion);
224 return index < this.Document.AnchorIndex;
228 /// 選択範囲内のUTF32コードポイントを文字列に変換します
230 /// <returns>成功した場合は真。そうでない場合は偽を返す</returns>
231 public bool ConvertToChar()
233 if (this.SelectionLength == 0 || this.RectSelection)
235 string str = this.Document.ToString(this.SelectionStart, this.SelectionLength);
236 string[] codes = str.Split(new char[] { ' ' },StringSplitOptions.RemoveEmptyEntries);
237 StringBuilder result = new StringBuilder();
238 foreach (string code in codes)
243 if (Int32.TryParse(code.TrimStart('U'),NumberStyles.HexNumber,null, out utf32_code))
244 result.Append(Char.ConvertFromUtf32(utf32_code));
248 this.Document.Replace(this.SelectionStart, this.SelectionLength, result.ToString());
253 /// 選択文字列をUTF32のコードポイントに変換します
255 public void ConvertToCodePoint()
257 if (this.SelectionLength == 0 || this.RectSelection)
259 string str = this.Document.ToString(this.SelectionStart, this.SelectionLength);
260 StringInfo info = new StringInfo(str);
261 StringBuilder result = new StringBuilder();
262 for (int i = 0; i < str.Length;)
264 int utf32_code = Char.ConvertToUtf32(str, i);
265 result.Append("U" + Convert.ToString(utf32_code,16));
267 if(Char.IsHighSurrogate(str[i]))
272 this.Document.Replace(this.SelectionStart, this.SelectionLength, result.ToString());
278 public void DeSelectAll()
280 if (this.Document.FireUpdateEvent == false)
281 throw new InvalidOperationException("");
283 this.View.Selections.Clear();
289 /// <param name="tp"></param>
290 /// <param name="type"></param>
291 /// <returns>真ならマーカーがある</returns>
292 public bool IsMarker(TextPoint tp,HilightType type)
294 if (this.Document.FireUpdateEvent == false)
295 throw new InvalidOperationException("");
296 int index = this.View.LayoutLines.GetIndexFromTextPoint(tp);
297 return this.IsMarker(index, type);
303 /// <param name="index"></param>
304 /// <param name="type"></param>
305 /// <returns>真ならマーカーがある</returns>
306 public bool IsMarker(int index, HilightType type)
308 foreach(int id in this.Document.Markers.IDs)
310 foreach (Marker m in this.Document.GetMarkers(id, index))
312 if (m.hilight == type)
322 public void AdjustCaret()
324 int row = this.Document.CaretPostion.row;
325 if (row > this.View.LayoutLines.Count - 1)
326 row = this.View.LayoutLines.Count - 1;
327 int col = this.Document.CaretPostion.col;
328 if (col > 0 && col > this.View.LayoutLines[row].Length)
329 col = this.View.LayoutLines[row].Length;
332 int sel_start = this.SelectionStart;
333 int sel_length = this.SelectionLength;
335 this.JumpCaret(row, col);
337 this.Document.Select(sel_start, sel_length);
341 /// キャレットを指定した位置に移動させる
343 /// <param name="index"></param>
344 /// <param name="autoExpand">折り畳みを展開するなら真</param>
345 public void JumpCaret(int index,bool autoExpand = true)
347 if (index < 0 || index > this.Document.Length)
348 throw new ArgumentOutOfRangeException("indexが設定できる範囲を超えています");
349 TextPoint tp = this.View.GetLayoutLineFromIndex(index);
351 this.JumpCaret(tp.row, tp.col,autoExpand);
355 /// キャレットを指定した位置に移動させる
357 /// <param name="row"></param>
358 /// <param name="col"></param>
359 /// <param name="autoExpand">折り畳みを展開するなら真</param>
360 public void JumpCaret(int row, int col, bool autoExpand = true)
362 if (this.Document.FireUpdateEvent == false)
363 throw new InvalidOperationException("");
365 this.View.JumpCaret(row, col,autoExpand);
367 this.View.AdjustCaretAndSrc();
369 this.SelectWithMoveCaret(false);
375 /// <param name="row">行</param>
376 /// <param name="isSelected">選択状態にするかどうか</param>
377 public void JumpToLineHead(int row,bool isSelected)
379 this.View.JumpCaret(row, 0);
380 this.View.AdjustCaretAndSrc();
381 this.SelectWithMoveCaret(isSelected);
387 /// <param name="row">行</param>
388 /// <param name="isSelected">選択状態にするかどうか</param>
389 public void JumpToLineEnd(int row, bool isSelected)
391 this.View.JumpCaret(row, this.View.LayoutLines[row].Length - 1);
392 this.View.AdjustCaretAndSrc();
393 this.SelectWithMoveCaret(isSelected);
399 /// <param name="isSelected"></param>
400 public void JumpToHead(bool isSelected)
402 if (this.View.TryScroll(0, 0))
404 this.View.JumpCaret(0, 0);
405 this.View.AdjustCaretAndSrc();
406 this.SelectWithMoveCaret(isSelected);
412 /// <param name="isSelected"></param>
413 public void JumpToEnd(bool isSelected)
415 int srcRow = this.View.LayoutLines.Count - this.View.LineCountOnScreen - 1;
418 if (this.View.TryScroll(0, srcRow))
420 this.View.JumpCaret(this.View.LayoutLines.Count - 1, 0);
421 this.View.AdjustCaretAndSrc();
422 this.SelectWithMoveCaret(isSelected);
428 /// <param name="dir">方向を指定する</param>
429 /// <param name="delta">ピクセル単位の値でスクロール量を指定する</param>
430 /// <param name="isSelected">選択状態にするなら真</param>
431 /// <param name="withCaret">同時にキャレットを移動させるなら真</param>
432 public void ScrollByPixel(ScrollDirection dir,double delta, bool isSelected, bool withCaret)
434 if (this.Document.FireUpdateEvent == false)
435 throw new InvalidOperationException("");
437 if (dir == ScrollDirection.Left || dir == ScrollDirection.Right)
439 this.View.TryScroll(delta, 0);
443 if(dir == ScrollDirection.Up)
445 this.View.TryScroll(0, -delta);
447 else if (dir == ScrollDirection.Down)
449 this.View.TryScroll(0, delta);
454 //カーソルを適切な位置に移動させる必要がある
455 TextPoint tp = this.View.GetTextPointFromPostion(this.View.CaretLocation);
456 this.View.JumpCaret(tp.row, tp.col);
457 this.View.AdjustCaretAndSrc();
458 this.SelectWithMoveCaret(isSelected);
461 this.Document.SelectGrippers.BottomLeft.MoveByIndex(this.View, this.SelectionStart);
462 this.Document.SelectGrippers.BottomRight.MoveByIndex(this.View, this.SelectionStart + this.SelectionLength);
468 /// <param name="dir">方向を指定する</param>
469 /// <param name="delta">スクロールする量。ScrollDirectionの値がUpやDownなら行数。LeftやRightならピクセル単位の値となる</param>
470 /// <param name="isSelected">選択状態にするなら真</param>
471 /// <param name="withCaret">同時にキャレットを移動させるなら真</param>
472 public void Scroll(ScrollDirection dir, int delta, bool isSelected,bool withCaret)
474 if (this.Document.FireUpdateEvent == false)
475 throw new InvalidOperationException("");
476 int toRow = this.View.Src.Row;
477 double toX = this.View.Src.X;
480 case ScrollDirection.Up:
481 toRow = Math.Max(0, this.View.Src.Row - delta);
482 toRow = this.View.AdjustRow(toRow, false);
484 case ScrollDirection.Down:
485 toRow = Math.Min(this.View.Src.Row + delta, this.View.LayoutLines.Count - 1);
486 toRow = this.View.AdjustRow(toRow, true);
488 case ScrollDirection.Left:
491 case ScrollDirection.Right:
495 throw new ArgumentOutOfRangeException();
497 this.Scroll(toX, toRow, isSelected, withCaret);
503 /// <param name="toX">スクロール先の座標</param>
504 /// <param name="toRow">スクロール先の行</param>
505 /// <param name="isSelected">選択状態にするなら真</param>
506 /// <param name="withCaret">同時にキャレットを移動させるなら真</param>
507 public void Scroll(double toX, int toRow, bool isSelected, bool withCaret)
511 this.View.Scroll(toX, toRow);
512 this.View.JumpCaret(toRow, 0);
513 this.View.AdjustCaretAndSrc();
514 this.SelectWithMoveCaret(isSelected);
518 this.View.Scroll(toX, toRow);
521 this.Document.SelectGrippers.BottomLeft.MoveByIndex(this.View, this.SelectionStart);
522 this.Document.SelectGrippers.BottomRight.MoveByIndex(this.View, this.SelectionStart + this.SelectionLength);
528 /// <returns>移動できない場合は真を返す</returns>
529 /// <param name="realLength">負の値なら左側へ、そうでないなら右側へ移動する</param>
530 /// <param name="isSelected">選択範囲とするなら真。そうでないなら偽</param>
531 /// <param name="alignWord">単語単位で移動するなら真。そうでないなら偽</param>
532 public void MoveCaretHorizontical(int realLength, bool isSelected,bool alignWord = false)
534 TextPoint caret = this.Document.CaretPostion;
536 caret = GetNextCaret(caret, realLength, alignWord ? MoveFlow.Word : MoveFlow.Character,out moved);
537 this.View.JumpCaret(caret.row, caret.col, false);
538 this.View.AdjustCaretAndSrc(AdjustFlow.Both);
539 this.SelectWithMoveCaret(isSelected);
545 /// <param name="caret">起点となるキャレット位置</param>
546 /// <param name="count">移動量</param>
547 /// <param name="method">移動方法</param>
548 /// <param name="moved">実際に移動した量</param>
549 /// <returns>移動後のキャレット位置</returns>
550 public TextPoint GetNextCaret(TextPoint caret, int count,MoveFlow method,out int moved)
553 if(method == MoveFlow.Character || method == MoveFlow.Word)
555 for (int i = Math.Abs(count); i > 0; i--)
557 bool moveFlow = count > 0;
558 if (this.Document.RightToLeft)
559 moveFlow = !moveFlow;
560 caret = this.MoveCaretHorizontical(caret, moveFlow);
562 if (method == FooEditEngine.MoveFlow.Word)
563 caret = this.AlignNearestWord(caret, moveFlow);
567 if(method == MoveFlow.Line || method == MoveFlow.Paragraph)
569 for (int i = Math.Abs(count); i > 0; i--)
571 caret = this.MoveCaretVertical(caret, count > 0, method == MoveFlow.Paragraph);
580 TextPoint AlignNearestWord(TextPoint caret,bool MoveFlow)
582 string str = this.View.LayoutLines[caret.row];
583 while (caret.col > 0 &&
584 caret.col < str.Length &&
585 str[caret.col] != Document.NewLine)
587 if (!Util.IsWordSeparator(str[caret.col]))
589 caret = this.MoveCaretHorizontical(caret, MoveFlow);
594 caret = this.MoveCaretHorizontical(caret, MoveFlow);
604 /// <returns>再描写する必要があるなら真を返す</returns>
605 /// <param name="deltarow">移動量</param>
606 /// <param name="isSelected"></param>
607 public void MoveCaretVertical(int deltarow,bool isSelected)
609 TextPoint caret = this.Document.CaretPostion;
611 caret = this.GetNextCaret(caret, deltarow, MoveFlow.Line,out moved);
612 this.View.JumpCaret(caret.row, caret.col, true);
613 this.View.AdjustCaretAndSrc(AdjustFlow.Both);
614 this.SelectWithMoveCaret(isSelected);
618 /// キャレット位置の文字を一文字削除する
620 public void DoDeleteAction()
622 if (this.SelectionLength != 0)
624 this.SelectedText = "";
628 if (this.Document.FireUpdateEvent == false)
629 throw new InvalidOperationException("");
631 TextPoint CaretPostion = this.Document.CaretPostion;
632 int index = this.View.GetIndexFromLayoutLine(CaretPostion);
634 if (index == this.Document.Length)
637 int lineHeadIndex = this.View.LayoutLines.GetIndexFromLineNumber(CaretPostion.row);
638 int next = this.View.LayoutLines.GetLayout(CaretPostion.row).AlignIndexToNearestCluster(CaretPostion.col, AlignDirection.Forward) + lineHeadIndex;
640 if (this.Document[index] == Document.NewLine)
643 this.Document.Replace(index, next - index, "", true);
646 public bool IsRectInsertMode()
648 if (!this.RectSelection || this.View.Selections.Count == 0)
650 foreach(Selection sel in this.View.Selections)
659 /// キャレット位置の文字を一文字削除し、キャレット位置を後ろにずらす
661 public void DoBackSpaceAction()
663 if (this.IsRectInsertMode())
665 this.ReplaceBeforeSelectionArea(this.View.Selections, 1, "");
668 else if (this.SelectionLength > 0)
670 this.SelectedText = "";
674 if (this.Document.FireUpdateEvent == false)
675 throw new InvalidOperationException("");
677 TextPoint CurrentPostion = this.Document.CaretPostion;
679 if (CurrentPostion.row == 0 && CurrentPostion.col == 0)
682 int oldIndex = this.View.GetIndexFromLayoutLine(CurrentPostion);
684 int newCol, newIndex;
685 if (CurrentPostion.col > 0)
687 newCol = this.View.LayoutLines.GetLayout(CurrentPostion.row).AlignIndexToNearestCluster(CurrentPostion.col - 1, AlignDirection.Back);
688 newIndex = this.View.GetIndexFromLayoutLine(new TextPoint(CurrentPostion.row, newCol));
692 newIndex = this.View.GetIndexFromLayoutLine(CurrentPostion);
696 this.Document.Replace(newIndex, oldIndex - newIndex, "", true);
702 public void DoEnterAction()
704 this.DoInputChar('\n');
708 /// キャレット位置に文字を入力し、その分だけキャレットを進める。isInsertModeの値により動作が変わります
710 /// <param name="ch"></param>
711 public void DoInputChar(char ch)
713 this.DoInputString(ch.ToString());
716 string GetIndentSpace(int col_index)
718 int space_count = this.Document.TabStops - (col_index % this.Document.TabStops);
719 return new string(Enumerable.Repeat(' ',space_count).ToArray());
723 /// キャレット位置に文字列を挿入し、その分だけキャレットを進める。isInsertModeの値により動作が変わります
725 /// <param name="str">挿入したい文字列</param>
726 /// <param name="fromTip">真の場合、矩形選択の幅にかかわらず矩形編集モードとして動作します。そうでない場合は選択領域を文字列で置き換えます</param>
727 public void DoInputString(string str,bool fromTip = false)
729 TextPoint CaretPos = this.Document.CaretPostion;
731 if (str == "\t" && this.IndentMode == IndentMode.Space)
732 str = this.GetIndentSpace(CaretPos.col);
734 if (this.IsRectInsertMode())
736 this.ReplaceBeforeSelectionArea(this.View.Selections, 0, str);
739 else if (this.SelectionLength != 0)
741 this.RepleaceSelectionArea(this.View.Selections, str, fromTip);
745 if (this.Document.FireUpdateEvent == false)
746 throw new InvalidOperationException("");
748 int index = this.View.GetIndexFromLayoutLine(this.Document.CaretPostion);
750 if (this.View.InsertMode == false && index < this.Document.Length && this.Document[index] != Document.NewLine)
752 string lineString = this.View.LayoutLines[CaretPos.row];
753 int end = this.View.LayoutLines.GetLayout(CaretPos.row).AlignIndexToNearestCluster(CaretPos.col + str.Length - 1, AlignDirection.Forward);
754 if (end > lineString.Length - 1)
755 end = lineString.Length - 1;
756 end += this.View.LayoutLines.GetIndexFromLineNumber(CaretPos.row);
757 length = end - index;
759 if (str == Document.NewLine.ToString())
761 int lineHeadIndex = this.View.LayoutLines.GetIndexFromLineNumber(CaretPos.row);
762 int lineLength = this.View.LayoutLines.GetLengthFromLineNumber(CaretPos.row);
763 FoldingItem foldingData = this.View.LayoutLines.FoldingCollection.GetFarestHiddenFoldingData(lineHeadIndex, lineLength);
764 if (foldingData != null && !foldingData.Expand && index > foldingData.Start && index <= foldingData.End)
765 index = foldingData.End + 1;
767 this.Document.Replace(index, length, str, true);
771 /// キャレットの移動に合わせて選択する
773 /// <param name="isSelected">選択状態にするかどうか</param>
775 /// キャレットを移動後、このメソッドを呼び出さない場合、Select()メソッドは正常に機能しません
777 void SelectWithMoveCaret(bool isSelected)
779 if (this.Document.CaretPostion.col < 0 || this.Document.CaretPostion.row < 0)
782 if (this.Document.FireUpdateEvent == false)
783 throw new InvalidOperationException("");
785 int CaretPostion = this.View.GetIndexFromLayoutLine(this.Document.CaretPostion);
787 SelectCollection Selections = this.View.Selections;
790 this.Document.Select(this.Document.AnchorIndex, CaretPostion - this.Document.AnchorIndex);
792 this.Document.AnchorIndex = CaretPostion;
793 this.Document.Select(CaretPostion, 0);
798 /// JumpCaretで移動した位置からキャレットを移動し、選択状態にする
800 /// <param name="tp"></param>
801 /// <param name="alignWord">単語単位で選択するかどうか</param>
802 public void MoveCaretAndSelect(TextPoint tp,bool alignWord = false)
804 TextPoint endSelectPostion = tp;
805 int CaretPostion = this.View.GetIndexFromLayoutLine(tp);
808 if (this.IsReverseSelect())
809 while (CaretPostion >= 0 && CaretPostion < this.Document.Length && !Util.IsWordSeparator(this.Document[CaretPostion])) CaretPostion--;
811 while (CaretPostion < this.Document.Length && !Util.IsWordSeparator(this.Document[CaretPostion])) CaretPostion++;
812 if (CaretPostion < 0)
814 endSelectPostion = this.View.LayoutLines.GetTextPointFromIndex(CaretPostion);
816 this.Document.Select(this.Document.AnchorIndex, CaretPostion - this.Document.AnchorIndex);
817 this.View.JumpCaret(endSelectPostion.row, endSelectPostion.col);
818 this.View.AdjustCaretAndSrc();
822 /// グリッパーとキャレットを同時に移動する
824 /// <param name="p">ポインターの座標</param>
825 /// <param name="hittedGripper">動かす対象となるグリッパー</param>
826 /// <returns>移動できた場合は真を返す。そうでなければ偽を返す</returns>
827 /// <remarks>グリッパー内にポインターが存在しない場合、グリッパーはポインターの座標近くの行に移動する</remarks>
828 public bool MoveCaretAndGripper(Point p, Gripper hittedGripper)
830 bool HittedCaret = false;
831 TextPoint tp = this.View.GetTextPointFromPostion(p);
832 if (tp == this.Document.CaretPostion)
837 if (HittedCaret || hittedGripper != null)
839 TextPointSearchRange searchRange;
840 if (this.View.HitTextArea(p.X, p.Y))
841 searchRange = TextPointSearchRange.TextAreaOnly;
842 else if (this.SelectionLength > 0)
843 searchRange = TextPointSearchRange.Full;
847 if (hittedGripper != null)
849 tp = this.View.GetTextPointFromPostion(hittedGripper.AdjustPoint(p), searchRange);
850 if (tp == TextPoint.Null)
852 if (Object.ReferenceEquals(hittedGripper, this.Document.SelectGrippers.BottomRight))
853 this.MoveCaretAndSelect(tp);
854 else if(Object.ReferenceEquals(hittedGripper, this.Document.SelectGrippers.BottomLeft))
855 this.MoveSelectBefore(tp);
859 tp = this.View.GetTextPointFromPostion(p, searchRange);
860 if (tp != TextPoint.Null)
862 this.MoveCaretAndSelect(tp);
869 this.Document.SelectGrippers.BottomLeft.Enabled = this.SelectionLength != 0;
875 public void MoveSelectBefore(TextPoint tp)
879 if (this.IsReverseSelect())
881 NewAnchorIndex = this.View.GetIndexFromLayoutLine(tp);
882 SelectionLength = this.SelectionLength + NewAnchorIndex - this.Document.AnchorIndex;
883 this.Document.Select(this.SelectionStart, SelectionLength);
887 NewAnchorIndex = this.View.GetIndexFromLayoutLine(tp);
888 SelectionLength = this.SelectionLength + this.Document.AnchorIndex - NewAnchorIndex;
889 this.Document.Select(NewAnchorIndex, SelectionLength);
891 this.Document.AnchorIndex = NewAnchorIndex;
895 /// 行単位で移動後のキャレット位置を取得する
897 /// <param name="count">移動量</param>
898 /// <param name="current">現在のキャレット位置</param>
899 /// <param name="move_pargraph">パラグラフ単位で移動するなら真</param>
900 /// <returns>移動後のキャレット位置</returns>
901 public TextPoint GetTextPointAfterMoveLine(int count, TextPoint current, bool move_pargraph = false)
903 if(this.Document.LineBreak == LineBreakMethod.None || move_pargraph == true)
905 int row = current.row + count;
909 else if (row >= this.View.LayoutLines.Count)
910 row = this.View.LayoutLines.Count - 1;
912 row = this.View.AdjustRow(row, count > 0);
914 Point pos = this.View.LayoutLines.GetLayout(current.row).GetPostionFromIndex(current.col);
915 int col = this.View.LayoutLines.GetLayout(row).GetIndexFromPostion(pos.X, pos.Y);
916 return new TextPoint(row, col);
920 Point pos = this.View.GetPostionFromTextPoint(current);
921 pos.Y += this.View.render.emSize.Height * count;
923 pos.Y += this.View.render.emSize.Height / 2;
924 var new_tp = this.View.GetTextPointFromPostion(pos,TextPointSearchRange.Full);
934 public void ResetCaretPostion()
940 /// 選択文字列のインデントを一つ増やす
942 public void UpIndent()
944 if (this.RectSelection || this.SelectionLength == 0)
946 int selectionStart = this.SelectionStart;
947 string insertStr = this.IndentMode == IndentMode.Space ? this.GetIndentSpace(0) : "\t";
948 string text = this.InsertLineHead(GetTextFromLineSelectArea(this.View.Selections), insertStr);
949 this.RepleaceSelectionArea(this.View.Selections,text);
950 this.Document.Select(selectionStart, text.Length);
954 /// 選択文字列のインデントを一つ減らす
956 public void DownIndent()
958 if (this.RectSelection || this.SelectionLength == 0)
960 int selectionStart = this.SelectionStart;
961 string insertStr = this.IndentMode == IndentMode.Space ? this.GetIndentSpace(0) : "\t";
962 string text = this.RemoveLineHead(GetTextFromLineSelectArea(this.View.Selections), insertStr, insertStr.Length);
963 this.RepleaceSelectionArea(this.View.Selections, text);
964 this.Document.Select(selectionStart, text.Length);
967 string InsertLineHead(string s, string str)
969 string[] lines = s.Split(new string[] { Document.NewLine.ToString() }, StringSplitOptions.None);
970 StringBuilder output = new StringBuilder();
971 for (int i = 0; i < lines.Length; i++)
973 if(lines[i].Length > 0)
974 output.Append(str + lines[i] + Document.NewLine);
975 else if(i < lines.Length - 1)
976 output.Append(lines[i] + Document.NewLine);
978 return output.ToString();
981 public string RemoveLineHead(string s, string str,int remove_count)
983 string[] lines = s.Split(new string[] { Document.NewLine.ToString() }, StringSplitOptions.None);
984 StringBuilder output = new StringBuilder();
985 for (int i = 0; i < lines.Length; i++)
987 if (lines[i].StartsWith(str))
988 output.Append(lines[i].Substring(remove_count) + Document.NewLine);
989 else if (i < lines.Length - 1)
990 output.Append(lines[i] + Document.NewLine);
992 return output.ToString();
998 /// <param name="caret">キャレット</param>
999 /// <param name="isMoveNext">真なら1文字すすめ、そうでなければ戻す</param>
1000 /// <remarks>このメソッドを呼び出した後でScrollToCaretメソッドとSelectWithMoveCaretメソッドを呼び出す必要があります</remarks>
1001 TextPoint MoveCaretHorizontical(TextPoint caret,bool isMoveNext)
1003 if (this.Document.FireUpdateEvent == false)
1004 throw new InvalidOperationException("");
1005 int delta = isMoveNext ? 0 : -1;
1006 int prevcol = caret.col;
1007 int col = caret.col + delta;
1008 string lineString = this.View.LayoutLines[caret.row];
1009 if (col < 0 || caret.row >= this.View.LayoutLines.Count)
1016 caret = this.MoveCaretVertical(caret,false);
1017 caret.col = this.View.LayoutLines.GetLengthFromLineNumber(caret.row) - 1; //最終行以外はすべて改行コードが付くはず
1019 else if (col >= lineString.Length || lineString[col] == Document.NewLine)
1021 if (caret.row < this.View.LayoutLines.Count - 1)
1023 caret = this.MoveCaretVertical(caret, true);
1029 AlignDirection direction = isMoveNext ? AlignDirection.Forward : AlignDirection.Back;
1030 caret.col = this.View.LayoutLines.GetLayout(caret.row).AlignIndexToNearestCluster(col, direction);
1038 /// <param name="caret">計算の起点となるテキストポイント</param>
1039 /// <param name="isMoveNext">プラス方向に移動するなら真</param>
1040 /// <param name="move_pargraph">パラグラフ単位で移動するするなら真</param>
1041 /// <remarks>このメソッドを呼び出した後でScrollToCaretメソッドとSelectWithMoveCaretメソッドを呼び出す必要があります</remarks>
1042 TextPoint MoveCaretVertical(TextPoint caret,bool isMoveNext, bool move_pargraph = false)
1044 if (this.Document.FireUpdateEvent == false)
1045 throw new InvalidOperationException("");
1047 return this.GetTextPointAfterMoveLine(isMoveNext ? 1 : -1, this.Document.CaretPostion, move_pargraph);
1050 private void ReplaceBeforeSelectionArea(SelectCollection Selections, int removeLength, string insertStr)
1052 if (removeLength == 0 && insertStr.Length == 0)
1055 if (this.RectSelection == false || this.Document.FireUpdateEvent == false)
1056 throw new InvalidOperationException();
1058 SelectCollection temp = this.View.Selections;
1059 int selectStart = temp.First().start;
1060 int selectEnd = temp.Last().start + temp.Last().length;
1062 //ドキュメント操作後に行うとうまくいかないので、あらかじめ取得しておく
1063 TextPoint start = this.View.LayoutLines.GetTextPointFromIndex(selectStart);
1064 TextPoint end = this.View.LayoutLines.GetTextPointFromIndex(selectEnd);
1066 bool reverse = temp.First().start > temp.Last().start;
1068 int lineHeadIndex = this.View.LayoutLines.GetIndexFromLineNumber(this.View.LayoutLines.GetLineNumberFromIndex(selectStart));
1069 if (selectStart - removeLength < lineHeadIndex)
1072 this.Document.UndoManager.BeginUndoGroup();
1073 this.Document.FireUpdateEvent = false;
1077 for (int i = 0; i < temp.Count; i++)
1079 this.ReplaceBeforeSelection(temp[i], removeLength, insertStr);
1084 for (int i = temp.Count - 1; i >= 0; i--)
1086 this.ReplaceBeforeSelection(temp[i], removeLength, insertStr);
1090 this.Document.FireUpdateEvent = true;
1091 this.Document.UndoManager.EndUndoGroup();
1093 int delta = insertStr.Length - removeLength;
1098 this.JumpCaret(start.row, start.col);
1100 this.JumpCaret(end.row, end.col);
1102 this.Document.Select(start, 0, end.row - start.row);
1105 private void ReplaceBeforeSelection(Selection sel, int removeLength, string insertStr)
1107 sel = Util.NormalizeIMaker<Selection>(sel);
1108 this.Document.Replace(sel.start - removeLength, removeLength, insertStr);
1111 private void RepleaceSelectionArea(SelectCollection Selections, string value,bool updateInsertPoint = false)
1116 if (this.RectSelection == false)
1118 Selection sel = Selection.Create(this.Document.AnchorIndex, 0);
1119 if (Selections.Count > 0)
1120 sel = Util.NormalizeIMaker<Selection>(this.View.Selections.First());
1122 this.Document.Replace(sel.start, sel.length, value);
1126 if (this.Document.FireUpdateEvent == false)
1127 throw new InvalidOperationException("");
1129 int StartIndex = this.SelectionStart;
1131 SelectCollection newInsertPoint = new SelectCollection();
1133 if (this.SelectionLength == 0)
1137 this.Document.UndoManager.BeginUndoGroup();
1139 this.Document.FireUpdateEvent = false;
1141 string[] line = value.Split(new string[] { Document.NewLine.ToString() }, StringSplitOptions.RemoveEmptyEntries);
1143 TextPoint Current = this.View.GetLayoutLineFromIndex(this.SelectionStart);
1145 for (i = 0; i < line.Length && Current.row < this.View.LayoutLines.Count; i++, Current.row++)
1147 if (Current.col > this.View.LayoutLines[Current.row].Length)
1148 Current.col = this.View.LayoutLines[Current.row].Length;
1149 StartIndex = this.View.GetIndexFromLayoutLine(Current);
1150 this.Document.Replace(StartIndex, 0, line[i]);
1151 StartIndex += line[i].Length;
1154 for (; i < line.Length; i++)
1156 StartIndex = this.Document.Length;
1157 string str = Document.NewLine + line[i];
1158 this.Document.Replace(StartIndex, 0, str);
1159 StartIndex += str.Length;
1162 this.Document.FireUpdateEvent = true;
1164 this.Document.UndoManager.EndUndoGroup();
1168 SelectCollection temp = new SelectCollection(this.View.Selections); //コピーしないとReplaceCommandを呼び出した段階で書き換えられてしまう
1170 this.Document.UndoManager.BeginUndoGroup();
1172 this.Document.FireUpdateEvent = false;
1174 if (temp.First().start < temp.Last().start)
1176 for (int i = temp.Count - 1; i >= 0; i--)
1178 Selection sel = Util.NormalizeIMaker<Selection>(temp[i]);
1180 StartIndex = sel.start;
1182 this.Document.Replace(sel.start, sel.length, value);
1184 newInsertPoint.Add(Selection.Create(sel.start + (value.Length - sel.length) * i,0));
1189 for (int i = 0; i < temp.Count; i++)
1191 Selection sel = Util.NormalizeIMaker<Selection>(temp[i]);
1193 StartIndex = sel.start;
1195 this.Document.Replace(sel.start, sel.length, value);
1197 newInsertPoint.Add(Selection.Create(sel.start + (value.Length - sel.length) * i, 0));
1201 this.Document.FireUpdateEvent = true;
1203 this.Document.UndoManager.EndUndoGroup();
1205 this.JumpCaret(StartIndex);
1206 if (updateInsertPoint && newInsertPoint.Count > 0)
1207 this.View.Selections = newInsertPoint;
1210 private string GetTextFromLineSelectArea(SelectCollection Selections)
1212 Selection sel = Util.NormalizeIMaker<Selection>(Selections.First());
1214 string str = this.Document.ToString(sel.start, sel.length);
1219 string GetTextFromRectangleSelectArea(SelectCollection Selections)
1221 StringBuilder temp = new StringBuilder();
1222 if (Selections.First().start < Selections.Last().start)
1224 for (int i = 0; i < this.View.Selections.Count; i++)
1226 Selection sel = Util.NormalizeIMaker<Selection>(Selections[i]);
1228 string str = this.Document.ToString(sel.start, sel.length);
1229 if (str.IndexOf(Environment.NewLine) == -1)
1230 temp.AppendLine(str);
1237 for (int i = this.View.Selections.Count - 1; i >= 0; i--)
1239 Selection sel = Util.NormalizeIMaker<Selection>(Selections[i]);
1241 string str = this.Document.ToString(sel.start, sel.length).Replace(Document.NewLine.ToString(), Environment.NewLine);
1242 if (str.IndexOf(Environment.NewLine) == -1)
1243 temp.AppendLine(str);
1248 return temp.ToString();
1251 void View_LineBreakChanged(object sender, EventArgs e)
1257 void View_PageBoundChanged(object sender, EventArgs e)
1259 if (this.Document.LineBreak == LineBreakMethod.PageBound && this.View.PageBound.Width - this.View.LineBreakingMarginWidth > 0)
1260 this.Document.PerformLayout();
1264 void render_ChangedRenderResource(object sender, ChangedRenderRsourceEventArgs e)
1266 if (e.type == ResourceType.Font)
1268 if (this.Document.LineBreak == LineBreakMethod.PageBound)
1269 this.Document.PerformLayout();
1272 if (e.type == ResourceType.InlineChar)
1274 int oldLineCountOnScreen = this.View.LineCountOnScreen;
1275 this.View.CalculateLineCountOnScreen();
1276 if(this.View.LineCountOnScreen != oldLineCountOnScreen)
1281 void Document_Update(object sender, DocumentUpdateEventArgs e)
1285 case UpdateType.Replace:
1286 this.JumpCaret(e.startIndex + e.insertLength,true);
1288 case UpdateType.Clear:
1289 this.JumpCaret(0,0, false);