#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
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; }
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)); }
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; }
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; }
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);
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.
{
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
}
{
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());
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;
}
long newCaretPosition = GetPosition() - 1;
// if (m_newParagraphs.GetPartialParagraph())
// newCaretPosition --;
-
+
UpdateAppearance(newCaretPosition, true /* send update event */);
break;
}
/// 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)
{
{
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();
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
!wxRichTextTabsEq(attr1.GetTabs(), attr2.GetTabs()))
return false;
+ if ((flags & wxTEXT_ATTR_PAGE_BREAK) &&
+ (attr1.HasPageBreak() != attr2.HasPageBreak()))
+ return false;
+
return true;
}
!wxRichTextTabsEq(attr1.GetTabs(), attr2.GetTabs()))
return false;
+ if ((flags & wxTEXT_ATTR_PAGE_BREAK) &&
+ (attr1.HasPageBreak() != attr2.HasPageBreak()))
+ return false;
+
return true;
}
if (style.HasURL())
destStyle.SetURL(style.GetURL());
+ if (style.HasPageBreak())
+ destStyle.SetPageBreak();
+
return true;
}
destStyle.SetURL(style.GetURL());
}
+ if (style.HasPageBreak())
+ {
+ if (!(compareWith && compareWith->HasPageBreak()))
+ destStyle.SetPageBreak();
+ }
+
return true;
}
if (attr.HasURL())
newAttr.SetURL(attr.GetURL());
+ if (attr.HasPageBreak())
+ newAttr.SetPageBreak();
+
return newAttr;
}
GetBuffer().Reset();
GetBuffer().SetRichTextCtrl(this);
+
+ SetCaret(new wxCaret(this, wxRICHTEXT_DEFAULT_CARET_WIDTH, 16));
+ GetCaret()->Show();
if (style & wxTE_READONLY)
SetEditable(false);
/// Painting
void wxRichTextCtrl::OnPaint(wxPaintEvent& WXUNUSED(event))
{
- if (GetCaret())
+ if (GetCaret() && GetCaret()->IsVisible())
GetCaret()->Hide();
{
// 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())
{
GetBuffer().Draw(dc, GetBuffer().GetRange(), GetInternalSelectionRange(), drawingArea, 0 /* descent */, 0 /* flags */);
}
- if (GetCaret())
+ if (GetCaret() && !GetCaret()->IsVisible())
GetCaret()->Show();
PositionCaret();
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
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.