+/// Keyboard navigation
+
+/*
+
+Left: left one character
+Right: right one character
+Up: up one line
+Down: down one line
+Ctrl-Left: left one word
+Ctrl-Right: right one word
+Ctrl-Up: previous paragraph start
+Ctrl-Down: next start of paragraph
+Home: start of line
+End: end of line
+Ctrl-Home: start of document
+Ctrl-End: end of document
+Page-Up: Up a screen
+Page-Down: Down a screen
+
+Maybe:
+
+Ctrl-Alt-PgUp: Start of window
+Ctrl-Alt-PgDn: End of window
+F8: Start selection mode
+Esc: End selection mode
+
+Adding Shift does the above but starts/extends selection.
+
+
+ */
+
+bool wxRichTextCtrl::KeyboardNavigate(int keyCode, int flags)
+{
+ bool success = false;
+
+ if (keyCode == WXK_RIGHT || keyCode == WXK_NUMPAD_RIGHT)
+ {
+ if (flags & wxRICHTEXT_CTRL_DOWN)
+ success = WordRight(1, flags);
+ else
+ success = MoveRight(1, flags);
+ }
+ else if (keyCode == WXK_LEFT || keyCode == WXK_NUMPAD_LEFT)
+ {
+ if (flags & wxRICHTEXT_CTRL_DOWN)
+ success = WordLeft(1, flags);
+ else
+ success = MoveLeft(1, flags);
+ }
+ else if (keyCode == WXK_UP || keyCode == WXK_NUMPAD_UP)
+ {
+ if (flags & wxRICHTEXT_CTRL_DOWN)
+ success = MoveToParagraphStart(flags);
+ else
+ success = MoveUp(1, flags);
+ }
+ else if (keyCode == WXK_DOWN || keyCode == WXK_NUMPAD_DOWN)
+ {
+ if (flags & wxRICHTEXT_CTRL_DOWN)
+ success = MoveToParagraphEnd(flags);
+ else
+ success = MoveDown(1, flags);
+ }
+ else if (keyCode == WXK_PAGEUP || keyCode == WXK_NUMPAD_PAGEUP)
+ {
+ success = PageUp(1, flags);
+ }
+ else if (keyCode == WXK_PAGEDOWN || keyCode == WXK_NUMPAD_PAGEDOWN)
+ {
+ success = PageDown(1, flags);
+ }
+ else if (keyCode == WXK_HOME || keyCode == WXK_NUMPAD_HOME)
+ {
+ if (flags & wxRICHTEXT_CTRL_DOWN)
+ success = MoveHome(flags);
+ else
+ success = MoveToLineStart(flags);
+ }
+ else if (keyCode == WXK_END || keyCode == WXK_NUMPAD_END)
+ {
+ if (flags & wxRICHTEXT_CTRL_DOWN)
+ success = MoveEnd(flags);
+ else
+ success = MoveToLineEnd(flags);
+ }
+
+ if (success)
+ {
+ ScrollIntoView(m_caretPosition, keyCode);
+ SetDefaultStyleToCursorStyle();
+ }
+
+ return success;
+}
+
+/// Extend the selection. Selections are in caret positions.
+bool wxRichTextCtrl::ExtendSelection(long oldPos, long newPos, int flags)
+{
+ if (flags & wxRICHTEXT_SHIFT_DOWN)
+ {
+ if (oldPos == newPos)
+ return false;
+
+ wxRichTextSelection oldSelection = m_selection;
+
+ m_selection.SetContainer(GetFocusObject());
+
+ wxRichTextRange oldRange;
+ if (m_selection.IsValid())
+ oldRange = m_selection.GetRange();
+ else
+ oldRange = wxRICHTEXT_NO_SELECTION;
+ wxRichTextRange newRange;
+
+ // If not currently selecting, start selecting
+ if (oldRange.GetStart() == -2)
+ {
+ m_selectionAnchor = oldPos;
+
+ if (oldPos > newPos)
+ newRange.SetRange(newPos+1, oldPos);
+ else
+ newRange.SetRange(oldPos+1, newPos);
+ }
+ else
+ {
+ // Always ensure that the selection range start is greater than
+ // the end.
+ if (newPos > m_selectionAnchor)
+ newRange.SetRange(m_selectionAnchor+1, newPos);
+ else if (newPos == m_selectionAnchor)
+ newRange = wxRichTextRange(-2, -2);
+ else
+ newRange.SetRange(newPos+1, m_selectionAnchor);
+ }
+
+ m_selection.SetRange(newRange);
+
+ RefreshForSelectionChange(oldSelection, m_selection);
+
+ if (newRange.GetStart() > newRange.GetEnd())
+ {
+ wxLogDebug(wxT("Strange selection range"));
+ }
+
+ return true;
+ }
+ else
+ return false;
+}
+
+/// Scroll into view, returning true if we scrolled.
+/// This takes a _caret_ position.
+bool wxRichTextCtrl::ScrollIntoView(long position, int keyCode)
+{
+ wxRichTextLine* line = GetVisibleLineForCaretPosition(position);
+
+ if (!line)
+ return false;
+
+ int ppuX, ppuY;
+ GetScrollPixelsPerUnit(& ppuX, & ppuY);
+
+ int startXUnits, startYUnits;
+ GetViewStart(& startXUnits, & startYUnits);
+ int startY = startYUnits * ppuY;
+
+ int sx = 0, sy = 0;
+ GetVirtualSize(& sx, & sy);
+ int sxUnits = 0;
+ int syUnits = 0;
+ if (ppuY != 0)
+ syUnits = sy/ppuY;
+
+ wxRect rect = GetScaledRect(line->GetRect());
+
+ bool scrolled = false;
+
+ wxSize clientSize = GetClientSize();
+
+ int leftMargin, rightMargin, topMargin, bottomMargin;
+
+ {
+ wxClientDC dc(this);
+ wxRichTextObject::GetTotalMargin(dc, & GetBuffer(), GetBuffer().GetAttributes(), leftMargin, rightMargin,
+ topMargin, bottomMargin);
+ }
+ clientSize.y -= (int) (0.5 + bottomMargin * GetScale());
+
+ if (GetWindowStyle() & wxRE_CENTRE_CARET)
+ {
+ int y = rect.y - GetClientSize().y/2;
+ int yUnits = (int) (0.5 + ((float) y)/(float) ppuY);
+ if (y >= 0 && (y + clientSize.y) < (int) (0.5 + GetBuffer().GetCachedSize().y * GetScale()))
+ {
+ if (startYUnits != yUnits)
+ {
+ SetScrollbars(ppuX, ppuY, sxUnits, syUnits, 0, yUnits);
+ scrolled = true;
+ }
+#if !wxRICHTEXT_USE_OWN_CARET
+ if (scrolled)
+#endif
+ PositionCaret();
+
+ return scrolled;
+ }
+ }
+
+ // Going down
+ if (keyCode == WXK_DOWN || keyCode == WXK_NUMPAD_DOWN ||
+ keyCode == WXK_RIGHT || keyCode == WXK_NUMPAD_RIGHT ||
+ keyCode == WXK_END || keyCode == WXK_NUMPAD_END ||
+ keyCode == WXK_PAGEDOWN || keyCode == WXK_NUMPAD_PAGEDOWN)
+ {
+ if ((rect.y + rect.height) > (clientSize.y + startY))
+ {
+ // Make it scroll so this item is at the bottom
+ // of the window
+ int y = rect.y - (clientSize.y - rect.height);
+ int yUnits = (int) (0.5 + ((float) y)/(float) ppuY);
+
+ // If we're still off the screen, scroll another line down
+ if ((rect.y + rect.height) > (clientSize.y + (yUnits*ppuY)))
+ yUnits ++;
+
+ if (startYUnits != yUnits)
+ {
+ SetScrollbars(ppuX, ppuY, sxUnits, syUnits, 0, yUnits);
+ scrolled = true;
+ }
+ }
+ else if (rect.y < (startY + (int) (0.5 + GetBuffer().GetTopMargin() * GetScale())))
+ {
+ // Make it scroll so this item is at the top
+ // of the window
+ int y = rect.y - (int) (0.5 + GetBuffer().GetTopMargin() * GetScale());
+ int yUnits = (int) (0.5 + ((float) y)/(float) ppuY);
+
+ if (startYUnits != yUnits)
+ {
+ SetScrollbars(ppuX, ppuY, sxUnits, syUnits, 0, yUnits);
+ scrolled = true;
+ }
+ }
+ }
+ // Going up
+ else if (keyCode == WXK_UP || keyCode == WXK_NUMPAD_UP ||
+ keyCode == WXK_LEFT || keyCode == WXK_NUMPAD_LEFT ||
+ keyCode == WXK_HOME || keyCode == WXK_NUMPAD_HOME ||
+ keyCode == WXK_PAGEUP || keyCode == WXK_NUMPAD_PAGEUP )
+ {
+ if (rect.y < (startY + (int) (0.5 + GetBuffer().GetBottomMargin() * GetScale())))
+ {
+ // Make it scroll so this item is at the top
+ // of the window
+ int y = rect.y - (int) (0.5 + GetBuffer().GetTopMargin() * GetScale());
+ int yUnits = (int) (0.5 + ((float) y)/(float) ppuY);
+
+ if (startYUnits != yUnits)
+ {
+ SetScrollbars(ppuX, ppuY, sxUnits, syUnits, 0, yUnits);
+ scrolled = true;
+ }
+ }
+ else if ((rect.y + rect.height) > (clientSize.y + startY))
+ {
+ // Make it scroll so this item is at the bottom
+ // of the window
+ int y = rect.y - (clientSize.y - rect.height);
+ int yUnits = (int) (0.5 + ((float) y)/(float) ppuY);
+
+ // If we're still off the screen, scroll another line down
+ if ((rect.y + rect.height) > (clientSize.y + (yUnits*ppuY)))
+ yUnits ++;
+
+ if (startYUnits != yUnits)
+ {
+ SetScrollbars(ppuX, ppuY, sxUnits, syUnits, 0, yUnits);
+ scrolled = true;
+ }
+ }
+ }
+
+#if !wxRICHTEXT_USE_OWN_CARET
+ if (scrolled)
+#endif
+ PositionCaret();
+
+ return scrolled;
+}
+
+/// Is the given position visible on the screen?
+bool wxRichTextCtrl::IsPositionVisible(long pos) const
+{
+ wxRichTextLine* line = GetVisibleLineForCaretPosition(pos-1);
+
+ if (!line)
+ return false;
+
+ int ppuX, ppuY;
+ GetScrollPixelsPerUnit(& ppuX, & ppuY);
+
+ int startX, startY;
+ GetViewStart(& startX, & startY);
+ startX = 0;
+ startY = startY * ppuY;
+
+ wxRect rect = GetScaledRect(line->GetRect());
+ wxSize clientSize = GetClientSize();
+ clientSize.y -= (int) (0.5 + GetBuffer().GetBottomMargin() * GetScale());
+
+ return (rect.GetTop() >= (startY + (int) (0.5 + GetBuffer().GetTopMargin() * GetScale()))) &&
+ (rect.GetBottom() <= (startY + clientSize.y));
+}
+
+void wxRichTextCtrl::SetCaretPosition(long position, bool showAtLineStart)
+{
+ m_caretPosition = position;
+ m_caretAtLineStart = showAtLineStart;
+}
+
+/// Move caret one visual step forward: this may mean setting a flag
+/// and keeping the same position if we're going from the end of one line
+/// to the start of the next, which may be the exact same caret position.
+void wxRichTextCtrl::MoveCaretForward(long oldPosition)
+{
+ wxRichTextParagraph* para = GetFocusObject()->GetParagraphAtPosition(oldPosition);
+
+ // Only do the check if we're not at the end of the paragraph (where things work OK
+ // anyway)
+ if (para && (oldPosition != para->GetRange().GetEnd() - 1))
+ {
+ wxRichTextLine* line = GetFocusObject()->GetLineAtPosition(oldPosition);
+
+ if (line)
+ {
+ wxRichTextRange lineRange = line->GetAbsoluteRange();
+
+ // We're at the end of a line. See whether we need to
+ // stay at the same actual caret position but change visual
+ // position, or not.
+ if (oldPosition == lineRange.GetEnd())
+ {
+ if (m_caretAtLineStart)
+ {
+ // We're already at the start of the line, so actually move on now.
+ m_caretPosition = oldPosition + 1;
+ m_caretAtLineStart = false;
+ }
+ else
+ {
+ // We're showing at the end of the line, so keep to
+ // the same position but indicate that we're to show
+ // at the start of the next line.
+ m_caretPosition = oldPosition;
+ m_caretAtLineStart = true;
+ }
+ SetDefaultStyleToCursorStyle();
+ return;
+ }
+ }
+ }
+ m_caretPosition ++;
+ SetDefaultStyleToCursorStyle();
+}
+
+/// Move caret one visual step backward: this may mean setting a flag
+/// and keeping the same position if we're going from the end of one line
+/// to the start of the next, which may be the exact same caret position.
+void wxRichTextCtrl::MoveCaretBack(long oldPosition)
+{
+ wxRichTextParagraph* para = GetFocusObject()->GetParagraphAtPosition(oldPosition);
+
+ // Only do the check if we're not at the start of the paragraph (where things work OK
+ // anyway)
+ if (para && (oldPosition != para->GetRange().GetStart()))
+ {
+ wxRichTextLine* line = GetFocusObject()->GetLineAtPosition(oldPosition);
+
+ if (line)
+ {
+ wxRichTextRange lineRange = line->GetAbsoluteRange();
+
+ // We're at the start of a line. See whether we need to
+ // stay at the same actual caret position but change visual
+ // position, or not.
+ if (oldPosition == lineRange.GetStart())
+ {
+ m_caretPosition = oldPosition-1;
+ m_caretAtLineStart = true;
+ return;
+ }
+ else if (oldPosition == lineRange.GetEnd())
+ {
+ if (m_caretAtLineStart)
+ {
+ // We're at the start of the line, so keep the same caret position
+ // but clear the start-of-line flag.
+ m_caretPosition = oldPosition;
+ m_caretAtLineStart = false;
+ }
+ else
+ {
+ // We're showing at the end of the line, so go back
+ // to the previous character position.
+ m_caretPosition = oldPosition - 1;
+ }
+ SetDefaultStyleToCursorStyle();
+ return;
+ }
+ }
+ }
+ m_caretPosition --;
+ SetDefaultStyleToCursorStyle();
+}
+
+/// Move right
+bool wxRichTextCtrl::MoveRight(int noPositions, int flags)
+{
+ long endPos = GetFocusObject()->GetOwnRange().GetEnd();
+
+ if (m_caretPosition + noPositions < endPos)
+ {
+ long oldPos = m_caretPosition;
+ long newPos = m_caretPosition + noPositions;
+
+ bool extendSel = ExtendSelection(m_caretPosition, newPos, flags);
+ if (!extendSel)
+ SelectNone();
+
+ // Determine by looking at oldPos and m_caretPosition whether
+ // we moved from the end of a line to the start of the next line, in which case
+ // we want to adjust the caret position such that it is positioned at the
+ // start of the next line, rather than jumping past the first character of the
+ // line.
+ if (noPositions == 1)
+ MoveCaretForward(oldPos);
+ else
+ SetCaretPosition(newPos);
+
+ PositionCaret();
+ SetDefaultStyleToCursorStyle();
+
+ return true;
+ }
+ else
+ return false;
+}
+
+/// Move left
+bool wxRichTextCtrl::MoveLeft(int noPositions, int flags)
+{
+ long startPos = -1;
+
+ if (m_caretPosition > startPos - noPositions + 1)
+ {
+ long oldPos = m_caretPosition;
+ long newPos = m_caretPosition - noPositions;
+ bool extendSel = ExtendSelection(m_caretPosition, newPos, flags);
+ if (!extendSel)
+ SelectNone();
+
+ if (noPositions == 1)
+ MoveCaretBack(oldPos);
+ else
+ SetCaretPosition(newPos);
+
+ PositionCaret();
+ SetDefaultStyleToCursorStyle();
+
+ return true;
+ }
+ else
+ return false;
+}
+
+// Find the caret position for the combination of hit-test flags and character position.
+// Returns the caret position and also an indication of where to place the caret (caretLineStart)
+// since this is ambiguous (same position used for end of line and start of next).
+long wxRichTextCtrl::FindCaretPositionForCharacterPosition(long position, int hitTestFlags, wxRichTextParagraphLayoutBox* container,
+ bool& caretLineStart)
+{
+ // If end of previous line, and hitTest is wxRICHTEXT_HITTEST_BEFORE,
+ // we want to be at the end of the last line but with m_caretAtLineStart set to true,
+ // so we view the caret at the start of the line.
+ caretLineStart = false;
+ long caretPosition = position;
+
+ if (hitTestFlags & wxRICHTEXT_HITTEST_BEFORE)
+ {
+ wxRichTextLine* thisLine = container->GetLineAtPosition(position-1);
+ wxRichTextRange lineRange;
+ if (thisLine)
+ lineRange = thisLine->GetAbsoluteRange();
+
+ if (thisLine && (position-1) == lineRange.GetEnd())
+ {
+ caretPosition --;
+ caretLineStart = true;
+ }
+ else
+ {
+ wxRichTextParagraph* para = container->GetParagraphAtPosition(position);
+ if (para && para->GetRange().GetStart() == position)
+ caretPosition --;
+ }
+ }
+ return caretPosition;
+}
+
+/// Move up
+bool wxRichTextCtrl::MoveUp(int noLines, int flags)
+{
+ return MoveDown(- noLines, flags);
+}
+
+/// Move up
+bool wxRichTextCtrl::MoveDown(int noLines, int flags)
+{
+ if (!GetCaret())
+ return false;
+
+ long lineNumber = GetFocusObject()->GetVisibleLineNumber(m_caretPosition, true, m_caretAtLineStart);
+ wxPoint pt = GetCaret()->GetPosition();
+ long newLine = lineNumber + noLines;
+ bool notInThisObject = false;
+
+ if (lineNumber != -1)
+ {
+ if (noLines > 0)
+ {
+ long lastLine = GetFocusObject()->GetVisibleLineNumber(GetFocusObject()->GetOwnRange().GetEnd());
+ if (newLine > lastLine)
+ notInThisObject = true;
+ }
+ else
+ {
+ if (newLine < 0)
+ notInThisObject = true;
+ }
+ }
+
+ wxRichTextParagraphLayoutBox* container = GetFocusObject();
+ int hitTestFlags = wxRICHTEXT_HITTEST_NO_NESTED_OBJECTS|wxRICHTEXT_HITTEST_NO_FLOATING_OBJECTS|wxRICHTEXT_HITTEST_HONOUR_ATOMIC;
+
+ bool lineIsEmpty = false;
+ if (notInThisObject)
+ {
+ // If we know we're navigating out of the current object,
+ // try to find an object anywhere in the buffer at the new position (up or down a bit)
+ container = & GetBuffer();
+ hitTestFlags &= ~wxRICHTEXT_HITTEST_NO_NESTED_OBJECTS;
+
+ if (noLines > 0) // going down
+ {
+ pt.y = GetFocusObject()->GetPosition().y + GetFocusObject()->GetCachedSize().y + 2;
+ }
+ else // going up
+ {
+ pt.y = GetFocusObject()->GetPosition().y - 2;
+ }
+ }
+ else
+ {
+ wxRichTextLine* lineObj = GetFocusObject()->GetLineForVisibleLineNumber(newLine);
+ if (lineObj)
+ {
+ pt.y = lineObj->GetAbsolutePosition().y + 2;
+ if (lineObj->GetRange().GetStart() == lineObj->GetRange().GetEnd())
+ lineIsEmpty = true;
+ }
+ else
+ return false;
+ }
+
+ long newPos = 0;
+ wxClientDC dc(this);
+ PrepareDC(dc);
+ dc.SetFont(GetFont());
+
+ wxRichTextObject* hitObj = NULL;
+ wxRichTextObject* contextObj = NULL;
+ wxRichTextDrawingContext context(& GetBuffer());
+ int hitTest = container->HitTest(dc, context, pt, newPos, & hitObj, & contextObj, hitTestFlags);
+
+ if (hitObj &&
+ ((hitTest & wxRICHTEXT_HITTEST_NONE) == 0) &&
+ (! (hitObj == (& m_buffer) && ((hitTest & wxRICHTEXT_HITTEST_OUTSIDE) != 0))) // outside the buffer counts as 'do nothing'
+ )
+ {
+ if (notInThisObject)
+ {
+ wxRichTextParagraphLayoutBox* actualContainer = wxDynamicCast(contextObj, wxRichTextParagraphLayoutBox);
+ if (actualContainer && actualContainer != GetFocusObject() && actualContainer->AcceptsFocus())
+ {
+ SetFocusObject(actualContainer, false /* don't set caret position yet */);
+
+ container = actualContainer;
+ }
+ }
+
+ bool caretLineStart = true;
+
+ // If the line is empty, there is only one possible position for the caret,
+ // so force the 'before' state so FindCaretPositionForCharacterPosition doesn't
+ // just return the same position.
+ if (lineIsEmpty)
+ {
+ hitTest &= ~wxRICHTEXT_HITTEST_AFTER;
+ hitTest |= wxRICHTEXT_HITTEST_BEFORE;
+ }
+ long caretPosition = FindCaretPositionForCharacterPosition(newPos, hitTest, container, caretLineStart);
+ long newSelEnd = caretPosition;
+ bool extendSel;
+
+ if (notInThisObject)
+ extendSel = false;
+ else
+ extendSel = ExtendSelection(m_caretPosition, newSelEnd, flags);
+
+ if (!extendSel)
+ SelectNone();
+
+ SetCaretPosition(caretPosition, caretLineStart);
+ PositionCaret();
+ SetDefaultStyleToCursorStyle();
+
+ return true;
+ }
+
+ return false;
+}
+
+/// Move to the end of the paragraph
+bool wxRichTextCtrl::MoveToParagraphEnd(int flags)
+{
+ wxRichTextParagraph* para = GetFocusObject()->GetParagraphAtPosition(m_caretPosition, true);
+ if (para)
+ {
+ long newPos = para->GetRange().GetEnd() - 1;
+ bool extendSel = ExtendSelection(m_caretPosition, newPos, flags);
+ if (!extendSel)
+ SelectNone();
+
+ SetCaretPosition(newPos);
+ PositionCaret();
+ SetDefaultStyleToCursorStyle();
+
+ return true;
+ }
+
+ return false;
+}
+
+/// Move to the start of the paragraph
+bool wxRichTextCtrl::MoveToParagraphStart(int flags)
+{
+ wxRichTextParagraph* para = GetFocusObject()->GetParagraphAtPosition(m_caretPosition, true);
+ if (para)
+ {
+ long newPos = para->GetRange().GetStart() - 1;
+ bool extendSel = ExtendSelection(m_caretPosition, newPos, flags);
+ if (!extendSel)
+ SelectNone();
+
+ SetCaretPosition(newPos, true);
+ PositionCaret();
+ SetDefaultStyleToCursorStyle();
+
+ return true;
+ }
+
+ return false;
+}
+
+/// Move to the end of the line
+bool wxRichTextCtrl::MoveToLineEnd(int flags)
+{
+ wxRichTextLine* line = GetVisibleLineForCaretPosition(m_caretPosition);
+
+ if (line)
+ {
+ wxRichTextRange lineRange = line->GetAbsoluteRange();
+ long newPos = lineRange.GetEnd();
+ bool extendSel = ExtendSelection(m_caretPosition, newPos, flags);
+ if (!extendSel)
+ SelectNone();
+
+ SetCaretPosition(newPos);
+ PositionCaret();
+ SetDefaultStyleToCursorStyle();
+
+ return true;
+ }
+
+ return false;
+}
+
+/// Move to the start of the line
+bool wxRichTextCtrl::MoveToLineStart(int flags)
+{
+ wxRichTextLine* line = GetVisibleLineForCaretPosition(m_caretPosition);
+ if (line)
+ {
+ wxRichTextRange lineRange = line->GetAbsoluteRange();
+ long newPos = lineRange.GetStart()-1;
+
+ bool extendSel = ExtendSelection(m_caretPosition, newPos, flags);
+ if (!extendSel)
+ SelectNone();
+
+ wxRichTextParagraph* para = GetFocusObject()->GetParagraphForLine(line);
+
+ SetCaretPosition(newPos, para->GetRange().GetStart() != lineRange.GetStart());
+ PositionCaret();
+ SetDefaultStyleToCursorStyle();
+
+ return true;
+ }
+
+ return false;
+}
+
+/// Move to the start of the buffer
+bool wxRichTextCtrl::MoveHome(int flags)
+{
+ if (m_caretPosition != -1)
+ {
+ bool extendSel = ExtendSelection(m_caretPosition, -1, flags);
+ if (!extendSel)
+ SelectNone();
+
+ SetCaretPosition(-1);
+ PositionCaret();
+ SetDefaultStyleToCursorStyle();
+
+ return true;
+ }
+ else
+ return false;
+}
+
+/// Move to the end of the buffer
+bool wxRichTextCtrl::MoveEnd(int flags)
+{
+ long endPos = GetFocusObject()->GetOwnRange().GetEnd()-1;
+
+ if (m_caretPosition != endPos)
+ {
+ bool extendSel = ExtendSelection(m_caretPosition, endPos, flags);
+ if (!extendSel)
+ SelectNone();
+
+ SetCaretPosition(endPos);
+ PositionCaret();
+ SetDefaultStyleToCursorStyle();
+
+ return true;
+ }
+ else
+ return false;
+}
+
+/// Move noPages pages up
+bool wxRichTextCtrl::PageUp(int noPages, int flags)
+{
+ return PageDown(- noPages, flags);
+}
+
+/// Move noPages pages down
+bool wxRichTextCtrl::PageDown(int noPages, int flags)
+{
+ // Calculate which line occurs noPages * screen height further down.
+ wxRichTextLine* line = GetVisibleLineForCaretPosition(m_caretPosition);
+ if (line)
+ {
+ wxSize clientSize = GetClientSize();
+ int newY = line->GetAbsolutePosition().y + noPages*clientSize.y;
+
+ wxRichTextLine* newLine = GetFocusObject()->GetLineAtYPosition(newY);
+ if (newLine)
+ {
+ wxRichTextRange lineRange = newLine->GetAbsoluteRange();
+ long pos = lineRange.GetStart()-1;
+ if (pos != m_caretPosition)
+ {
+ wxRichTextParagraph* para = GetFocusObject()->GetParagraphForLine(newLine);
+
+ bool extendSel = ExtendSelection(m_caretPosition, pos, flags);
+ if (!extendSel)
+ SelectNone();
+
+ SetCaretPosition(pos, para->GetRange().GetStart() != lineRange.GetStart());
+ PositionCaret();
+ SetDefaultStyleToCursorStyle();
+
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+static bool wxRichTextCtrlIsWhitespace(const wxString& str)
+{
+ return str == wxT(" ") || str == wxT("\t") || (!str.empty() && (str[0] == (wxChar) 160));
+}
+
+// Finds the caret position for the next word
+long wxRichTextCtrl::FindNextWordPosition(int direction) const
+{
+ long endPos = GetFocusObject()->GetOwnRange().GetEnd();
+
+ if (direction > 0)
+ {
+ long i = m_caretPosition+1+direction; // +1 for conversion to character pos
+
+ // First skip current text to space
+ while (i < endPos && i > -1)
+ {
+ // i is in character, not caret positions
+ wxString text = GetFocusObject()->GetTextForRange(wxRichTextRange(i, i));
+ wxRichTextLine* line = GetFocusObject()->GetLineAtPosition(i, false);
+ if (line && (i == line->GetAbsoluteRange().GetEnd()))
+ {
+ break;
+ }
+ else if (!wxRichTextCtrlIsWhitespace(text) && !text.empty())
+ i += direction;
+ else
+ {
+ break;
+ }
+ }
+ while (i < endPos && i > -1)
+ {
+ // i is in character, not caret positions
+ wxString text = GetFocusObject()->GetTextForRange(wxRichTextRange(i, i));
+ wxRichTextLine* line = GetFocusObject()->GetLineAtPosition(i, false);
+ if (line && (i == line->GetAbsoluteRange().GetEnd()))
+ return wxMax(-1, i);
+
+ if (text.empty()) // End of paragraph, or maybe an image
+ return wxMax(-1, i - 1);
+ else if (wxRichTextCtrlIsWhitespace(text) || text.empty())
+ i += direction;
+ else
+ {
+ // Convert to caret position
+ return wxMax(-1, i - 1);
+ }
+ }
+ if (i >= endPos)
+ return endPos-1;
+ return i-1;
+ }
+ else
+ {
+ long i = m_caretPosition;
+
+ // First skip white space
+ while (i < endPos && i > -1)
+ {
+ // i is in character, not caret positions
+ wxString text = GetFocusObject()->GetTextForRange(wxRichTextRange(i, i));
+ wxRichTextLine* line = GetFocusObject()->GetLineAtPosition(i, false);
+
+ if (text.empty() || (line && (i == line->GetAbsoluteRange().GetStart()))) // End of paragraph, or maybe an image
+ break;
+ else if (wxRichTextCtrlIsWhitespace(text) || text.empty())
+ i += direction;
+ else
+ break;
+ }
+ // Next skip current text to space
+ while (i < endPos && i > -1)
+ {
+ // i is in character, not caret positions
+ wxString text = GetFocusObject()->GetTextForRange(wxRichTextRange(i, i));
+ wxRichTextLine* line = GetFocusObject()->GetLineAtPosition(i, false);
+ if (line && line->GetAbsoluteRange().GetStart() == i)
+ return i-1;
+
+ if (!wxRichTextCtrlIsWhitespace(text) /* && !text.empty() */)
+ i += direction;
+ else
+ {
+ return i;
+ }
+ }
+ if (i < -1)
+ return -1;
+ return i;
+ }
+}
+
+/// Move n words left
+bool wxRichTextCtrl::WordLeft(int WXUNUSED(n), int flags)
+{
+ long pos = FindNextWordPosition(-1);
+ if (pos != m_caretPosition)
+ {
+ wxRichTextParagraph* para = GetFocusObject()->GetParagraphAtPosition(pos, true);
+
+ bool extendSel = ExtendSelection(m_caretPosition, pos, flags);
+ if (!extendSel)
+ SelectNone();
+
+ SetCaretPosition(pos, para->GetRange().GetStart() != pos);
+ PositionCaret();
+ SetDefaultStyleToCursorStyle();
+
+ return true;
+ }
+
+ return false;
+}
+
+/// Move n words right
+bool wxRichTextCtrl::WordRight(int WXUNUSED(n), int flags)
+{
+ long pos = FindNextWordPosition(1);
+ if (pos != m_caretPosition)
+ {
+ wxRichTextParagraph* para = GetFocusObject()->GetParagraphAtPosition(pos, true);
+
+ bool extendSel = ExtendSelection(m_caretPosition, pos, flags);
+ if (!extendSel)
+ SelectNone();
+
+ SetCaretPosition(pos, para->GetRange().GetStart() != pos);
+ PositionCaret();
+ SetDefaultStyleToCursorStyle();
+
+ return true;
+ }
+
+ return false;
+}
+
+/// Sizing
+void wxRichTextCtrl::OnSize(wxSizeEvent& event)
+{
+ // Only do sizing optimization for large buffers
+ if (GetBuffer().GetOwnRange().GetEnd() > m_delayedLayoutThreshold)
+ {
+ m_fullLayoutRequired = true;
+ m_fullLayoutTime = wxGetLocalTimeMillis();
+ m_fullLayoutSavedPosition = GetFirstVisiblePosition();
+ LayoutContent(true /* onlyVisibleRect */);
+ }
+ else
+ GetBuffer().Invalidate(wxRICHTEXT_ALL);
+
+#if wxRICHTEXT_BUFFERED_PAINTING
+ RecreateBuffer();
+#endif
+
+ event.Skip();
+}
+
+// Force any pending layout due to large buffer
+void wxRichTextCtrl::ForceDelayedLayout()
+{
+ if (m_fullLayoutRequired)
+ {
+ m_fullLayoutRequired = false;
+ m_fullLayoutTime = 0;
+ GetBuffer().Invalidate(wxRICHTEXT_ALL);
+ ShowPosition(m_fullLayoutSavedPosition);
+ Refresh(false);
+ Update();
+ }
+}
+
+/// Idle-time processing
+void wxRichTextCtrl::OnIdle(wxIdleEvent& event)
+{
+#if wxRICHTEXT_USE_OWN_CARET
+ if (((wxRichTextCaret*) GetCaret())->GetNeedsUpdate())
+ {
+ ((wxRichTextCaret*) GetCaret())->SetNeedsUpdate(false);
+ PositionCaret();
+ GetCaret()->Show();
+ }
+#endif
+
+ const int layoutInterval = wxRICHTEXT_DEFAULT_LAYOUT_INTERVAL;
+
+ if (m_fullLayoutRequired && (wxGetLocalTimeMillis() > (m_fullLayoutTime + layoutInterval)))
+ {
+ m_fullLayoutRequired = false;
+ m_fullLayoutTime = 0;
+ GetBuffer().Invalidate(wxRICHTEXT_ALL);
+ ShowPosition(m_fullLayoutSavedPosition);
+ Refresh(false);
+ }
+
+ if (m_caretPositionForDefaultStyle != -2)
+ {
+ // If the caret position has changed, no longer reflect the default style
+ // in the UI.
+ if (GetCaretPosition() != m_caretPositionForDefaultStyle)
+ m_caretPositionForDefaultStyle = -2;
+ }
+
+ event.Skip();
+}
+
+/// Scrolling
+void wxRichTextCtrl::OnScroll(wxScrollWinEvent& event)
+{
+#if wxRICHTEXT_USE_OWN_CARET
+ if (!((wxRichTextCaret*) GetCaret())->GetNeedsUpdate())
+ {
+ GetCaret()->Hide();
+ ((wxRichTextCaret*) GetCaret())->SetNeedsUpdate();
+ }
+#endif
+
+ event.Skip();
+}
+
+/// Set up scrollbars, e.g. after a resize
+void wxRichTextCtrl::SetupScrollbars(bool atTop)
+{
+ if (IsFrozen())
+ return;
+
+ if (GetBuffer().IsEmpty() || !m_verticalScrollbarEnabled)
+ {
+ SetScrollbars(0, 0, 0, 0, 0, 0);
+ return;
+ }
+
+ // TODO: reimplement scrolling so we scroll by line, not by fixed number
+ // of pixels. See e.g. wxVScrolledWindow for ideas.
+ int pixelsPerUnit = 5;
+ wxSize clientSize = GetClientSize();
+
+ int maxHeight = (int) (0.5 + GetScale() * (GetBuffer().GetCachedSize().y + GetBuffer().GetTopMargin()));
+
+ // Round up so we have at least maxHeight pixels
+ int unitsY = (int) (((float)maxHeight/(float)pixelsPerUnit) + 0.5);
+
+ int startX = 0, startY = 0;
+ if (!atTop)
+ GetViewStart(& startX, & startY);
+
+ int maxPositionX = 0;
+ int maxPositionY = (int) ((((float)(wxMax((unitsY*pixelsPerUnit) - clientSize.y, 0)))/((float)pixelsPerUnit)) + 0.5);
+
+ int newStartX = wxMin(maxPositionX, startX);
+ int newStartY = wxMin(maxPositionY, startY);
+
+ int oldPPUX, oldPPUY;
+ int oldStartX, oldStartY;
+ int oldVirtualSizeX = 0, oldVirtualSizeY = 0;
+ GetScrollPixelsPerUnit(& oldPPUX, & oldPPUY);
+ GetViewStart(& oldStartX, & oldStartY);
+ GetVirtualSize(& oldVirtualSizeX, & oldVirtualSizeY);
+ if (oldPPUY > 0)
+ oldVirtualSizeY /= oldPPUY;
+
+ if (oldPPUX == 0 && oldPPUY == pixelsPerUnit && oldVirtualSizeY == unitsY && oldStartX == newStartX && oldStartY == newStartY)
+ return;
+
+ // Don't set scrollbars if there were none before, and there will be none now.
+ if (oldPPUY != 0 && (oldVirtualSizeY*oldPPUY < clientSize.y) && (unitsY*pixelsPerUnit < clientSize.y))
+ return;
+
+ // Move to previous scroll position if
+ // possible
+ SetScrollbars(0, pixelsPerUnit, 0, unitsY, newStartX, newStartY);
+}
+
+/// Paint the background
+void wxRichTextCtrl::PaintBackground(wxDC& dc)
+{
+ wxColour backgroundColour = GetBackgroundColour();
+ if (!backgroundColour.IsOk())
+ backgroundColour = wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE);
+
+ // Clear the background
+ dc.SetBrush(wxBrush(backgroundColour));
+ dc.SetPen(*wxTRANSPARENT_PEN);
+ wxRect windowRect(GetClientSize());
+ windowRect.x -= 2; windowRect.y -= 2;
+ windowRect.width += 4; windowRect.height += 4;
+
+ // We need to shift the rectangle to take into account
+ // scrolling. Converting device to logical coordinates.
+ CalcUnscrolledPosition(windowRect.x, windowRect.y, & windowRect.x, & windowRect.y);
+ dc.DrawRectangle(windowRect);
+}
+
+#if wxRICHTEXT_BUFFERED_PAINTING
+/// Recreate buffer bitmap if necessary
+bool wxRichTextCtrl::RecreateBuffer(const wxSize& size)
+{
+ wxSize sz = size;
+ if (sz == wxDefaultSize)
+ sz = GetClientSize();
+
+ if (sz.x < 1 || sz.y < 1)
+ return false;
+
+ if (!m_bufferBitmap.IsOk() || m_bufferBitmap.GetWidth() < sz.x || m_bufferBitmap.GetHeight() < sz.y)
+ m_bufferBitmap = wxBitmap(sz.x, sz.y);
+ return m_bufferBitmap.IsOk();
+}
+#endif
+
+// ----------------------------------------------------------------------------
+// file IO functions
+// ----------------------------------------------------------------------------
+#if wxUSE_FFILE && wxUSE_STREAMS
+bool wxRichTextCtrl::DoLoadFile(const wxString& filename, int fileType)
+{
+ SetFocusObject(& GetBuffer(), true);
+
+ bool success = GetBuffer().LoadFile(filename, (wxRichTextFileType)fileType);
+ if (success)
+ m_filename = filename;
+
+ DiscardEdits();
+ SetInsertionPoint(0);
+ LayoutContent();
+ PositionCaret();
+ SetupScrollbars(true);
+ Refresh(false);
+ wxTextCtrl::SendTextUpdatedEvent(this);
+
+ if (success)
+ return true;
+ else
+ {
+ wxLogError(_("File couldn't be loaded."));
+
+ return false;
+ }
+}
+
+bool wxRichTextCtrl::DoSaveFile(const wxString& filename, int fileType)
+{
+ if (GetBuffer().SaveFile(filename, (wxRichTextFileType)fileType))
+ {
+ m_filename = filename;
+
+ DiscardEdits();
+
+ return true;
+ }
+
+ wxLogError(_("The text couldn't be saved."));
+
+ return false;
+}
+#endif // wxUSE_FFILE && wxUSE_STREAMS
+
+// ----------------------------------------------------------------------------
+// wxRichTextCtrl specific functionality
+// ----------------------------------------------------------------------------
+
+/// Add a new paragraph of text to the end of the buffer
+wxRichTextRange wxRichTextCtrl::AddParagraph(const wxString& text)
+{
+ wxRichTextRange range = GetFocusObject()->AddParagraph(text);
+ GetBuffer().Invalidate();
+ LayoutContent();
+ return range;
+}
+
+/// Add an image
+wxRichTextRange wxRichTextCtrl::AddImage(const wxImage& image)
+{
+ wxRichTextRange range = GetFocusObject()->AddImage(image);
+ GetBuffer().Invalidate();
+ LayoutContent();
+ return range;
+}
+
+// ----------------------------------------------------------------------------
+// selection and ranges
+// ----------------------------------------------------------------------------
+
+/// Select none
+void wxRichTextCtrl::SelectNone()
+{
+ if (m_selection.IsValid())
+ {
+ wxRichTextSelection oldSelection = m_selection;
+
+ m_selection.Reset();
+
+ RefreshForSelectionChange(oldSelection, m_selection);
+ }
+ m_selectionAnchor = -2;
+ m_selectionAnchorObject = NULL;
+ m_selectionState = wxRichTextCtrlSelectionState_Normal;
+}
+
+static bool wxIsWordDelimiter(const wxString& text)
+{
+ return !text.IsEmpty() && !wxIsalnum(text[0]);
+}
+
+/// Select the word at the given character position
+bool wxRichTextCtrl::SelectWord(long position)
+{
+ if (position < 0 || position > GetFocusObject()->GetOwnRange().GetEnd())
+ return false;
+
+ wxRichTextParagraph* para = GetFocusObject()->GetParagraphAtPosition(position);
+ if (!para)
+ return false;
+
+ if (position == para->GetRange().GetEnd())
+ position --;
+
+ long positionStart = position;
+ long positionEnd = position;
+
+ for (positionStart = position; positionStart >= para->GetRange().GetStart(); positionStart --)
+ {
+ wxString text = GetFocusObject()->GetTextForRange(wxRichTextRange(positionStart, positionStart));
+ if (wxIsWordDelimiter(text))
+ {
+ positionStart ++;
+ break;
+ }
+ }
+ if (positionStart < para->GetRange().GetStart())
+ positionStart = para->GetRange().GetStart();
+
+ for (positionEnd = position; positionEnd < para->GetRange().GetEnd(); positionEnd ++)
+ {
+ wxString text = GetFocusObject()->GetTextForRange(wxRichTextRange(positionEnd, positionEnd));
+ if (wxIsWordDelimiter(text))
+ {
+ positionEnd --;
+ break;
+ }
+ }
+ if (positionEnd >= para->GetRange().GetEnd())
+ positionEnd = para->GetRange().GetEnd();
+
+ if (positionEnd < positionStart)
+ return false;
+
+ SetSelection(positionStart, positionEnd+1);
+
+ if (positionStart >= 0)
+ {
+ MoveCaret(positionStart-1, true);
+ SetDefaultStyleToCursorStyle();
+ }
+
+ return true;
+}
+
+wxString wxRichTextCtrl::GetStringSelection() const
+{
+ long from, to;
+ GetSelection(&from, &to);
+
+ return GetRange(from, to);
+}
+
+// ----------------------------------------------------------------------------
+// hit testing
+// ----------------------------------------------------------------------------
+
+wxTextCtrlHitTestResult
+wxRichTextCtrl::HitTest(const wxPoint& pt, wxTextCoord *x, wxTextCoord *y) const
+{
+ // implement in terms of the other overload as the native ports typically
+ // can get the position and not (x, y) pair directly (although wxUniv
+ // directly gets x and y -- and so overrides this method as well)
+ long pos;
+ wxTextCtrlHitTestResult rc = HitTest(pt, &pos);
+
+ if ( rc != wxTE_HT_UNKNOWN )
+ {
+ PositionToXY(pos, x, y);
+ }
+
+ return rc;
+}
+
+wxTextCtrlHitTestResult
+wxRichTextCtrl::HitTest(const wxPoint& pt,
+ long * pos) const