/// <summary> /// Determines whether a location within the document is valid. /// </summary> /// <param name="Loc">The location to validate.</param> /// <returns>True if the supplied location is within the bounds of the document.</returns> public bool IsValidTextLocation(TextLocation Loc) { bool bResult = false; if(Loc.Line >= 0 && Loc.Line < mLines.Count && Loc.Column >= 0 && Loc.Column <= GetLineLength(Loc.Line)) { bResult = true; } return bResult; }
/// <summary> /// Event handler for when the mouse cursor has been moved. /// </summary> /// <param name="e">Information about the event.</param> protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); if(mIsSelecting) { Rectangle TextArea = GetTextAreaRect(); if(TextArea.Contains(e.Location)) { mScrollSelectedTextTimer.Enabled = false; TextLocation NewCaretPos = GetCharacterPosFromCoord(e.X, e.Y); if(mCaretPosition != NewCaretPos) { mCaretPosition = NewCaretPos; UpdateCaret(); Invalidate(); } } else { mScrollSelectedTextTimer.Enabled = true; } } mLastMousePos = e.Location; }
/// <summary> /// Finds the first occurence of the specified string within the document if it exists. /// </summary> /// <param name="Txt">The string to find.</param> /// <param name="StartLoc">The position within the document to begin searching.</param> /// <param name="EndLoc">The position within the document to end searching.</param> /// <param name="Flags">Flags telling the document how to conduct its search.</param> /// <param name="Result">The location within the document of the supplied text.</param> /// <returns>True if the text was found.</returns> public bool Find(string Txt, TextLocation StartLoc, TextLocation EndLoc, RichTextBoxFinds Flags, out FindResult Result) { if((Flags & RichTextBoxFinds.Reverse) == RichTextBoxFinds.Reverse) { return FindReverse(Txt, ref StartLoc, ref EndLoc, Flags, out Result); } else { return FindForward(Txt, ref StartLoc, ref EndLoc, Flags, out Result); } }
/// <summary> /// Event handler or when the text in the "find" text box has changed or the state of the "match case" checkbox has changed. /// </summary> /// <param name="sender">The object that initiated the event.</param> /// <param name="e">Additional information about the event.</param> private void mToolStripTextBox_Find_TextChanged(object sender, EventArgs e) { if(mToolStripTextBox_Find.Text.Trim().Length == 0) { mCaretPosition = mSelectionStart; UpdateFindBoxColor(true); UpdateCaret(); Invalidate(); } else { TextLocation StartLocation = mCaretPosition <= mSelectionStart ? mCaretPosition : mSelectionStart; FindNext(StartLocation); } }
/// <summary> /// Event handler for when a mouse button has been pressed. /// </summary> /// <param name="e">Information about the event.</param> protected override void OnMouseDown(MouseEventArgs e) { base.OnMouseDown(e); if((e.Button & MouseButtons.Left) == MouseButtons.Left || (e.Button & MouseButtons.Right) == MouseButtons.Right) { bool bHadSelection = this.HasSelectedText; bool bUpdateCaret = true; TextLocation CharacterPosition = GetCharacterPosFromCoord(e.X, e.Y); mIsSelecting = e.Button == MouseButtons.Left; if(!mIsSelecting) { int SelectionStart; int SelectionLength; if(GetSelectionForLine(CharacterPosition.Line, out SelectionStart, out SelectionLength) && CharacterPosition.Column >= SelectionStart && CharacterPosition.Column < SelectionStart + SelectionLength) { bUpdateCaret = false; } } if(bUpdateCaret && (mCaretPosition != CharacterPosition || bHadSelection)) { #if !__MonoCS__ mCaretPosition = CharacterPosition; ushort KeyState = GetAsyncKeyState(VK_SHIFT); // if shift key is not pressed if((KeyState & 0x8000) == 0) { mSelectionStart = mCaretPosition; if(bHadSelection) { OnSelectionChanged(new EventArgs()); } } else { // if shift is pressed then we must be changing the selection bHadSelection = true; OnSelectionChanged(new EventArgs()); } #endif } // NOTE: This is very important. If you do not set the ActiveControl to null then // child controls such as the "find" toolstrip container won't give focus back! // This means the caret may never come back and babies will cry. this.ActiveControl = null; UpdateCaret(); if(bHadSelection) { Invalidate(); } } }
/// <summary> /// Moves the caret up in the document. /// </summary> /// <param name="NumLines">The number of lines to move the caret up.</param> /// <param name="bMoveSelectionStart">Pass true if the selection start is moved with the caret.</param> private void MoveCaretUpLines(int NumLines, bool bMoveSelectionStart) { if(NumLines < 0) { throw new ArgumentOutOfRangeException("NumLines", "NumLines must be greater than 0."); } if(mDocument == null) { return; } mCaretPosition.Line = Math.Max(mCaretPosition.Line - NumLines, 0); if(mCaretPosition.Column > mDocument.GetLineLength(mCaretPosition.Line)) { mCaretPosition.Column = mDocument.GetLineLength(mCaretPosition.Line); } if(bMoveSelectionStart) { mSelectionStart = mCaretPosition; } }
/// <summary> /// Searches up within the text box for the specified search string. /// </summary> /// <param name="UpSearchStart">The location within the document to begin searching. void FindPrevious(TextLocation? UpSearchStart) { if(this.mDocument != null) { FindResult Result; RichTextBoxFinds Flags = (mCheckBox_MatchCase.Checked ? RichTextBoxFinds.MatchCase : RichTextBoxFinds.None) | RichTextBoxFinds.Reverse; if(!UpSearchStart.HasValue) { UpSearchStart = this.SelectionStart <= this.SelectionEnd ? this.SelectionStart : this.SelectionEnd; } bool bFound = true; string TxtToSearchFor = mToolStripTextBox_Find.Text.Trim(); if(!this.Find(TxtToSearchFor, UpSearchStart.Value, this.mDocument.BeginningOfDocument, Flags, out Result)) { if(!this.Find(TxtToSearchFor, this.mDocument.EndOfDocument, new TextLocation(UpSearchStart.Value.Line, 0), Flags, out Result)) { bFound = false; } } UpdateFindBoxColor(bFound); } }
/// <summary> /// Scrolls the caret to the beginning of the document. /// </summary> public void ScrollToHome() { if(mDocument != null) { bool bHadSelection = this.HasSelectedText; mSelectionStart = mCaretPosition = mDocument.BeginningOfDocument; if(bHadSelection) { OnSelectionChanged(new EventArgs()); } ScrollToCaretOrInvalidate(); } }
/// <summary> /// Event handler for when the document has been modified. /// </summary> /// <param name="sender">The object that initiated the event.</param> /// <param name="e">Additional information about the event.</param> void mDocument_Modified(object sender, EventArgs e) { TextLocation PrevDocSize = mCurrentDocumentSize; mCurrentDocumentSize.Line = mDocument.LineCount; mCurrentDocumentSize.Column = mDocument.GetLineLength(mCurrentDocumentSize.Line - 1); bool bInvalidate = true; bool bHadSelection = this.HasSelectedText; if(mCurrentDocumentSize > PrevDocSize) { // we have to do these checks before we update he scrollbars if(mAutoScroll && !bHadSelection && mCaretPosition.Column == PrevDocSize.Column && mCaretPosition.Line == Math.Max(0, PrevDocSize.Line - 1) && mVScrollBar.Value == Math.Max(0, GetMaximumVScrollValue())) { // ScrollToCaret() is dependent on scrollbar location so update that now instead of later UpdateScrollbars(); mCaretPosition.Line = Math.Max(0, mDocument.LineCount - 1); mCaretPosition.Column = mDocument.GetLineLength(mCaretPosition.Line); mSelectionStart = mCaretPosition; bInvalidate = !ScrollToCaret(); } else { UpdateScrollbars(); } } else { // if doc size shrunk we need to make sure the caret is still within range if(mCaretPosition > mCurrentDocumentSize) { mCaretPosition = mCurrentDocumentSize; if(bHadSelection) { if(mCaretPosition > mSelectionStart) { mSelectionStart = mCaretPosition; } OnSelectionChanged(new EventArgs()); } else { mSelectionStart = mCaretPosition; } } else if(bHadSelection && mSelectionStart > mCurrentDocumentSize) { mSelectionStart = mCurrentDocumentSize; OnSelectionChanged(new EventArgs()); } UpdateScrollbars(); } if(bInvalidate) { UpdateCaret(); Invalidate(); } }
/// <summary> /// Selects a range of text and brings the selection into view. /// </summary> /// <param name="Range">The location of the selection and its length.</param> public void Select(FindResult Range) { if(mDocument != null) { TextLocation StartLoc = new TextLocation(Range.Line, Range.Column); if(!IsValidTextLocation(StartLoc)) { throw new ArgumentOutOfRangeException("Range"); } TextLocation EndLoc = new TextLocation(Range.Line, Math.Min(Range.Column + Range.Length, mDocument.GetLineLength(Range.Line))); Select(StartLoc, EndLoc); } }
/// <summary> /// Finds the first occurence of the specified string within the document if it exists. /// </summary> /// <param name="Txt">The string to find.</param> /// <param name="StartLoc">The position within the document to begin searching.</param> /// <param name="EndLoc">The position within the document to end searching.</param> /// <param name="Flags">Flags telling the document how to conduct its search.</param> /// <param name="Result">The location within the document of the supplied text.</param> /// <returns>True if the text was found.</returns> public bool Find(string Txt, TextLocation StartLoc, TextLocation EndLoc, RichTextBoxFinds Flags, out FindResult Result) { bool bResult = false; if(mDocument == null) { Result = FindResult.Empty; } else { bResult = mDocument.Find(Txt, StartLoc, EndLoc, Flags, out Result); } if(bResult && (Flags & RichTextBoxFinds.NoHighlight) != RichTextBoxFinds.NoHighlight) { Select(Result); } return bResult; }
/// <summary> /// Selects a range of text and brings the selection into view. /// </summary> /// <param name="StartLoc">The start location of the selection.</param> /// <param name="EndLoc">The end location (and caret location) of the selection.</param> public void Select(TextLocation StartLoc, TextLocation EndLoc) { if(mDocument != null) { if(!IsValidTextLocation(StartLoc)) { throw new ArgumentOutOfRangeException("StartLoc"); } if(!IsValidTextLocation(EndLoc)) { throw new ArgumentOutOfRangeException("EndLoc"); } mSelectionStart = StartLoc; mCaretPosition = EndLoc; OnSelectionChanged(new EventArgs()); ScrollToCaretOrInvalidate(); } }
/// <summary> /// Determines whether a location within the current document is valid. /// </summary> /// <param name="Loc">The location to validate.</param> /// <returns>True if the supplied location is within the bounds of the document.</returns> bool IsValidTextLocation(TextLocation Loc) { bool bResult = false; if(mDocument != null) { bResult = mDocument.IsValidTextLocation(Loc); } return bResult; }
/// <summary> /// Selects all text and scrolls to the caret. /// </summary> public void SelectAll() { mSelectionStart = mDocument.BeginningOfDocument; mCaretPosition = mDocument.EndOfDocument; OnSelectionChanged(new EventArgs()); if(!ScrollToCaret()) { UpdateCaret(); Invalidate(); } }
/// <summary> /// Searches for a string in the reverse direction of <see cref="FindForward"/>. /// </summary> /// <param name="Txt">The text to search for.</param> /// <param name="StartLoc">The starting location of the search.</param> /// <param name="EndLoc">The ending location of the search.</param> /// <param name="Flags">Flags controlling how the searching is conducted.</param> /// <param name="Result">Receives the results of the search.</param> /// <returns>True if a match was found.</returns> private bool FindReverse(string Txt, ref TextLocation StartLoc, ref TextLocation EndLoc, RichTextBoxFinds Flags, out FindResult Result) { bool bFound = false; bool bMatchWord; bool bIsWord; StringComparison ComparisonFlags; SetupFindState(Txt, ref StartLoc, ref EndLoc, Flags, out Result, out bIsWord, out ComparisonFlags, out bMatchWord); for(int CurLineIndex = StartLoc.Line; CurLineIndex >= EndLoc.Line && !bFound; --CurLineIndex) { if(GetLineLength(CurLineIndex) == 0) { continue; } DocumentLine CurLineBldr = mLines[CurLineIndex]; string LineTxt; int ColumnIndex = 0; if(CurLineIndex == StartLoc.Line && StartLoc.Column < GetLineLength(CurLineIndex)) { LineTxt = CurLineBldr.ToString(0, StartLoc.Column); } else if(CurLineIndex == EndLoc.Line && EndLoc.Column > 0) { LineTxt = CurLineBldr.ToString(EndLoc.Column, CurLineBldr.Length - EndLoc.Column); ColumnIndex = EndLoc.Column; } else { LineTxt = CurLineBldr.ToString(); } int Index = LineTxt.LastIndexOf(Txt, ComparisonFlags); if(Index != -1) { ColumnIndex += Index; CheckForWholeWord(Txt, ref Result, bMatchWord, ref bFound, bIsWord, CurLineIndex, CurLineBldr, ColumnIndex, Index); } } return bFound; }
/// <summary> /// Searches down within the text box for the specified search string. /// </summary> /// <param name="DownSearchStart">The location within the document to begin searching.</param> void FindNext(TextLocation? DownSearchStart) { if(this.mDocument != null) { FindResult Result; RichTextBoxFinds Flags = mCheckBox_MatchCase.Checked ? RichTextBoxFinds.MatchCase : RichTextBoxFinds.None; if(!DownSearchStart.HasValue) { DownSearchStart = this.SelectionStart >= this.SelectionEnd ? this.SelectionStart : this.SelectionEnd; } bool bFound = true; string TxtToSearchFor = mToolStripTextBox_Find.Text.Trim(); if(!this.Find(TxtToSearchFor, DownSearchStart.Value, this.mDocument.EndOfDocument, Flags, out Result)) { if(!this.Find(TxtToSearchFor, this.mDocument.BeginningOfDocument, new TextLocation(DownSearchStart.Value.Line, mDocument.GetLineLength(DownSearchStart.Value.Line)), Flags, out Result)) { //MessageBox.Show(this, "The specified search string does not exist!", mToolStripTextBox_Find.Text); bFound = false; } } UpdateFindBoxColor(bFound); } }
/// <summary> /// Performs general housekeeping for setting up a search. /// </summary> /// <param name="Txt">The text to search for.</param> /// <param name="StartLoc">The location to begin searching from.</param> /// <param name="EndLoc">The location to stop searching at.</param> /// <param name="Flags">Flags controlling how the search is performed.</param> /// <param name="Result">Receives the resulting location if a match is found.</param> /// <param name="bIsWord">Set to true if <paramref name="Txt"/> is a valid word.</param> /// <param name="ComparisonFlags">Receives flags controlling how strings are compared.</param> /// <param name="bMatchWord">Is set to true if only full words are to be matched.</param> private void SetupFindState(string Txt, ref TextLocation StartLoc, ref TextLocation EndLoc, RichTextBoxFinds Flags, out FindResult Result, out bool bIsWord, out StringComparison ComparisonFlags, out bool bMatchWord) { Result = FindResult.Empty; if(!IsValidTextLocation(StartLoc)) { throw new ArgumentException("StartLoc is an invalid text location!"); } if(!IsValidTextLocation(EndLoc)) { throw new ArgumentException("EndLoc is an invalid text location!"); } if((Flags & RichTextBoxFinds.Reverse) == RichTextBoxFinds.Reverse) { if(StartLoc < EndLoc) { throw new ArgumentException("StartLoc must be greater than EndLoc when doing a reverse search!"); } } else { if(StartLoc > EndLoc) { throw new ArgumentException("StartLoc must be less than EndLoc when doing a forward search!"); } } bMatchWord = (Flags & RichTextBoxFinds.WholeWord) == RichTextBoxFinds.WholeWord; bIsWord = IsWord(0, Txt); ComparisonFlags = StringComparison.OrdinalIgnoreCase; if((Flags & RichTextBoxFinds.MatchCase) == RichTextBoxFinds.MatchCase) { ComparisonFlags = StringComparison.Ordinal; } }
/// <summary> /// Event handler for when a key has been pressed. /// </summary> /// <param name="e">Information about the event.</param> protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); if(mDocument != null) { bool bHadSelection = (mCaretPosition != mSelectionStart); TextLocation OldCaretPosition = !bHadSelection ? mCaretPosition : mSelectionStart; bool bHandleShift = false; if((e.KeyCode == Keys.Insert || e.KeyCode == Keys.C) && e.Control) { CopySelectedText(); } else if(e.KeyCode == Keys.A && e.Control) { e.Handled = true; SelectAll(); } else if(e.KeyCode == Keys.F && e.Control) { EnterFindMode(); } else if(e.KeyCode == Keys.F3) { if(e.Shift) { FindPrevious(); } else { FindNext(); } } else if(e.KeyCode == Keys.End) { if(e.Control) { mCaretPosition.Line = Math.Max(0, mDocument.LineCount - 1); } mCaretPosition.Column = mDocument.GetLineLength(mCaretPosition.Line); bHandleShift = true; ScrollToCaret(); } else if(e.KeyCode == Keys.Home) { mCaretPosition.Column = 0; if(e.Control) { mCaretPosition.Line = 0; } bHandleShift = true; ScrollToCaret(); } else if(e.KeyCode == Keys.Up) { MoveCaretUpLines(1, false); bHandleShift = true; ScrollToCaret(); } else if(e.KeyCode == Keys.Down) { MoveCaretDownLines(1, false); bHandleShift = true; ScrollToCaret(); } else if(e.KeyCode == Keys.Right) { ++mCaretPosition.Column; if(mCaretPosition.Column >= mDocument.GetLineLength(mCaretPosition.Line)) { ++mCaretPosition.Line; if(mCaretPosition.Line >= mDocument.LineCount) { --mCaretPosition.Line; --mCaretPosition.Column; } else { mCaretPosition.Column = 0; } } bHandleShift = true; ScrollToCaret(); } else if(e.KeyCode == Keys.Left) { --mCaretPosition.Column; if(mCaretPosition.Column < 0) { --mCaretPosition.Line; if(mCaretPosition.Line < 0) { mCaretPosition.Line = 0; mCaretPosition.Column = 0; } else { mCaretPosition.Column = mDocument.GetLineLength(mCaretPosition.Line); } } bHandleShift = true; } else if(e.KeyCode == Keys.PageUp) { mCaretPosition.Line = Math.Max(0, mCaretPosition.Line - mVScrollBar.LargeChange); int LineLength = mDocument.GetLineLength(mCaretPosition.Line); if(mCaretPosition.Column > LineLength) { mCaretPosition.Column = LineLength; } bHandleShift = true; } else if(e.KeyCode == Keys.PageDown) { mCaretPosition.Line = Math.Min(mDocument.LineCount - 1, mCaretPosition.Line + mVScrollBar.LargeChange); int LineLength = mDocument.GetLineLength(mCaretPosition.Line); if(mCaretPosition.Column > LineLength) { mCaretPosition.Column = LineLength; } bHandleShift = true; } if(bHandleShift) { // prevent the key from propagating to children e.Handled = true; if(e.Shift) { mSelectionStart = OldCaretPosition; } else { mSelectionStart = mCaretPosition; } if(mSelectionStart == mCaretPosition) { if(bHadSelection) { OnSelectionChanged(new EventArgs()); } } else { OnSelectionChanged(new EventArgs()); } ScrollToCaretOrInvalidate(); } } }