/* * 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 { /// /// 移動量の単位を表す /// public enum MoveFlow { /// /// 文字 /// Character, /// /// 単語単位 /// Word, /// /// 行単位 /// Line, /// /// パラグラフ単位 /// Paragraph } internal enum ScrollDirection { Up, Down, Left, Right, } /// /// インデントの方法を表す /// public enum IndentMode { /// /// タブ /// Tab, /// /// スペース /// Space, } /// /// ユーザー側からの処理を担当するクラス。一部を除き、こちらで行われた操作はアンドゥの対象になります /// internal sealed class Controller { EditView View; Document _Document; public Controller(Document doc, EditView view) { this.Document = doc; this.View = view; this.View.render.ChangedRenderResource += render_ChangedRenderResource; this.View.PageBoundChanged += View_PageBoundChanged; //this.Document.Clear(); } public Document Document { get { return this._Document; } set { //メモリリークを防ぐためにパンドラーを除く if (this._Document != null) { this._Document.Update -= Document_Update; this._Document.StatusUpdate -= Document_StatusChanged; this._Document.SelectionChanged -= Document_SelectionChanged; this._Document.PerformLayouted -= View_LineBreakChanged; this._Document.CaretChanged -= _Document_CaretChanged; } this._Document = value; this._Document.Update += new DocumentUpdateEventHandler(Document_Update); this._Document.StatusUpdate += Document_StatusChanged; this._Document.SelectionChanged += Document_SelectionChanged; this._Document.PerformLayouted += View_LineBreakChanged; this._Document.CaretChanged += _Document_CaretChanged; } } private void _Document_CaretChanged(object sender, EventArgs e) { TextPoint pos = this.Document.CaretPostion; this.JumpCaret(pos.row, pos.col); } private void Document_SelectionChanged(object sender, EventArgs e) { if (this.IsReverseSelect()) { if (this.Document.SelectGrippers.BottomRight.Enabled) this.Document.SelectGrippers.BottomRight.MoveByIndex(this.View, this.SelectionStart); if (this.Document.SelectGrippers.BottomLeft.Enabled) this.Document.SelectGrippers.BottomLeft.MoveByIndex(this.View, this.SelectionStart + this.SelectionLength); } else { if (this.Document.SelectGrippers.BottomLeft.Enabled) this.Document.SelectGrippers.BottomLeft.MoveByIndex(this.View, this.SelectionStart); if (this.Document.SelectGrippers.BottomRight.Enabled) this.Document.SelectGrippers.BottomRight.MoveByIndex(this.View, this.SelectionStart + this.SelectionLength); } } void Document_StatusChanged(object sender,EventArgs e) { this.AdjustCaret(); } /// /// 矩形選択モードなら真を返し、そうでない場合は偽を返す /// public bool RectSelection { get { return this.Document.RectSelection; } set { this.Document.RectSelection = value; } } /// /// インデントの方法を表す /// public IndentMode IndentMode { get { return this.Document.IndentMode; } set { this.Document.IndentMode = value; } } /// /// 選択範囲の開始位置 /// /// SelectionLengthが0の場合、キャレット位置を表します public int SelectionStart { get { if (this.View.Selections.Count == 0) return this.Document.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.Document.CaretPostion); return index < this.Document.AnchorIndex; } /// /// 選択範囲内の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.Replace(this.SelectionStart, this.SelectionLength, result.ToString()); 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.Replace(this.SelectionStart, this.SelectionLength, result.ToString()); } /// /// 選択を解除する /// 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(id, index)) { if (m.hilight == type) return true; } } return false; } /// /// キャレット位置を再調整する /// public void AdjustCaret() { int row = this.Document.CaretPostion.row; if (row > this.View.LayoutLines.Count - 1) row = this.View.LayoutLines.Count - 1; int col = this.Document.CaretPostion.col; if (col > 0 && col > this.View.LayoutLines[row].Length) col = this.View.LayoutLines[row].Length; //選択領域が消えてしまうので覚えておく int sel_start = this.SelectionStart; int sel_length = this.SelectionLength; this.JumpCaret(row, col); this.Document.Select(sel_start, sel_length); } /// /// キャレットを指定した位置に移動させる /// /// /// 折り畳みを展開するなら真 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); } double noti; /// /// スクロールする /// /// 方向を指定する /// ピクセル単位の値でスクロール量を指定する /// 選択状態にするなら真 /// 同時にキャレットを移動させるなら真 public void ScrollByPixel(ScrollDirection dir,int delta, bool isSelected, bool withCaret) { if (this.Document.FireUpdateEvent == false) throw new InvalidOperationException(""); if (dir == ScrollDirection.Left || dir == ScrollDirection.Right) { this.View.TryScroll(delta, 0); return; } if(dir == ScrollDirection.Up || dir == ScrollDirection.Down) { noti += delta; if (noti < this.View.render.emSize.Height) return; int delta_row = (int)(noti / this.View.render.emSize.Height + 1.0); noti = 0; this.View.TryScroll(0, delta); } if (withCaret) { //カーソルを適切な位置に移動させる必要がある this.View.AdjustCaretAndSrc(); this.SelectWithMoveCaret(isSelected); } this.Document.SelectGrippers.BottomLeft.MoveByIndex(this.View, this.SelectionStart); this.Document.SelectGrippers.BottomRight.MoveByIndex(this.View, this.SelectionStart + this.SelectionLength); } /// /// スクロールする /// /// 方向を指定する /// スクロールする量。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.Document.SelectGrippers.BottomLeft.MoveByIndex(this.View, this.SelectionStart); this.Document.SelectGrippers.BottomRight.MoveByIndex(this.View, this.SelectionStart + this.SelectionLength); } /// /// キャレットを桁方向に移動させる /// /// 移動できない場合は真を返す /// 負の値なら左側へ、そうでないなら右側へ移動する /// 選択範囲とするなら真。そうでないなら偽 /// 単語単位で移動するなら真。そうでないなら偽 public void MoveCaretHorizontical(int realLength, bool isSelected,bool alignWord = false) { TextPoint caret = this.Document.CaretPostion; int moved; caret = GetNextCaret(caret, realLength, alignWord ? MoveFlow.Word : MoveFlow.Character,out moved); this.View.JumpCaret(caret.row, caret.col, false); this.View.AdjustCaretAndSrc(AdjustFlow.Both); this.SelectWithMoveCaret(isSelected); } /// /// 移動後のキャレット位置を求める /// /// 起点となるキャレット位置 /// 移動量 /// 移動方法 /// 実際に移動した量 /// 移動後のキャレット位置 public TextPoint GetNextCaret(TextPoint caret, int count,MoveFlow method,out int moved) { moved = 0; if(method == MoveFlow.Character || method == MoveFlow.Word) { for (int i = Math.Abs(count); i > 0; i--) { bool moveFlow = count > 0; if (this.Document.RightToLeft) moveFlow = !moveFlow; caret = this.MoveCaretHorizontical(caret, moveFlow); if (method == FooEditEngine.MoveFlow.Word) caret = this.AlignNearestWord(caret, moveFlow); moved++; } } if(method == MoveFlow.Line || method == MoveFlow.Paragraph) { for (int i = Math.Abs(count); i > 0; i--) { caret = this.MoveCaretVertical(caret, count > 0, method == MoveFlow.Paragraph); moved++; } } if (count < 0) moved = -moved; return caret; } TextPoint AlignNearestWord(TextPoint caret,bool MoveFlow) { string str = this.View.LayoutLines[caret.row]; while (caret.col > 0 && caret.col < str.Length && str[caret.col] != Document.NewLine) { if (!Util.IsWordSeparator(str[caret.col])) { caret = this.MoveCaretHorizontical(caret, MoveFlow); } else { if(MoveFlow) caret = this.MoveCaretHorizontical(caret, MoveFlow); break; } } return caret; } /// /// キャレットを行方向に移動させる /// /// 再描写する必要があるなら真を返す /// 移動量 /// public void MoveCaretVertical(int deltarow,bool isSelected) { TextPoint caret = this.Document.CaretPostion; int moved; caret = this.GetNextCaret(caret, deltarow, MoveFlow.Line,out moved); this.View.JumpCaret(caret.row, caret.col, true); 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.Document.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.Replace(index, next - index, "", true); } public bool IsRectInsertMode() { if (!this.RectSelection || this.View.Selections.Count == 0) 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.Document.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.Replace(newIndex, oldIndex - newIndex, "", true); } /// /// キャレット位置で行を分割する /// 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.Document.TabStops - (col_index % this.Document.TabStops); return new string(Enumerable.Repeat(' ',space_count).ToArray()); } /// /// キャレット位置に文字列を挿入し、その分だけキャレットを進める。isInsertModeの値により動作が変わります /// /// 挿入したい文字列 /// 真の場合、矩形選択の幅にかかわらず矩形編集モードとして動作します。そうでない場合は選択領域を文字列で置き換えます public void DoInputString(string str,bool fromTip = false) { TextPoint CaretPos = this.Document.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.Document.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.Replace(index, length, str, true); } /// /// キャレットの移動に合わせて選択する /// /// 選択状態にするかどうか /// /// キャレットを移動後、このメソッドを呼び出さない場合、Select()メソッドは正常に機能しません /// void SelectWithMoveCaret(bool isSelected) { if (this.Document.CaretPostion.col < 0 || this.Document.CaretPostion.row < 0) return; if (this.Document.FireUpdateEvent == false) throw new InvalidOperationException(""); int CaretPostion = this.View.GetIndexFromLayoutLine(this.Document.CaretPostion); SelectCollection Selections = this.View.Selections; if (isSelected) { this.Document.Select(this.Document.AnchorIndex, CaretPostion - this.Document.AnchorIndex); }else{ this.Document.AnchorIndex = CaretPostion; this.Document.Select(CaretPostion, 0); } } /// /// JumpCaretで移動した位置からキャレットを移動し、選択状態にする /// /// /// 単語単位で選択するかどうか public void MoveCaretAndSelect(TextPoint tp,bool alignWord = false) { TextPoint endSelectPostion = tp; int CaretPostion = this.View.GetIndexFromLayoutLine(tp); if (alignWord) { if (this.IsReverseSelect()) while (CaretPostion >= 0 && CaretPostion < this.Document.Length && !Util.IsWordSeparator(this.Document[CaretPostion])) CaretPostion--; else while (CaretPostion < this.Document.Length && !Util.IsWordSeparator(this.Document[CaretPostion])) CaretPostion++; if (CaretPostion < 0) CaretPostion = 0; endSelectPostion = this.View.LayoutLines.GetTextPointFromIndex(CaretPostion); } this.Document.Select(this.Document.AnchorIndex, CaretPostion - this.Document.AnchorIndex); this.View.JumpCaret(endSelectPostion.row, endSelectPostion.col); this.View.AdjustCaretAndSrc(); } /// /// グリッパーとキャレットを同時に移動する /// /// ポインターの座標 /// 動かす対象となるグリッパー /// 移動できた場合は真を返す。そうでなければ偽を返す /// グリッパー内にポインターが存在しない場合、グリッパーはポインターの座標近くの行に移動する public bool MoveCaretAndGripper(Point p, Gripper hittedGripper) { bool HittedCaret = false; TextPoint tp = this.View.GetTextPointFromPostion(p); if (tp == this.Document.CaretPostion) { HittedCaret = true; } if (HittedCaret || hittedGripper != null) { TextPointSearchRange searchRange; if (this.View.HitTextArea(p.X, p.Y)) searchRange = TextPointSearchRange.TextAreaOnly; else if (this.SelectionLength > 0) searchRange = TextPointSearchRange.Full; else return false; if (hittedGripper != null) { tp = this.View.GetTextPointFromPostion(hittedGripper.AdjustPoint(p), searchRange); if (tp == TextPoint.Null) return false; if (Object.ReferenceEquals(hittedGripper, this.Document.SelectGrippers.BottomRight)) this.MoveCaretAndSelect(tp); else if(Object.ReferenceEquals(hittedGripper, this.Document.SelectGrippers.BottomLeft)) this.MoveSelectBefore(tp); } else { tp = this.View.GetTextPointFromPostion(p, searchRange); if (tp != TextPoint.Null) { this.MoveCaretAndSelect(tp); } else { return false; } } this.Document.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.Document.AnchorIndex; this.Document.Select(this.SelectionStart, SelectionLength); } else { NewAnchorIndex = this.View.GetIndexFromLayoutLine(tp); SelectionLength = this.SelectionLength + this.Document.AnchorIndex - NewAnchorIndex; this.Document.Select(NewAnchorIndex, SelectionLength); } this.Document.AnchorIndex = NewAnchorIndex; } /// /// 行単位で移動後のキャレット位置を取得する /// /// 移動量 /// 現在のキャレット位置 /// パラグラフ単位で移動するなら真 /// 移動後のキャレット位置 public TextPoint GetTextPointAfterMoveLine(int count, TextPoint current, bool move_pargraph = false) { if(this.Document.LineBreak == LineBreakMethod.None || move_pargraph == true) { 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); Point pos = this.View.LayoutLines.GetLayout(current.row).GetPostionFromIndex(current.col); int col = this.View.LayoutLines.GetLayout(row).GetIndexFromPostion(pos.X, pos.Y); return new TextPoint(row, col); } else { Point pos = this.View.GetPostionFromTextPoint(current); pos.Y += this.View.render.emSize.Height * count; //この値を足さないとうまく動作しない pos.Y += this.View.render.emSize.Height / 2; var new_tp = this.View.GetTextPointFromPostion(pos,TextPointSearchRange.Full); return new_tp; } } /// /// キャレット位置を既定の位置に戻す /// public void ResetCaretPostion() { this.JumpCaret(0); } /// /// 選択文字列のインデントを一つ増やす /// 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.Document.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, insertStr.Length); this.RepleaceSelectionArea(this.View.Selections, text); this.Document.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,int remove_count) { 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(remove_count) + Document.NewLine); else if (i < lines.Length - 1) output.Append(lines[i] + Document.NewLine); } return output.ToString(); } /// /// キャレットを一文字移動させる /// /// キャレット /// 真なら1文字すすめ、そうでなければ戻す /// このメソッドを呼び出した後でScrollToCaretメソッドとSelectWithMoveCaretメソッドを呼び出す必要があります TextPoint MoveCaretHorizontical(TextPoint caret,bool isMoveNext) { if (this.Document.FireUpdateEvent == false) throw new InvalidOperationException(""); int delta = isMoveNext ? 0 : -1; int prevcol = caret.col; int col = caret.col + delta; string lineString = this.View.LayoutLines[caret.row]; if (col < 0 || caret.row >= this.View.LayoutLines.Count) { if (caret.row == 0) { caret.col = 0; return caret; } caret = this.MoveCaretVertical(caret,false); caret.col = this.View.LayoutLines.GetLengthFromLineNumber(caret.row) - 1; //最終行以外はすべて改行コードが付くはず } else if (col >= lineString.Length || lineString[col] == Document.NewLine) { if (caret.row < this.View.LayoutLines.Count - 1) { caret = this.MoveCaretVertical(caret, true); caret.col = 0; } } else { AlignDirection direction = isMoveNext ? AlignDirection.Forward : AlignDirection.Back; caret.col = this.View.LayoutLines.GetLayout(caret.row).AlignIndexToNearestCluster(col, direction); } return caret; } /// /// キャレットを行方向に移動させる /// /// 計算の起点となるテキストポイント /// プラス方向に移動するなら真 /// パラグラフ単位で移動するするなら真 /// このメソッドを呼び出した後でScrollToCaretメソッドとSelectWithMoveCaretメソッドを呼び出す必要があります TextPoint MoveCaretVertical(TextPoint caret,bool isMoveNext, bool move_pargraph = false) { if (this.Document.FireUpdateEvent == false) throw new InvalidOperationException(""); return this.GetTextPointAfterMoveLine(isMoveNext ? 1 : -1, this.Document.CaretPostion, move_pargraph); } 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.Document.Select(start, 0, end.row - start.row); } private void ReplaceBeforeSelection(Selection sel, int removeLength, string insertStr) { sel = Util.NormalizeIMaker(sel); this.Document.Replace(sel.start - removeLength, removeLength, insertStr); } private void RepleaceSelectionArea(SelectCollection Selections, string value,bool updateInsertPoint = false) { if (value == null) return; if (this.RectSelection == false) { Selection sel = Selection.Create(this.Document.AnchorIndex, 0); if (Selections.Count > 0) sel = Util.NormalizeIMaker(this.View.Selections.First()); this.Document.Replace(sel.start, sel.length, value); 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.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(); } else { SelectCollection temp = new SelectCollection(this.View.Selections); //コピーしないとReplaceCommandを呼び出した段階で書き換えられてしまう 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.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.Document.LineBreak == LineBreakMethod.PageBound && this.View.PageBound.Width - this.View.LineBreakingMarginWidth > 0) this.Document.PerformLayout(); this.AdjustCaret(); } void render_ChangedRenderResource(object sender, ChangedRenderRsourceEventArgs e) { if (e.type == ResourceType.Font) { if (this.Document.LineBreak == LineBreakMethod.PageBound) this.Document.PerformLayout(); this.AdjustCaret(); } if (e.type == ResourceType.InlineChar) { int oldLineCountOnScreen = this.View.LineCountOnScreen; this.View.CalculateLineCountOnScreen(); if(this.View.LineCountOnScreen != oldLineCountOnScreen) this.AdjustCaret(); } } void Document_Update(object sender, DocumentUpdateEventArgs e) { switch (e.type) { case UpdateType.Replace: this.JumpCaret(e.startIndex + e.insertLength,true); break; case UpdateType.Clear: this.JumpCaret(0,0, false); break; } } } }