public Rectangle GetCaretPosition(IGraphics gr, SelectionPoint sp, CaretDirection d) { if (rootBlock == null) { return(Rectangle.Empty); } CaretPositionInfo cpi = new CaretPositionInfo(); rootBlock.GetCaretPosition(gr, 0, 0, sp, ref cpi); if (d == CaretDirection.LTR) { cpi.UseSecondary = true; } return(cpi.ToRectangle()); }
public Rectangle GetCaretPosition(IGraphics gr, SelectionPoint sp, CaretDirection d) { if ( rootBlock == null ) return Rectangle.Empty; CaretPositionInfo cpi=new CaretPositionInfo(); rootBlock.GetCaretPosition(gr, 0, 0, sp, ref cpi); if ( d == CaretDirection.LTR ) cpi.UseSecondary=true; return cpi.ToRectangle(); }
/// <summary> /// Search from the given lscp (inclusive) towards the specified direction for the /// closest navigable cp. Return true is one such cp is found, false otherwise. /// </summary> private bool FindNextOrPreviousVisibleCp( int lscp, CaretDirection direction, out int lscpVisisble ) { lscpVisisble = lscp; SpanRider plsrunSpanRider = new SpanRider(_plsrunVector); if (direction == CaretDirection.Forward) { while (lscpVisisble < _metrics._lscpLim) { plsrunSpanRider.At(lscpVisisble - _cpFirst); LSRun run = GetRun((Plsrun) plsrunSpanRider.CurrentElement); // When scanning forward, only trailine edges of visiable content are navigable. if (run.IsVisible) { return true; } lscpVisisble += plsrunSpanRider.Length; // move to start of next span } } else { Debug.Assert(direction == CaretDirection.Backward || direction == CaretDirection.Backspace); // lscpCurrent can be right after the end of the line, we snap it back to be at the end of the line. lscpVisisble = Math.Min(lscpVisisble, _metrics._lscpLim - 1); while (lscpVisisble >= _cpFirst) { plsrunSpanRider.At(lscpVisisble - _cpFirst); LSRun run = GetRun((Plsrun) plsrunSpanRider.CurrentElement); // When scanning backward, visiable content has caret stop at its leading edge. if (run.IsVisible) { return true; } // When scanning backward, the newline sequence has caret stop at its leading edge. if (run.IsNewline) { // set navigable cp at the start of newline sequence. lscpVisisble = _cpFirst + plsrunSpanRider.CurrentSpanStart; return true; } lscpVisisble = _cpFirst + plsrunSpanRider.CurrentSpanStart - 1; // move to the end of previous span } } lscpVisisble = lscp; return false; }
/// <summary> /// Given a specified current character index, calculate the character index /// to the closest caret stop before or at the current index; and the number /// of codepoints from the closest caret stop to the next caret stop. /// </summary> private bool GetNextOrPreviousCaretStop( int currentIndex, CaretDirection direction, out int caretStopIndex, out int offsetToNextCaretStopIndex ) { caretStopIndex = currentIndex; offsetToNextCaretStopIndex = 0; if ( HasCollapsed && _collapsedRange != null && currentIndex >= _collapsedRange.TextSourceCharacterIndex ) { // current index is within collapsed range, caretStopIndex = _collapsedRange.TextSourceCharacterIndex; if (currentIndex < _collapsedRange.TextSourceCharacterIndex + _collapsedRange.Length) offsetToNextCaretStopIndex = _collapsedRange.Length; return true; } LsQSubInfo[] sublineInfo = new LsQSubInfo[_depthQueryMax]; LsTextCell lsTextCell = new LsTextCell(); int lscpVisisble = GetInternalCp(currentIndex); bool found = FindNextOrPreviousVisibleCp(lscpVisisble, direction, out lscpVisisble); if (!found) { return false; // there is no caret stop anymore in the given direction. } int actualSublineCount; QueryLineCpPpoint( lscpVisisble, sublineInfo, out actualSublineCount, out lsTextCell ); // Locate the current caret stop caretStopIndex = GetExternalCp(lsTextCell.lscpStartCell); if ( actualSublineCount > 0 && lscpVisisble >= lsTextCell.lscpStartCell && lscpVisisble <= lsTextCell.lscpEndCell ) { // the last subline contains the run that owns the querying lscp LSRun lsrun = GetRun((Plsrun)sublineInfo[actualSublineCount - 1].plsrun); if (lsrun.IsHitTestable) { if ( lsrun.HasExtendedCharacter || (direction != CaretDirection.Backspace && lsrun.NeedsCaretInfo) ) { // LsTextCell.lscpEndCell is the index to the last lscp still in the cell. // The number of LSCP within the text cell is equal to the number of CP. offsetToNextCaretStopIndex = lsTextCell.lscpEndCell + 1 - lsTextCell.lscpStartCell; } else { // caret stops before every codepoint caretStopIndex = GetExternalCp(lscpVisisble); offsetToNextCaretStopIndex = 1; } } else { // run is not hit-testable, caret navigation is not allowed in the run, // the next caret stop is therefore either at the end of the run or the end of the line whichever reached first. offsetToNextCaretStopIndex = Math.Min(Length, lsrun.Length - caretStopIndex + lsrun.OffsetToFirstCp + _cpFirst); } } return true; }
/// <summary> /// Calculate previous caret character hit based on the caret action /// </summary> private CharacterHit GetPreviousCaretCharacterHitByBehavior( CharacterHit characterHit, CaretDirection direction ) { Debug.Assert(direction == CaretDirection.Backward || direction == CaretDirection.Backspace); if ((_statusFlags & StatusFlags.IsDisposed) != 0) { throw new ObjectDisposedException(SR.Get(SRID.TextLineHasBeenDisposed)); } TextFormatterImp.VerifyCaretCharacterHit(characterHit, _cpFirst, _metrics._cchLength); if (_ploline.Value == IntPtr.Zero) { return characterHit; } if ( characterHit.FirstCharacterIndex == _cpFirst && characterHit.TrailingLength == 0) { // We are already at the beginning of the line return characterHit; } int caretStopIndex; int offsetToNextCaretStopIndex; bool found = GetNextOrPreviousCaretStop( characterHit.FirstCharacterIndex, direction, out caretStopIndex, out offsetToNextCaretStopIndex ); if (!found) { // The current index is before the 1st caret stop. return characterHit; } if ( offsetToNextCaretStopIndex != 0 && characterHit.TrailingLength == 0 && caretStopIndex != _cpFirst && caretStopIndex >= characterHit.FirstCharacterIndex ) { // If the current character hit is at the leading edge and it is not at the first caret stop, // move it to leading edge of the previous caret stop. At this point, the current character stop // fully encloses the input index and the input is at the leading edge. found = GetNextOrPreviousCaretStop( caretStopIndex - 1, // position at the character immediately preceding the current caret stop direction, out caretStopIndex, out offsetToNextCaretStopIndex ); if (!found) { // The current index is before the 1st caret stop. return characterHit; } } // The current chracter hit is either beyond the last caret stop, // or it's at the trailing edge of the current caret stop, // or the current index is at the leading edge of the first caret stop. // // In such cases, move to the leading edge of the closest caret stop. return new CharacterHit(caretStopIndex, 0); }
protected override void MoveCaret(CaretDirection dir, bool extend) { }
protected override void DeleteTextOp(CaretDirection dir) { }
// Deletes text (i.e. backspace, delete keys) protected abstract void DeleteTextOp(CaretDirection dir);
internal void MoveCaret(CaretDirection direction) { // FIXME should we use IsWordSeparator to detect whitespace, instead // of looking for actual spaces in the Word move cases? bool nowrap = false; switch(direction) { case CaretDirection.CharForwardNoWrap: nowrap = true; goto case CaretDirection.CharForward; case CaretDirection.CharForward: { caret.pos++; if (caret.pos > caret.line.TextLengthWithoutEnding ()) { if (!nowrap) { // Go into next line if (caret.line.line_no < this.lines) { caret.line = GetLine(caret.line.line_no+1); caret.pos = 0; caret.tag = caret.line.tags; } else { caret.pos--; } } else { // Single line; we stay where we are caret.pos--; } } else { if ((caret.tag.Start - 1 + caret.tag.Length) < caret.pos) { caret.tag = caret.tag.Next; } } UpdateCaret(); return; } case CaretDirection.CharBackNoWrap: nowrap = true; goto case CaretDirection.CharBack; case CaretDirection.CharBack: { if (caret.pos > 0) { // caret.pos--; // folded into the if below if (--caret.pos > 0) { if (caret.tag.Start > caret.pos) { caret.tag = caret.tag.Previous; } } } else { if (caret.line.line_no > 1 && !nowrap) { caret.line = GetLine(caret.line.line_no - 1); caret.pos = caret.line.TextLengthWithoutEnding (); caret.tag = LineTag.FindTag(caret.line, caret.pos); } } UpdateCaret(); return; } case CaretDirection.WordForward: { int len; len = caret.line.text.Length; if (caret.pos < len) { while ((caret.pos < len) && (caret.line.text[caret.pos] != ' ')) { caret.pos++; } if (caret.pos < len) { // Skip any whitespace while ((caret.pos < len) && (caret.line.text[caret.pos] == ' ')) { caret.pos++; } } caret.tag = LineTag.FindTag(caret.line, caret.pos); } else { if (caret.line.line_no < this.lines) { caret.line = GetLine(caret.line.line_no + 1); caret.pos = 0; caret.tag = caret.line.tags; } } UpdateCaret(); return; } case CaretDirection.WordBack: { if (caret.pos > 0) { caret.pos--; while ((caret.pos > 0) && (caret.line.text[caret.pos] == ' ')) { caret.pos--; } while ((caret.pos > 0) && (caret.line.text[caret.pos] != ' ')) { caret.pos--; } if (caret.line.text.ToString(caret.pos, 1) == " ") { if (caret.pos != 0) { caret.pos++; } else { caret.line = GetLine(caret.line.line_no - 1); caret.pos = caret.line.text.Length; } } caret.tag = LineTag.FindTag(caret.line, caret.pos); } else { if (caret.line.line_no > 1) { caret.line = GetLine(caret.line.line_no - 1); caret.pos = caret.line.text.Length; caret.tag = LineTag.FindTag(caret.line, caret.pos); } } UpdateCaret(); return; } case CaretDirection.LineUp: { if (caret.line.line_no > 1) { int pixel; pixel = (int)caret.line.widths[caret.pos]; PositionCaret(pixel, GetLine(caret.line.line_no - 1).Y); DisplayCaret (); } return; } case CaretDirection.LineDown: { if (caret.line.line_no < lines) { int pixel; pixel = (int)caret.line.widths[caret.pos]; PositionCaret(pixel, GetLine(caret.line.line_no + 1).Y); DisplayCaret (); } return; } case CaretDirection.Home: { if (caret.pos > 0) { caret.pos = 0; caret.tag = caret.line.tags; UpdateCaret(); } return; } case CaretDirection.End: { if (caret.pos < caret.line.TextLengthWithoutEnding ()) { caret.pos = caret.line.TextLengthWithoutEnding (); caret.tag = LineTag.FindTag(caret.line, caret.pos); UpdateCaret(); } return; } case CaretDirection.PgUp: { if (caret.line.line_no == 1 && owner.richtext) { owner.vscroll.Value = 0; Line line = GetLine (1); PositionCaret (line, 0); } int y_offset = caret.line.Y + caret.line.height - 1 - viewport_y; int index; LineTag top = FindCursor ((int) caret.line.widths [caret.pos], viewport_y - viewport_height, out index); owner.vscroll.Value = Math.Min (top.Line.Y, owner.vscroll.Maximum - viewport_height); PositionCaret ((int) caret.line.widths [caret.pos], y_offset + viewport_y); return; } case CaretDirection.PgDn: { if (caret.line.line_no == lines && owner.richtext) { owner.vscroll.Value = owner.vscroll.Maximum - viewport_height + 1; Line line = GetLine (lines); PositionCaret (line, line.TextLengthWithoutEnding()); } int y_offset = caret.line.Y - viewport_y; int index; LineTag top = FindCursor ((int) caret.line.widths [caret.pos], viewport_y + viewport_height, out index); owner.vscroll.Value = Math.Min (top.Line.Y, owner.vscroll.Maximum - viewport_height); PositionCaret ((int) caret.line.widths [caret.pos], y_offset + viewport_y); return; } case CaretDirection.CtrlPgUp: { PositionCaret(0, viewport_y); DisplayCaret (); return; } case CaretDirection.CtrlPgDn: { Line line; LineTag tag; int index; tag = FindCursor (0, viewport_y + viewport_height, out index); if (tag.Line.line_no > 1) { line = GetLine(tag.Line.line_no - 1); } else { line = tag.Line; } PositionCaret(line, line.Text.Length); DisplayCaret (); return; } case CaretDirection.CtrlHome: { caret.line = GetLine(1); caret.pos = 0; caret.tag = caret.line.tags; UpdateCaret(); return; } case CaretDirection.CtrlEnd: { caret.line = GetLine(lines); caret.pos = caret.line.TextLengthWithoutEnding (); caret.tag = LineTag.FindTag(caret.line, caret.pos); UpdateCaret(); return; } case CaretDirection.SelectionStart: { caret.line = selection_start.line; caret.pos = selection_start.pos; caret.tag = selection_start.tag; UpdateCaret(); return; } case CaretDirection.SelectionEnd: { caret.line = selection_end.line; caret.pos = selection_end.pos; caret.tag = selection_end.tag; UpdateCaret(); return; } } }
private static bool IsTowardsTextEnd(CaretDirection dir) { return dir == CaretDirection.Right || dir == CaretDirection.WordRight || dir == CaretDirection.LineEnd || dir == CaretDirection.LineDown || dir == CaretDirection.PageDown || dir == CaretDirection.TextEnd; }
protected override void DeleteTextOp(CaretDirection dir) { if (ReadOnly) return; switch (dir) { case CaretDirection.WordLeft: case CaretDirection.Left: { if (layout.Items.Length == 0) break; int widthText = layout.Items[layout.Items.Length - 1].bounds.Right; int bottomCaret = caretBounds.Bottom; if (GetSelectionLength()>0) { SetSelectionText(""); SelectInternal(GetSelectionStart(), 0); } else { int startPos = GetSelectionStart(); int newPos = dir==CaretDirection.Left ? ComputeCharLeftPos(startPos) : ComputeWordLeftPos(startPos); if (newPos < startPos) { int nbCharsToDelete = startPos - newPos; SetTextActual(Text.Substring(0, GetSelectionStart() - nbCharsToDelete) + Text.Substring(GetSelectionStart())); SelectInternal(GetSelectionStart()-nbCharsToDelete, 0); } } OnTextChanged(EventArgs.Empty); // In the case of multiline we ensure that we recover any blank lines created by backspacing at the bottom (if the text is bigger than the view area). // In the case of non multiline, we recover any character space that is now there after deleting if (!Multiline && layout.Items.Length > 0) { if (textAlign == HorizontalAlignment.Center && XViewOffset > maxXY/2 - TextDrawArea.Width/2) { XViewOffset -= widthText - layout.Items[layout.Items.Length - 1].bounds.Right; int x = maxXY/2 - TextDrawArea.Width/2; if (x > xViewOffset) { XViewOffset = x; } } else if (textAlign == HorizontalAlignment.Left && XViewOffset > 0) { XViewOffset -= widthText - layout.Items[layout.Items.Length - 1].bounds.Right; if (xViewOffset < 0) { XViewOffset = 0; } } } else { //Move the caret first CaretSetEndSelection(); if (bottomCaret != caretBounds.Bottom) { int yViewOffset = YViewOffset - bottomCaret + caretBounds.Bottom - 1; if (yViewOffset < 0) yViewOffset = 0; YViewOffset = yViewOffset; } } InvalidateDirty(); break; } case CaretDirection.Right: case CaretDirection.WordRight: { if (GetSelectionLength()>0) { SetSelectionText(""); } else { int startPos = GetSelectionStart(); int newPos = dir==CaretDirection.Right ? ComputeCharRightPos(startPos) : ComputeWordRightPos(startPos); if (newPos > startPos) { SetTextActual(Text.Substring(0, GetSelectionStart()) + Text.Substring(GetSelectionStart() + newPos - startPos)); } } SelectInternal(GetSelectionStart(),0); InvalidateDirty(); OnTextChanged(EventArgs.Empty); break; } } CaretSetEndSelection(); ScrollToCaretNoRedraw(); }
// Caret navigation protected override void MoveCaret(CaretDirection dir, bool extend) { int startSel = GetSelectionStart(); int newPos = startSel; if ((extend && selectionLengthActual>0) || (!extend && IsTowardsTextEnd(dir))) { newPos += GetSelectionLength(); } switch (dir) { case CaretDirection.Left: newPos = ComputeCharLeftPos(newPos); break; case CaretDirection.Right: newPos = ComputeCharRightPos(newPos); break; case CaretDirection.WordLeft: newPos = ComputeWordLeftPos(newPos); break; case CaretDirection.WordRight: newPos = ComputeWordRightPos(newPos); break; case CaretDirection.LineStart: newPos = ComputeLineStartPos(newPos); break; case CaretDirection.LineEnd: newPos = ComputeLineEndPos(newPos); break; case CaretDirection.LineUp: if (caretBounds.Top >= caretBounds.Height ) { newPos = ComputeLineOffset(newPos, -1); } break; case CaretDirection.LineDown: if (layout.Items.Length == 0) { return; } if (caretBounds.Top < layout.Items[layout.Items.Length - 1].bounds.Top) { newPos = ComputeLineOffset(newPos, 1); } break; case CaretDirection.PageUp: newPos = ComputeLineOffset(newPos, - (int) TextDrawArea.Height / Font.Height); break; case CaretDirection.PageDown: newPos = ComputeLineOffset(newPos, (int) TextDrawArea.Height / Font.Height); break; case CaretDirection.TextStart: newPos = 0; break; case CaretDirection.TextEnd: newPos = Text.Length; break; } if (extend) { UpdateSelectionInternal(newPos); } else { SelectInternal(newPos, 0); CaretSetPosition(newPos); ScrollToCaretNoRedraw(); InvalidateDirty(); } }
private void UpdateCaretPosition(SelectionPoint sp, bool resetX, CaretDirection d) { Rectangle rc=Rectangle.Empty; if ( layoutEngine != null ) { using ( IGraphics gr=grFactory.CreateGraphics(this) ) rc=layoutEngine.GetCaretPosition(gr, sp, d); } if ( !rc.IsEmpty ) { SetCaretPosition(rc.Location, rc.Height); if ( resetX ) linePosition=rc.Left; caret.Visible=!currentSelection.IsRange; return; } // TODO: M: not sure why caret does not update when window resizing caret.Visible=false; Logger.Log("WARN: failed to get caret position"); }
private void UpdateCaretPosition(bool resetX, CaretDirection d) { if ( currentSelection.IsEmpty ) return; SelectionPoint sp=currentSelection.IsRange ? currentSelection.End : currentSelection.Start; UpdateCaretPosition(sp, resetX, d); }
protected abstract void MoveCaret(CaretDirection dir, bool extend);