/* * Copyright (C) 2013 FooProject * * 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 * the Free Software Foundation; either version 3 of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ using System; using System.Globalization; using System.Text; using System.Linq; using System.Diagnostics; using System.Text.RegularExpressions; #if WINFORM using System.Drawing; #endif namespace FooEditEngine { internal enum MoveFlow { Horizontical, Vertical, } internal enum ScrollDirection { Up, Down, Left, Right, } /// /// インデントの方法を表す /// public enum IndentMode { Tab, Space, } /// /// ユーザー側からの処理を担当するクラス。一部を除き、こちらで行われた操作はアンドゥの対象になります /// internal sealed class Controller { EditView View; Document Document; int AnchorIndex; public Controller(Document doc, EditView view) { this.Document = doc; this.Document.Update += new DocumentUpdateEventHandler(Document_Update); this.View = view; this.View.render.ChangedRightToLeft += render_ChangedRightToLeft; this.View.render.ChangedRenderResource += render_ChangedRenderResource; this.View.PerformLayouted += View_LineBreakChanged; this.View.PageBoundChanged += View_PageBoundChanged; this.SelectionChanged += new EventHandler((s, e) => { }); this.Document.Clear(); } /// /// 選択領域変更時に通知される /// public event EventHandler SelectionChanged; /// /// 矩形選択モードなら真を返し、そうでない場合は偽を返す /// public bool RectSelection { get; set; } /// /// インデントの方法を表す /// public IndentMode IndentMode { get; set; } /// /// 選択範囲の開始位置 /// /// SelectionLengthが0の場合、キャレット位置を表します public int SelectionStart { get { if (this.View.Selections.Count == 0) return this.AnchorIndex; else return this.View.Selections.First().start; } } /// /// 選択範囲の長さ /// /// 矩形選択モードの場合、選択範囲の文字数ではなく、開始位置から終了位置までの長さとなります public int SelectionLength { get { if (this.View.Selections.Count == 0) return 0; Selection last = this.View.Selections.Last(); return last.start + last.length - this.SelectionStart; } } /// /// 選択範囲内の文字列を返す /// /// /// 未選択状態で代入したときは追加され、そうでない場合は選択範囲の文字列と置き換えられます。 /// public string SelectedText { get { if (this.View.LayoutLines.Count == 0 || this.View.Selections.Count == 0) return null; if (this.RectSelection) return GetTextFromRectangleSelectArea(this.View.Selections); else return GetTextFromLineSelectArea(this.View.Selections).Replace(Document.NewLine.ToString(), Environment.NewLine); } set { if (this.Document.FireUpdateEvent == false) throw new InvalidOperationException(""); if (value == null) return; this.RepleaceSelectionArea(this.View.Selections, value.Replace(Environment.NewLine,Document.NewLine.ToString())); } } /// /// 選択範囲が逆転しているかどうかを判定する /// /// 逆転しているなら真を返す public bool IsReverseSelect() { int index = this.View.LayoutLines.GetIndexFromTextPoint(this.View.CaretPostion); return index < this.AnchorIndex; } /// /// 指定された範囲を選択する /// /// /// /// RectSelectionの値によって動作が変わります。真の場合は矩形選択モードに、そうでない場合は行ごとに選択されます public void Select(int start, int length) { if (this.Document.FireUpdateEvent == false) throw new InvalidOperationException(""); if (start < 0 || start + length < 0 || start + length > this.Document.Length) throw new ArgumentOutOfRangeException("startかendが指定できる範囲を超えてます"); this.View.Selections.Clear(); if (length < 0) { int oldStart = start; start += length; length = oldStart - start; } if (this.RectSelection && length != 0) { TextPoint startTextPoint = this.View.GetLayoutLineFromIndex(start); TextPoint endTextPoint = this.View.GetLayoutLineFromIndex(start + length); this.SelectByRectangle(new TextRectangle(startTextPoint, endTextPoint)); } else if(length != 0) { this.View.Selections.Add(Selection.Create(start, length)); } this.SelectionChanged(this, null); } public void Select(TextPoint tp, int width, int height) { if (this.Document.FireUpdateEvent == false || !this.RectSelection) throw new InvalidOperationException(""); TextPoint end = tp; end.row = tp.row + height; end.col = tp.col + width; if (end.row > this.View.LayoutLines.Count - 1) throw new ArgumentOutOfRangeException(""); this.View.Selections.Clear(); this.SelectByRectangle(new TextRectangle(tp,end)); this.SelectionChanged(this, null); } private void SelectByRectangle(TextRectangle rect) { if (this.Document.FireUpdateEvent == false) throw new InvalidOperationException(""); if (rect.TopLeft <= rect.BottomRight) { for (int i = rect.TopLeft.row; i <= rect.BottomLeft.row; i++) { int length = this.View.LayoutLines.GetLengthFromLineNumber(i); int leftCol = rect.TopLeft.col, rightCol = rect.TopRight.col, lastCol = length; if(length > 0 && this.View.LayoutLines[i][length - 1] == Document.NewLine) lastCol = length - 1; if (lastCol < 0) lastCol = 0; if (rect.TopLeft.col > lastCol) leftCol = lastCol; if (rect.TopRight.col > lastCol) rightCol = lastCol; int StartIndex = this.View.LayoutLines.GetIndexFromTextPoint(new TextPoint(i, leftCol)); int EndIndex = this.View.LayoutLines.GetIndexFromTextPoint(new TextPoint(i, rightCol)); Selection sel; sel = Selection.Create(StartIndex, EndIndex - StartIndex); this.View.Selections.Add(sel); } } } /// /// 単語単位で選択する /// /// 探索を開始するインデックス /// 選択の起点となるとインデックスを変更するなら真。そうでなければ偽 public void SelectWord(int index, bool changeAnchor = false) { if (this.Document.FireUpdateEvent == false) throw new InvalidOperationException(""); if (this.Document.Length <= 0 || index >= this.Document.Length) return; Document str = this.Document; int start = index; while (start > 0 && !Util.IsWordSeparator(str[start])) start--; if (Util.IsWordSeparator(str[start])) start++; int end = index; while (end < this.Document.Length && !Util.IsWordSeparator(str[end])) end++; this.Select(start, end - start); if(changeAnchor) this.AnchorIndex = start; } /// /// 選択範囲内のUTF32コードポイントを文字列に変換します /// /// 成功した場合は真。そうでない場合は偽を返す public bool ConvertToChar() { if (this.SelectionLength == 0 || this.RectSelection) return false; string str = this.Document.ToString(this.SelectionStart, this.SelectionLength); string[] codes = str.Split(new char[] { ' ' },StringSplitOptions.RemoveEmptyEntries); StringBuilder result = new StringBuilder(); foreach (string code in codes) { int utf32_code; if (code[0] != 'U') return false; if (Int32.TryParse(code.TrimStart('U'),NumberStyles.HexNumber,null, out utf32_code)) result.Append(Char.ConvertFromUtf32(utf32_code)); else return false; } this.Document.Lock(); this.Document.Replace(this.SelectionStart, this.SelectionLength, result.ToString()); this.Document.UnLock(); return true; } /// /// 選択文字列をUTF32のコードポイントに変換します /// public void ConvertToCodePoint() { if (this.SelectionLength == 0 || this.RectSelection) return; string str = this.Document.ToString(this.SelectionStart, this.SelectionLength); StringInfo info = new StringInfo(str); StringBuilder result = new StringBuilder(); for (int i = 0; i < str.Length;) { int utf32_code = Char.ConvertToUtf32(str, i); result.Append("U" + Convert.ToString(utf32_code,16)); result.Append(' '); if(Char.IsHighSurrogate(str[i])) i += 2; else i++; } this.Document.Lock(); this.Document.Replace(this.SelectionStart, this.SelectionLength, result.ToString()); this.Document.UnLock(); } /// /// 選択を解除する /// public void DeSelectAll() { if (this.Document.FireUpdateEvent == false) throw new InvalidOperationException(""); this.View.Selections.Clear(); } /// /// 任意のマーカーかどうか /// /// /// /// 真ならマーカーがある public bool IsMarker(TextPoint tp,HilightType type) { if (this.Document.FireUpdateEvent == false) throw new InvalidOperationException(""); int index = this.View.LayoutLines.GetIndexFromTextPoint(tp); return this.IsMarker(index, type); } /// /// 任意のマーカーかどうか判定する /// /// /// /// 真ならマーカーがある public bool IsMarker(int index, HilightType type) { foreach(int id in this.Document.Markers.IDs) { foreach (Marker m in this.Document.GetMarkers(index, id)) { if (m.hilight == type) return true; } } return false; } /// /// キャレット位置を再調整する /// public void AdjustCaret() { int row = this.View.CaretPostion.row; if (row > this.View.LayoutLines.Count - 1) row = this.View.LayoutLines.Count - 1; int col = this.View.CaretPostion.col; if (col > 0 && col > this.View.LayoutLines[row].Length) col = this.View.LayoutLines[row].Length; this.JumpCaret(row, col); } /// /// キャレットを指定した位置に移動させる /// /// /// 折り畳みを展開するなら真 public void JumpCaret(int index,bool autoExpand = true) { if (index < 0 || index > this.Document.Length) throw new ArgumentOutOfRangeException("indexが設定できる範囲を超えています"); TextPoint tp = this.View.GetLayoutLineFromIndex(index); this.JumpCaret(tp.row, tp.col,autoExpand); } /// /// キャレットを指定した位置に移動させる /// /// /// /// 折り畳みを展開するなら真 public void JumpCaret(int row, int col, bool autoExpand = true) { if (this.Document.FireUpdateEvent == false) throw new InvalidOperationException(""); this.View.JumpCaret(row, col,autoExpand); this.View.AdjustCaretAndSrc(); this.SelectWithMoveCaret(false); } /// /// 行の先頭に移動する /// /// 行 /// 選択状態にするかどうか public void JumpToLineHead(int row,bool isSelected) { this.View.JumpCaret(row, 0); this.View.AdjustCaretAndSrc(); this.SelectWithMoveCaret(isSelected); } /// /// 行の終わりに移動する /// /// 行 /// 選択状態にするかどうか public void JumpToLineEnd(int row, bool isSelected) { this.View.JumpCaret(row, this.View.LayoutLines[row].Length - 1); this.View.AdjustCaretAndSrc(); this.SelectWithMoveCaret(isSelected); } /// /// ドキュメントの先頭に移動する /// /// public void JumpToHead(bool isSelected) { if (this.View.TryScroll(0, 0)) return; this.View.JumpCaret(0, 0); this.View.AdjustCaretAndSrc(); this.SelectWithMoveCaret(isSelected); } /// /// ドキュメントの終わりにに移動する /// /// public void JumpToEnd(bool isSelected) { int srcRow = this.View.LayoutLines.Count - this.View.LineCountOnScreen - 1; if(srcRow < 0) srcRow = 0; if (this.View.TryScroll(0, srcRow)) return; this.View.JumpCaret(this.View.LayoutLines.Count - 1, 0); this.View.AdjustCaretAndSrc(); this.SelectWithMoveCaret(isSelected); } /// /// スクロールする /// /// 方向を指定する /// スクロールする量。ScrollDirectionの値がUpやDownなら行数。LeftやRightならピクセル単位の値となる /// 選択状態にするなら真 /// 同時にキャレットを移動させるなら真 public void Scroll(ScrollDirection dir, int delta, bool isSelected,bool withCaret) { if (this.Document.FireUpdateEvent == false) throw new InvalidOperationException(""); int toRow = this.View.Src.Row; double toX = this.View.Src.X; switch (dir) { case ScrollDirection.Up: toRow = Math.Max(0, this.View.Src.Row - delta); toRow = this.View.AdjustRow(toRow, false); break; case ScrollDirection.Down: toRow = Math.Min(this.View.Src.Row + delta, this.View.LayoutLines.Count - 1); toRow = this.View.AdjustRow(toRow, true); break; case ScrollDirection.Left: toX -= delta; break; case ScrollDirection.Right: toX += delta; break; default: throw new ArgumentOutOfRangeException(); } this.Scroll(toX, toRow, isSelected, withCaret); } /// /// スクロールする /// /// スクロール先の座標 /// スクロール先の行 /// 選択状態にするなら真 /// 同時にキャレットを移動させるなら真 public void Scroll(double toX, int toRow, bool isSelected, bool withCaret) { if (withCaret) { this.View.Scroll(toX, toRow); this.View.JumpCaret(toRow, 0); this.View.AdjustCaretAndSrc(); this.SelectWithMoveCaret(isSelected); } else { this.View.Scroll(toX, toRow); this.View.IsFocused = false; } } /// /// キャレットを桁方向に移動させる /// /// 移動できない場合は真を返す /// 負の値なら左側へ、そうでないなら右側へ移動する /// 選択範囲とするなら真。そうでないなら偽 /// 単語単位で移動するなら真。そうでないなら偽 public void MoveCaretHorizontical(int realLength, bool isSelected,bool alignWord = false) { for (int i = Math.Abs(realLength); i > 0; i--) { bool MoveFlow = realLength > 0; if (this.View.render.RightToLeft) MoveFlow = !MoveFlow; this.MoveCaretHorizontical(MoveFlow); if (alignWord) this.AlignNearestWord(MoveFlow); } this.View.AdjustCaretAndSrc(AdjustFlow.Col); this.SelectWithMoveCaret(isSelected); } void AlignNearestWord(bool MoveFlow) { string str = this.View.LayoutLines[this.View.CaretPostion.row]; while (this.View.CaretPostion.col > 0 && this.View.CaretPostion.col < str.Length && str[this.View.CaretPostion.col] != Document.NewLine) { if (!Util.IsWordSeparator(str[this.View.CaretPostion.col])) { this.MoveCaretHorizontical(MoveFlow); } else { if(MoveFlow) this.MoveCaretHorizontical(MoveFlow); break; } } } /// /// キャレットを行方向に移動させる /// /// 再描写する必要があるなら真を返す /// 移動量 /// public void MoveCaretVertical(int deltarow,bool isSelected) { for (int i = Math.Abs(deltarow); i > 0; i--) this.MoveCaretVertical(deltarow > 0); this.View.AdjustCaretAndSrc(AdjustFlow.Both); this.SelectWithMoveCaret(isSelected); } /// /// キャレット位置の文字を一文字削除する /// public void DoDeleteAction() { if (this.SelectionLength != 0) { this.SelectedText = ""; return; } if (this.Document.FireUpdateEvent == false) throw new InvalidOperationException(""); TextPoint CaretPostion = this.View.CaretPostion; int index = this.View.GetIndexFromLayoutLine(CaretPostion); if (index == this.Document.Length) return; int lineHeadIndex = this.View.LayoutLines.GetIndexFromLineNumber(CaretPostion.row); int next = this.View.LayoutLines.GetLayout(CaretPostion.row).AlignIndexToNearestCluster(CaretPostion.col, AlignDirection.Forward) + lineHeadIndex; if (this.Document[index] == Document.NewLine) next = index + 1; this.Document.Lock(); this.Document.Replace(index, next - index, ""); this.Document.UnLock(); } public bool IsRectInsertMode() { if (!this.RectSelection) return false; foreach(Selection sel in this.View.Selections) { if (sel.length != 0) return false; } return true; } /// /// キャレット位置の文字を一文字削除し、キャレット位置を後ろにずらす /// public void DoBackSpaceAction() { if (this.IsRectInsertMode()) { this.ReplaceBeforeSelectionArea(this.View.Selections, 1, ""); return; } else if (this.SelectionLength > 0) { this.SelectedText = ""; return; } if (this.Document.FireUpdateEvent == false) throw new InvalidOperationException(""); TextPoint CurrentPostion = this.View.CaretPostion; if (CurrentPostion.row == 0 && CurrentPostion.col == 0) return; int oldIndex = this.View.GetIndexFromLayoutLine(CurrentPostion); int newCol, newIndex; if (CurrentPostion.col > 0) { newCol = this.View.LayoutLines.GetLayout(CurrentPostion.row).AlignIndexToNearestCluster(CurrentPostion.col - 1, AlignDirection.Back); newIndex = this.View.GetIndexFromLayoutLine(new TextPoint(CurrentPostion.row, newCol)); } else { newIndex = this.View.GetIndexFromLayoutLine(CurrentPostion); newIndex--; } this.Document.Lock(); this.Document.Replace(newIndex, oldIndex - newIndex, ""); this.Document.UnLock(); } /// /// キャレット位置で行を分割する /// public void DoEnterAction() { this.DoInputChar('\n'); } /// /// キャレット位置に文字を入力し、その分だけキャレットを進める。isInsertModeの値により動作が変わります /// /// public void DoInputChar(char ch) { this.DoInputString(ch.ToString()); } string GetIndentSpace(int col_index) { int space_count = this.View.TabStops - (col_index % this.View.TabStops); return new string(Enumerable.Repeat(' ',space_count).ToArray()); } /// /// キャレット位置に文字列を挿入し、その分だけキャレットを進める。isInsertModeの値により動作が変わります /// /// /// public void DoInputString(string str,bool fromTip = false) { TextPoint CaretPos = this.View.CaretPostion; if (str == "\t" && this.IndentMode == IndentMode.Space) str = this.GetIndentSpace(CaretPos.col); if (this.IsRectInsertMode()) { this.ReplaceBeforeSelectionArea(this.View.Selections, 0, str); return; } else if (this.SelectionLength != 0) { this.RepleaceSelectionArea(this.View.Selections, str, fromTip); return; } if (this.Document.FireUpdateEvent == false) throw new InvalidOperationException(""); int index = this.View.GetIndexFromLayoutLine(this.View.CaretPostion); int length = 0; if (this.View.InsertMode == false && index < this.Document.Length && this.Document[index] != Document.NewLine) { string lineString = this.View.LayoutLines[CaretPos.row]; int end = this.View.LayoutLines.GetLayout(CaretPos.row).AlignIndexToNearestCluster(CaretPos.col + str.Length - 1, AlignDirection.Forward); if (end > lineString.Length - 1) end = lineString.Length - 1; end += this.View.LayoutLines.GetIndexFromLineNumber(CaretPos.row); length = end - index; } if (str == Document.NewLine.ToString()) { int lineHeadIndex = this.View.LayoutLines.GetIndexFromLineNumber(CaretPos.row); int lineLength = this.View.LayoutLines.GetLengthFromLineNumber(CaretPos.row); FoldingItem foldingData = this.View.LayoutLines.FoldingCollection.GetFarestHiddenFoldingData(lineHeadIndex, lineLength); if (foldingData != null && !foldingData.Expand && index > foldingData.Start && index <= foldingData.End) index = foldingData.End + 1; } this.Document.Lock(); this.Document.Replace(index, length, str); this.Document.UnLock(); } /// /// キャレットの移動に合わせて選択する /// /// 選択状態にするかどうか /// /// キャレットを移動後、このメソッドを呼び出さない場合、Select()メソッドは正常に機能しません /// void SelectWithMoveCaret(bool isSelected) { if (this.View.CaretPostion.col < 0 || this.View.CaretPostion.row < 0) return; if (this.Document.FireUpdateEvent == false) throw new InvalidOperationException(""); int CaretPostion = this.View.GetIndexFromLayoutLine(this.View.CaretPostion); SelectCollection Selections = this.View.Selections; if (isSelected) { this.Select(this.AnchorIndex, CaretPostion - this.AnchorIndex); }else{ this.AnchorIndex = CaretPostion; this.Select(CaretPostion, 0); } } /// /// JumpCaretで移動した位置からキャレットを移動し、選択状態にする /// /// public void MoveCaretAndSelect(TextPoint tp) { int CaretPostion = this.View.GetIndexFromLayoutLine(tp); this.Select(this.AnchorIndex, CaretPostion - this.AnchorIndex); this.View.JumpCaret(tp.row, tp.col); this.View.AdjustCaretAndSrc(); } /// /// グリッパーとキャレットを同時に移動する /// /// ポインターの座標 /// 動かす対象となるグリッパー /// 移動できた場合は真を返す。そうでなければ偽を返す /// グリッパー内にポインターが存在しない場合、グリッパーはポインターの座標近くの行に移動する public bool MoveCaretAndGripper(Point p, GripperView hittedGripper) { bool HittedCaret = false; TextPoint tp = this.View.GetTextPointFromPostion(p); if (tp == this.View.CaretPostion) { HittedCaret = true; } if (HittedCaret || hittedGripper != null) { if (hittedGripper != null) { tp = this.View.GetTextPointFromPostion(hittedGripper.AdjustPoint(p)); if (this.IsReverseSelect()) { if (Object.ReferenceEquals(hittedGripper, this.View.SelectGrippers.BottomRight)) this.MoveSelectBefore(tp); else this.MoveCaretAndSelect(tp); } else { if (Object.ReferenceEquals(hittedGripper, this.View.SelectGrippers.BottomLeft)) this.MoveSelectBefore(tp); else this.MoveCaretAndSelect(tp); } hittedGripper.Move(this.View, tp); } else { tp = this.View.GetTextPointFromPostion(p); if (tp != TextPoint.Null) this.MoveCaretAndSelect(tp); } this.View.SelectGrippers.BottomLeft.Enabled = this.SelectionLength != 0; return true; } return false; } public void MoveSelectBefore(TextPoint tp) { int NewAnchorIndex; int SelectionLength; if (this.IsReverseSelect()) { NewAnchorIndex = this.View.GetIndexFromLayoutLine(tp); SelectionLength = this.SelectionLength + NewAnchorIndex - this.AnchorIndex; this.Select(this.SelectionStart, SelectionLength); } else { NewAnchorIndex = this.View.GetIndexFromLayoutLine(tp); SelectionLength = this.SelectionLength + this.AnchorIndex - NewAnchorIndex; this.Select(NewAnchorIndex, SelectionLength); } this.AnchorIndex = NewAnchorIndex; } /// /// キャレット位置を既定の位置に戻す /// public void ResetCaretPostion() { this.JumpCaret(0); } /// /// 行単位で移動後のキャレット位置を取得する /// /// 移動量 /// 現在のキャレット位置 /// 移動後のキャレット位置 public TextPoint GetTextPointAfterMoveLine(int count, TextPoint current) { int row = current.row + count; if (row < 0) row = 0; else if (row >= this.View.LayoutLines.Count) row = this.View.LayoutLines.Count - 1; row = this.View.AdjustRow(row, count > 0); double colpos = this.View.GetColPostionFromIndex(current.row, current.col); int col = this.View.GetIndexFromColPostion(row, colpos); return new TextPoint(row, col); } /// /// 選択文字列のインデントを一つ増やす /// public void UpIndent() { if (this.RectSelection || this.SelectionLength == 0) return; int selectionStart = this.SelectionStart; string insertStr = this.IndentMode == IndentMode.Space ? this.GetIndentSpace(0) : "\t"; string text = this.InsertLineHead(GetTextFromLineSelectArea(this.View.Selections), insertStr); this.RepleaceSelectionArea(this.View.Selections,text); this.Select(selectionStart, text.Length); } /// /// 選択文字列のインデントを一つ減らす /// public void DownIndent() { if (this.RectSelection || this.SelectionLength == 0) return; int selectionStart = this.SelectionStart; string insertStr = this.IndentMode == IndentMode.Space ? this.GetIndentSpace(0) : "\t"; string text = this.RemoveLineHead(GetTextFromLineSelectArea(this.View.Selections), insertStr); this.RepleaceSelectionArea(this.View.Selections, text); this.Select(selectionStart, text.Length); } string InsertLineHead(string s, string str) { string[] lines = s.Split(new string[] { Document.NewLine.ToString() }, StringSplitOptions.None); StringBuilder output = new StringBuilder(); for (int i = 0; i < lines.Length; i++) { if(lines[i].Length > 0) output.Append(str + lines[i] + Document.NewLine); else if(i < lines.Length - 1) output.Append(lines[i] + Document.NewLine); } return output.ToString(); } public string RemoveLineHead(string s, string str) { string[] lines = s.Split(new string[] { Document.NewLine.ToString() }, StringSplitOptions.None); StringBuilder output = new StringBuilder(); for (int i = 0; i < lines.Length; i++) { if (lines[i].StartsWith(str)) output.Append(lines[i].Substring(1) + Document.NewLine); else if (i < lines.Length - 1) output.Append(lines[i] + Document.NewLine); } return output.ToString(); } /// /// キャレットを一文字移動させる /// /// 真なら1文字すすめ、そうでなければ戻す /// このメソッドを呼び出した後でScrollToCaretメソッドとSelectWithMoveCaretメソッドを呼び出す必要があります void MoveCaretHorizontical(bool isMoveNext) { if (this.Document.FireUpdateEvent == false) throw new InvalidOperationException(""); int delta = isMoveNext ? 0 : -1; int prevcol = this.View.CaretPostion.col; int col = this.View.CaretPostion.col + delta; string lineString = this.View.LayoutLines[this.View.CaretPostion.row]; if (col < 0 || this.View.CaretPostion.row >= this.View.LayoutLines.Count) { if (this.View.CaretPostion.row == 0) { col = 0; return; } this.MoveCaretVertical(false); this.View.AdjustCaretAndSrc(AdjustFlow.Row); //この段階で調整しないとスクロールされない col = this.View.LayoutLines.GetLengthFromLineNumber(this.View.CaretPostion.row) - 1; //最終行以外はすべて改行コードが付くはず } else if (col >= lineString.Length || lineString[col] == Document.NewLine) { if (this.View.CaretPostion.row < this.View.LayoutLines.Count - 1) { this.MoveCaretVertical(true); this.View.AdjustCaretAndSrc(AdjustFlow.Row); //この段階で調整しないとスクロールされない col = 0; } } else { AlignDirection direction = isMoveNext ? AlignDirection.Forward : AlignDirection.Back; col = this.View.LayoutLines.GetLayout(this.View.CaretPostion.row).AlignIndexToNearestCluster(col, direction); } this.View.JumpCaret(this.View.CaretPostion.row, col,false); } /// /// キャレットを行方向に移動させる /// /// プラス方向に移動するなら真 /// このメソッドを呼び出した後でScrollToCaretメソッドとSelectWithMoveCaretメソッドを呼び出す必要があります void MoveCaretVertical(bool isMoveNext) { if (this.Document.FireUpdateEvent == false) throw new InvalidOperationException(""); TextPoint nextPoint = this.GetTextPointAfterMoveLine(isMoveNext ? 1 : -1, this.View.CaretPostion); this.View.JumpCaret(nextPoint.row, nextPoint.col,false); } private void ReplaceBeforeSelectionArea(SelectCollection Selections, int removeLength, string insertStr) { if (removeLength == 0 && insertStr.Length == 0) return; if (this.RectSelection == false || this.Document.FireUpdateEvent == false) throw new InvalidOperationException(); SelectCollection temp = this.View.Selections; int selectStart = temp.First().start; int selectEnd = temp.Last().start + temp.Last().length; //ドキュメント操作後に行うとうまくいかないので、あらかじめ取得しておく TextPoint start = this.View.LayoutLines.GetTextPointFromIndex(selectStart); TextPoint end = this.View.LayoutLines.GetTextPointFromIndex(selectEnd); bool reverse = temp.First().start > temp.Last().start; int lineHeadIndex = this.View.LayoutLines.GetIndexFromLineNumber(this.View.LayoutLines.GetLineNumberFromIndex(selectStart)); if (selectStart - removeLength < lineHeadIndex) return; this.Document.UndoManager.BeginUndoGroup(); this.Document.FireUpdateEvent = false; if (reverse) { for (int i = 0; i < temp.Count; i++) { this.ReplaceBeforeSelection(temp[i], removeLength, insertStr); } } else { for (int i = temp.Count - 1; i >= 0; i--) { this.ReplaceBeforeSelection(temp[i], removeLength, insertStr); } } this.Document.FireUpdateEvent = true; this.Document.UndoManager.EndUndoGroup(); int delta = insertStr.Length - removeLength; start.col += delta; end.col += delta; if (reverse) this.JumpCaret(start.row, start.col); else this.JumpCaret(end.row, end.col); this.Select(start, 0, end.row - start.row); } private void ReplaceBeforeSelection(Selection sel, int removeLength, string insertStr) { sel = Util.NormalizeIMaker(sel); this.Document.Lock(); this.Document.Replace(sel.start - removeLength, removeLength, insertStr); this.Document.UnLock(); } private void RepleaceSelectionArea(SelectCollection Selections, string value,bool updateInsertPoint = false) { if (value == null) return; if (this.RectSelection == false) { Selection sel = Selection.Create(this.AnchorIndex, 0); if (Selections.Count > 0) sel = Util.NormalizeIMaker(this.View.Selections.First()); this.Document.Lock(); this.Document.Replace(sel.start, sel.length, value); this.Document.UnLock(); return; } if (this.Document.FireUpdateEvent == false) throw new InvalidOperationException(""); int StartIndex = this.SelectionStart; SelectCollection newInsertPoint = new SelectCollection(); if (this.SelectionLength == 0) { int i; this.Document.Lock(); this.Document.UndoManager.BeginUndoGroup(); this.Document.FireUpdateEvent = false; string[] line = value.Split(new string[] { Document.NewLine.ToString() }, StringSplitOptions.RemoveEmptyEntries); TextPoint Current = this.View.GetLayoutLineFromIndex(this.SelectionStart); for (i = 0; i < line.Length && Current.row < this.View.LayoutLines.Count; i++, Current.row++) { if (Current.col > this.View.LayoutLines[Current.row].Length) Current.col = this.View.LayoutLines[Current.row].Length; StartIndex = this.View.GetIndexFromLayoutLine(Current); this.Document.Replace(StartIndex, 0, line[i]); StartIndex += line[i].Length; } for (; i < line.Length; i++) { StartIndex = this.Document.Length; string str = Document.NewLine + line[i]; this.Document.Replace(StartIndex, 0, str); StartIndex += str.Length; } this.Document.FireUpdateEvent = true; this.Document.UndoManager.EndUndoGroup(); this.Document.UnLock(); } else { SelectCollection temp = new SelectCollection(this.View.Selections); //コピーしないとReplaceCommandを呼び出した段階で書き換えられてしまう this.Document.Lock(); this.Document.UndoManager.BeginUndoGroup(); this.Document.FireUpdateEvent = false; if (temp.First().start < temp.Last().start) { for (int i = temp.Count - 1; i >= 0; i--) { Selection sel = Util.NormalizeIMaker(temp[i]); StartIndex = sel.start; this.Document.Replace(sel.start, sel.length, value); newInsertPoint.Add(Selection.Create(sel.start + (value.Length - sel.length) * i,0)); } } else { for (int i = 0; i < temp.Count; i++) { Selection sel = Util.NormalizeIMaker(temp[i]); StartIndex = sel.start; this.Document.Replace(sel.start, sel.length, value); newInsertPoint.Add(Selection.Create(sel.start + (value.Length - sel.length) * i, 0)); } } this.Document.FireUpdateEvent = true; this.Document.UndoManager.EndUndoGroup(); this.Document.UnLock(); } this.JumpCaret(StartIndex); if (updateInsertPoint && newInsertPoint.Count > 0) this.View.Selections = newInsertPoint; } private string GetTextFromLineSelectArea(SelectCollection Selections) { Selection sel = Util.NormalizeIMaker(Selections.First()); string str = this.Document.ToString(sel.start, sel.length); return str; } string GetTextFromRectangleSelectArea(SelectCollection Selections) { StringBuilder temp = new StringBuilder(); if (Selections.First().start < Selections.Last().start) { for (int i = 0; i < this.View.Selections.Count; i++) { Selection sel = Util.NormalizeIMaker(Selections[i]); string str = this.Document.ToString(sel.start, sel.length); if (str.IndexOf(Environment.NewLine) == -1) temp.AppendLine(str); else temp.Append(str); } } else { for (int i = this.View.Selections.Count - 1; i >= 0; i--) { Selection sel = Util.NormalizeIMaker(Selections[i]); string str = this.Document.ToString(sel.start, sel.length).Replace(Document.NewLine.ToString(), Environment.NewLine); if (str.IndexOf(Environment.NewLine) == -1) temp.AppendLine(str); else temp.Append(str); } } return temp.ToString(); } void View_LineBreakChanged(object sender, EventArgs e) { this.DeSelectAll(); this.AdjustCaret(); } void View_PageBoundChanged(object sender, EventArgs e) { if (this.View.LineBreak == LineBreakMethod.PageBound && this.View.PageBound.Width - this.View.LineBreakingMarginWidth > 0) this.View.PerfomLayouts(); this.AdjustCaret(); } void render_ChangedRenderResource(object sender, ChangedRenderRsourceEventArgs e) { if (e.type == ResourceType.Font) { if (this.View.LineBreak == LineBreakMethod.PageBound) this.View.PerfomLayouts(); this.AdjustCaret(); } if (e.type == ResourceType.InlineChar) { int oldLineCountOnScreen = this.View.LineCountOnScreen; this.View.CalculateLineCountOnScreen(); if(this.View.LineCountOnScreen != oldLineCountOnScreen) this.AdjustCaret(); } } void render_ChangedRightToLeft(object sender, EventArgs e) { this.AdjustCaret(); } void Document_Update(object sender, DocumentUpdateEventArgs e) { switch (e.type) { case UpdateType.Replace: if(e.startIndex < this.Document.Length && this.Document[e.startIndex] == Document.NewLine) this.View.CalculateLineCountOnScreen(); this.JumpCaret(e.startIndex + e.insertLength,true); break; case UpdateType.Clear: this.JumpCaret(0,0, false); break; } } } }