From: Julian Smart Date: Mon, 30 Oct 2006 17:51:38 +0000 (+0000) Subject: Reuse wxCaret object X-Git-Url: https://git.saurik.com/wxWidgets.git/commitdiff_plain/ea160b2e92c787857cfb32673c5ff028f64db2a3 Reuse wxCaret object Add page break specification Optimize drawing after character input git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@42734 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- diff --git a/include/wx/richtext/richtextbuffer.h b/include/wx/richtext/richtextbuffer.h index 7b49ad732d..486d4a2607 100644 --- a/include/wx/richtext/richtextbuffer.h +++ b/include/wx/richtext/richtextbuffer.h @@ -198,6 +198,7 @@ class WXDLLIMPEXP_RICHTEXT wxRichTextBuffer; #define wxTEXT_ATTR_BULLET_TEXT 0x00080000 #define wxTEXT_ATTR_BULLET_NAME 0x00100000 #define wxTEXT_ATTR_URL 0x00200000 +#define wxTEXT_ATTR_PAGE_BREAK 0x00400000 /*! * Styles for wxTextAttrEx::SetBulletStyle @@ -346,6 +347,7 @@ public: void SetBulletName(const wxString& name) { m_bulletName = name; SetFlags(GetFlags() | wxTEXT_ATTR_BULLET_NAME); } void SetBulletFont(const wxString& bulletFont) { m_bulletFont = bulletFont; } void SetURL(const wxString& url) { m_urlTarget = url; SetFlags(GetFlags() | wxTEXT_ATTR_URL); } + void SetPageBreak(bool pageBreak = true) { SetFlags(pageBreak ? (GetFlags() | wxTEXT_ATTR_PAGE_BREAK) : (GetFlags() & ~wxTEXT_ATTR_PAGE_BREAK)); } const wxString& GetCharacterStyleName() const { return m_characterStyleName; } const wxString& GetParagraphStyleName() const { return m_paragraphStyleName; } @@ -377,6 +379,7 @@ public: bool HasBulletText() const { return HasFlag(wxTEXT_ATTR_BULLET_TEXT); } bool HasBulletName() const { return HasFlag(wxTEXT_ATTR_BULLET_NAME); } bool HasURL() const { return HasFlag(wxTEXT_ATTR_URL); } + bool HasPageBreak() const { return HasFlag(wxTEXT_ATTR_PAGE_BREAK); } // Is this a character style? bool IsCharacterStyle() const { return (0 != (GetFlags() & wxTEXT_ATTR_CHARACTER)); } @@ -492,6 +495,7 @@ public: void SetBulletFont(const wxString& bulletFont) { m_bulletFont = bulletFont; } void SetBulletName(const wxString& name) { m_bulletName = name; m_flags |= wxTEXT_ATTR_BULLET_NAME; } void SetURL(const wxString& url) { m_urlTarget = url; m_flags |= wxTEXT_ATTR_URL; } + void SetPageBreak(bool pageBreak = true) { SetFlags(pageBreak ? (GetFlags() | wxTEXT_ATTR_PAGE_BREAK) : (GetFlags() & ~wxTEXT_ATTR_PAGE_BREAK)); } const wxColour& GetTextColour() const { return m_colText; } const wxColour& GetBackgroundColour() const { return m_colBack; } @@ -546,6 +550,7 @@ public: bool HasBulletText() const { return (m_flags & wxTEXT_ATTR_BULLET_TEXT) != 0; } bool HasBulletName() const { return (m_flags & wxTEXT_ATTR_BULLET_NAME) != 0; } bool HasURL() const { return HasFlag(wxTEXT_ATTR_URL); } + bool HasPageBreak() const { return HasFlag(wxTEXT_ATTR_PAGE_BREAK); } bool HasFlag(long flag) const { return (m_flags & flag) != 0; } @@ -1985,7 +1990,8 @@ public: bool Undo(); /// Update the control appearance - void UpdateAppearance(long caretPosition, bool sendUpdateEvent = false); + void UpdateAppearance(long caretPosition, bool sendUpdateEvent = false, + wxArrayInt* optimizationLineCharPositions = NULL, wxArrayInt* optimizationLineYPositions = NULL); /// Replace the buffer paragraphs with the given fragment. void ApplyParagraphs(const wxRichTextParagraphLayoutBox& fragment); diff --git a/include/wx/richtext/richtextctrl.h b/include/wx/richtext/richtextctrl.h index fa3a217538..7e17b06b43 100644 --- a/include/wx/richtext/richtextctrl.h +++ b/include/wx/richtext/richtextctrl.h @@ -760,6 +760,9 @@ public: SetCaretPositionForDefaultStyle(GetCaretPosition()); } + /// Get the first visible point in the window + wxPoint GetFirstVisiblePoint() const; + // Implementation /// Font names take a long time to retrieve, so cache them (on demand) diff --git a/src/richtext/richtextbuffer.cpp b/src/richtext/richtextbuffer.cpp index 39129eb982..5730cbdeab 100644 --- a/src/richtext/richtextbuffer.cpp +++ b/src/richtext/richtextbuffer.cpp @@ -43,6 +43,9 @@ WX_DEFINE_LIST(wxRichTextObjectList) WX_DEFINE_LIST(wxRichTextLineList) +// Switch off if the platform doesn't like it for some reason +#define wxRICHTEXT_USE_OPTIMIZED_DRAWING 1 + /*! * wxRichTextObject * This is the base for drawable objects. @@ -526,7 +529,12 @@ bool wxRichTextParagraphLayoutBox::Draw(wxDC& dc, const wxRichTextRange& range, { wxRect childRect(child->GetPosition(), child->GetCachedSize()); - if (((style & wxRICHTEXT_DRAW_IGNORE_CACHE) == 0) && childRect.GetTop() > rect.GetBottom() || childRect.GetBottom() < rect.GetTop()) + if (((style & wxRICHTEXT_DRAW_IGNORE_CACHE) == 0) && childRect.GetTop() > rect.GetBottom()) + { + // Stop drawing + break; + } + else if (((style & wxRICHTEXT_DRAW_IGNORE_CACHE) == 0) && childRect.GetBottom() < rect.GetTop()) { // Skip } @@ -5911,6 +5919,57 @@ bool wxRichTextAction::Do() { case wxRICHTEXT_INSERT: { + // Store a list of line start character and y positions so we can figure out which area + // we need to refresh + wxArrayInt optimizationLineCharPositions; + wxArrayInt optimizationLineYPositions; + +#if wxRICHTEXT_USE_OPTIMIZED_DRAWING + // NOTE: we're assuming that the buffer is laid out correctly at this point. + // If we had several actions, which only invalidate and leave layout until the + // paint handler is called, then this might not be true. So we may need to switch + // optimisation on only when we're simply adding text and not simultaneously + // deleting a selection, for example. Or, we make sure the buffer is laid out correctly + // first, but of course this means we'll be doing it twice. + if (!m_buffer->GetDirty() && m_ctrl) // can only do optimisation if the buffer is already laid out correctly + { + wxSize clientSize = m_ctrl->GetClientSize(); + wxPoint firstVisiblePt = m_ctrl->GetFirstVisiblePoint(); + int lastY = firstVisiblePt.y + clientSize.y; + + wxRichTextParagraph* para = m_buffer->GetParagraphAtPosition(GetPosition()); + wxRichTextObjectList::compatibility_iterator node = m_buffer->GetChildren().Find(para); + while (node) + { + wxRichTextParagraph* child = (wxRichTextParagraph*) node->GetData(); + wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst(); + while (node2) + { + wxRichTextLine* line = node2->GetData(); + wxPoint pt = line->GetAbsolutePosition(); + wxRichTextRange range = line->GetAbsoluteRange(); + + if (pt.y > lastY) + { + node2 = NULL; + node = NULL; + } + else if (range.GetStart() > GetPosition() && pt.y >= firstVisiblePt.y) + { + optimizationLineCharPositions.Add(range.GetStart()); + optimizationLineYPositions.Add(pt.y); + } + + if (node2) + node2 = node2->GetNext(); + } + + if (node) + node = node->GetNext(); + } + } +#endif + m_buffer->InsertFragment(GetPosition(), m_newParagraphs); m_buffer->UpdateRanges(); m_buffer->Invalidate(GetRange()); @@ -5925,8 +5984,12 @@ bool wxRichTextAction::Do() newCaretPosition --; newCaretPosition = wxMin(newCaretPosition, (m_buffer->GetRange().GetEnd()-1)); + - UpdateAppearance(newCaretPosition, true /* send update event */); + if (optimizationLineCharPositions.GetCount() > 0) + UpdateAppearance(newCaretPosition, true /* send update event */, & optimizationLineCharPositions, & optimizationLineYPositions); + else + UpdateAppearance(newCaretPosition, true /* send update event */); break; } @@ -5971,7 +6034,7 @@ bool wxRichTextAction::Undo() long newCaretPosition = GetPosition() - 1; // if (m_newParagraphs.GetPartialParagraph()) // newCaretPosition --; - + UpdateAppearance(newCaretPosition, true /* send update event */); break; @@ -6003,7 +6066,7 @@ bool wxRichTextAction::Undo() } /// Update the control appearance -void wxRichTextAction::UpdateAppearance(long caretPosition, bool sendUpdateEvent) +void wxRichTextAction::UpdateAppearance(long caretPosition, bool sendUpdateEvent, wxArrayInt* optimizationLineCharPositions, wxArrayInt* optimizationLineYPositions) { if (m_ctrl) { @@ -6012,7 +6075,98 @@ void wxRichTextAction::UpdateAppearance(long caretPosition, bool sendUpdateEvent { m_ctrl->LayoutContent(); m_ctrl->PositionCaret(); - m_ctrl->Refresh(false); + +#if wxRICHTEXT_USE_OPTIMIZED_DRAWING + // Find refresh rectangle if we are in a position to optimise refresh + if (m_cmdId == wxRICHTEXT_INSERT && optimizationLineCharPositions && optimizationLineCharPositions->GetCount() > 0) + { + size_t i; + + wxSize clientSize = m_ctrl->GetClientSize(); + wxPoint firstVisiblePt = m_ctrl->GetFirstVisiblePoint(); + + // Start/end positions + int firstY = 0; + int lastY = firstVisiblePt.y + clientSize.y; + + bool foundStart = false; + bool foundEnd = false; + + // position offset - how many characters were inserted + int positionOffset = GetRange().GetLength(); + + // find the first line which is being drawn at the same position as it was + // before. Since we're talking about a simple insertion, we can assume + // that the rest of the window does not need to be redrawn. + + wxRichTextParagraph* para = m_buffer->GetParagraphAtPosition(GetPosition()); + wxRichTextObjectList::compatibility_iterator node = m_buffer->GetChildren().Find(para); + while (node) + { + wxRichTextParagraph* child = (wxRichTextParagraph*) node->GetData(); + wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst(); + while (node2) + { + wxRichTextLine* line = node2->GetData(); + wxPoint pt = line->GetAbsolutePosition(); + wxRichTextRange range = line->GetAbsoluteRange(); + + // we want to find the first line that is in the same position + // as before. This will mean we're at the end of the changed text. + + if (pt.y > lastY) // going past the end of the window, no more info + { + node2 = NULL; + node = NULL; + } + else + { + if (!foundStart) + { + firstY = pt.y - firstVisiblePt.y; + foundStart = true; + } + + // search for this line being at the same position as before + for (i = 0; i < optimizationLineCharPositions->GetCount(); i++) + { + if (((*optimizationLineCharPositions)[i] + positionOffset == range.GetStart()) && + ((*optimizationLineYPositions)[i] == pt.y)) + { + // Stop, we're now the same as we were + foundEnd = true; + lastY = pt.y - firstVisiblePt.y; + + node2 = NULL; + node = NULL; + break; + } + } + } + + if (node2) + node2 = node2->GetNext(); + } + + if (node) + node = node->GetNext(); + } + + if (!foundStart) + firstY = firstVisiblePt.y; + if (!foundEnd) + lastY = firstVisiblePt.y + clientSize.y; + + wxRect rect(firstVisiblePt.x, firstY, firstVisiblePt.x + clientSize.x, lastY - firstY); + m_ctrl->RefreshRect(rect); + + // TODO: we need to make sure that lines are only drawn if in the update region. The rect + // passed to Draw is currently used in different ways (to pass the position the content should + // be drawn at as well as the relevant region). + } + else +#endif + m_ctrl->Refresh(false); if (sendUpdateEvent) m_ctrl->SendTextUpdatedEvent(); @@ -6213,7 +6367,8 @@ bool wxTextAttrEq(const wxTextAttrEx& attr1, const wxRichTextAttr& attr2) attr1.GetBulletFont() == attr2.GetBulletFont() && attr1.GetCharacterStyleName() == attr2.GetCharacterStyleName() && attr1.GetParagraphStyleName() == attr2.GetParagraphStyleName() && - attr1.GetListStyleName() == attr2.GetListStyleName()); + attr1.GetListStyleName() == attr2.GetListStyleName() && + attr1.HasPageBreak() == attr2.HasPageBreak()); } /// Compare two attribute objects, but take into account the flags @@ -6302,6 +6457,10 @@ bool wxTextAttrEqPartial(const wxTextAttrEx& attr1, const wxTextAttrEx& attr2, i !wxRichTextTabsEq(attr1.GetTabs(), attr2.GetTabs())) return false; + if ((flags & wxTEXT_ATTR_PAGE_BREAK) && + (attr1.HasPageBreak() != attr2.HasPageBreak())) + return false; + return true; } @@ -6392,6 +6551,10 @@ bool wxTextAttrEqPartial(const wxTextAttrEx& attr1, const wxRichTextAttr& attr2, !wxRichTextTabsEq(attr1.GetTabs(), attr2.GetTabs())) return false; + if ((flags & wxTEXT_ATTR_PAGE_BREAK) && + (attr1.HasPageBreak() != attr2.HasPageBreak())) + return false; + return true; } @@ -6515,6 +6678,9 @@ bool wxRichTextApplyStyle(wxTextAttrEx& destStyle, const wxTextAttrEx& style) if (style.HasURL()) destStyle.SetURL(style.GetURL()); + if (style.HasPageBreak()) + destStyle.SetPageBreak(); + return true; } @@ -6729,6 +6895,12 @@ bool wxRichTextApplyStyle(wxTextAttrEx& destStyle, const wxRichTextAttr& style, destStyle.SetURL(style.GetURL()); } + if (style.HasPageBreak()) + { + if (!(compareWith && compareWith->HasPageBreak())) + destStyle.SetPageBreak(); + } + return true; } @@ -7106,6 +7278,9 @@ wxRichTextAttr wxRichTextAttr::Combine(const wxRichTextAttr& attr, if (attr.HasURL()) newAttr.SetURL(attr.GetURL()); + if (attr.HasPageBreak()) + newAttr.SetPageBreak(); + return newAttr; } diff --git a/src/richtext/richtextctrl.cpp b/src/richtext/richtextctrl.cpp index ae7de57722..ad6bccb6ef 100644 --- a/src/richtext/richtextctrl.cpp +++ b/src/richtext/richtextctrl.cpp @@ -130,6 +130,9 @@ bool wxRichTextCtrl::Create( wxWindow* parent, wxWindowID id, const wxString& va GetBuffer().Reset(); GetBuffer().SetRichTextCtrl(this); + + SetCaret(new wxCaret(this, wxRICHTEXT_DEFAULT_CARET_WIDTH, 16)); + GetCaret()->Show(); if (style & wxTE_READONLY) SetEditable(false); @@ -241,7 +244,7 @@ void wxRichTextCtrl::Clear() /// Painting void wxRichTextCtrl::OnPaint(wxPaintEvent& WXUNUSED(event)) { - if (GetCaret()) + if (GetCaret() && GetCaret()->IsVisible()) GetCaret()->Hide(); { @@ -260,7 +263,11 @@ void wxRichTextCtrl::OnPaint(wxPaintEvent& WXUNUSED(event)) // Paint the background PaintBackground(dc); - wxRect drawingArea(GetLogicalPoint(wxPoint(0, 0)), GetClientSize()); + // wxRect drawingArea(GetLogicalPoint(wxPoint(0, 0)), GetClientSize()); + + wxRect drawingArea(GetUpdateRegion().GetBox()); + drawingArea.SetPosition(GetLogicalPoint(drawingArea.GetPosition())); + wxRect availableSpace(GetClientSize()); if (GetBuffer().GetDirty()) { @@ -272,7 +279,7 @@ void wxRichTextCtrl::OnPaint(wxPaintEvent& WXUNUSED(event)) GetBuffer().Draw(dc, GetBuffer().GetRange(), GetInternalSelectionRange(), drawingArea, 0 /* descent */, 0 /* flags */); } - if (GetCaret()) + if (GetCaret() && !GetCaret()->IsVisible()) GetCaret()->Show(); PositionCaret(); @@ -285,21 +292,24 @@ void wxRichTextCtrl::OnEraseBackground(wxEraseEvent& WXUNUSED(event)) void wxRichTextCtrl::OnSetFocus(wxFocusEvent& WXUNUSED(event)) { - wxCaret* caret = new wxCaret(this, wxRICHTEXT_DEFAULT_CARET_WIDTH, 16); - SetCaret(caret); - caret->Show(); - PositionCaret(); + if (GetCaret()) + { + if (!GetCaret()->IsVisible()) + GetCaret()->Show(); + PositionCaret(); + } - if (!IsFrozen()) - Refresh(false); + // if (!IsFrozen()) + // Refresh(false); } void wxRichTextCtrl::OnKillFocus(wxFocusEvent& WXUNUSED(event)) { - SetCaret(NULL); + if (GetCaret() && GetCaret()->IsVisible()) + GetCaret()->Hide(); - if (!IsFrozen()) - Refresh(false); + // if (!IsFrozen()) + // Refresh(false); } /// Left-click @@ -2945,6 +2955,18 @@ long wxRichTextCtrl::GetFirstVisiblePosition() const return 0; } +/// Get the first visible point in the window +wxPoint wxRichTextCtrl::GetFirstVisiblePoint() const +{ + int ppuX, ppuY; + int startXUnits, startYUnits; + + GetScrollPixelsPerUnit(& ppuX, & ppuY); + GetViewStart(& startXUnits, & startYUnits); + + return wxPoint(startXUnits * ppuX, startYUnits * ppuY); +} + /// The adjusted caret position is the character position adjusted to take /// into account whether we're at the start of a paragraph, in which case /// style information should be taken from the next position, not current one. diff --git a/src/richtext/richtextprint.cpp b/src/richtext/richtextprint.cpp index 5319131f74..8652db7c63 100644 --- a/src/richtext/richtextprint.cpp +++ b/src/richtext/richtextprint.cpp @@ -80,18 +80,22 @@ void wxRichTextPrintout::OnPreparePrinting() // child is a paragraph wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph); wxASSERT (child != NULL); - + wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst(); while (node2) { wxRichTextLine* line = node2->GetData(); - + // Set the line to the page-adjusted position line->SetPosition(wxPoint(line->GetPosition().x, line->GetPosition().y - yOffset)); int lineY = child->GetPosition().y + line->GetPosition().y; - if ((lineY + line->GetSize().y) > rect.GetBottom()) + // Break the page if either we're going off the bottom, or this paragraph specifies + // an explicit page break + + if (((lineY + line->GetSize().y) > rect.GetBottom()) || + ((node2 == child->GetLines().GetFirst()) && child->GetAttributes().HasPageBreak())) { // New page starting at this line int newY = rect.y;