///////////////////////////////////////////////////////////////////////////// // Name: src/richtext/richtextbuffer.cpp // Purpose: Buffer for wxRichTextCtrl // Author: Julian Smart // Modified by: // Created: 2005-09-30 // Copyright: (c) Julian Smart // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// // For compilers that support precompilation, includes "wx.h". #include "wx/wxprec.h" #ifdef __BORLANDC__ #pragma hdrstop #endif #if wxUSE_RICHTEXT #include "wx/richtext/richtextbuffer.h" #ifndef WX_PRECOMP #include "wx/dc.h" #include "wx/intl.h" #include "wx/log.h" #include "wx/dataobj.h" #include "wx/module.h" #endif #include "wx/settings.h" #include "wx/filename.h" #include "wx/clipbrd.h" #include "wx/wfstream.h" #include "wx/mstream.h" #include "wx/sstream.h" #include "wx/textfile.h" #include "wx/hashmap.h" #include "wx/dynarray.h" #include "wx/richtext/richtextctrl.h" #include "wx/richtext/richtextstyles.h" #include "wx/richtext/richtextimagedlg.h" #include "wx/richtext/richtextsizepage.h" #include "wx/richtext/richtextxml.h" #include "wx/listimpl.cpp" #include "wx/arrimpl.cpp" 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 // Use GetPartialTextExtents for platforms that support it natively #define wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS 1 const wxChar wxRichTextLineBreakChar = (wxChar) 29; // Helper classes for floating layout struct wxRichTextFloatRectMap { wxRichTextFloatRectMap(int sY, int eY, int w, wxRichTextObject* obj) { startY = sY; endY = eY; width = w; anchor = obj; } int startY, endY; int width; wxRichTextObject* anchor; }; WX_DEFINE_SORTED_ARRAY(wxRichTextFloatRectMap*, wxRichTextFloatRectMapArray); int wxRichTextFloatRectMapCmp(wxRichTextFloatRectMap* r1, wxRichTextFloatRectMap* r2) { return r1->startY - r2->startY; } class wxRichTextFloatCollector { public: wxRichTextFloatCollector(const wxRect& availableRect); ~wxRichTextFloatCollector(); // Collect the floating objects info in the given paragraph void CollectFloat(wxRichTextParagraph* para); void CollectFloat(wxRichTextParagraph* para, wxRichTextObject* floating); // Return the last paragraph we collected wxRichTextParagraph* LastParagraph(); // Given the start y position and the height of the line, // find out how wide the line can be wxRect GetAvailableRect(int startY, int endY); // Given a floating box, find its fit position int GetFitPosition(int direction, int start, int height) const; int GetFitPosition(const wxRichTextFloatRectMapArray& array, int start, int height) const; // Find the last y position int GetLastRectBottom(); // Draw the floats inside a rect void Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style); // HitTest the floats int HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, int flags); // Get floating object count int GetFloatingObjectCount() const { return m_left.GetCount() + m_right.GetCount(); } // Get floating objects bool GetFloatingObjects(wxRichTextObjectList& objects) const; // Delete a float bool DeleteFloat(wxRichTextObject* obj); // Do we have this float already? bool HasFloat(wxRichTextObject* obj); bool HasFloats() const { return m_left.GetCount() >0 || m_right.GetCount() > 0; } static int SearchAdjacentRect(const wxRichTextFloatRectMapArray& array, int point); static int GetWidthFromFloatRect(const wxRichTextFloatRectMapArray& array, int index, int startY, int endY); static void FreeFloatRectMapArray(wxRichTextFloatRectMapArray& array); static void DrawFloat(const wxRichTextFloatRectMapArray& array, wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style); static int HitTestFloat(const wxRichTextFloatRectMapArray& array, wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, int flags); private: wxRichTextFloatRectMapArray m_left; wxRichTextFloatRectMapArray m_right; //int m_width; wxRect m_availableRect; wxRichTextParagraph* m_para; }; // Delete a float bool wxRichTextFloatCollector::DeleteFloat(wxRichTextObject* obj) { size_t i; for (i = 0; i < m_left.GetCount(); i++) { if (m_left[i]->anchor == obj) { m_left.RemoveAt(i); return true; } } for (i = 0; i < m_right.GetCount(); i++) { if (m_right[i]->anchor == obj) { m_right.RemoveAt(i); return true; } } return false; } // Do we have this float already? bool wxRichTextFloatCollector::HasFloat(wxRichTextObject* obj) { size_t i; for (i = 0; i < m_left.GetCount(); i++) { if (m_left[i]->anchor == obj) { return true; } } for (i = 0; i < m_right.GetCount(); i++) { if (m_right[i]->anchor == obj) { return true; } } return false; } // Get floating objects bool wxRichTextFloatCollector::GetFloatingObjects(wxRichTextObjectList& objects) const { size_t i; for (i = 0; i < m_left.GetCount(); i++) objects.Append(m_left[i]->anchor); for (i = 0; i < m_right.GetCount(); i++) objects.Append(m_right[i]->anchor); return true; } /* * Binary search helper function * The argument point is the Y coordinate, and this fuction * always return the floating rect that contain this coordinate * or under this coordinate. */ int wxRichTextFloatCollector::SearchAdjacentRect(const wxRichTextFloatRectMapArray& array, int point) { int end = array.GetCount() - 1; int start = 0; int ret = 0; wxASSERT(end >= 0); while (true) { if (start > end) { break; } int mid = (start + end) / 2; if (array[mid]->startY <= point && array[mid]->endY >= point) return mid; else if (array[mid]->startY > point) { end = mid - 1; ret = mid; } else if (array[mid]->endY < point) { start = mid + 1; ret = start; } } return ret; } int wxRichTextFloatCollector::GetWidthFromFloatRect(const wxRichTextFloatRectMapArray& array, int index, int startY, int endY) { int ret = 0; int len = array.GetCount(); wxASSERT(index >= 0 && index < len); if (array[index]->startY < startY && array[index]->endY > startY) ret = ret < array[index]->width ? array[index]->width : ret; while (index < len && array[index]->startY <= endY) { ret = ret < array[index]->width ? array[index]->width : ret; index++; } return ret; } wxRichTextFloatCollector::wxRichTextFloatCollector(const wxRect& rect) : m_left(wxRichTextFloatRectMapCmp), m_right(wxRichTextFloatRectMapCmp) { m_availableRect = rect; m_para = NULL; } void wxRichTextFloatCollector::FreeFloatRectMapArray(wxRichTextFloatRectMapArray& array) { int len = array.GetCount(); for (int i = 0; i < len; i++) delete array[i]; } wxRichTextFloatCollector::~wxRichTextFloatCollector() { FreeFloatRectMapArray(m_left); FreeFloatRectMapArray(m_right); } int wxRichTextFloatCollector::GetFitPosition(const wxRichTextFloatRectMapArray& array, int start, int height) const { if (array.GetCount() == 0) return start; int i = SearchAdjacentRect(array, start); int last = start; while (i < (int) array.GetCount()) { if (array[i]->startY - last >= height) return last + 1; last = array[i]->endY; i++; } return last + 1; } int wxRichTextFloatCollector::GetFitPosition(int direction, int start, int height) const { if (direction == wxTEXT_BOX_ATTR_FLOAT_LEFT) return GetFitPosition(m_left, start, height); else if (direction == wxTEXT_BOX_ATTR_FLOAT_RIGHT) return GetFitPosition(m_right, start, height); else { wxASSERT("Never should be here"); return start; } } // Adds a floating image to the float collector. // The actual positioning is done by wxRichTextParagraph::LayoutFloat. void wxRichTextFloatCollector::CollectFloat(wxRichTextParagraph* para, wxRichTextObject* floating) { int direction = floating->GetFloatDirection(); wxPoint pos = floating->GetPosition(); wxSize size = floating->GetCachedSize(); wxRichTextFloatRectMap *map = new wxRichTextFloatRectMap(pos.y, pos.y + size.y, size.x, floating); switch (direction) { case wxTEXT_BOX_ATTR_FLOAT_NONE: delete map; break; case wxTEXT_BOX_ATTR_FLOAT_LEFT: // Just a not-enough simple assertion wxASSERT (m_left.Index(map) == wxNOT_FOUND); m_left.Add(map); break; case wxTEXT_BOX_ATTR_FLOAT_RIGHT: wxASSERT (m_right.Index(map) == wxNOT_FOUND); m_right.Add(map); break; default: delete map; wxASSERT("Unrecognised float attribute."); } m_para = para; } void wxRichTextFloatCollector::CollectFloat(wxRichTextParagraph* para) { wxRichTextObjectList::compatibility_iterator node = para->GetChildren().GetFirst(); while (node) { wxRichTextObject* floating = node->GetData(); if (floating->IsFloating()) { CollectFloat(para, floating); } node = node->GetNext(); } m_para = para; } wxRichTextParagraph* wxRichTextFloatCollector::LastParagraph() { return m_para; } wxRect wxRichTextFloatCollector::GetAvailableRect(int startY, int endY) { int widthLeft = 0, widthRight = 0; if (m_left.GetCount() != 0) { int i = SearchAdjacentRect(m_left, startY); if (i < (int) m_left.GetCount()) widthLeft = GetWidthFromFloatRect(m_left, i, startY, endY); } if (m_right.GetCount() != 0) { int j = SearchAdjacentRect(m_right, startY); if (j < (int) m_right.GetCount()) widthRight = GetWidthFromFloatRect(m_right, j, startY, endY); } // TODO: actually we want to use the actual image positions to find the // available remaining space, since the image might not be right up against // the left or right edge of the container. return wxRect(widthLeft + m_availableRect.x, 0, m_availableRect.width - widthLeft - widthRight, 0); } int wxRichTextFloatCollector::GetLastRectBottom() { int ret = 0; int len = m_left.GetCount(); if (len) { ret = ret > m_left[len-1]->endY ? ret : m_left[len-1]->endY; } len = m_right.GetCount(); if (len) { ret = ret > m_right[len-1]->endY ? ret : m_right[len-1]->endY; } return ret; } void wxRichTextFloatCollector::DrawFloat(const wxRichTextFloatRectMapArray& array, wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& WXUNUSED(range), const wxRichTextSelection& selection, const wxRect& rect, int descent, int style) { int start = rect.y; int end = rect.y + rect.height; int i, j; i = SearchAdjacentRect(array, start); if (i < 0 || i >= (int) array.GetCount()) return; j = SearchAdjacentRect(array, end); if (j < 0 || j >= (int) array.GetCount()) j = array.GetCount() - 1; while (i <= j) { wxRichTextObject* obj = array[i]->anchor; wxRichTextRange r = obj->GetRange(); obj->Draw(dc, context, r, selection, wxRect(obj->GetPosition(), obj->GetCachedSize()), descent, style); i++; } } void wxRichTextFloatCollector::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style) { if (m_left.GetCount() > 0) DrawFloat(m_left, dc, context, range, selection, rect, descent, style); if (m_right.GetCount() > 0) DrawFloat(m_right, dc, context, range, selection, rect, descent, style); } int wxRichTextFloatCollector::HitTestFloat(const wxRichTextFloatRectMapArray& array, wxDC& WXUNUSED(dc), wxRichTextDrawingContext& WXUNUSED(context), const wxPoint& pt, long& textPosition, wxRichTextObject** obj, int WXUNUSED(flags)) { int i; if (array.GetCount() == 0) return wxRICHTEXT_HITTEST_NONE; i = SearchAdjacentRect(array, pt.y); if (i < 0 || i >= (int) array.GetCount()) return wxRICHTEXT_HITTEST_NONE; if (!array[i]->anchor->IsShown()) return wxRICHTEXT_HITTEST_NONE; wxPoint point = array[i]->anchor->GetPosition(); wxSize size = array[i]->anchor->GetCachedSize(); if (point.x <= pt.x && point.x + size.x >= pt.x && point.y <= pt.y && point.y + size.y >= pt.y) { textPosition = array[i]->anchor->GetRange().GetStart(); * obj = array[i]->anchor; if (pt.x > (pt.x + pt.x + size.x) / 2) return wxRICHTEXT_HITTEST_BEFORE; else return wxRICHTEXT_HITTEST_AFTER; } return wxRICHTEXT_HITTEST_NONE; } int wxRichTextFloatCollector::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, int flags) { int ret = HitTestFloat(m_left, dc, context, pt, textPosition, obj, flags); if (ret == wxRICHTEXT_HITTEST_NONE) { ret = HitTestFloat(m_right, dc, context, pt, textPosition, obj, flags); } return ret; } // Helpers for efficiency inline void wxCheckSetFont(wxDC& dc, const wxFont& font) { dc.SetFont(font); } inline void wxCheckSetPen(wxDC& dc, const wxPen& pen) { const wxPen& pen1 = dc.GetPen(); if (pen1.IsOk() && pen.IsOk()) { if (pen1.GetWidth() == pen.GetWidth() && pen1.GetStyle() == pen.GetStyle() && pen1.GetColour() == pen.GetColour()) return; } dc.SetPen(pen); } inline void wxCheckSetBrush(wxDC& dc, const wxBrush& brush) { const wxBrush& brush1 = dc.GetBrush(); if (brush1.IsOk() && brush.IsOk()) { if (brush1.GetStyle() == brush.GetStyle() && brush1.GetColour() == brush.GetColour()) return; } dc.SetBrush(brush); } /*! * wxRichTextObject * This is the base for drawable objects. */ IMPLEMENT_CLASS(wxRichTextObject, wxObject) wxRichTextObject::wxRichTextObject(wxRichTextObject* parent) { m_refCount = 1; m_parent = parent; m_descent = 0; m_show = true; } wxRichTextObject::~wxRichTextObject() { } void wxRichTextObject::Dereference() { m_refCount --; if (m_refCount <= 0) delete this; } /// Copy void wxRichTextObject::Copy(const wxRichTextObject& obj) { m_size = obj.m_size; m_maxSize = obj.m_maxSize; m_minSize = obj.m_minSize; m_pos = obj.m_pos; m_range = obj.m_range; m_ownRange = obj.m_ownRange; m_attributes = obj.m_attributes; m_properties = obj.m_properties; m_descent = obj.m_descent; m_show = obj.m_show; } // Get/set the top-level container of this object. wxRichTextParagraphLayoutBox* wxRichTextObject::GetContainer() const { const wxRichTextObject* p = this; while (p) { if (p->IsTopLevel()) { return wxDynamicCast(p, wxRichTextParagraphLayoutBox); } p = p->GetParent(); } return NULL; } void wxRichTextObject::SetMargins(int margin) { SetMargins(margin, margin, margin, margin); } void wxRichTextObject::SetMargins(int leftMargin, int rightMargin, int topMargin, int bottomMargin) { GetAttributes().GetTextBoxAttr().GetMargins().GetLeft().SetValue(leftMargin, wxTEXT_ATTR_UNITS_PIXELS); GetAttributes().GetTextBoxAttr().GetMargins().GetRight().SetValue(rightMargin, wxTEXT_ATTR_UNITS_PIXELS); GetAttributes().GetTextBoxAttr().GetMargins().GetTop().SetValue(topMargin, wxTEXT_ATTR_UNITS_PIXELS); GetAttributes().GetTextBoxAttr().GetMargins().GetBottom().SetValue(bottomMargin, wxTEXT_ATTR_UNITS_PIXELS); } int wxRichTextObject::GetLeftMargin() const { return GetAttributes().GetTextBoxAttr().GetMargins().GetLeft().GetValue(); } int wxRichTextObject::GetRightMargin() const { return GetAttributes().GetTextBoxAttr().GetMargins().GetRight().GetValue(); } int wxRichTextObject::GetTopMargin() const { return GetAttributes().GetTextBoxAttr().GetMargins().GetTop().GetValue(); } int wxRichTextObject::GetBottomMargin() const { return GetAttributes().GetTextBoxAttr().GetMargins().GetBottom().GetValue(); } // Calculate the available content space in the given rectangle, given the // margins, border and padding specified in the object's attributes. wxRect wxRichTextObject::GetAvailableContentArea(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& outerRect) const { wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect; marginRect = outerRect; wxRichTextAttr attr(GetAttributes()); ((wxRichTextObject*)this)->AdjustAttributes(attr, context); GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect); return contentRect; } // Invalidate the buffer. With no argument, invalidates whole buffer. void wxRichTextObject::Invalidate(const wxRichTextRange& invalidRange) { if (invalidRange != wxRICHTEXT_NONE) { // If this is a floating object, size may not be recalculated // after floats have been collected in an early stage of Layout. // So avoid resetting the cache for floating objects during layout. if (!IsFloating() || !wxRichTextBuffer::GetFloatingLayoutMode()) SetCachedSize(wxDefaultSize); SetMaxSize(wxDefaultSize); SetMinSize(wxDefaultSize); } } // Convert units in tenths of a millimetre to device units int wxRichTextObject::ConvertTenthsMMToPixels(wxDC& dc, int units) const { // Unscale double scale = 1.0; if (GetBuffer()) scale = GetBuffer()->GetScale() / GetBuffer()->GetDimensionScale(); int p = ConvertTenthsMMToPixels(dc.GetPPI().x, units, scale); return p; } // Convert units in tenths of a millimetre to device units int wxRichTextObject::ConvertTenthsMMToPixels(int ppi, int units, double scale) { // There are ppi pixels in 254.1 "1/10 mm" double pixels = ((double) units * (double)ppi) / 254.1; if (scale != 1.0) pixels /= scale; int pixelsInt = int(pixels + 0.5); // If the result is very small, make it at least one pixel in size. if (pixelsInt == 0 && units > 0) pixelsInt = 1; return pixelsInt; } // Convert units in pixels to tenths of a millimetre int wxRichTextObject::ConvertPixelsToTenthsMM(wxDC& dc, int pixels) const { int p = pixels; double scale = 1.0; if (GetBuffer()) scale = GetBuffer()->GetScale(); return ConvertPixelsToTenthsMM(dc.GetPPI().x, p, scale); } int wxRichTextObject::ConvertPixelsToTenthsMM(int ppi, int pixels, double scale) { // There are ppi pixels in 254.1 "1/10 mm" double p = double(pixels); if (scale != 1.0) p *= scale; int units = int( p * 254.1 / (double) ppi ); return units; } // Draw the borders and background for the given rectangle and attributes. // Width and height are taken to be the outer margin size, not the content. bool wxRichTextObject::DrawBoxAttributes(wxDC& dc, wxRichTextBuffer* buffer, const wxRichTextAttr& attr, const wxRect& boxRect, int flags, wxRichTextObject* obj) { // Assume boxRect is the area around the content wxRect marginRect = boxRect; wxRect contentRect, borderRect, paddingRect, outlineRect; GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect); // Margin is transparent. Draw background from margin. if (attr.HasBackgroundColour() || (flags & wxRICHTEXT_DRAW_SELECTED)) { wxColour colour; if (flags & wxRICHTEXT_DRAW_SELECTED) { // TODO: get selection colour from control? colour = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT); } else colour = attr.GetBackgroundColour(); wxPen pen(colour); wxBrush brush(colour); dc.SetPen(pen); dc.SetBrush(brush); dc.DrawRectangle(borderRect); } if (flags & wxRICHTEXT_DRAW_GUIDELINES) { wxRichTextAttr editBorderAttr; // TODO: make guideline colour configurable editBorderAttr.GetTextBoxAttr().GetBorder().SetColour(*wxLIGHT_GREY); editBorderAttr.GetTextBoxAttr().GetBorder().SetWidth(1, wxTEXT_ATTR_UNITS_PIXELS); editBorderAttr.GetTextBoxAttr().GetBorder().SetStyle(wxTEXT_BOX_ATTR_BORDER_SOLID); if (obj) { wxRichTextCell* cell = wxDynamicCast(obj, wxRichTextCell); if (cell) { // This ensures that thin lines drawn by adjacent cells (left and above) // don't get overwritten by the guidelines. editBorderAttr.GetTextBoxAttr().GetBorder().GetLeft().Reset(); editBorderAttr.GetTextBoxAttr().GetBorder().GetTop().Reset(); } } DrawBorder(dc, buffer, editBorderAttr.GetTextBoxAttr().GetBorder(), borderRect, flags); } if (attr.GetTextBoxAttr().GetBorder().IsValid()) DrawBorder(dc, buffer, attr.GetTextBoxAttr().GetBorder(), borderRect); if (attr.GetTextBoxAttr().GetOutline().IsValid()) DrawBorder(dc, buffer, attr.GetTextBoxAttr().GetOutline(), outlineRect); return true; } // Draw a border bool wxRichTextObject::DrawBorder(wxDC& dc, wxRichTextBuffer* buffer, const wxTextAttrBorders& attr, const wxRect& rect, int WXUNUSED(flags)) { int borderLeft = 0, borderRight = 0, borderTop = 0, borderBottom = 0; wxTextAttrDimensionConverter converter(dc, buffer ? buffer->GetScale() : 1.0); if (attr.GetLeft().IsValid() && attr.GetLeft().GetStyle() != wxTEXT_BOX_ATTR_BORDER_NONE) { borderLeft = converter.GetPixels(attr.GetLeft().GetWidth()); wxColour col(attr.GetLeft().GetColour()); // If pen width is > 1, resorts to a solid rectangle. if (borderLeft == 1) { int penStyle = wxSOLID; if (attr.GetLeft().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DOTTED) penStyle = wxDOT; else if (attr.GetLeft().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DASHED) penStyle = wxLONG_DASH; wxPen pen(col, 1, penStyle); dc.SetPen(pen); // Note that the last point is not drawn. dc.DrawLine(rect.x, rect.y, rect.x, rect.y + rect.height); } else if (borderLeft > 1) { wxPen pen(col); wxBrush brush(col); dc.SetPen(pen); dc.SetBrush(brush); dc.DrawRectangle(rect.x, rect.y, borderLeft, rect.height); } } if (attr.GetRight().IsValid() && attr.GetRight().GetStyle() != wxTEXT_BOX_ATTR_BORDER_NONE) { borderRight = converter.GetPixels(attr.GetRight().GetWidth()); wxColour col(attr.GetRight().GetColour()); // If pen width is > 1, resorts to a solid rectangle. if (borderRight == 1) { int penStyle = wxSOLID; if (attr.GetRight().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DOTTED) penStyle = wxDOT; else if (attr.GetRight().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DASHED) penStyle = wxLONG_DASH; wxPen pen(col, 1, penStyle); dc.SetPen(pen); // Note that the last point is not drawn. dc.DrawLine(rect.x + rect.width - 1, rect.y, rect.x + rect.width - 1, rect.y + rect.height); } else if (borderRight > 1) { wxPen pen(col); wxBrush brush(col); dc.SetPen(pen); dc.SetBrush(brush); dc.DrawRectangle(rect.x + rect.width - borderRight, rect.y, borderRight, rect.height); } } if (attr.GetTop().IsValid() && attr.GetTop().GetStyle() != wxTEXT_BOX_ATTR_BORDER_NONE) { borderTop = converter.GetPixels(attr.GetTop().GetWidth()); wxColour col(attr.GetTop().GetColour()); // If pen width is > 1, resorts to a solid rectangle. if (borderTop == 1) { int penStyle = wxSOLID; if (attr.GetTop().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DOTTED) penStyle = wxDOT; else if (attr.GetTop().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DASHED) penStyle = wxLONG_DASH; wxPen pen(col, 1, penStyle); dc.SetPen(pen); dc.DrawLine(rect.x, rect.y, rect.x + rect.width, rect.y); } else if (borderTop > 1) { wxPen pen(col); wxBrush brush(col); dc.SetPen(pen); dc.SetBrush(brush); dc.DrawRectangle(rect.x, rect.y, rect.width, borderTop); } } if (attr.GetBottom().IsValid() && attr.GetBottom().GetStyle() != wxTEXT_BOX_ATTR_BORDER_NONE) { borderBottom = converter.GetPixels(attr.GetBottom().GetWidth()); wxColour col(attr.GetBottom().GetColour()); // If pen width is > 1, resorts to a solid rectangle. if (borderBottom == 1) { int penStyle = wxSOLID; if (attr.GetBottom().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DOTTED) penStyle = wxDOT; else if (attr.GetBottom().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DASHED) penStyle = wxLONG_DASH; wxPen pen(col, 1, penStyle); dc.SetPen(pen); dc.DrawLine(rect.x, rect.y + rect.height - 1, rect.x + rect.width, rect.y + rect.height - 1); } else if (borderBottom > 1) { wxPen pen(col); wxBrush brush(col); dc.SetPen(pen); dc.SetBrush(brush); dc.DrawRectangle(rect.x, rect.y + rect.height - borderBottom, rect.width, borderBottom); } } return true; } // Get the various rectangles of the box model in pixels. You can either specify contentRect (inner) // or marginRect (outer), and the other must be the default rectangle (no width or height). // Note that the outline doesn't affect the position of the rectangle, it's drawn in whatever space // is available. // // | Margin | Border | Padding | CONTENT | Padding | Border | Margin | bool wxRichTextObject::GetBoxRects(wxDC& dc, wxRichTextBuffer* buffer, const wxRichTextAttr& attr, wxRect& marginRect, wxRect& borderRect, wxRect& contentRect, wxRect& paddingRect, wxRect& outlineRect) { int borderLeft = 0, borderRight = 0, borderTop = 0, borderBottom = 0; int outlineLeft = 0, outlineRight = 0, outlineTop = 0, outlineBottom = 0; int paddingLeft = 0, paddingRight = 0, paddingTop = 0, paddingBottom = 0; int marginLeft = 0, marginRight = 0, marginTop = 0, marginBottom = 0; wxTextAttrDimensionConverter converter(dc, buffer ? buffer->GetScale() : 1.0); if (attr.GetTextBoxAttr().GetMargins().GetLeft().IsValid()) marginLeft = converter.GetPixels(attr.GetTextBoxAttr().GetMargins().GetLeft()); if (attr.GetTextBoxAttr().GetMargins().GetRight().IsValid()) marginRight = converter.GetPixels(attr.GetTextBoxAttr().GetMargins().GetRight()); if (attr.GetTextBoxAttr().GetMargins().GetTop().IsValid()) marginTop = converter.GetPixels(attr.GetTextBoxAttr().GetMargins().GetTop()); if (attr.GetTextBoxAttr().GetMargins().GetBottom().IsValid()) marginBottom = converter.GetPixels(attr.GetTextBoxAttr().GetMargins().GetBottom()); if (attr.GetTextBoxAttr().GetBorder().GetLeft().GetWidth().IsValid()) borderLeft = converter.GetPixels(attr.GetTextBoxAttr().GetBorder().GetLeft().GetWidth()); if (attr.GetTextBoxAttr().GetBorder().GetRight().GetWidth().IsValid()) borderRight = converter.GetPixels(attr.GetTextBoxAttr().GetBorder().GetRight().GetWidth()); if (attr.GetTextBoxAttr().GetBorder().GetTop().GetWidth().IsValid()) borderTop = converter.GetPixels(attr.GetTextBoxAttr().GetBorder().GetTop().GetWidth()); if (attr.GetTextBoxAttr().GetBorder().GetBottom().GetWidth().IsValid()) borderBottom = converter.GetPixels(attr.GetTextBoxAttr().GetBorder().GetBottom().GetWidth()); if (attr.GetTextBoxAttr().GetPadding().GetLeft().IsValid()) paddingLeft = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetLeft()); if (attr.GetTextBoxAttr().GetPadding().GetRight().IsValid()) paddingRight = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetRight()); if (attr.GetTextBoxAttr().GetPadding().GetTop().IsValid()) paddingTop = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetTop()); if (attr.GetTextBoxAttr().GetPadding().GetBottom().IsValid()) paddingBottom = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetBottom()); if (attr.GetTextBoxAttr().GetOutline().GetLeft().GetWidth().IsValid()) outlineLeft = converter.GetPixels(attr.GetTextBoxAttr().GetOutline().GetLeft().GetWidth()); if (attr.GetTextBoxAttr().GetOutline().GetRight().GetWidth().IsValid()) outlineRight = converter.GetPixels(attr.GetTextBoxAttr().GetOutline().GetRight().GetWidth()); if (attr.GetTextBoxAttr().GetOutline().GetTop().GetWidth().IsValid()) outlineTop = converter.GetPixels(attr.GetTextBoxAttr().GetOutline().GetTop().GetWidth()); if (attr.GetTextBoxAttr().GetOutline().GetBottom().GetWidth().IsValid()) outlineBottom = converter.GetPixels(attr.GetTextBoxAttr().GetOutline().GetBottom().GetWidth()); int leftTotal = marginLeft + borderLeft + paddingLeft; int rightTotal = marginRight + borderRight + paddingRight; int topTotal = marginTop + borderTop + paddingTop; int bottomTotal = marginBottom + borderBottom + paddingBottom; if (marginRect != wxRect()) { contentRect.x = marginRect.x + leftTotal; contentRect.y = marginRect.y + topTotal; contentRect.width = marginRect.width - (leftTotal + rightTotal); contentRect.height = marginRect.height - (topTotal + bottomTotal); } else { marginRect.x = contentRect.x - leftTotal; marginRect.y = contentRect.y - topTotal; marginRect.width = contentRect.width + (leftTotal + rightTotal); marginRect.height = contentRect.height + (topTotal + bottomTotal); } borderRect.x = marginRect.x + marginLeft; borderRect.y = marginRect.y + marginTop; borderRect.width = marginRect.width - (marginLeft + marginRight); borderRect.height = marginRect.height - (marginTop + marginBottom); paddingRect.x = marginRect.x + marginLeft + borderLeft; paddingRect.y = marginRect.y + marginTop + borderTop; paddingRect.width = marginRect.width - (marginLeft + marginRight + borderLeft + borderRight); paddingRect.height = marginRect.height - (marginTop + marginBottom + borderTop + borderBottom); // The outline is outside the margin and doesn't influence the overall box position or content size. outlineRect.x = marginRect.x - outlineLeft; outlineRect.y = marginRect.y - outlineTop; outlineRect.width = marginRect.width + (outlineLeft + outlineRight); outlineRect.height = marginRect.height + (outlineTop + outlineBottom); return true; } // Get the total margin for the object in pixels, taking into account margin, padding and border size bool wxRichTextObject::GetTotalMargin(wxDC& dc, wxRichTextBuffer* buffer, const wxRichTextAttr& attr, int& leftMargin, int& rightMargin, int& topMargin, int& bottomMargin) { // Assume boxRect is the area around the content wxRect contentRect, marginRect, borderRect, paddingRect, outlineRect; marginRect = wxRect(0, 0, 1000, 1000); GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect); leftMargin = contentRect.GetLeft() - marginRect.GetLeft(); rightMargin = marginRect.GetRight() - contentRect.GetRight(); topMargin = contentRect.GetTop() - marginRect.GetTop(); bottomMargin = marginRect.GetBottom() - contentRect.GetBottom(); return true; } // Returns the rectangle which the child has available to it given restrictions specified in the // child attribute, e.g. 50% width of the parent, 400 pixels, x position 20% of the parent, etc. // availableContainerSpace might be a parent that the cell has to compute its width relative to. // E.g. a cell that's 50% of its parent. wxRect wxRichTextObject::AdjustAvailableSpace(wxDC& dc, wxRichTextBuffer* buffer, const wxRichTextAttr& WXUNUSED(parentAttr), const wxRichTextAttr& childAttr, const wxRect& availableParentSpace, const wxRect& availableContainerSpace) { wxRect rect = availableParentSpace; double scale = 1.0; if (buffer) scale = buffer->GetScale(); wxTextAttrDimensionConverter converter(dc, scale, availableContainerSpace.GetSize()); if (childAttr.GetTextBoxAttr().GetWidth().IsValid()) rect.width = converter.GetPixels(childAttr.GetTextBoxAttr().GetWidth(), wxHORIZONTAL); if (childAttr.GetTextBoxAttr().GetHeight().IsValid()) rect.height = converter.GetPixels(childAttr.GetTextBoxAttr().GetHeight(), wxVERTICAL); // Can specify either left or right for the position (we're assuming we can't // set the left and right edges to effectively set the size. Would we want to do that?) if (childAttr.GetTextBoxAttr().GetPosition().GetLeft().IsValid()) { rect.x = rect.x + converter.GetPixels(childAttr.GetTextBoxAttr().GetPosition().GetLeft(), wxHORIZONTAL); } else if (childAttr.GetTextBoxAttr().GetPosition().GetRight().IsValid()) { int x = converter.GetPixels(childAttr.GetTextBoxAttr().GetPosition().GetRight(), wxHORIZONTAL); if (childAttr.GetTextBoxAttr().GetPosition().GetRight().GetPosition() == wxTEXT_BOX_ATTR_POSITION_RELATIVE) rect.x = availableContainerSpace.x + availableContainerSpace.width - rect.width; else rect.x += x; } if (childAttr.GetTextBoxAttr().GetPosition().GetTop().IsValid()) { rect.y = rect.y + converter.GetPixels(childAttr.GetTextBoxAttr().GetPosition().GetTop(), wxVERTICAL); } else if (childAttr.GetTextBoxAttr().GetPosition().GetBottom().IsValid()) { int y = converter.GetPixels(childAttr.GetTextBoxAttr().GetPosition().GetBottom(), wxVERTICAL); if (childAttr.GetTextBoxAttr().GetPosition().GetBottom().GetPosition() == wxTEXT_BOX_ATTR_POSITION_RELATIVE) rect.y = availableContainerSpace.y + availableContainerSpace.height - rect.height; else rect.y += y; } if (rect.GetWidth() > availableParentSpace.GetWidth()) rect.SetWidth(availableParentSpace.GetWidth()); return rect; } // Dump to output stream for debugging void wxRichTextObject::Dump(wxTextOutputStream& stream) { stream << GetClassInfo()->GetClassName() << wxT("\n"); stream << wxString::Format(wxT("Size: %d,%d. Position: %d,%d, Range: %ld,%ld"), m_size.x, m_size.y, m_pos.x, m_pos.y, m_range.GetStart(), m_range.GetEnd()) << wxT("\n"); stream << wxString::Format(wxT("Text colour: %d,%d,%d."), (int) m_attributes.GetTextColour().Red(), (int) m_attributes.GetTextColour().Green(), (int) m_attributes.GetTextColour().Blue()) << wxT("\n"); } // Gets the containing buffer wxRichTextBuffer* wxRichTextObject::GetBuffer() const { const wxRichTextObject* obj = this; while (obj && !wxDynamicCast(obj, wxRichTextBuffer)) obj = obj->GetParent(); return wxDynamicCast(obj, wxRichTextBuffer); } // Get the absolute object position, by traversing up the child/parent hierarchy wxPoint wxRichTextObject::GetAbsolutePosition() const { wxPoint pt = GetPosition(); wxRichTextObject* p = GetParent(); while (p) { pt = pt + p->GetPosition(); p = p->GetParent(); } return pt; } // Hit-testing: returns a flag indicating hit test details, plus // information about position int wxRichTextObject::HitTest(wxDC& WXUNUSED(dc), wxRichTextDrawingContext& WXUNUSED(context), const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int WXUNUSED(flags)) { if (!IsShown()) return wxRICHTEXT_HITTEST_NONE; wxRect rect = GetRect(); if (pt.x >= rect.x && pt.x < rect.x + rect.width && pt.y >= rect.y && pt.y < rect.y + rect.height) { *obj = this; *contextObj = GetParentContainer(); textPosition = GetRange().GetStart(); return wxRICHTEXT_HITTEST_ON; } else return wxRICHTEXT_HITTEST_NONE; } // Lays out the object first with a given amount of space, and then if no width was specified in attr, // lays out the object again using the maximum ('best') size bool wxRichTextObject::LayoutToBestSize(wxDC& dc, wxRichTextDrawingContext& context, wxRichTextBuffer* buffer, const wxRichTextAttr& parentAttr, const wxRichTextAttr& attr, const wxRect& availableParentSpace, const wxRect& availableContainerSpace, int style) { wxRect availableChildRect = AdjustAvailableSpace(dc, buffer, parentAttr, attr, availableParentSpace, availableContainerSpace); wxRect originalAvailableRect = availableChildRect; Layout(dc, context, availableChildRect, availableContainerSpace, style); wxSize maxSize = GetMaxSize(); // Don't ignore if maxSize.x is zero, since we need to redo the paragraph's lines // on this basis if (!attr.GetTextBoxAttr().GetWidth().IsValid() && maxSize.x < availableChildRect.width) { // Redo the layout with a fixed, minimum size this time. Invalidate(wxRICHTEXT_ALL); wxRichTextAttr newAttr(attr); newAttr.GetTextBoxAttr().GetWidth().SetValue(maxSize.x, wxTEXT_ATTR_UNITS_PIXELS); newAttr.GetTextBoxAttr().GetWidth().SetPosition(wxTEXT_BOX_ATTR_POSITION_ABSOLUTE); availableChildRect = AdjustAvailableSpace(dc, buffer, parentAttr, newAttr, availableParentSpace, availableContainerSpace); // If a paragraph, align the whole paragraph. // Problem with this: if we're limited by a floating object, a line may be centered // w.r.t. the smaller resulting box rather than the actual available width. // FIXME: aligning whole paragraph not compatible with floating objects if (attr.HasAlignment() && (!wxRichTextBuffer::GetFloatingLayoutMode() || (GetContainer()->GetFloatCollector() && !GetContainer()->GetFloatCollector()->HasFloats()))) { // centering, right-justification if (attr.GetAlignment() == wxTEXT_ALIGNMENT_CENTRE) { availableChildRect.x = (originalAvailableRect.GetWidth() - availableChildRect.GetWidth())/2 + availableChildRect.x; } else if (attr.GetAlignment() == wxTEXT_ALIGNMENT_RIGHT) { availableChildRect.x = availableChildRect.x + originalAvailableRect.GetWidth() - availableChildRect.GetWidth(); } } Layout(dc, context, availableChildRect, availableContainerSpace, style); } /* __________________ | ____________ | | | | | */ return true; } // Adjusts the attributes for virtual attribute provision, collapsed borders, etc. bool wxRichTextObject::AdjustAttributes(wxRichTextAttr& attr, wxRichTextDrawingContext& context) { context.ApplyVirtualAttributes(attr, this); return true; } // Move the object recursively, by adding the offset from old to new void wxRichTextObject::Move(const wxPoint& pt) { SetPosition(pt); } /*! * wxRichTextCompositeObject * This is the base for drawable objects. */ IMPLEMENT_CLASS(wxRichTextCompositeObject, wxRichTextObject) wxRichTextCompositeObject::wxRichTextCompositeObject(wxRichTextObject* parent): wxRichTextObject(parent) { } wxRichTextCompositeObject::~wxRichTextCompositeObject() { DeleteChildren(); } /// Get the nth child wxRichTextObject* wxRichTextCompositeObject::GetChild(size_t n) const { wxASSERT ( n < m_children.GetCount() ); return m_children.Item(n)->GetData(); } /// Append a child, returning the position size_t wxRichTextCompositeObject::AppendChild(wxRichTextObject* child) { m_children.Append(child); child->SetParent(this); return m_children.GetCount() - 1; } /// Insert the child in front of the given object, or at the beginning bool wxRichTextCompositeObject::InsertChild(wxRichTextObject* child, wxRichTextObject* inFrontOf) { if (inFrontOf) { wxRichTextObjectList::compatibility_iterator node = m_children.Find(inFrontOf); m_children.Insert(node, child); } else m_children.Insert(child); child->SetParent(this); return true; } /// Delete the child bool wxRichTextCompositeObject::RemoveChild(wxRichTextObject* child, bool deleteChild) { wxRichTextObjectList::compatibility_iterator node = m_children.Find(child); if (node) { wxRichTextObject* obj = node->GetData(); m_children.Erase(node); if (deleteChild) delete obj; return true; } return false; } /// Delete all children bool wxRichTextCompositeObject::DeleteChildren() { wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextObjectList::compatibility_iterator oldNode = node; wxRichTextObject* child = node->GetData(); child->Dereference(); // Only delete if reference count is zero node = node->GetNext(); m_children.Erase(oldNode); } return true; } /// Get the child count size_t wxRichTextCompositeObject::GetChildCount() const { return m_children.GetCount(); } /// Copy void wxRichTextCompositeObject::Copy(const wxRichTextCompositeObject& obj) { wxRichTextObject::Copy(obj); DeleteChildren(); wxRichTextObjectList::compatibility_iterator node = obj.m_children.GetFirst(); while (node) { wxRichTextObject* child = node->GetData(); wxRichTextObject* newChild = child->Clone(); newChild->SetParent(this); m_children.Append(newChild); node = node->GetNext(); } } /// Hit-testing: returns a flag indicating hit test details, plus /// information about position int wxRichTextCompositeObject::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int flags) { if (!IsShown()) return wxRICHTEXT_HITTEST_NONE; wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextObject* child = node->GetData(); if (child->IsShown() && child->IsTopLevel() && (flags & wxRICHTEXT_HITTEST_NO_NESTED_OBJECTS)) { // Just check if we hit the overall object int ret = child->wxRichTextObject::HitTest(dc, context, pt, textPosition, obj, contextObj, flags); if (ret != wxRICHTEXT_HITTEST_NONE) return ret; } else if (child->IsShown()) { int ret = child->HitTest(dc, context, pt, textPosition, obj, contextObj, flags); if (ret != wxRICHTEXT_HITTEST_NONE) return ret; } node = node->GetNext(); } return wxRICHTEXT_HITTEST_NONE; } /// Finds the absolute position and row height for the given character position bool wxRichTextCompositeObject::FindPosition(wxDC& dc, wxRichTextDrawingContext& context, long index, wxPoint& pt, int* height, bool forceLineStart) { wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextObject* child = node->GetData(); // Don't recurse if the child is a top-level object, // such as a text box, because the character position will no longer // apply. By definition, a top-level object has its own range of // character positions. if (!child->IsTopLevel() && child->FindPosition(dc, context, index, pt, height, forceLineStart)) return true; node = node->GetNext(); } return false; } /// Calculate range void wxRichTextCompositeObject::CalculateRange(long start, long& end) { long current = start; long lastEnd = current; if (IsTopLevel()) { current = 0; lastEnd = 0; } wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextObject* child = node->GetData(); long childEnd = 0; child->CalculateRange(current, childEnd); lastEnd = childEnd; current = childEnd + 1; node = node->GetNext(); } if (IsTopLevel()) { // A top-level object always has a range of size 1, // because its children don't count at this level. end = start; m_range.SetRange(start, start); // An object with no children has zero length if (m_children.GetCount() == 0) lastEnd --; m_ownRange.SetRange(0, lastEnd); } else { end = lastEnd; // An object with no children has zero length if (m_children.GetCount() == 0) end --; m_range.SetRange(start, end); } } /// Delete range from layout. bool wxRichTextCompositeObject::DeleteRange(const wxRichTextRange& range) { wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextObject* obj = (wxRichTextObject*) node->GetData(); wxRichTextObjectList::compatibility_iterator next = node->GetNext(); // Delete the range in each paragraph // When a chunk has been deleted, internally the content does not // now match the ranges. // However, so long as deletion is not done on the same object twice this is OK. // If you may delete content from the same object twice, recalculate // the ranges inbetween DeleteRange calls by calling CalculateRanges, and // adjust the range you're deleting accordingly. if (!obj->GetRange().IsOutside(range)) { // No need to delete within a top-level object; just removing this object will do fine if (!obj->IsTopLevel()) obj->DeleteRange(range); // Delete an empty object, or paragraph within this range. if (obj->IsEmpty() || (range.GetStart() <= obj->GetRange().GetStart() && range.GetEnd() >= obj->GetRange().GetEnd())) { // An empty paragraph has length 1, so won't be deleted unless the // whole range is deleted. RemoveChild(obj, true); } } node = next; } return true; } /// Get any text in this object for the given range wxString wxRichTextCompositeObject::GetTextForRange(const wxRichTextRange& range) const { wxString text; wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextObject* child = node->GetData(); wxRichTextRange childRange = range; if (!child->GetRange().IsOutside(range)) { childRange.LimitTo(child->GetRange()); wxString childText = child->GetTextForRange(childRange); text += childText; } node = node->GetNext(); } return text; } /// Get the child object at the given character position wxRichTextObject* wxRichTextCompositeObject::GetChildAtPosition(long pos) const { wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextObject* child = node->GetData(); if (child->GetRange().GetStart() == pos) return child; node = node->GetNext(); } return NULL; } /// Recursively merge all pieces that can be merged. bool wxRichTextCompositeObject::Defragment(wxRichTextDrawingContext& context, const wxRichTextRange& range) { wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextObject* child = node->GetData(); if (range == wxRICHTEXT_ALL || !child->GetRange().IsOutside(range)) { wxRichTextCompositeObject* composite = wxDynamicCast(child, wxRichTextCompositeObject); if (composite) { composite->Defragment(context); node = node->GetNext(); } else { // Optimization: if there are no virtual attributes, we won't need to // to split objects in order to paint individually attributed chunks. // So only merge in this case. if (!context.GetVirtualAttributesEnabled()) { if (node->GetNext()) { wxRichTextObject* nextChild = node->GetNext()->GetData(); if (child->CanMerge(nextChild, context) && child->Merge(nextChild, context)) { nextChild->Dereference(); m_children.Erase(node->GetNext()); } else node = node->GetNext(); } else node = node->GetNext(); } else { // If we might have virtual attributes, we first see if we have to split // objects so that they may be painted with potential virtual attributes, // since text objects can only draw or measure with a single attributes object // at a time. wxRichTextObject* childAfterSplit = child; if (child->CanSplit(context)) { childAfterSplit = child->Split(context); node = m_children.Find(childAfterSplit); } if (node->GetNext()) { wxRichTextObject* nextChild = node->GetNext()->GetData(); // First split child and nextChild so we have smaller fragments to merge. // Then Merge only has to test per-object virtual attributes // because for an object with all the same sub-object attributes, // then any general virtual attributes should be merged with sub-objects by // the implementation. wxRichTextObject* nextChildAfterSplit = nextChild; if (nextChildAfterSplit->CanSplit(context)) nextChildAfterSplit = nextChild->Split(context); bool splitNextChild = nextChild != nextChildAfterSplit; // See if we can merge this new fragment with (perhaps the first part of) the next object. // Note that we use nextChild because if we had split nextChild, the first object always // remains (and further parts are appended). However we must use childAfterSplit since // it's the last part of a possibly split child. if (childAfterSplit->CanMerge(nextChild, context) && childAfterSplit->Merge(nextChild, context)) { nextChild->Dereference(); m_children.Erase(node->GetNext()); // Don't set node -- we'll see if we can merge again with the next // child. UNLESS we split this or the next child, in which case we know we have to // move on to the end of the next child. if (splitNextChild) node = m_children.Find(nextChildAfterSplit); } else { if (splitNextChild) node = m_children.Find(nextChildAfterSplit); // start from the last object in the split else node = node->GetNext(); } } else node = node->GetNext(); } } } else node = node->GetNext(); } // Delete any remaining empty objects, but leave at least one empty object per composite object. if (GetChildCount() > 1) { node = m_children.GetFirst(); while (node) { wxRichTextObjectList::compatibility_iterator next = node->GetNext(); wxRichTextObject* child = node->GetData(); if (range == wxRICHTEXT_ALL || !child->GetRange().IsOutside(range)) { if (child->IsEmpty()) { child->Dereference(); m_children.Erase(node); } node = next; } else node = node->GetNext(); } } return true; } /// Dump to output stream for debugging void wxRichTextCompositeObject::Dump(wxTextOutputStream& stream) { wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextObject* child = node->GetData(); child->Dump(stream); node = node->GetNext(); } } /// Get/set the object size for the given range. Returns false if the range /// is invalid for this object. bool wxRichTextCompositeObject::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, const wxPoint& position, const wxSize& parentSize, wxArrayInt* partialExtents) const { if (!range.IsWithin(GetRange())) return false; wxSize sz; wxArrayInt childExtents; wxArrayInt* p; if (partialExtents) p = & childExtents; else p = NULL; wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextObject* child = node->GetData(); if (!child->GetRange().IsOutside(range)) { // Floating objects have a zero size within the paragraph. if (child->IsFloating() && wxRichTextBuffer::GetFloatingLayoutMode()) { if (partialExtents) { int lastSize; if (partialExtents->GetCount() > 0) lastSize = (*partialExtents)[partialExtents->GetCount()-1]; else lastSize = 0; partialExtents->Add(0 /* zero size */ + lastSize); } } else { wxSize childSize; wxRichTextRange rangeToUse = range; rangeToUse.LimitTo(child->GetRange()); if (child->IsTopLevel()) rangeToUse = child->GetOwnRange(); int childDescent = 0; // At present wxRICHTEXT_HEIGHT_ONLY is only fast if we're already cached the size, // but it's only going to be used after caching has taken place. if ((flags & wxRICHTEXT_HEIGHT_ONLY) && child->GetCachedSize().y != 0) { childDescent = child->GetDescent(); childSize = child->GetCachedSize(); sz.y = wxMax(sz.y, childSize.y); sz.x += childSize.x; descent = wxMax(descent, childDescent); } else if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, context, flags, wxPoint(position.x + sz.x, position.y), parentSize, p)) { sz.y = wxMax(sz.y, childSize.y); sz.x += childSize.x; descent = wxMax(descent, childDescent); if ((flags & wxRICHTEXT_CACHE_SIZE) && (rangeToUse == child->GetRange() || child->IsTopLevel())) { child->SetCachedSize(childSize); child->SetDescent(childDescent); } if (partialExtents) { int lastSize; if (partialExtents->GetCount() > 0) lastSize = (*partialExtents)[partialExtents->GetCount()-1]; else lastSize = 0; size_t i; for (i = 0; i < childExtents.GetCount(); i++) { partialExtents->Add(childExtents[i] + lastSize); } } } } if (p) p->Clear(); } node = node->GetNext(); } size = sz; return true; } // Invalidate the buffer. With no argument, invalidates whole buffer. void wxRichTextCompositeObject::Invalidate(const wxRichTextRange& invalidRange) { wxRichTextObject::Invalidate(invalidRange); wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextObject* child = node->GetData(); if (invalidRange != wxRICHTEXT_ALL && invalidRange != wxRICHTEXT_NONE && child->GetRange().IsOutside(invalidRange)) { // Skip } else if (child->IsTopLevel()) { if (wxRichTextBuffer::GetFloatingLayoutMode() && child->IsFloating() && GetBuffer()->GetFloatCollector() && GetBuffer()->GetFloatCollector()->HasFloat(child)) { // Don't invalidate subhierarchy if we've already been laid out } else { if (invalidRange == wxRICHTEXT_NONE) child->Invalidate(wxRICHTEXT_NONE); else child->Invalidate(wxRICHTEXT_ALL); // All children must be invalidated if within parent range } } else child->Invalidate(invalidRange); node = node->GetNext(); } } // Move the object recursively, by adding the offset from old to new void wxRichTextCompositeObject::Move(const wxPoint& pt) { wxPoint oldPos = GetPosition(); SetPosition(pt); wxPoint offset = pt - oldPos; wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextObject* child = node->GetData(); wxPoint childPos = child->GetPosition() + offset; child->Move(childPos); node = node->GetNext(); } } /*! * wxRichTextParagraphLayoutBox * This box knows how to lay out paragraphs. */ IMPLEMENT_DYNAMIC_CLASS(wxRichTextParagraphLayoutBox, wxRichTextCompositeObject) wxRichTextParagraphLayoutBox::wxRichTextParagraphLayoutBox(wxRichTextObject* parent): wxRichTextCompositeObject(parent) { Init(); } wxRichTextParagraphLayoutBox::~wxRichTextParagraphLayoutBox() { if (m_floatCollector) { delete m_floatCollector; m_floatCollector = NULL; } } /// Initialize the object. void wxRichTextParagraphLayoutBox::Init() { m_ctrl = NULL; // For now, assume is the only box and has no initial size. m_range = wxRichTextRange(0, -1); m_ownRange = wxRichTextRange(0, -1); m_invalidRange = wxRICHTEXT_ALL; m_partialParagraph = false; m_floatCollector = NULL; } void wxRichTextParagraphLayoutBox::Clear() { DeleteChildren(); if (m_floatCollector) delete m_floatCollector; m_floatCollector = NULL; m_partialParagraph = false; } /// Copy void wxRichTextParagraphLayoutBox::Copy(const wxRichTextParagraphLayoutBox& obj) { Clear(); wxRichTextCompositeObject::Copy(obj); m_partialParagraph = obj.m_partialParagraph; m_defaultAttributes = obj.m_defaultAttributes; } // Gather information about floating objects; only gather floats for those paragraphs that // will not be formatted again due to optimization, after which floats will be gathered per-paragraph // during layout. bool wxRichTextParagraphLayoutBox::UpdateFloatingObjects(const wxRect& availableRect, wxRichTextObject* untilObj) { if (m_floatCollector != NULL) delete m_floatCollector; m_floatCollector = new wxRichTextFloatCollector(availableRect); wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); // Only gather floats up to the point we'll start formatting paragraphs. while (untilObj && node && node->GetData() != untilObj) { wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph); wxASSERT (child != NULL); if (child) m_floatCollector->CollectFloat(child); node = node->GetNext(); } return true; } // Returns the style sheet associated with the overall buffer. wxRichTextStyleSheet* wxRichTextParagraphLayoutBox::GetStyleSheet() const { return GetBuffer() ? GetBuffer()->GetStyleSheet() : (wxRichTextStyleSheet*) NULL; } // Get the number of floating objects at this level int wxRichTextParagraphLayoutBox::GetFloatingObjectCount() const { if (m_floatCollector) return m_floatCollector->GetFloatingObjectCount(); else return 0; } // Get a list of floating objects bool wxRichTextParagraphLayoutBox::GetFloatingObjects(wxRichTextObjectList& objects) const { if (m_floatCollector) { return m_floatCollector->GetFloatingObjects(objects); } else return false; } // Calculate ranges void wxRichTextParagraphLayoutBox::UpdateRanges() { long start = 0; if (GetParent()) start = GetRange().GetStart(); long end; CalculateRange(start, end); } // HitTest int wxRichTextParagraphLayoutBox::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int flags) { if (!IsShown()) return wxRICHTEXT_HITTEST_NONE; int ret = wxRICHTEXT_HITTEST_NONE; if (wxRichTextBuffer::GetFloatingLayoutMode() && m_floatCollector && (flags & wxRICHTEXT_HITTEST_NO_FLOATING_OBJECTS) == 0) ret = m_floatCollector->HitTest(dc, context, pt, textPosition, obj, flags); if (ret == wxRICHTEXT_HITTEST_NONE) return wxRichTextCompositeObject::HitTest(dc, context, pt, textPosition, obj, contextObj, flags); else { *contextObj = this; return ret; } } /// Draw the floating objects void wxRichTextParagraphLayoutBox::DrawFloats(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style) { if (wxRichTextBuffer::GetFloatingLayoutMode() && m_floatCollector) m_floatCollector->Draw(dc, context, range, selection, rect, descent, style); } void wxRichTextParagraphLayoutBox::MoveAnchoredObjectToParagraph(wxRichTextParagraph* from, wxRichTextParagraph* to, wxRichTextObject* obj) { if (from == to) return; from->RemoveChild(obj); to->AppendChild(obj); } /// Draw the item bool wxRichTextParagraphLayoutBox::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style) { if (!IsShown()) return true; wxRect thisRect(GetPosition(), GetCachedSize()); wxRichTextAttr attr(GetAttributes()); AdjustAttributes(attr, context); int flags = style; if (selection.IsValid() && ((GetParentContainer() != this && selection.GetContainer() == this && selection.WithinSelection(GetRange().GetStart(), GetParentContainer())) || (selection.WithinSelection(GetRange().GetStart(), this)))) { flags |= wxRICHTEXT_DRAW_SELECTED; } // Don't draw guidelines if at top level int theseFlags = flags; if (!GetParent()) theseFlags &= ~wxRICHTEXT_DRAW_GUIDELINES; DrawBoxAttributes(dc, GetBuffer(), attr, thisRect, theseFlags, this); if (wxRichTextBuffer::GetFloatingLayoutMode()) DrawFloats(dc, context, range, selection, rect, descent, style); wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextObject* child = node->GetData(); if (child && !child->GetRange().IsOutside(range)) { wxRect childRect(child->GetPosition(), child->GetCachedSize()); wxRichTextRange childRange = range; if (child->IsTopLevel()) { childRange = child->GetOwnRange(); } 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 } else child->Draw(dc, context, childRange, selection, rect, descent, style); } node = node->GetNext(); } return true; } /// Lay the item out bool wxRichTextParagraphLayoutBox::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& parentRect, int style) { SetPosition(rect.GetPosition()); if (!IsShown()) return true; wxRect availableSpace; bool formatRect = (style & wxRICHTEXT_LAYOUT_SPECIFIED_RECT) == wxRICHTEXT_LAYOUT_SPECIFIED_RECT; wxRichTextAttr attr(GetAttributes()); AdjustAttributes(attr, context); // If only laying out a specific area, the passed rect has a different meaning: // the visible part of the buffer. This is used in wxRichTextCtrl::OnSize, // so that during a size, only the visible part will be relaid out, or // it would take too long causing flicker. As an approximation, we assume that // everything up to the start of the visible area is laid out correctly. if (formatRect) { wxRect rect2(0, 0, rect.width, rect.height); availableSpace = GetAvailableContentArea(dc, context, rect2); // Invalidate the part of the buffer from the first visible line // to the end. If other parts of the buffer are currently invalid, // then they too will be taken into account if they are above // the visible point. long startPos = 0; wxRichTextLine* line = GetLineAtYPosition(rect.y); if (line) startPos = line->GetAbsoluteRange().GetStart(); Invalidate(wxRichTextRange(startPos, GetOwnRange().GetEnd())); } else { availableSpace = GetAvailableContentArea(dc, context, rect); } // Fix the width if we're at the top level if (!GetParent()) attr.GetTextBoxAttr().GetWidth().SetValue(rect.GetWidth(), wxTEXT_ATTR_UNITS_PIXELS); int leftMargin, rightMargin, topMargin, bottomMargin; wxRichTextObject::GetTotalMargin(dc, GetBuffer(), attr, leftMargin, rightMargin, topMargin, bottomMargin); int maxWidth = 0; int maxHeight = 0; // The maximum paragraph maximum width, so we can set the overall maximum width for this object int maxMaxWidth = 0; // The maximum paragraph minimum width, so we can set the overall minimum width for this object int maxMinWidth = 0; // If we have vertical alignment, we must recalculate everything. bool hasVerticalAlignment = (attr.GetTextBoxAttr().HasVerticalAlignment() && (attr.GetTextBoxAttr().GetVerticalAlignment() > wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT_TOP)); wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); bool layoutAll = true; // Get invalid range, rounding to paragraph start/end. wxRichTextRange invalidRange = GetInvalidRange(true); if (invalidRange == wxRICHTEXT_NONE && !formatRect) return true; if (invalidRange == wxRICHTEXT_ALL || hasVerticalAlignment) layoutAll = true; else // If we know what range is affected, start laying out from that point on. if (invalidRange.GetStart() >= GetOwnRange().GetStart()) { wxRichTextParagraph* firstParagraph = GetParagraphAtPosition(invalidRange.GetStart()); if (firstParagraph) { wxRichTextObjectList::compatibility_iterator firstNode = m_children.Find(firstParagraph); wxRichTextObjectList::compatibility_iterator previousNode; if ( firstNode ) previousNode = firstNode->GetPrevious(); if (firstNode) { if (previousNode) { wxRichTextParagraph* previousParagraph = wxDynamicCast(previousNode->GetData(), wxRichTextParagraph); availableSpace.y = previousParagraph->GetPosition().y + previousParagraph->GetCachedSize().y; } // Now we're going to start iterating from the first affected paragraph. node = firstNode; layoutAll = false; } } } // Gather information about only those floating objects that will not be formatted, // after which floats will be gathered per-paragraph during layout. if (wxRichTextBuffer::GetFloatingLayoutMode()) UpdateFloatingObjects(availableSpace, node ? node->GetData() : (wxRichTextObject*) NULL); // A way to force speedy rest-of-buffer layout (the 'else' below) bool forceQuickLayout = false; // First get the size of the paragraphs we won't be laying out wxRichTextObjectList::compatibility_iterator n = m_children.GetFirst(); while (n && n != node) { wxRichTextParagraph* child = wxDynamicCast(n->GetData(), wxRichTextParagraph); if (child) { maxWidth = wxMax(maxWidth, child->GetCachedSize().x); maxMinWidth = wxMax(maxMinWidth, child->GetMinSize().x); maxMaxWidth = wxMax(maxMaxWidth, child->GetMaxSize().x); } n = n->GetNext(); } while (node) { // Assume this box only contains paragraphs wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph); // Unsure if this is needed // wxCHECK_MSG( child, false, wxT("Unknown object in layout") ); if (child && child->IsShown()) { // TODO: what if the child hasn't been laid out (e.g. involved in Undo) but still has 'old' lines if ( !forceQuickLayout && (layoutAll || child->GetLines().IsEmpty() || !child->GetRange().IsOutside(invalidRange)) ) { // Lays out the object first with a given amount of space, and then if no width was specified in attr, // lays out the object again using the minimum size child->LayoutToBestSize(dc, context, GetBuffer(), attr, child->GetAttributes(), availableSpace, rect, style&~wxRICHTEXT_LAYOUT_SPECIFIED_RECT); // Layout must set the cached size availableSpace.y += child->GetCachedSize().y; maxWidth = wxMax(maxWidth, child->GetCachedSize().x); maxMinWidth = wxMax(maxMinWidth, child->GetMinSize().x); maxMaxWidth = wxMax(maxMaxWidth, child->GetMaxSize().x); // If we're just formatting the visible part of the buffer, // and we're now past the bottom of the window, and we don't have any // floating objects (since they may cause wrapping to change for the rest of the // the buffer), start quick layout. if (!hasVerticalAlignment && formatRect && child->GetPosition().y > rect.GetBottom() && GetFloatingObjectCount() == 0) forceQuickLayout = true; } else { // We're outside the immediately affected range, so now let's just // move everything up or down. This assumes that all the children have previously // been laid out and have wrapped line lists associated with them. // TODO: check all paragraphs before the affected range. int inc = availableSpace.y - child->GetPosition().y; while (node) { wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph); if (child) { if (child->GetLines().GetCount() == 0) { // Lays out the object first with a given amount of space, and then if no width was specified in attr, // lays out the object again using the minimum size child->LayoutToBestSize(dc, context, GetBuffer(), attr, child->GetAttributes(), availableSpace, rect, style&~wxRICHTEXT_LAYOUT_SPECIFIED_RECT); //child->Layout(dc, availableChildRect, style); } else child->Move(wxPoint(child->GetPosition().x, child->GetPosition().y + inc)); availableSpace.y += child->GetCachedSize().y; maxWidth = wxMax(maxWidth, child->GetCachedSize().x); maxMinWidth = wxMax(maxMinWidth, child->GetMinSize().x); maxMaxWidth = wxMax(maxMaxWidth, child->GetMaxSize().x); } node = node->GetNext(); } break; } } node = node->GetNext(); } node = m_children.GetLast(); if (node && node->GetData()->IsShown()) { wxRichTextObject* child = node->GetData(); maxHeight = child->GetPosition().y - (GetPosition().y + topMargin) + child->GetCachedSize().y; } else maxHeight = 0; // topMargin + bottomMargin; // Check the bottom edge of any floating object if (wxRichTextBuffer::GetFloatingLayoutMode() && GetFloatCollector() && GetFloatCollector()->HasFloats()) { int bottom = GetFloatCollector()->GetLastRectBottom(); if (bottom > maxHeight) maxHeight = bottom; } if (attr.GetTextBoxAttr().GetSize().GetWidth().IsValid()) { wxRect r = AdjustAvailableSpace(dc, GetBuffer(), wxRichTextAttr() /* not used */, attr, parentRect, parentRect); int w = r.GetWidth(); // Convert external to content rect w = w - leftMargin - rightMargin; maxWidth = wxMax(maxWidth, w); maxMaxWidth = wxMax(maxMaxWidth, w); } else { // TODO: Make sure the layout box's position reflects // the position of the children, but without // breaking layout of a box within a paragraph. } if (attr.GetTextBoxAttr().GetSize().GetHeight().IsValid()) { wxRect r = AdjustAvailableSpace(dc, GetBuffer(), wxRichTextAttr() /* not used */, attr, parentRect, parentRect); int h = r.GetHeight(); // Convert external to content rect h = h - topMargin - bottomMargin; maxHeight = wxMax(maxHeight, h); } // We need to add back the margins etc. { wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect; contentRect = wxRect(wxPoint(0, 0), wxSize(maxWidth, maxHeight)); GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect); SetCachedSize(marginRect.GetSize()); } // The maximum size is the greatest of all maximum widths for all paragraphs. { wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect; contentRect = wxRect(wxPoint(0, 0), wxSize(maxMaxWidth, maxHeight)); // Actually max height is a lie, we can't know it GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect); SetMaxSize(marginRect.GetSize()); } // The minimum size is the greatest of all minimum widths for all paragraphs. { wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect; contentRect = wxRect(wxPoint(0, 0), wxSize(maxMinWidth, maxHeight)); // Actually max height is a lie, we can't know it GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect); SetMinSize(marginRect.GetSize()); } if (attr.GetTextBoxAttr().HasVerticalAlignment() && (attr.GetTextBoxAttr().GetVerticalAlignment() > wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT_TOP)) { int yOffset = 0; int leftOverSpace = availableSpace.height - topMargin - bottomMargin - maxHeight; if (leftOverSpace > 0) { if (attr.GetTextBoxAttr().GetVerticalAlignment() == wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT_CENTRE) { yOffset = (leftOverSpace/2); } else if (attr.GetTextBoxAttr().GetVerticalAlignment() == wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT_BOTTOM) { yOffset = leftOverSpace; } } // Move all the children to vertically align the content // This doesn't take into account floating objects, unfortunately. if (yOffset != 0) { wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph); if (child) child->Move(wxPoint(child->GetPosition().x, child->GetPosition().y + yOffset)); node = node->GetNext(); } } } m_invalidRange = wxRICHTEXT_NONE; return true; } /// Get/set the size for the given range. bool wxRichTextParagraphLayoutBox::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, const wxPoint& position, const wxSize& parentSize, wxArrayInt* WXUNUSED(partialExtents)) const { wxSize sz; wxRichTextObjectList::compatibility_iterator startPara = wxRichTextObjectList::compatibility_iterator(); wxRichTextObjectList::compatibility_iterator endPara = wxRichTextObjectList::compatibility_iterator(); // First find the first paragraph whose starting position is within the range. wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { // child is a paragraph wxRichTextObject* child = node->GetData(); const wxRichTextRange& r = child->GetRange(); if (r.GetStart() <= range.GetStart() && r.GetEnd() >= range.GetStart()) { startPara = node; break; } node = node->GetNext(); } // Next find the last paragraph containing part of the range node = m_children.GetFirst(); while (node) { // child is a paragraph wxRichTextObject* child = node->GetData(); const wxRichTextRange& r = child->GetRange(); if (r.GetStart() <= range.GetEnd() && r.GetEnd() >= range.GetEnd()) { endPara = node; break; } node = node->GetNext(); } if (!startPara || !endPara) return false; // Now we can add up the sizes for (node = startPara; node ; node = node->GetNext()) { // child is a paragraph wxRichTextObject* child = node->GetData(); const wxRichTextRange& childRange = child->GetRange(); wxRichTextRange rangeToFind = range; rangeToFind.LimitTo(childRange); if (child->IsTopLevel()) rangeToFind = child->GetOwnRange(); wxSize childSize; int childDescent = 0; child->GetRangeSize(rangeToFind, childSize, childDescent, dc, context, flags, position, parentSize); descent = wxMax(childDescent, descent); sz.x = wxMax(sz.x, childSize.x); sz.y += childSize.y; if (node == endPara) break; } size = sz; return true; } /// Get the paragraph at the given position wxRichTextParagraph* wxRichTextParagraphLayoutBox::GetParagraphAtPosition(long pos, bool caretPosition) const { if (caretPosition) pos ++; // First find the first paragraph whose starting position is within the range. wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { // child is a paragraph wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph); // wxASSERT (child != NULL); if (child) { // Return first child in buffer if position is -1 // if (pos == -1) // return child; if (child->GetRange().Contains(pos)) return child; } node = node->GetNext(); } return NULL; } /// Get the line at the given position wxRichTextLine* wxRichTextParagraphLayoutBox::GetLineAtPosition(long pos, bool caretPosition) const { if (caretPosition) pos ++; // First find the first paragraph whose starting position is within the range. wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextObject* obj = (wxRichTextObject*) node->GetData(); if (obj->GetRange().Contains(pos)) { // child is a paragraph wxRichTextParagraph* child = wxDynamicCast(obj, wxRichTextParagraph); // wxASSERT (child != NULL); if (child) { wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst(); while (node2) { wxRichTextLine* line = node2->GetData(); wxRichTextRange range = line->GetAbsoluteRange(); if (range.Contains(pos) || // If the position is end-of-paragraph, then return the last line of // of the paragraph. ((range.GetEnd() == child->GetRange().GetEnd()-1) && (pos == child->GetRange().GetEnd()))) return line; node2 = node2->GetNext(); } } } node = node->GetNext(); } int lineCount = GetLineCount(); if (lineCount > 0) return GetLineForVisibleLineNumber(lineCount-1); else return NULL; } /// Get the line at the given y pixel position, or the last line. wxRichTextLine* wxRichTextParagraphLayoutBox::GetLineAtYPosition(int y) const { wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph); // wxASSERT (child != NULL); if (child) { wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst(); while (node2) { wxRichTextLine* line = node2->GetData(); wxRect rect(line->GetRect()); if (y <= rect.GetBottom()) return line; node2 = node2->GetNext(); } } node = node->GetNext(); } // Return last line int lineCount = GetLineCount(); if (lineCount > 0) return GetLineForVisibleLineNumber(lineCount-1); else return NULL; } /// Get the number of visible lines int wxRichTextParagraphLayoutBox::GetLineCount() const { int count = 0; wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph); // wxASSERT (child != NULL); if (child) count += child->GetLines().GetCount(); node = node->GetNext(); } return count; } /// Get the paragraph for a given line wxRichTextParagraph* wxRichTextParagraphLayoutBox::GetParagraphForLine(wxRichTextLine* line) const { return GetParagraphAtPosition(line->GetAbsoluteRange().GetStart()); } /// Get the line size at the given position wxSize wxRichTextParagraphLayoutBox::GetLineSizeAtPosition(long pos, bool caretPosition) const { wxRichTextLine* line = GetLineAtPosition(pos, caretPosition); if (line) { return line->GetSize(); } else return wxSize(0, 0); } /// Convenience function to add a paragraph of text wxRichTextRange wxRichTextParagraphLayoutBox::AddParagraph(const wxString& text, wxRichTextAttr* paraStyle) { // Don't use the base style, just the default style, and the base style will // be combined at display time. // Divide into paragraph and character styles. wxRichTextAttr defaultCharStyle; wxRichTextAttr defaultParaStyle; // If the default style is a named paragraph style, don't apply any character formatting // to the initial text string. if (GetDefaultStyle().HasParagraphStyleName() && GetStyleSheet()) { wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(GetDefaultStyle().GetParagraphStyleName()); if (def) defaultParaStyle = def->GetStyleMergedWithBase(GetStyleSheet()); } else wxRichTextSplitParaCharStyles(GetDefaultStyle(), defaultParaStyle, defaultCharStyle); wxRichTextAttr* pStyle = paraStyle ? paraStyle : (wxRichTextAttr*) & defaultParaStyle; wxRichTextAttr* cStyle = & defaultCharStyle; wxRichTextParagraph* para = new wxRichTextParagraph(text, this, pStyle, cStyle); para->GetAttributes().GetTextBoxAttr().Reset(); AppendChild(para); UpdateRanges(); return para->GetRange(); } /// Adds multiple paragraphs, based on newlines. wxRichTextRange wxRichTextParagraphLayoutBox::AddParagraphs(const wxString& text, wxRichTextAttr* paraStyle) { // Don't use the base style, just the default style, and the base style will // be combined at display time. // Divide into paragraph and character styles. wxRichTextAttr defaultCharStyle; wxRichTextAttr defaultParaStyle; // If the default style is a named paragraph style, don't apply any character formatting // to the initial text string. if (GetDefaultStyle().HasParagraphStyleName() && GetStyleSheet()) { wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(GetDefaultStyle().GetParagraphStyleName()); if (def) defaultParaStyle = def->GetStyleMergedWithBase(GetStyleSheet()); } else wxRichTextSplitParaCharStyles(GetDefaultStyle(), defaultParaStyle, defaultCharStyle); wxRichTextAttr* pStyle = paraStyle ? paraStyle : (wxRichTextAttr*) & defaultParaStyle; wxRichTextAttr* cStyle = & defaultCharStyle; wxRichTextParagraph* firstPara = NULL; wxRichTextParagraph* lastPara = NULL; wxRichTextRange range(-1, -1); size_t i = 0; size_t len = text.length(); wxString line; wxRichTextParagraph* para = new wxRichTextParagraph(wxEmptyString, this, pStyle, cStyle); para->GetAttributes().GetTextBoxAttr().Reset(); AppendChild(para); firstPara = para; lastPara = para; while (i < len) { wxChar ch = text[i]; if (ch == wxT('\n') || ch == wxT('\r')) { if (i != (len-1)) { wxRichTextPlainText* plainText = (wxRichTextPlainText*) para->GetChildren().GetFirst()->GetData(); plainText->SetText(line); para = new wxRichTextParagraph(wxEmptyString, this, pStyle, cStyle); para->GetAttributes().GetTextBoxAttr().Reset(); AppendChild(para); lastPara = para; line = wxEmptyString; } } else line += ch; i ++; } if (!line.empty()) { wxRichTextPlainText* plainText = (wxRichTextPlainText*) para->GetChildren().GetFirst()->GetData(); plainText->SetText(line); } UpdateRanges(); return wxRichTextRange(firstPara->GetRange().GetStart(), lastPara->GetRange().GetEnd()); } /// Convenience function to add an image wxRichTextRange wxRichTextParagraphLayoutBox::AddImage(const wxImage& image, wxRichTextAttr* paraStyle) { // Don't use the base style, just the default style, and the base style will // be combined at display time. // Divide into paragraph and character styles. wxRichTextAttr defaultCharStyle; wxRichTextAttr defaultParaStyle; // If the default style is a named paragraph style, don't apply any character formatting // to the initial text string. if (GetDefaultStyle().HasParagraphStyleName() && GetStyleSheet()) { wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(GetDefaultStyle().GetParagraphStyleName()); if (def) defaultParaStyle = def->GetStyleMergedWithBase(GetStyleSheet()); } else wxRichTextSplitParaCharStyles(GetDefaultStyle(), defaultParaStyle, defaultCharStyle); wxRichTextAttr* pStyle = paraStyle ? paraStyle : (wxRichTextAttr*) & defaultParaStyle; wxRichTextAttr* cStyle = & defaultCharStyle; wxRichTextParagraph* para = new wxRichTextParagraph(this, pStyle); para->GetAttributes().GetTextBoxAttr().Reset(); AppendChild(para); para->AppendChild(new wxRichTextImage(image, this, cStyle)); UpdateRanges(); return para->GetRange(); } /// Insert fragment into this box at the given position. If partialParagraph is true, /// it is assumed that the last (or only) paragraph is just a piece of data with no paragraph /// marker. bool wxRichTextParagraphLayoutBox::InsertFragment(long position, wxRichTextParagraphLayoutBox& fragment) { // First, find the first paragraph whose starting position is within the range. wxRichTextParagraph* para = GetParagraphAtPosition(position); if (para) { wxRichTextAttr originalAttr = para->GetAttributes(); wxRichTextObjectList::compatibility_iterator node = m_children.Find(para); // Now split at this position, returning the object to insert the new // ones in front of. wxRichTextObject* nextObject = para->SplitAt(position); // Special case: partial paragraph, just one paragraph. Might be a small amount of // text, for example, so let's optimize. if (fragment.GetPartialParagraph() && fragment.GetChildren().GetCount() == 1) { // Add the first para to this para... wxRichTextObjectList::compatibility_iterator firstParaNode = fragment.GetChildren().GetFirst(); if (!firstParaNode) return false; // Iterate through the fragment paragraph inserting the content into this paragraph. wxRichTextParagraph* firstPara = wxDynamicCast(firstParaNode->GetData(), wxRichTextParagraph); wxASSERT (firstPara != NULL); wxRichTextObjectList::compatibility_iterator objectNode = firstPara->GetChildren().GetFirst(); while (objectNode) { wxRichTextObject* newObj = objectNode->GetData()->Clone(); if (!nextObject) { // Append para->AppendChild(newObj); } else { // Insert before nextObject para->InsertChild(newObj, nextObject); } objectNode = objectNode->GetNext(); } return true; } else { // Procedure for inserting a fragment consisting of a number of // paragraphs: // // 1. Remove and save the content that's after the insertion point, for adding // back once we've added the fragment. // 2. Add the content from the first fragment paragraph to the current // paragraph. // 3. Add remaining fragment paragraphs after the current paragraph. // 4. Add back the saved content from the first paragraph. If partialParagraph // is true, add it to the last paragraph added and not a new one. // 1. Remove and save objects after split point. wxList savedObjects; if (nextObject) para->MoveToList(nextObject, savedObjects); // 2. Add the content from the 1st fragment paragraph. wxRichTextObjectList::compatibility_iterator firstParaNode = fragment.GetChildren().GetFirst(); if (!firstParaNode) return false; wxRichTextParagraph* firstPara = wxDynamicCast(firstParaNode->GetData(), wxRichTextParagraph); wxASSERT(firstPara != NULL); if (!(fragment.GetAttributes().GetFlags() & wxTEXT_ATTR_KEEP_FIRST_PARA_STYLE)) para->SetAttributes(firstPara->GetAttributes()); // Save empty paragraph attributes for appending later // These are character attributes deliberately set for a new paragraph. Without this, // we couldn't pass default attributes when appending a new paragraph. wxRichTextAttr emptyParagraphAttributes; wxRichTextObjectList::compatibility_iterator objectNode = firstPara->GetChildren().GetFirst(); if (objectNode && firstPara->GetChildren().GetCount() == 1 && objectNode->GetData()->IsEmpty()) emptyParagraphAttributes = objectNode->GetData()->GetAttributes(); while (objectNode) { wxRichTextObject* newObj = objectNode->GetData()->Clone(); // Append para->AppendChild(newObj); objectNode = objectNode->GetNext(); } // 3. Add remaining fragment paragraphs after the current paragraph. wxRichTextObjectList::compatibility_iterator nextParagraphNode = node->GetNext(); wxRichTextObject* nextParagraph = NULL; if (nextParagraphNode) nextParagraph = nextParagraphNode->GetData(); wxRichTextObjectList::compatibility_iterator i = fragment.GetChildren().GetFirst()->GetNext(); wxRichTextParagraph* finalPara = para; bool needExtraPara = (!i || !fragment.GetPartialParagraph()); // If there was only one paragraph, we need to insert a new one. while (i) { wxRichTextParagraph* para = wxDynamicCast(i->GetData(), wxRichTextParagraph); wxASSERT( para != NULL ); finalPara = (wxRichTextParagraph*) para->Clone(); if (nextParagraph) InsertChild(finalPara, nextParagraph); else AppendChild(finalPara); i = i->GetNext(); } // If there was only one paragraph, or we have full paragraphs in our fragment, // we need to insert a new one. if (needExtraPara) { finalPara = new wxRichTextParagraph; if (nextParagraph) InsertChild(finalPara, nextParagraph); else AppendChild(finalPara); } // 4. Add back the remaining content. if (finalPara) { if (nextObject) finalPara->MoveFromList(savedObjects); // Ensure there's at least one object if (finalPara->GetChildCount() == 0) { wxRichTextPlainText* text = new wxRichTextPlainText(wxEmptyString); text->SetAttributes(emptyParagraphAttributes); finalPara->AppendChild(text); } } if ((fragment.GetAttributes().GetFlags() & wxTEXT_ATTR_KEEP_FIRST_PARA_STYLE) && firstPara) finalPara->SetAttributes(firstPara->GetAttributes()); else if (finalPara && finalPara != para) finalPara->SetAttributes(originalAttr); return true; } } else { // Append wxRichTextObjectList::compatibility_iterator i = fragment.GetChildren().GetFirst(); while (i) { wxRichTextParagraph* para = wxDynamicCast(i->GetData(), wxRichTextParagraph); wxASSERT( para != NULL ); AppendChild(para->Clone()); i = i->GetNext(); } return true; } } /// Make a copy of the fragment corresponding to the given range, putting it in 'fragment'. /// If there was an incomplete paragraph at the end, partialParagraph is set to true. bool wxRichTextParagraphLayoutBox::CopyFragment(const wxRichTextRange& range, wxRichTextParagraphLayoutBox& fragment) { wxRichTextObjectList::compatibility_iterator i = GetChildren().GetFirst(); while (i) { wxRichTextParagraph* para = wxDynamicCast(i->GetData(), wxRichTextParagraph); wxASSERT( para != NULL ); if (!para->GetRange().IsOutside(range)) { fragment.AppendChild(para->Clone()); } i = i->GetNext(); } // Now top and tail the first and last paragraphs in our new fragment (which might be the same). if (!fragment.IsEmpty()) { wxRichTextParagraph* firstPara = wxDynamicCast(fragment.GetChildren().GetFirst()->GetData(), wxRichTextParagraph); wxASSERT( firstPara != NULL ); wxRichTextParagraph* lastPara = wxDynamicCast(fragment.GetChildren().GetLast()->GetData(), wxRichTextParagraph); wxASSERT( lastPara != NULL ); if (!firstPara || !lastPara) return false; bool isFragment = (range.GetEnd() < lastPara->GetRange().GetEnd()); long firstPos = firstPara->GetRange().GetStart(); // Adjust for renumbering from zero wxRichTextRange topTailRange(range.GetStart() - firstPos, range.GetEnd() - firstPos); long end; fragment.CalculateRange(0, end); // Chop off the start of the paragraph if (topTailRange.GetStart() > 0) { wxRichTextRange r(0, topTailRange.GetStart()-1); firstPara->DeleteRange(r); // Make sure the numbering is correct fragment.CalculateRange(0, end); // Now, we've deleted some positions, so adjust the range // accordingly. topTailRange.SetStart(range.GetLength()); topTailRange.SetEnd(fragment.GetOwnRange().GetEnd()); } else { topTailRange.SetStart(range.GetLength()); topTailRange.SetEnd(fragment.GetOwnRange().GetEnd()); } if (topTailRange.GetStart() < lastPara->GetRange().GetEnd()) { lastPara->DeleteRange(topTailRange); // Make sure the numbering is correct long end; fragment.CalculateRange(0, end); // We only have part of a paragraph at the end fragment.SetPartialParagraph(true); } else { // We have a partial paragraph (don't save last new paragraph marker) // or complete paragraph fragment.SetPartialParagraph(isFragment); } } return true; } /// Given a position, get the number of the visible line (potentially many to a paragraph), /// starting from zero at the start of the buffer. long wxRichTextParagraphLayoutBox::GetVisibleLineNumber(long pos, bool caretPosition, bool startOfLine) const { if (caretPosition) pos ++; int lineCount = 0; wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph); // wxASSERT( child != NULL ); if (child) { if (child->GetRange().Contains(pos)) { wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst(); while (node2) { wxRichTextLine* line = node2->GetData(); wxRichTextRange lineRange = line->GetAbsoluteRange(); if (lineRange.Contains(pos) || pos == lineRange.GetStart()) { // If the caret is displayed at the end of the previous wrapped line, // we want to return the line it's _displayed_ at (not the actual line // containing the position). if (lineRange.GetStart() == pos && !startOfLine && child->GetRange().GetStart() != pos) return lineCount - 1; else return lineCount; } lineCount ++; node2 = node2->GetNext(); } // If we didn't find it in the lines, it must be // the last position of the paragraph. So return the last line. return lineCount-1; } else lineCount += child->GetLines().GetCount(); } node = node->GetNext(); } // Not found return -1; } /// Given a line number, get the corresponding wxRichTextLine object. wxRichTextLine* wxRichTextParagraphLayoutBox::GetLineForVisibleLineNumber(long lineNumber) const { int lineCount = 0; wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph); // wxASSERT(child != NULL); if (child) { if (lineNumber < (int) (child->GetLines().GetCount() + lineCount)) { wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst(); while (node2) { wxRichTextLine* line = node2->GetData(); if (lineCount == lineNumber) return line; lineCount ++; node2 = node2->GetNext(); } } else lineCount += child->GetLines().GetCount(); } node = node->GetNext(); } // Didn't find it return NULL; } /// Delete range from layout. bool wxRichTextParagraphLayoutBox::DeleteRange(const wxRichTextRange& range) { wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); wxRichTextParagraph* firstPara = NULL; while (node) { wxRichTextParagraph* obj = wxDynamicCast(node->GetData(), wxRichTextParagraph); // wxASSERT (obj != NULL); wxRichTextObjectList::compatibility_iterator next = node->GetNext(); if (obj) { // Delete the range in each paragraph if (!obj->GetRange().IsOutside(range)) { // Deletes the content of this object within the given range obj->DeleteRange(range); wxRichTextRange thisRange = obj->GetRange(); wxRichTextAttr thisAttr = obj->GetAttributes(); // If the whole paragraph is within the range to delete, // delete the whole thing. if (range.GetStart() <= thisRange.GetStart() && range.GetEnd() >= thisRange.GetEnd()) { // Delete the whole object RemoveChild(obj, true); obj = NULL; } else if (!firstPara) firstPara = obj; // If the range includes the paragraph end, we need to join this // and the next paragraph. if (range.GetEnd() <= thisRange.GetEnd()) { // We need to move the objects from the next paragraph // to this paragraph wxRichTextParagraph* nextParagraph = NULL; if ((range.GetEnd() < thisRange.GetEnd()) && obj) nextParagraph = obj; else { // We're ending at the end of the paragraph, so merge the _next_ paragraph. if (next) nextParagraph = wxDynamicCast(next->GetData(), wxRichTextParagraph); } bool applyFinalParagraphStyle = firstPara && nextParagraph && nextParagraph != firstPara; wxRichTextAttr nextParaAttr; if (applyFinalParagraphStyle) { // Special case when deleting the end of a paragraph - use _this_ paragraph's style, // not the next one. if (range.GetStart() == range.GetEnd() && range.GetStart() == thisRange.GetEnd()) nextParaAttr = thisAttr; else nextParaAttr = nextParagraph->GetAttributes(); } if (firstPara && nextParagraph && firstPara != nextParagraph) { // Move the objects to the previous para wxRichTextObjectList::compatibility_iterator node1 = nextParagraph->GetChildren().GetFirst(); while (node1) { wxRichTextObject* obj1 = node1->GetData(); firstPara->AppendChild(obj1); wxRichTextObjectList::compatibility_iterator next1 = node1->GetNext(); nextParagraph->GetChildren().Erase(node1); node1 = next1; } // Delete the paragraph RemoveChild(nextParagraph, true); } // Avoid empty paragraphs if (firstPara && firstPara->GetChildren().GetCount() == 0) { wxRichTextPlainText* text = new wxRichTextPlainText(wxEmptyString); firstPara->AppendChild(text); } if (applyFinalParagraphStyle) firstPara->SetAttributes(nextParaAttr); return true; } } } node = next; } return true; } /// Get any text in this object for the given range wxString wxRichTextParagraphLayoutBox::GetTextForRange(const wxRichTextRange& range) const { int lineCount = 0; wxString text; wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextObject* child = node->GetData(); if (!child->GetRange().IsOutside(range)) { wxRichTextRange childRange = range; childRange.LimitTo(child->GetRange()); wxString childText = child->GetTextForRange(childRange); text += childText; if ((childRange.GetEnd() == child->GetRange().GetEnd()) && node->GetNext()) text += wxT("\n"); lineCount ++; } node = node->GetNext(); } return text; } /// Get all the text wxString wxRichTextParagraphLayoutBox::GetText() const { return GetTextForRange(GetOwnRange()); } /// Get the paragraph by number wxRichTextParagraph* wxRichTextParagraphLayoutBox::GetParagraphAtLine(long paragraphNumber) const { if ((size_t) paragraphNumber >= GetChildCount()) return NULL; return (wxRichTextParagraph*) GetChild((size_t) paragraphNumber); } /// Get the length of the paragraph int wxRichTextParagraphLayoutBox::GetParagraphLength(long paragraphNumber) const { wxRichTextParagraph* para = GetParagraphAtLine(paragraphNumber); if (para) return para->GetRange().GetLength() - 1; // don't include newline else return 0; } /// Get the text of the paragraph wxString wxRichTextParagraphLayoutBox::GetParagraphText(long paragraphNumber) const { wxRichTextParagraph* para = GetParagraphAtLine(paragraphNumber); if (para) return para->GetTextForRange(para->GetRange()); else return wxEmptyString; } /// Convert zero-based line column and paragraph number to a position. long wxRichTextParagraphLayoutBox::XYToPosition(long x, long y) const { wxRichTextParagraph* para = GetParagraphAtLine(y); if (para) { return para->GetRange().GetStart() + x; } else return -1; } /// Convert zero-based position to line column and paragraph number bool wxRichTextParagraphLayoutBox::PositionToXY(long pos, long* x, long* y) const { wxRichTextParagraph* para = GetParagraphAtPosition(pos); if (para) { int count = 0; wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextObject* child = node->GetData(); if (child == para) break; count ++; node = node->GetNext(); } *y = count; *x = pos - para->GetRange().GetStart(); return true; } else return false; } /// Get the leaf object in a paragraph at this position. wxRichTextObject* wxRichTextParagraphLayoutBox::GetLeafObjectAtPosition(long position) const { wxRichTextParagraph* para = GetParagraphAtPosition(position); if (para) { wxRichTextObjectList::compatibility_iterator node = para->GetChildren().GetFirst(); while (node) { wxRichTextObject* child = node->GetData(); if (child->GetRange().Contains(position)) return child; node = node->GetNext(); } if (position == para->GetRange().GetEnd() && para->GetChildCount() > 0) return para->GetChildren().GetLast()->GetData(); } return NULL; } /// Set character or paragraph text attributes: apply character styles only to immediate text nodes bool wxRichTextParagraphLayoutBox::SetStyle(const wxRichTextRange& range, const wxRichTextAttr& style, int flags) { bool characterStyle = false; bool paragraphStyle = false; if (style.IsCharacterStyle()) characterStyle = true; if (style.IsParagraphStyle()) paragraphStyle = true; wxRichTextBuffer* buffer = GetBuffer(); bool withUndo = ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0); bool applyMinimal = ((flags & wxRICHTEXT_SETSTYLE_OPTIMIZE) != 0); bool parasOnly = ((flags & wxRICHTEXT_SETSTYLE_PARAGRAPHS_ONLY) != 0); bool charactersOnly = ((flags & wxRICHTEXT_SETSTYLE_CHARACTERS_ONLY) != 0); bool resetExistingStyle = ((flags & wxRICHTEXT_SETSTYLE_RESET) != 0); bool removeStyle = ((flags & wxRICHTEXT_SETSTYLE_REMOVE) != 0); // Apply paragraph style first, if any wxRichTextAttr wholeStyle(style); if (!removeStyle && wholeStyle.HasParagraphStyleName() && buffer->GetStyleSheet()) { wxRichTextParagraphStyleDefinition* def = buffer->GetStyleSheet()->FindParagraphStyle(wholeStyle.GetParagraphStyleName()); if (def) wxRichTextApplyStyle(wholeStyle, def->GetStyleMergedWithBase(buffer->GetStyleSheet())); } // Limit the attributes to be set to the content to only character attributes. wxRichTextAttr characterAttributes(wholeStyle); characterAttributes.SetFlags(characterAttributes.GetFlags() & (wxTEXT_ATTR_CHARACTER)); if (!removeStyle && characterAttributes.HasCharacterStyleName() && buffer->GetStyleSheet()) { wxRichTextCharacterStyleDefinition* def = buffer->GetStyleSheet()->FindCharacterStyle(characterAttributes.GetCharacterStyleName()); if (def) wxRichTextApplyStyle(characterAttributes, def->GetStyleMergedWithBase(buffer->GetStyleSheet())); } // If we are associated with a control, make undoable; otherwise, apply immediately // to the data. bool haveControl = (buffer->GetRichTextCtrl() != NULL); wxRichTextAction* action = NULL; if (haveControl && withUndo) { action = new wxRichTextAction(NULL, _("Change Style"), wxRICHTEXT_CHANGE_STYLE, buffer, this, buffer->GetRichTextCtrl()); action->SetRange(range); action->SetPosition(buffer->GetRichTextCtrl()->GetCaretPosition()); } wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph); // wxASSERT (para != NULL); if (para && para->GetChildCount() > 0) { // Stop searching if we're beyond the range of interest if (para->GetRange().GetStart() > range.GetEnd()) break; if (!para->GetRange().IsOutside(range)) { // We'll be using a copy of the paragraph to make style changes, // not updating the buffer directly. wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL); if (haveControl && withUndo) { newPara = new wxRichTextParagraph(*para); action->GetNewParagraphs().AppendChild(newPara); // Also store the old ones for Undo action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para)); } else newPara = para; // If we're specifying paragraphs only, then we really mean character formatting // to be included in the paragraph style if ((paragraphStyle || parasOnly) && !charactersOnly) { if (removeStyle) { // Removes the given style from the paragraph wxRichTextRemoveStyle(newPara->GetAttributes(), style); } else if (resetExistingStyle) newPara->GetAttributes() = wholeStyle; else { if (applyMinimal) { // Only apply attributes that will make a difference to the combined // style as seen on the display wxRichTextAttr combinedAttr(para->GetCombinedAttributes(true)); wxRichTextApplyStyle(newPara->GetAttributes(), wholeStyle, & combinedAttr); } else wxRichTextApplyStyle(newPara->GetAttributes(), wholeStyle); } } // When applying paragraph styles dynamically, don't change the text objects' attributes // since they will computed as needed. Only apply the character styling if it's _only_ // character styling. This policy is subject to change and might be put under user control. // Hm. we might well be applying a mix of paragraph and character styles, in which // case we _do_ want to apply character styles regardless of what para styles are set. // But if we're applying a paragraph style, which has some character attributes, but // we only want the paragraphs to hold this character style, then we _don't_ want to // apply the character style. So we need to be able to choose. if (!parasOnly && (characterStyle|charactersOnly) && range.GetStart() != newPara->GetRange().GetEnd()) { wxRichTextRange childRange(range); childRange.LimitTo(newPara->GetRange()); // Find the starting position and if necessary split it so // we can start applying a different style. // TODO: check that the style actually changes or is different // from style outside of range wxRichTextObject* firstObject wxDUMMY_INITIALIZE(NULL); wxRichTextObject* lastObject wxDUMMY_INITIALIZE(NULL); if (childRange.GetStart() == newPara->GetRange().GetStart()) firstObject = newPara->GetChildren().GetFirst()->GetData(); else firstObject = newPara->SplitAt(range.GetStart()); // Increment by 1 because we're apply the style one _after_ the split point long splitPoint = childRange.GetEnd(); if (splitPoint != newPara->GetRange().GetEnd()) splitPoint ++; // Find last object if (splitPoint == newPara->GetRange().GetEnd()) lastObject = newPara->GetChildren().GetLast()->GetData(); else // lastObject is set as a side-effect of splitting. It's // returned as the object before the new object. (void) newPara->SplitAt(splitPoint, & lastObject); wxASSERT(firstObject != NULL); wxASSERT(lastObject != NULL); if (!firstObject || !lastObject) continue; wxRichTextObjectList::compatibility_iterator firstNode = newPara->GetChildren().Find(firstObject); wxRichTextObjectList::compatibility_iterator lastNode = newPara->GetChildren().Find(lastObject); wxASSERT(firstNode); wxASSERT(lastNode); wxRichTextObjectList::compatibility_iterator node2 = firstNode; while (node2) { wxRichTextObject* child = node2->GetData(); if (removeStyle) { // Removes the given style from the paragraph wxRichTextRemoveStyle(child->GetAttributes(), style); } else if (resetExistingStyle) { // Preserve the URL as it's not really a formatting style but a property of the object wxString url; if (child->GetAttributes().HasURL() && !characterAttributes.HasURL()) url = child->GetAttributes().GetURL(); child->GetAttributes() = characterAttributes; if (!url.IsEmpty()) child->GetAttributes().SetURL(url); } else { if (applyMinimal) { // Only apply attributes that will make a difference to the combined // style as seen on the display wxRichTextAttr combinedAttr(newPara->GetCombinedAttributes(child->GetAttributes(), true)); wxRichTextApplyStyle(child->GetAttributes(), characterAttributes, & combinedAttr); } else wxRichTextApplyStyle(child->GetAttributes(), characterAttributes); } if (node2 == lastNode) break; node2 = node2->GetNext(); } } } } node = node->GetNext(); } // Do action, or delay it until end of batch. if (haveControl && withUndo) buffer->SubmitAction(action); return true; } // Just change the attributes for this single object. void wxRichTextParagraphLayoutBox::SetStyle(wxRichTextObject* obj, const wxRichTextAttr& textAttr, int flags) { wxRichTextBuffer* buffer = GetBuffer(); bool withUndo = flags & wxRICHTEXT_SETSTYLE_WITH_UNDO; bool resetExistingStyle = ((flags & wxRICHTEXT_SETSTYLE_RESET) != 0); bool haveControl = (buffer->GetRichTextCtrl() != NULL); wxRichTextAction *action = NULL; wxRichTextAttr newAttr = obj->GetAttributes(); if (resetExistingStyle) newAttr = textAttr; else newAttr.Apply(textAttr); if (haveControl && withUndo) { action = new wxRichTextAction(NULL, _("Change Object Style"), wxRICHTEXT_CHANGE_ATTRIBUTES, buffer, obj->GetContainer(), buffer->GetRichTextCtrl()); action->SetRange(obj->GetRange().FromInternal()); action->SetPosition(buffer->GetRichTextCtrl()->GetCaretPosition()); action->MakeObject(obj); action->GetAttributes() = newAttr; } else obj->GetAttributes() = newAttr; if (haveControl && withUndo) buffer->SubmitAction(action); } /// Get the text attributes for this position. bool wxRichTextParagraphLayoutBox::GetStyle(long position, wxRichTextAttr& style) { return DoGetStyle(position, style, true); } bool wxRichTextParagraphLayoutBox::GetUncombinedStyle(long position, wxRichTextAttr& style) { return DoGetStyle(position, style, false); } /// Implementation helper for GetStyle. If combineStyles is true, combine base, paragraph and /// context attributes. bool wxRichTextParagraphLayoutBox::DoGetStyle(long position, wxRichTextAttr& style, bool combineStyles) { wxRichTextObject* obj wxDUMMY_INITIALIZE(NULL); if (style.IsParagraphStyle()) { obj = GetParagraphAtPosition(position); if (obj) { if (combineStyles) { // Start with the base style style = GetAttributes(); style.GetTextBoxAttr().Reset(); // Apply the paragraph style wxRichTextApplyStyle(style, obj->GetAttributes()); } else style = obj->GetAttributes(); return true; } } else { obj = GetLeafObjectAtPosition(position); if (obj) { if (combineStyles) { wxRichTextParagraph* para = wxDynamicCast(obj->GetParent(), wxRichTextParagraph); style = para ? para->GetCombinedAttributes(obj->GetAttributes()) : obj->GetAttributes(); } else style = obj->GetAttributes(); return true; } } return false; } static bool wxHasStyle(long flags, long style) { return (flags & style) != 0; } /// Combines 'style' with 'currentStyle' for the purpose of summarising the attributes of a range of /// content. bool wxRichTextParagraphLayoutBox::CollectStyle(wxRichTextAttr& currentStyle, const wxRichTextAttr& style, wxRichTextAttr& clashingAttr, wxRichTextAttr& absentAttr) { currentStyle.CollectCommonAttributes(style, clashingAttr, absentAttr); return true; } /// Get the combined style for a range - if any attribute is different within the range, /// that attribute is not present within the flags. /// *** Note that this is not recursive, and so assumes that content inside a paragraph is not itself /// nested. bool wxRichTextParagraphLayoutBox::GetStyleForRange(const wxRichTextRange& range, wxRichTextAttr& style) { style = wxRichTextAttr(); wxRichTextAttr clashingAttrPara, clashingAttrChar; wxRichTextAttr absentAttrPara, absentAttrChar; wxRichTextObjectList::compatibility_iterator node = GetChildren().GetFirst(); while (node) { wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph); if (para && !(para->GetRange().GetStart() > range.GetEnd() || para->GetRange().GetEnd() < range.GetStart())) { if (para->GetChildren().GetCount() == 0) { wxRichTextAttr paraStyle = para->GetCombinedAttributes(true /* use box attributes */); CollectStyle(style, paraStyle, clashingAttrPara, absentAttrPara); } else { wxRichTextRange paraRange(para->GetRange()); paraRange.LimitTo(range); // First collect paragraph attributes only wxRichTextAttr paraStyle = para->GetCombinedAttributes(); paraStyle.SetFlags(paraStyle.GetFlags() & wxTEXT_ATTR_PARAGRAPH); CollectStyle(style, paraStyle, clashingAttrPara, absentAttrPara); wxRichTextObjectList::compatibility_iterator childNode = para->GetChildren().GetFirst(); while (childNode) { wxRichTextObject* child = childNode->GetData(); if (!(child->GetRange().GetStart() > range.GetEnd() || child->GetRange().GetEnd() < range.GetStart())) { wxRichTextAttr childStyle = para->GetCombinedAttributes(child->GetAttributes(), true /* include box attributes */); // Now collect character attributes only childStyle.SetFlags(childStyle.GetFlags() & wxTEXT_ATTR_CHARACTER); CollectStyle(style, childStyle, clashingAttrChar, absentAttrChar); } childNode = childNode->GetNext(); } } } node = node->GetNext(); } return true; } /// Set default style bool wxRichTextParagraphLayoutBox::SetDefaultStyle(const wxRichTextAttr& style) { m_defaultAttributes = style; return true; } /// Test if this whole range has character attributes of the specified kind. If any /// of the attributes are different within the range, the test fails. You /// can use this to implement, for example, bold button updating. style must have /// flags indicating which attributes are of interest. bool wxRichTextParagraphLayoutBox::HasCharacterAttributes(const wxRichTextRange& range, const wxRichTextAttr& style) const { int foundCount = 0; int matchingCount = 0; wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph); // wxASSERT (para != NULL); if (para) { // Stop searching if we're beyond the range of interest if (para->GetRange().GetStart() > range.GetEnd()) return foundCount == matchingCount && foundCount != 0; if (!para->GetRange().IsOutside(range)) { wxRichTextObjectList::compatibility_iterator node2 = para->GetChildren().GetFirst(); while (node2) { wxRichTextObject* child = node2->GetData(); // Allow for empty string if no buffer wxRichTextRange childRange = child->GetRange(); if (childRange.GetLength() == 0 && GetRange().GetLength() == 1) childRange.SetEnd(childRange.GetEnd()+1); if (!childRange.IsOutside(range) && wxDynamicCast(child, wxRichTextPlainText)) { foundCount ++; wxRichTextAttr textAttr = para->GetCombinedAttributes(child->GetAttributes()); if (textAttr.EqPartial(style, false /* strong test - attributes must be valid in both objects */)) matchingCount ++; } node2 = node2->GetNext(); } } } node = node->GetNext(); } return foundCount == matchingCount && foundCount != 0; } /// Test if this whole range has paragraph attributes of the specified kind. If any /// of the attributes are different within the range, the test fails. You /// can use this to implement, for example, centering button updating. style must have /// flags indicating which attributes are of interest. bool wxRichTextParagraphLayoutBox::HasParagraphAttributes(const wxRichTextRange& range, const wxRichTextAttr& style) const { int foundCount = 0; int matchingCount = 0; wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph); // wxASSERT (para != NULL); if (para) { // Stop searching if we're beyond the range of interest if (para->GetRange().GetStart() > range.GetEnd()) return foundCount == matchingCount && foundCount != 0; if (!para->GetRange().IsOutside(range)) { wxRichTextAttr textAttr = GetAttributes(); // Apply the paragraph style wxRichTextApplyStyle(textAttr, para->GetAttributes()); foundCount ++; if (textAttr.EqPartial(style, false /* strong test */)) matchingCount ++; } } node = node->GetNext(); } return foundCount == matchingCount && foundCount != 0; } void wxRichTextParagraphLayoutBox::PrepareContent(wxRichTextParagraphLayoutBox& container) { wxRichTextBuffer* buffer = GetBuffer(); if (buffer && buffer->GetRichTextCtrl()) buffer->GetRichTextCtrl()->PrepareContent(container); } /// Set character or paragraph properties bool wxRichTextParagraphLayoutBox::SetProperties(const wxRichTextRange& range, const wxRichTextProperties& properties, int flags) { wxRichTextBuffer* buffer = GetBuffer(); bool withUndo = ((flags & wxRICHTEXT_SETPROPERTIES_WITH_UNDO) != 0); bool parasOnly = ((flags & wxRICHTEXT_SETPROPERTIES_PARAGRAPHS_ONLY) != 0); bool charactersOnly = ((flags & wxRICHTEXT_SETPROPERTIES_CHARACTERS_ONLY) != 0); bool resetExistingProperties = ((flags & wxRICHTEXT_SETPROPERTIES_RESET) != 0); bool removeProperties = ((flags & wxRICHTEXT_SETPROPERTIES_REMOVE) != 0); // If we are associated with a control, make undoable; otherwise, apply immediately // to the data. bool haveControl = (buffer->GetRichTextCtrl() != NULL); wxRichTextAction* action = NULL; if (haveControl && withUndo) { action = new wxRichTextAction(NULL, _("Change Properties"), wxRICHTEXT_CHANGE_PROPERTIES, buffer, this, buffer->GetRichTextCtrl()); action->SetRange(range); action->SetPosition(buffer->GetRichTextCtrl()->GetCaretPosition()); } wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph); // wxASSERT (para != NULL); if (para && para->GetChildCount() > 0) { // Stop searching if we're beyond the range of interest if (para->GetRange().GetStart() > range.GetEnd()) break; if (!para->GetRange().IsOutside(range)) { // We'll be using a copy of the paragraph to make style changes, // not updating the buffer directly. wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL); if (haveControl && withUndo) { newPara = new wxRichTextParagraph(*para); action->GetNewParagraphs().AppendChild(newPara); // Also store the old ones for Undo action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para)); } else newPara = para; if (parasOnly) { if (removeProperties) { // Removes the given style from the paragraph // TODO newPara->GetProperties().RemoveProperties(properties); } else if (resetExistingProperties) newPara->GetProperties() = properties; else newPara->GetProperties().MergeProperties(properties); } // When applying paragraph styles dynamically, don't change the text objects' attributes // since they will computed as needed. Only apply the character styling if it's _only_ // character styling. This policy is subject to change and might be put under user control. // Hm. we might well be applying a mix of paragraph and character styles, in which // case we _do_ want to apply character styles regardless of what para styles are set. // But if we're applying a paragraph style, which has some character attributes, but // we only want the paragraphs to hold this character style, then we _don't_ want to // apply the character style. So we need to be able to choose. if (!parasOnly && charactersOnly && range.GetStart() != newPara->GetRange().GetEnd()) { wxRichTextRange childRange(range); childRange.LimitTo(newPara->GetRange()); // Find the starting position and if necessary split it so // we can start applying different properties. // TODO: check that the properties actually change or are different // from properties outside of range wxRichTextObject* firstObject wxDUMMY_INITIALIZE(NULL); wxRichTextObject* lastObject wxDUMMY_INITIALIZE(NULL); if (childRange.GetStart() == newPara->GetRange().GetStart()) firstObject = newPara->GetChildren().GetFirst()->GetData(); else firstObject = newPara->SplitAt(range.GetStart()); // Increment by 1 because we're apply the style one _after_ the split point long splitPoint = childRange.GetEnd(); if (splitPoint != newPara->GetRange().GetEnd()) splitPoint ++; // Find last object if (splitPoint == newPara->GetRange().GetEnd()) lastObject = newPara->GetChildren().GetLast()->GetData(); else // lastObject is set as a side-effect of splitting. It's // returned as the object before the new object. (void) newPara->SplitAt(splitPoint, & lastObject); wxASSERT(firstObject != NULL); wxASSERT(lastObject != NULL); if (!firstObject || !lastObject) continue; wxRichTextObjectList::compatibility_iterator firstNode = newPara->GetChildren().Find(firstObject); wxRichTextObjectList::compatibility_iterator lastNode = newPara->GetChildren().Find(lastObject); wxASSERT(firstNode); wxASSERT(lastNode); wxRichTextObjectList::compatibility_iterator node2 = firstNode; while (node2) { wxRichTextObject* child = node2->GetData(); if (removeProperties) { // Removes the given properties from the paragraph child->GetProperties().RemoveProperties(properties); } else if (resetExistingProperties) child->GetProperties() = properties; else { child->GetProperties().MergeProperties(properties); } if (node2 == lastNode) break; node2 = node2->GetNext(); } } } } node = node->GetNext(); } // Do action, or delay it until end of batch. if (haveControl && withUndo) buffer->SubmitAction(action); return true; } void wxRichTextParagraphLayoutBox::Reset() { Clear(); wxRichTextBuffer* buffer = GetBuffer(); if (buffer && buffer->GetRichTextCtrl()) { wxRichTextEvent event(wxEVT_RICHTEXT_BUFFER_RESET, buffer->GetRichTextCtrl()->GetId()); event.SetEventObject(buffer->GetRichTextCtrl()); event.SetContainer(this); buffer->SendEvent(event, true); } AddParagraph(wxEmptyString); PrepareContent(*this); InvalidateHierarchy(wxRICHTEXT_ALL); } /// Invalidate the buffer. With no argument, invalidates whole buffer. void wxRichTextParagraphLayoutBox::Invalidate(const wxRichTextRange& invalidRange) { wxRichTextCompositeObject::Invalidate(invalidRange); DoInvalidate(invalidRange); } // Do the (in)validation for this object only void wxRichTextParagraphLayoutBox::DoInvalidate(const wxRichTextRange& invalidRange) { if (invalidRange == wxRICHTEXT_ALL) { m_invalidRange = wxRICHTEXT_ALL; } // Already invalidating everything else if (m_invalidRange == wxRICHTEXT_ALL) { } else { if ((invalidRange.GetStart() < m_invalidRange.GetStart()) || m_invalidRange.GetStart() == -1) m_invalidRange.SetStart(invalidRange.GetStart()); if (invalidRange.GetEnd() > m_invalidRange.GetEnd()) m_invalidRange.SetEnd(invalidRange.GetEnd()); } } // Do the (in)validation both up and down the hierarchy void wxRichTextParagraphLayoutBox::InvalidateHierarchy(const wxRichTextRange& invalidRange) { Invalidate(invalidRange); if (invalidRange != wxRICHTEXT_NONE) { // Now go up the hierarchy wxRichTextObject* thisObj = this; wxRichTextObject* p = GetParent(); while (p) { wxRichTextParagraphLayoutBox* l = wxDynamicCast(p, wxRichTextParagraphLayoutBox); if (l) l->DoInvalidate(thisObj->GetRange()); thisObj = p; p = p->GetParent(); } } } /// Get invalid range, rounding to entire paragraphs if argument is true. wxRichTextRange wxRichTextParagraphLayoutBox::GetInvalidRange(bool wholeParagraphs) const { if (m_invalidRange == wxRICHTEXT_ALL || m_invalidRange == wxRICHTEXT_NONE) return m_invalidRange; wxRichTextRange range = m_invalidRange; if (wholeParagraphs) { wxRichTextParagraph* para1 = GetParagraphAtPosition(range.GetStart()); if (para1) range.SetStart(para1->GetRange().GetStart()); // FIXME: be more intelligent about this. Check if we have floating objects // before the end of the range. But it's not clear how we can in general // tell where it's safe to stop laying out. // Anyway, this code is central to efficiency when laying in floating mode. if (!wxRichTextBuffer::GetFloatingLayoutMode()) { wxRichTextParagraph* para2 = GetParagraphAtPosition(range.GetEnd()); if (para2) range.SetEnd(para2->GetRange().GetEnd()); } else // Floating layout means that all children should be laid out, // because we can't tell how the whole buffer will be affected. range.SetEnd(GetOwnRange().GetEnd()); } return range; } /// Apply the style sheet to the buffer, for example if the styles have changed. bool wxRichTextParagraphLayoutBox::ApplyStyleSheet(wxRichTextStyleSheet* styleSheet) { wxASSERT(styleSheet != NULL); if (!styleSheet) return false; int foundCount = 0; wxRichTextAttr attr(GetBasicStyle()); if (GetBasicStyle().HasParagraphStyleName()) { wxRichTextParagraphStyleDefinition* paraDef = styleSheet->FindParagraphStyle(GetBasicStyle().GetParagraphStyleName()); if (paraDef) { attr.Apply(paraDef->GetStyleMergedWithBase(styleSheet)); SetBasicStyle(attr); foundCount ++; } } if (GetBasicStyle().HasCharacterStyleName()) { wxRichTextCharacterStyleDefinition* charDef = styleSheet->FindCharacterStyle(GetBasicStyle().GetCharacterStyleName()); if (charDef) { attr.Apply(charDef->GetStyleMergedWithBase(styleSheet)); SetBasicStyle(attr); foundCount ++; } } wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph); // wxASSERT (para != NULL); if (para) { // Combine paragraph and list styles. If there is a list style in the original attributes, // the current indentation overrides anything else and is used to find the item indentation. // Also, for applying paragraph styles, consider having 2 modes: (1) we merge with what we have, // thereby taking into account all user changes, (2) reset the style completely (except for indentation/list // exception as above). // Problem: when changing from one list style to another, there's a danger that the level info will get lost. // So when changing a list style interactively, could retrieve level based on current style, then // set appropriate indent and apply new style. int outline = -1; int num = -1; if (para->GetAttributes().HasOutlineLevel()) outline = para->GetAttributes().GetOutlineLevel(); if (para->GetAttributes().HasBulletNumber()) num = para->GetAttributes().GetBulletNumber(); if (!para->GetAttributes().GetParagraphStyleName().IsEmpty() && !para->GetAttributes().GetListStyleName().IsEmpty()) { int currentIndent = para->GetAttributes().GetLeftIndent(); wxRichTextParagraphStyleDefinition* paraDef = styleSheet->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName()); wxRichTextListStyleDefinition* listDef = styleSheet->FindListStyle(para->GetAttributes().GetListStyleName()); if (paraDef && !listDef) { para->GetAttributes() = paraDef->GetStyleMergedWithBase(styleSheet); foundCount ++; } else if (listDef && !paraDef) { // Set overall style defined for the list style definition para->GetAttributes() = listDef->GetStyleMergedWithBase(styleSheet); // Apply the style for this level wxRichTextApplyStyle(para->GetAttributes(), * listDef->GetLevelAttributes(listDef->FindLevelForIndent(currentIndent))); foundCount ++; } else if (listDef && paraDef) { // Combines overall list style, style for level, and paragraph style para->GetAttributes() = listDef->CombineWithParagraphStyle(currentIndent, paraDef->GetStyleMergedWithBase(styleSheet)); foundCount ++; } } else if (para->GetAttributes().GetParagraphStyleName().IsEmpty() && !para->GetAttributes().GetListStyleName().IsEmpty()) { int currentIndent = para->GetAttributes().GetLeftIndent(); wxRichTextListStyleDefinition* listDef = styleSheet->FindListStyle(para->GetAttributes().GetListStyleName()); // Overall list definition style para->GetAttributes() = listDef->GetStyleMergedWithBase(styleSheet); // Style for this level wxRichTextApplyStyle(para->GetAttributes(), * listDef->GetLevelAttributes(listDef->FindLevelForIndent(currentIndent))); foundCount ++; } else if (!para->GetAttributes().GetParagraphStyleName().IsEmpty() && para->GetAttributes().GetListStyleName().IsEmpty()) { wxRichTextParagraphStyleDefinition* def = styleSheet->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName()); if (def) { para->GetAttributes() = def->GetStyleMergedWithBase(styleSheet); foundCount ++; } } if (outline != -1) para->GetAttributes().SetOutlineLevel(outline); if (num != -1) para->GetAttributes().SetBulletNumber(num); } node = node->GetNext(); } return foundCount != 0; } /// Set list style bool wxRichTextParagraphLayoutBox::SetListStyle(const wxRichTextRange& range, wxRichTextListStyleDefinition* def, int flags, int startFrom, int specifiedLevel) { wxRichTextBuffer* buffer = GetBuffer(); wxRichTextStyleSheet* styleSheet = buffer->GetStyleSheet(); bool withUndo = ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0); // bool applyMinimal = ((flags & wxRICHTEXT_SETSTYLE_OPTIMIZE) != 0); bool specifyLevel = ((flags & wxRICHTEXT_SETSTYLE_SPECIFY_LEVEL) != 0); bool renumber = ((flags & wxRICHTEXT_SETSTYLE_RENUMBER) != 0); // Current number, if numbering int n = startFrom; wxASSERT (!specifyLevel || (specifyLevel && (specifiedLevel >= 0))); // If we are associated with a control, make undoable; otherwise, apply immediately // to the data. bool haveControl = (buffer->GetRichTextCtrl() != NULL); wxRichTextAction* action = NULL; if (haveControl && withUndo) { action = new wxRichTextAction(NULL, _("Change List Style"), wxRICHTEXT_CHANGE_STYLE, buffer, this, buffer->GetRichTextCtrl()); action->SetRange(range); action->SetPosition(buffer->GetRichTextCtrl()->GetCaretPosition()); } wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph); // wxASSERT (para != NULL); if (para && para->GetChildCount() > 0) { // Stop searching if we're beyond the range of interest if (para->GetRange().GetStart() > range.GetEnd()) break; if (!para->GetRange().IsOutside(range)) { // We'll be using a copy of the paragraph to make style changes, // not updating the buffer directly. wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL); if (haveControl && withUndo) { newPara = new wxRichTextParagraph(*para); action->GetNewParagraphs().AppendChild(newPara); // Also store the old ones for Undo action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para)); } else newPara = para; if (def) { int thisIndent = newPara->GetAttributes().GetLeftIndent(); int thisLevel = specifyLevel ? specifiedLevel : def->FindLevelForIndent(thisIndent); // How is numbering going to work? // If we are renumbering, or numbering for the first time, we need to keep // track of the number for each level. But we might be simply applying a different // list style. // In Word, applying a style to several paragraphs, even if at different levels, // reverts the level back to the same one. So we could do the same here. // Renumbering will need to be done when we promote/demote a paragraph. // Apply the overall list style, and item style for this level wxRichTextAttr listStyle(def->GetCombinedStyleForLevel(thisLevel, styleSheet)); wxRichTextApplyStyle(newPara->GetAttributes(), listStyle); // Now we need to do numbering // Preserve the existing list item continuation bullet style, if any if (para->GetAttributes().HasBulletStyle() && (para->GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_CONTINUATION)) newPara->GetAttributes().SetBulletStyle(newPara->GetAttributes().GetBulletStyle()|wxTEXT_ATTR_BULLET_STYLE_CONTINUATION); else { if (renumber) { newPara->GetAttributes().SetBulletNumber(n); } n ++; } } else if (!newPara->GetAttributes().GetListStyleName().IsEmpty()) { // if def is NULL, remove list style, applying any associated paragraph style // to restore the attributes newPara->GetAttributes().SetListStyleName(wxEmptyString); newPara->GetAttributes().SetLeftIndent(0, 0); newPara->GetAttributes().SetBulletText(wxEmptyString); newPara->GetAttributes().SetBulletStyle(0); // Eliminate the main list-related attributes newPara->GetAttributes().SetFlags(newPara->GetAttributes().GetFlags() & ~wxTEXT_ATTR_LEFT_INDENT & ~wxTEXT_ATTR_BULLET_STYLE & ~wxTEXT_ATTR_BULLET_NUMBER & ~wxTEXT_ATTR_BULLET_TEXT & wxTEXT_ATTR_LIST_STYLE_NAME); if (styleSheet && !newPara->GetAttributes().GetParagraphStyleName().IsEmpty()) { wxRichTextParagraphStyleDefinition* def = styleSheet->FindParagraphStyle(newPara->GetAttributes().GetParagraphStyleName()); if (def) { newPara->GetAttributes() = def->GetStyleMergedWithBase(styleSheet); } } } } } node = node->GetNext(); } // Do action, or delay it until end of batch. if (haveControl && withUndo) buffer->SubmitAction(action); return true; } bool wxRichTextParagraphLayoutBox::SetListStyle(const wxRichTextRange& range, const wxString& defName, int flags, int startFrom, int specifiedLevel) { wxRichTextBuffer* buffer = GetBuffer(); if (buffer && buffer->GetStyleSheet()) { wxRichTextListStyleDefinition* def = buffer->GetStyleSheet()->FindListStyle(defName); if (def) return SetListStyle(range, def, flags, startFrom, specifiedLevel); } return false; } /// Clear list for given range bool wxRichTextParagraphLayoutBox::ClearListStyle(const wxRichTextRange& range, int flags) { return SetListStyle(range, NULL, flags); } /// Number/renumber any list elements in the given range bool wxRichTextParagraphLayoutBox::NumberList(const wxRichTextRange& range, wxRichTextListStyleDefinition* def, int flags, int startFrom, int specifiedLevel) { return DoNumberList(range, range, 0, def, flags, startFrom, specifiedLevel); } /// Number/renumber any list elements in the given range. Also do promotion or demotion of items, if specified bool wxRichTextParagraphLayoutBox::DoNumberList(const wxRichTextRange& range, const wxRichTextRange& promotionRange, int promoteBy, wxRichTextListStyleDefinition* def, int flags, int startFrom, int specifiedLevel) { wxRichTextBuffer* buffer = GetBuffer(); wxRichTextStyleSheet* styleSheet = buffer->GetStyleSheet(); bool withUndo = ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0); // bool applyMinimal = ((flags & wxRICHTEXT_SETSTYLE_OPTIMIZE) != 0); #if wxDEBUG_LEVEL bool specifyLevel = ((flags & wxRICHTEXT_SETSTYLE_SPECIFY_LEVEL) != 0); #endif bool renumber = ((flags & wxRICHTEXT_SETSTYLE_RENUMBER) != 0); // Max number of levels const int maxLevels = 10; // The level we're looking at now int currentLevel = -1; // The item number for each level int levels[maxLevels]; int i; // Reset all numbering for (i = 0; i < maxLevels; i++) { if (startFrom != -1) levels[i] = startFrom-1; else if (renumber) // start again levels[i] = 0; else levels[i] = -1; // start from the number we found, if any } #if wxDEBUG_LEVEL wxASSERT(!specifyLevel || (specifyLevel && (specifiedLevel >= 0))); #endif // If we are associated with a control, make undoable; otherwise, apply immediately // to the data. bool haveControl = (buffer->GetRichTextCtrl() != NULL); wxRichTextAction* action = NULL; if (haveControl && withUndo) { action = new wxRichTextAction(NULL, _("Renumber List"), wxRICHTEXT_CHANGE_STYLE, buffer, this, buffer->GetRichTextCtrl()); action->SetRange(range); action->SetPosition(buffer->GetRichTextCtrl()->GetCaretPosition()); } wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph); // wxASSERT (para != NULL); if (para && para->GetChildCount() > 0) { // Stop searching if we're beyond the range of interest if (para->GetRange().GetStart() > range.GetEnd()) break; if (!para->GetRange().IsOutside(range)) { // We'll be using a copy of the paragraph to make style changes, // not updating the buffer directly. wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL); if (haveControl && withUndo) { newPara = new wxRichTextParagraph(*para); action->GetNewParagraphs().AppendChild(newPara); // Also store the old ones for Undo action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para)); } else newPara = para; wxRichTextListStyleDefinition* defToUse = def; if (!defToUse) { if (styleSheet && !newPara->GetAttributes().GetListStyleName().IsEmpty()) defToUse = styleSheet->FindListStyle(newPara->GetAttributes().GetListStyleName()); } if (defToUse) { int thisIndent = newPara->GetAttributes().GetLeftIndent(); int thisLevel = defToUse->FindLevelForIndent(thisIndent); // If we've specified a level to apply to all, change the level. if (specifiedLevel != -1) thisLevel = specifiedLevel; // Do promotion if specified if ((promoteBy != 0) && !para->GetRange().IsOutside(promotionRange)) { thisLevel = thisLevel - promoteBy; if (thisLevel < 0) thisLevel = 0; if (thisLevel > 9) thisLevel = 9; } // Apply the overall list style, and item style for this level wxRichTextAttr listStyle(defToUse->GetCombinedStyleForLevel(thisLevel, styleSheet)); wxRichTextApplyStyle(newPara->GetAttributes(), listStyle); // Preserve the existing list item continuation bullet style, if any if (para->GetAttributes().HasBulletStyle() && (para->GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_CONTINUATION)) newPara->GetAttributes().SetBulletStyle(newPara->GetAttributes().GetBulletStyle()|wxTEXT_ATTR_BULLET_STYLE_CONTINUATION); // OK, we've (re)applied the style, now let's get the numbering right. if (currentLevel == -1) currentLevel = thisLevel; // Same level as before, do nothing except increment level's number afterwards if (currentLevel == thisLevel) { } // A deeper level: start renumbering all levels after current level else if (thisLevel > currentLevel) { for (i = currentLevel+1; i <= thisLevel; i++) { levels[i] = 0; } currentLevel = thisLevel; } else if (thisLevel < currentLevel) { currentLevel = thisLevel; } // Use the current numbering if -1 and we have a bullet number already if (levels[currentLevel] == -1) { if (newPara->GetAttributes().HasBulletNumber()) levels[currentLevel] = newPara->GetAttributes().GetBulletNumber(); else levels[currentLevel] = 1; } else { if (!(para->GetAttributes().HasBulletStyle() && (para->GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_CONTINUATION))) levels[currentLevel] ++; } newPara->GetAttributes().SetBulletNumber(levels[currentLevel]); // Create the bullet text if an outline list if (listStyle.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE) { wxString text; for (i = 0; i <= currentLevel; i++) { if (!text.IsEmpty()) text += wxT("."); text += wxString::Format(wxT("%d"), levels[i]); } newPara->GetAttributes().SetBulletText(text); } } } } node = node->GetNext(); } // Do action, or delay it until end of batch. if (haveControl && withUndo) buffer->SubmitAction(action); return true; } bool wxRichTextParagraphLayoutBox::NumberList(const wxRichTextRange& range, const wxString& defName, int flags, int startFrom, int specifiedLevel) { wxRichTextBuffer* buffer = GetBuffer(); if (buffer->GetStyleSheet()) { wxRichTextListStyleDefinition* def = NULL; if (!defName.IsEmpty()) def = buffer->GetStyleSheet()->FindListStyle(defName); return NumberList(range, def, flags, startFrom, specifiedLevel); } return false; } /// Promote the list items within the given range. promoteBy can be a positive or negative number, e.g. 1 or -1 bool wxRichTextParagraphLayoutBox::PromoteList(int promoteBy, const wxRichTextRange& range, wxRichTextListStyleDefinition* def, int flags, int specifiedLevel) { // TODO // One strategy is to first work out the range within which renumbering must occur. Then could pass these two ranges // to NumberList with a flag indicating promotion is required within one of the ranges. // Find first and last paragraphs in range. Then for first, calculate new indentation and look back until we find // a paragraph that either has no list style, or has one that is different or whose indentation is less. // We start renumbering from the para after that different para we found. We specify that the numbering of that // list position will start from 1. // Similarly, we look after the last para in the promote range for an indentation that is less (or no list style). // We can end the renumbering at this point. // For now, only renumber within the promotion range. return DoNumberList(range, range, promoteBy, def, flags, 1, specifiedLevel); } bool wxRichTextParagraphLayoutBox::PromoteList(int promoteBy, const wxRichTextRange& range, const wxString& defName, int flags, int specifiedLevel) { wxRichTextBuffer* buffer = GetBuffer(); if (buffer->GetStyleSheet()) { wxRichTextListStyleDefinition* def = NULL; if (!defName.IsEmpty()) def = buffer->GetStyleSheet()->FindListStyle(defName); return PromoteList(promoteBy, range, def, flags, specifiedLevel); } return false; } /// Fills in the attributes for numbering a paragraph after previousParagraph. It also finds the /// position of the paragraph that it had to start looking from. bool wxRichTextParagraphLayoutBox::FindNextParagraphNumber(wxRichTextParagraph* previousParagraph, wxRichTextAttr& attr) const { // TODO: add GetNextChild/GetPreviousChild to composite // Search for a paragraph that isn't a continuation paragraph (no bullet) while (previousParagraph && previousParagraph->GetAttributes().HasBulletStyle() && previousParagraph->GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_CONTINUATION) { wxRichTextObjectList::compatibility_iterator node = ((wxRichTextCompositeObject*) previousParagraph->GetParent())->GetChildren().Find(previousParagraph); if (node) { node = node->GetPrevious(); if (node) previousParagraph = wxDynamicCast(node->GetData(), wxRichTextParagraph); else previousParagraph = NULL; } else previousParagraph = NULL; } if (!previousParagraph || !previousParagraph->GetAttributes().HasFlag(wxTEXT_ATTR_BULLET_STYLE) || previousParagraph->GetAttributes().GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_NONE) return false; wxRichTextBuffer* buffer = GetBuffer(); wxRichTextStyleSheet* styleSheet = buffer->GetStyleSheet(); if (styleSheet && !previousParagraph->GetAttributes().GetListStyleName().IsEmpty()) { wxRichTextListStyleDefinition* def = styleSheet->FindListStyle(previousParagraph->GetAttributes().GetListStyleName()); if (def) { // int thisIndent = previousParagraph->GetAttributes().GetLeftIndent(); // int thisLevel = def->FindLevelForIndent(thisIndent); bool isOutline = (previousParagraph->GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE) != 0; attr.SetFlags(previousParagraph->GetAttributes().GetFlags() & (wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_BULLET_NUMBER|wxTEXT_ATTR_BULLET_TEXT|wxTEXT_ATTR_BULLET_NAME)); if (previousParagraph->GetAttributes().HasBulletName()) attr.SetBulletName(previousParagraph->GetAttributes().GetBulletName()); attr.SetBulletStyle(previousParagraph->GetAttributes().GetBulletStyle()); attr.SetListStyleName(previousParagraph->GetAttributes().GetListStyleName()); int nextNumber = previousParagraph->GetAttributes().GetBulletNumber() + 1; attr.SetBulletNumber(nextNumber); if (isOutline) { wxString text = previousParagraph->GetAttributes().GetBulletText(); if (!text.IsEmpty()) { int pos = text.Find(wxT('.'), true); if (pos != wxNOT_FOUND) { text = text.Mid(0, text.Length() - pos - 1); } else text = wxEmptyString; if (!text.IsEmpty()) text += wxT("."); text += wxString::Format(wxT("%d"), nextNumber); attr.SetBulletText(text); } } return true; } else return false; } else return false; } /*! * wxRichTextParagraph * This object represents a single paragraph (or in a straight text editor, a line). */ IMPLEMENT_DYNAMIC_CLASS(wxRichTextParagraph, wxRichTextCompositeObject) wxArrayInt wxRichTextParagraph::sm_defaultTabs; wxRichTextParagraph::wxRichTextParagraph(wxRichTextObject* parent, wxRichTextAttr* style): wxRichTextCompositeObject(parent) { if (style) SetAttributes(*style); } wxRichTextParagraph::wxRichTextParagraph(const wxString& text, wxRichTextObject* parent, wxRichTextAttr* paraStyle, wxRichTextAttr* charStyle): wxRichTextCompositeObject(parent) { if (paraStyle) SetAttributes(*paraStyle); AppendChild(new wxRichTextPlainText(text, this, charStyle)); } wxRichTextParagraph::~wxRichTextParagraph() { ClearLines(); } /// Draw the item bool wxRichTextParagraph::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int WXUNUSED(descent), int style) { if (!IsShown()) return true; // Currently we don't merge these attributes with the parent, but we // should consider whether we should (e.g. if we set a border colour // for all paragraphs). But generally box attributes are likely to be // different for different objects. wxRect paraRect = GetRect(); wxRichTextAttr attr = GetCombinedAttributes(); AdjustAttributes(attr, context); DrawBoxAttributes(dc, GetBuffer(), attr, paraRect, 0); // Draw the bullet, if any if ((attr.GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_NONE) == 0 && (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_CONTINUATION) == 0) { if (attr.GetLeftSubIndent() != 0) { int spaceBeforePara = ConvertTenthsMMToPixels(dc, attr.GetParagraphSpacingBefore()); int leftIndent = ConvertTenthsMMToPixels(dc, attr.GetLeftIndent()); wxRichTextAttr bulletAttr(attr); // Combine with the font of the first piece of content, if one is specified if (GetChildren().GetCount() > 0) { wxRichTextObject* firstObj = (wxRichTextObject*) GetChildren().GetFirst()->GetData(); if (!firstObj->IsFloatable() && firstObj->GetAttributes().HasFont()) { wxRichTextApplyStyle(bulletAttr, firstObj->GetAttributes()); } } // Get line height from first line, if any wxRichTextLine* line = m_cachedLines.GetFirst() ? (wxRichTextLine* ) m_cachedLines.GetFirst()->GetData() : NULL; wxPoint linePos; int lineHeight wxDUMMY_INITIALIZE(0); if (line) { lineHeight = line->GetSize().y; linePos = line->GetPosition() + GetPosition(); } else { wxFont font; if (bulletAttr.HasFont() && GetBuffer()) font = GetBuffer()->GetFontTable().FindFont(bulletAttr); else font = (*wxNORMAL_FONT); wxCheckSetFont(dc, font); lineHeight = dc.GetCharHeight(); linePos = GetPosition(); linePos.y += spaceBeforePara; } wxRect bulletRect(GetPosition().x + leftIndent, linePos.y, linePos.x - (GetPosition().x + leftIndent), lineHeight); if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_BITMAP) { if (wxRichTextBuffer::GetRenderer()) wxRichTextBuffer::GetRenderer()->DrawBitmapBullet(this, dc, bulletAttr, bulletRect); } else if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_STANDARD) { if (wxRichTextBuffer::GetRenderer()) wxRichTextBuffer::GetRenderer()->DrawStandardBullet(this, dc, bulletAttr, bulletRect); } else { wxString bulletText = GetBulletText(); if (!bulletText.empty() && wxRichTextBuffer::GetRenderer()) wxRichTextBuffer::GetRenderer()->DrawTextBullet(this, dc, bulletAttr, bulletRect, bulletText); } } } // Draw the range for each line, one object at a time. wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst(); while (node) { wxRichTextLine* line = node->GetData(); wxRichTextRange lineRange = line->GetAbsoluteRange(); // Lines are specified relative to the paragraph wxPoint linePosition = line->GetPosition() + GetPosition(); // Don't draw if off the screen if (((style & wxRICHTEXT_DRAW_IGNORE_CACHE) != 0) || ((linePosition.y + line->GetSize().y) >= rect.y && linePosition.y <= rect.y + rect.height)) { wxPoint objectPosition = linePosition; int maxDescent = line->GetDescent(); // Loop through objects until we get to the one within range wxRichTextObjectList::compatibility_iterator node2 = m_children.GetFirst(); int i = 0; while (node2) { wxRichTextObject* child = node2->GetData(); if ((!child->IsFloating() || !wxRichTextBuffer::GetFloatingLayoutMode()) && child->GetRange().GetLength() > 0 && !child->GetRange().IsOutside(lineRange) && !lineRange.IsOutside(range)) { // Draw this part of the line at the correct position wxRichTextRange objectRange(child->GetRange()); objectRange.LimitTo(lineRange); wxSize objectSize; if (child->IsTopLevel()) { objectSize = child->GetCachedSize(); objectRange = child->GetOwnRange(); } else { #if wxRICHTEXT_USE_OPTIMIZED_LINE_DRAWING && wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS if (i < (int) line->GetObjectSizes().GetCount()) { objectSize.x = line->GetObjectSizes()[(size_t) i]; } else #endif { int descent = 0; child->GetRangeSize(objectRange, objectSize, descent, dc, context, wxRICHTEXT_UNFORMATTED, objectPosition); } } // Use the child object's width, but the whole line's height wxRect childRect(objectPosition, wxSize(objectSize.x, line->GetSize().y)); child->Draw(dc, context, objectRange, selection, childRect, maxDescent, style); objectPosition.x += objectSize.x; i ++; } else if (child->GetRange().GetStart() > lineRange.GetEnd()) // Can break out of inner loop now since we've passed this line's range break; node2 = node2->GetNext(); } } node = node->GetNext(); } return true; } // Get the range width using partial extents calculated for the whole paragraph. static int wxRichTextGetRangeWidth(const wxRichTextParagraph& para, const wxRichTextRange& range, const wxArrayInt& partialExtents) { wxASSERT(partialExtents.GetCount() >= (size_t) range.GetLength()); if (partialExtents.GetCount() < (size_t) range.GetLength()) return 0; int leftMostPos = 0; if (range.GetStart() - para.GetRange().GetStart() > 0) leftMostPos = partialExtents[range.GetStart() - para.GetRange().GetStart() - 1]; int rightMostPos = partialExtents[range.GetEnd() - para.GetRange().GetStart()]; int w = rightMostPos - leftMostPos; return w; } /// Lay the item out bool wxRichTextParagraph::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& parentRect, int style) { // Deal with floating objects firstly before the normal layout wxRichTextBuffer* buffer = GetBuffer(); wxASSERT(buffer); wxRichTextFloatCollector* collector = GetContainer()->GetFloatCollector(); if (wxRichTextBuffer::GetFloatingLayoutMode()) { wxASSERT(collector != NULL); if (collector) LayoutFloat(dc, context, rect, parentRect, style, collector); } wxRichTextAttr attr = GetCombinedAttributes(); AdjustAttributes(attr, context); // ClearLines(); // Increase the size of the paragraph due to spacing int spaceBeforePara = ConvertTenthsMMToPixels(dc, attr.GetParagraphSpacingBefore()); int spaceAfterPara = ConvertTenthsMMToPixels(dc, attr.GetParagraphSpacingAfter()); int leftIndent = ConvertTenthsMMToPixels(dc, attr.GetLeftIndent()); int leftSubIndent = ConvertTenthsMMToPixels(dc, attr.GetLeftSubIndent()); int rightIndent = ConvertTenthsMMToPixels(dc, attr.GetRightIndent()); int lineSpacing = 0; // Let's assume line spacing of 10 is normal, 15 is 1.5, 20 is 2, etc. if (attr.HasLineSpacing() && attr.GetLineSpacing() > 0 && attr.HasFont()) { wxFont font(buffer->GetFontTable().FindFont(attr)); if (font.IsOk()) { wxCheckSetFont(dc, font); lineSpacing = (int) (double(dc.GetCharHeight()) * (double(attr.GetLineSpacing())/10.0 - 1.0)); } } // Start position for each line relative to the paragraph int startPositionFirstLine = leftIndent; int startPositionSubsequentLines = leftIndent + leftSubIndent; // If we have a bullet in this paragraph, the start position for the first line's text // is actually leftIndent + leftSubIndent. if (attr.GetBulletStyle() != wxTEXT_ATTR_BULLET_STYLE_NONE) startPositionFirstLine = startPositionSubsequentLines; long lastEndPos = GetRange().GetStart()-1; long lastCompletedEndPos = lastEndPos; int currentWidth = 0; SetPosition(rect.GetPosition()); wxPoint currentPosition(0, spaceBeforePara); // We will calculate lines relative to paragraph int lineHeight = 0; int maxWidth = 0; int maxHeight = currentPosition.y; int maxAscent = 0; int maxDescent = 0; int lineCount = 0; int lineAscent = 0; int lineDescent = 0; wxRichTextObjectList::compatibility_iterator node; #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS wxUnusedVar(style); wxArrayInt partialExtents; wxSize paraSize; int paraDescent = 0; // This calculates the partial text extents GetRangeSize(GetRange(), paraSize, paraDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_CACHE_SIZE, rect.GetPosition(), parentRect.GetSize(), & partialExtents); #else node = m_children.GetFirst(); while (node) { wxRichTextObject* child = node->GetData(); //child->SetCachedSize(wxDefaultSize); child->Layout(dc, context, rect, style); node = node->GetNext(); } #endif // Split up lines // We may need to go back to a previous child, in which case create the new line, // find the child corresponding to the start position of the string, and // continue. wxRect availableRect; node = m_children.GetFirst(); while (node) { wxRichTextObject* child = node->GetData(); // If floating, ignore. We already laid out floats. // Also ignore if empty object, except if we haven't got any // size yet. if ((child->IsFloating() && wxRichTextBuffer::GetFloatingLayoutMode()) || !child->IsShown() || (child->GetRange().GetLength() == 0 && maxHeight > spaceBeforePara) ) { node = node->GetNext(); continue; } // If this is e.g. a composite text box, it will need to be laid out itself. // But if just a text fragment or image, for example, this will // do nothing. NB: won't we need to set the position after layout? // since for example if position is dependent on vertical line size, we // can't tell the position until the size is determined. So possibly introduce // another layout phase. // We may only be looking at part of a child, if we searched back for wrapping // and found a suitable point some way into the child. So get the size for the fragment // if necessary. long nextBreakPos = GetFirstLineBreakPosition(lastEndPos+1); long lastPosToUse = child->GetRange().GetEnd(); bool lineBreakInThisObject = (nextBreakPos > -1 && nextBreakPos <= child->GetRange().GetEnd()); if (lineBreakInThisObject) lastPosToUse = nextBreakPos; wxSize childSize; int childDescent = 0; int startOffset = (lineCount == 0 ? startPositionFirstLine : startPositionSubsequentLines); availableRect = wxRect(rect.x + startOffset, rect.y + currentPosition.y, rect.width - startOffset - rightIndent, rect.height); if (child->IsTopLevel()) { wxSize oldSize = child->GetCachedSize(); child->Invalidate(wxRICHTEXT_ALL); child->SetPosition(wxPoint(0, 0)); // Lays out the object first with a given amount of space, and then if no width was specified in attr, // lays out the object again using the minimum size // The position will be determined by its location in its line, // and not by the child's actual position. child->LayoutToBestSize(dc, context, buffer, attr, child->GetAttributes(), availableRect, parentRect, style); if (oldSize != child->GetCachedSize()) { partialExtents.Clear(); // Recalculate the partial text extents since the child object changed size GetRangeSize(GetRange(), paraSize, paraDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_CACHE_SIZE, wxPoint(0,0), parentRect.GetSize(), & partialExtents); } } // Problem: we need to layout composites here for which we need the available width, // but we can't get the available width without using the float collector which // needs to know the object height. if ((nextBreakPos == -1) && (lastEndPos == child->GetRange().GetStart() - 1)) // i.e. we want to get the whole thing { childSize = child->GetCachedSize(); childDescent = child->GetDescent(); } else { #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS // Get height only, then the width using the partial extents GetRangeSize(wxRichTextRange(lastEndPos+1, lastPosToUse), childSize, childDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_HEIGHT_ONLY, wxPoint(0,0), parentRect.GetSize()); childSize.x = wxRichTextGetRangeWidth(*this, wxRichTextRange(lastEndPos+1, lastPosToUse), partialExtents); #else GetRangeSize(wxRichTextRange(lastEndPos+1, lastPosToUse), childSize, childDescent, dc, context, wxRICHTEXT_UNFORMATTED, rect.GetPosition(), parentRect.GetSize()); #endif } bool doLoop = true; int loopIterations = 0; // If there are nested objects that need to lay themselves out, we have to do this in a // loop because the height of the object may well depend on the available width. // And because of floating object positioning, the available width depends on the // height of the object and whether it will clash with the floating objects. // So, we see whether the available width changes due to the presence of floating images. // If it does, then we'll use the new restricted width to find the object height again. // If this causes another restriction in the available width, we'll try again, until // either we lose patience or the available width settles down. do { loopIterations ++; wxRect oldAvailableRect = availableRect; // Available width depends on the floating objects and the line height. // Note: the floating objects may be placed vertically along the two sides of // buffer, so we may have different available line widths with different // [startY, endY]. So, we can't determine how wide the available // space is until we know the exact line height. if (childDescent == 0) { lineHeight = wxMax(lineHeight, childSize.y); lineDescent = maxDescent; lineAscent = maxAscent; } else { lineDescent = wxMax(childDescent, maxDescent); lineAscent = wxMax(childSize.y-childDescent, maxAscent); } lineHeight = wxMax(lineHeight, (lineDescent + lineAscent)); if (wxRichTextBuffer::GetFloatingLayoutMode() && collector) { wxRect floatAvailableRect = collector->GetAvailableRect(rect.y + currentPosition.y, rect.y + currentPosition.y + lineHeight); // Adjust availableRect to the space that is available when taking floating objects into account. if (floatAvailableRect.x + startOffset > availableRect.x) { int newX = floatAvailableRect.x + startOffset; int newW = availableRect.width - (newX - availableRect.x); availableRect.x = newX; availableRect.width = newW; } if (floatAvailableRect.width < availableRect.width) availableRect.width = floatAvailableRect.width; } currentPosition.x = availableRect.x - rect.x; if (child->IsTopLevel() && loopIterations <= 20) { if (availableRect != oldAvailableRect) { wxSize oldSize = child->GetCachedSize(); // Lays out the object first with a given amount of space, and then if no width was specified in attr, // lays out the object again using the minimum size child->Invalidate(wxRICHTEXT_ALL); child->LayoutToBestSize(dc, context, buffer, attr, child->GetAttributes(), availableRect, parentRect.GetSize(), style); childSize = child->GetCachedSize(); childDescent = child->GetDescent(); if (oldSize != child->GetCachedSize()) { partialExtents.Clear(); // Recalculate the partial text extents since the child object changed size GetRangeSize(GetRange(), paraSize, paraDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_CACHE_SIZE, wxPoint(0,0), parentRect.GetSize(), & partialExtents); } // Go around the loop finding the available rect for the given floating objects } else doLoop = false; } else doLoop = false; } while (doLoop); if (child->IsTopLevel()) { // We can move it to the correct position at this point // TODO: probably need to add margin child->Move(GetPosition() + wxPoint(currentWidth + (wxMax(leftIndent, leftIndent + leftSubIndent)), currentPosition.y)); } // Cases: // 1) There was a line break BEFORE the natural break // 2) There was a line break AFTER the natural break // 3) It's the last line // 4) The child still fits (carry on) - 'else' clause if ((lineBreakInThisObject && (childSize.x + currentWidth <= availableRect.width)) || (childSize.x + currentWidth > availableRect.width) #if 0 || ((childSize.x + currentWidth <= availableRect.width) && !node->GetNext()) #endif ) { long wrapPosition = 0; if ((childSize.x + currentWidth <= availableRect.width) && !node->GetNext() && !lineBreakInThisObject) wrapPosition = child->GetRange().GetEnd(); else // Find a place to wrap. This may walk back to previous children, // for example if a word spans several objects. // Note: one object must contains only one wxTextAtrr, so the line height will not // change inside one object. Thus, we can pass the remain line width to the // FindWrapPosition function. if (!FindWrapPosition(wxRichTextRange(lastCompletedEndPos+1, child->GetRange().GetEnd()), dc, context, availableRect.width, wrapPosition, & partialExtents)) { // If the function failed, just cut it off at the end of this child. wrapPosition = child->GetRange().GetEnd(); } // FindWrapPosition can still return a value that will put us in an endless wrapping loop if (wrapPosition <= lastCompletedEndPos) wrapPosition = wxMax(lastCompletedEndPos+1,child->GetRange().GetEnd()); // Line end position shouldn't be the same as the end, or greater. if (wrapPosition >= GetRange().GetEnd()) wrapPosition = wxMax(0, GetRange().GetEnd()-1); // wxLogDebug(wxT("Split at %ld"), wrapPosition); // Let's find the actual size of the current line now wxSize actualSize; wxRichTextRange actualRange(lastCompletedEndPos+1, wrapPosition); childDescent = 0; #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS if (!child->IsEmpty()) { // Get height only, then the width using the partial extents GetRangeSize(actualRange, actualSize, childDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_HEIGHT_ONLY, wxPoint(0,0), parentRect.GetSize()); actualSize.x = wxRichTextGetRangeWidth(*this, actualRange, partialExtents); } else #endif GetRangeSize(actualRange, actualSize, childDescent, dc, context, wxRICHTEXT_UNFORMATTED, wxPoint(0,0), parentRect.GetSize()); currentWidth = actualSize.x; // The descent for the whole line at this point, is the correct max descent maxDescent = childDescent; // Maximum ascent maxAscent = actualSize.y-childDescent; // lineHeight is given by the height for the whole line, since it will // take into account ascend/descend. lineHeight = actualSize.y; if (lineHeight == 0 && buffer) { wxFont font(buffer->GetFontTable().FindFont(attr)); wxCheckSetFont(dc, font); lineHeight = dc.GetCharHeight(); } if (maxDescent == 0) { int w, h; dc.GetTextExtent(wxT("X"), & w, &h, & maxDescent); } // Add a new line wxRichTextLine* line = AllocateLine(lineCount); // Set relative range so we won't have to change line ranges when paragraphs are moved line->SetRange(wxRichTextRange(actualRange.GetStart() - GetRange().GetStart(), actualRange.GetEnd() - GetRange().GetStart())); line->SetPosition(currentPosition); line->SetSize(wxSize(currentWidth, lineHeight)); line->SetDescent(maxDescent); maxHeight = currentPosition.y + lineHeight; // Now move down a line. TODO: add margins, spacing currentPosition.y += lineHeight; currentPosition.y += lineSpacing; maxDescent = 0; maxAscent = 0; maxWidth = wxMax(maxWidth, currentWidth+startOffset); currentWidth = 0; lineCount ++; // TODO: account for zero-length objects // wxASSERT(wrapPosition > lastCompletedEndPos); lastEndPos = wrapPosition; lastCompletedEndPos = lastEndPos; lineHeight = 0; if (wrapPosition < GetRange().GetEnd()-1) { // May need to set the node back to a previous one, due to searching back in wrapping wxRichTextObject* childAfterWrapPosition = FindObjectAtPosition(wrapPosition+1); if (childAfterWrapPosition) node = m_children.Find(childAfterWrapPosition); else node = node->GetNext(); } else node = node->GetNext(); // Apply paragraph styles such as alignment to the wrapped line ApplyParagraphStyle(line, attr, availableRect, dc); } else { // We still fit, so don't add a line, and keep going currentWidth += childSize.x; if (childDescent == 0) { // An object with a zero descend value wants to take up the whole // height regardless of baseline lineHeight = wxMax(lineHeight, childSize.y); } else { maxDescent = wxMax(childDescent, maxDescent); maxAscent = wxMax(childSize.y-childDescent, maxAscent); } lineHeight = wxMax(lineHeight, (maxDescent + maxAscent)); maxWidth = wxMax(maxWidth, currentWidth+startOffset); lastEndPos = child->GetRange().GetEnd(); node = node->GetNext(); } } //wxASSERT(!(lastCompletedEndPos != -1 && lastCompletedEndPos < GetRange().GetEnd()-1)); // Add the last line - it's the current pos -> last para pos // Substract -1 because the last position is always the end-paragraph position. if (lastCompletedEndPos <= GetRange().GetEnd()-1) { currentPosition.x = (lineCount == 0 ? startPositionFirstLine : startPositionSubsequentLines); wxRichTextLine* line = AllocateLine(lineCount); wxRichTextRange actualRange(lastCompletedEndPos+1, GetRange().GetEnd()-1); // Set relative range so we won't have to change line ranges when paragraphs are moved line->SetRange(wxRichTextRange(actualRange.GetStart() - GetRange().GetStart(), actualRange.GetEnd() - GetRange().GetStart())); line->SetPosition(currentPosition); if (lineHeight == 0 && buffer) { wxFont font(buffer->GetFontTable().FindFont(attr)); wxCheckSetFont(dc, font); lineHeight = dc.GetCharHeight(); } if (maxDescent == 0) { int w, h; dc.GetTextExtent(wxT("X"), & w, &h, & maxDescent); } line->SetSize(wxSize(currentWidth, lineHeight)); line->SetDescent(maxDescent); currentPosition.y += lineHeight; currentPosition.y += lineSpacing; lineCount ++; // Apply paragraph styles such as alignment to the wrapped line ApplyParagraphStyle(line, attr, availableRect, dc); } // Remove remaining unused line objects, if any ClearUnusedLines(lineCount); // We need to add back the margins etc. { wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect; contentRect = wxRect(wxPoint(0, 0), wxSize(maxWidth, currentPosition.y + spaceAfterPara)); GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect); SetCachedSize(marginRect.GetSize()); } // The maximum size is the length of the paragraph stretched out into a line. // So if there were a single word, or an image, or a fixed-size text box, the object could be shrunk around // this size. TODO: take into account line breaks. { wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect; contentRect = wxRect(wxPoint(0, 0), wxSize(paraSize.x + wxMax(leftIndent, leftIndent + leftSubIndent) + rightIndent, currentPosition.y + spaceAfterPara)); GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect); SetMaxSize(marginRect.GetSize()); } // Find the greatest minimum size. Currently we only look at non-text objects, // which isn't ideal but it would be slow to find the maximum word width to // use as the minimum. { int minWidth = 0; node = m_children.GetFirst(); while (node) { wxRichTextObject* child = node->GetData(); // If floating, ignore. We already laid out floats. // Also ignore if empty object, except if we haven't got any // size yet. if ((!child->IsFloating() || !wxRichTextBuffer::GetFloatingLayoutMode()) && child->GetRange().GetLength() != 0 && !wxDynamicCast(child, wxRichTextPlainText)) { if (child->GetCachedSize().x > minWidth) minWidth = child->GetMinSize().x; } node = node->GetNext(); } { // Give the minimum width at least one character width wxFont font(buffer->GetFontTable().FindFont(attr)); wxCheckSetFont(dc, font); int charWidth = dc.GetCharWidth(); minWidth = wxMax(charWidth, minWidth); } wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect; contentRect = wxRect(wxPoint(0, 0), wxSize(minWidth, currentPosition.y + spaceAfterPara)); GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect); SetMinSize(marginRect.GetSize()); } #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS #if wxRICHTEXT_USE_OPTIMIZED_LINE_DRAWING // Use the text extents to calculate the size of each fragment in each line wxRichTextLineList::compatibility_iterator lineNode = m_cachedLines.GetFirst(); while (lineNode) { wxRichTextLine* line = lineNode->GetData(); wxRichTextRange lineRange = line->GetAbsoluteRange(); // Loop through objects until we get to the one within range wxRichTextObjectList::compatibility_iterator node2 = m_children.GetFirst(); while (node2) { wxRichTextObject* child = node2->GetData(); if (child->GetRange().GetLength() > 0 && !child->GetRange().IsOutside(lineRange)) { wxRichTextRange rangeToUse = lineRange; rangeToUse.LimitTo(child->GetRange()); // Find the size of the child from the text extents, and store in an array // for drawing later int left = 0; if (rangeToUse.GetStart() > GetRange().GetStart()) left = partialExtents[(rangeToUse.GetStart()-1) - GetRange().GetStart()]; int right = partialExtents[rangeToUse.GetEnd() - GetRange().GetStart()]; int sz = right - left; line->GetObjectSizes().Add(sz); } else if (child->GetRange().GetStart() > lineRange.GetEnd()) // Can break out of inner loop now since we've passed this line's range break; node2 = node2->GetNext(); } lineNode = lineNode->GetNext(); } #endif #endif return true; } /// Apply paragraph styles, such as centering, to wrapped lines /// TODO: take into account box attributes, possibly void wxRichTextParagraph::ApplyParagraphStyle(wxRichTextLine* line, const wxRichTextAttr& attr, const wxRect& rect, wxDC& dc) { if (!attr.HasAlignment()) return; wxPoint pos = line->GetPosition(); wxPoint originalPos = pos; wxSize size = line->GetSize(); // centering, right-justification if (attr.HasAlignment() && attr.GetAlignment() == wxTEXT_ALIGNMENT_CENTRE) { int rightIndent = ConvertTenthsMMToPixels(dc, attr.GetRightIndent()); pos.x = (rect.GetWidth() - rightIndent - size.x)/2 + pos.x; line->SetPosition(pos); } else if (attr.HasAlignment() && attr.GetAlignment() == wxTEXT_ALIGNMENT_RIGHT) { int rightIndent = ConvertTenthsMMToPixels(dc, attr.GetRightIndent()); pos.x = pos.x + rect.GetWidth() - size.x - rightIndent; line->SetPosition(pos); } if (pos != originalPos) { wxPoint inc = pos - originalPos; wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextObject* child = node->GetData(); if (child->IsTopLevel() && !child->GetRange().IsOutside(line->GetAbsoluteRange())) child->Move(child->GetPosition() + inc); node = node->GetNext(); } } } /// Insert text at the given position bool wxRichTextParagraph::InsertText(long pos, const wxString& text) { wxRichTextObject* childToUse = NULL; wxRichTextObjectList::compatibility_iterator nodeToUse = wxRichTextObjectList::compatibility_iterator(); wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextObject* child = node->GetData(); if (child->GetRange().Contains(pos) && child->GetRange().GetLength() > 0) { childToUse = child; nodeToUse = node; break; } node = node->GetNext(); } if (childToUse) { wxRichTextPlainText* textObject = wxDynamicCast(childToUse, wxRichTextPlainText); if (textObject) { int posInString = pos - textObject->GetRange().GetStart(); wxString newText = textObject->GetText().Mid(0, posInString) + text + textObject->GetText().Mid(posInString); textObject->SetText(newText); int textLength = text.length(); textObject->SetRange(wxRichTextRange(textObject->GetRange().GetStart(), textObject->GetRange().GetEnd() + textLength)); // Increment the end range of subsequent fragments in this paragraph. // We'll set the paragraph range itself at a higher level. wxRichTextObjectList::compatibility_iterator node = nodeToUse->GetNext(); while (node) { wxRichTextObject* child = node->GetData(); child->SetRange(wxRichTextRange(textObject->GetRange().GetStart() + textLength, textObject->GetRange().GetEnd() + textLength)); node = node->GetNext(); } return true; } else { // TODO: if not a text object, insert at closest position, e.g. in front of it } } else { // Add at end. // Don't pass parent initially to suppress auto-setting of parent range. // We'll do that at a higher level. wxRichTextPlainText* textObject = new wxRichTextPlainText(text, this); AppendChild(textObject); return true; } return false; } void wxRichTextParagraph::Copy(const wxRichTextParagraph& obj) { wxRichTextCompositeObject::Copy(obj); } /// Clear the cached lines void wxRichTextParagraph::ClearLines() { WX_CLEAR_LIST(wxRichTextLineList, m_cachedLines); } /// Get/set the object size for the given range. Returns false if the range /// is invalid for this object. bool wxRichTextParagraph::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, const wxPoint& position, const wxSize& parentSize, wxArrayInt* partialExtents) const { if (!range.IsWithin(GetRange())) return false; if (flags & wxRICHTEXT_UNFORMATTED) { // Just use unformatted data, assume no line breaks wxSize sz; wxArrayInt childExtents; wxArrayInt* p; if (partialExtents) p = & childExtents; else p = NULL; int maxDescent = 0; int maxAscent = 0; int maxLineHeight = 0; wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextObject* child = node->GetData(); if (!child->GetRange().IsOutside(range)) { // Floating objects have a zero size within the paragraph. if (child->IsFloating() && wxRichTextBuffer::GetFloatingLayoutMode()) { if (partialExtents) { int lastSize; if (partialExtents->GetCount() > 0) lastSize = (*partialExtents)[partialExtents->GetCount()-1]; else lastSize = 0; partialExtents->Add(0 /* zero size */ + lastSize); } } else { wxSize childSize; wxRichTextRange rangeToUse = range; rangeToUse.LimitTo(child->GetRange()); int childDescent = 0; // At present wxRICHTEXT_HEIGHT_ONLY is only fast if we've already cached the size, // but it's only going to be used after caching has taken place. if ((flags & wxRICHTEXT_HEIGHT_ONLY) && child->GetCachedSize().y != 0) { childDescent = child->GetDescent(); childSize = child->GetCachedSize(); if (childDescent == 0) { maxLineHeight = wxMax(maxLineHeight, childSize.y); } else { maxDescent = wxMax(maxDescent, childDescent); maxAscent = wxMax(maxAscent, (childSize.y - childDescent)); } maxLineHeight = wxMax(maxLineHeight, (maxAscent + maxDescent)); sz.y = wxMax(sz.y, maxLineHeight); sz.x += childSize.x; descent = maxDescent; } else if (child->IsTopLevel()) { childDescent = child->GetDescent(); childSize = child->GetCachedSize(); if (childDescent == 0) { maxLineHeight = wxMax(maxLineHeight, childSize.y); } else { maxDescent = wxMax(maxDescent, childDescent); maxAscent = wxMax(maxAscent, (childSize.y - childDescent)); } maxLineHeight = wxMax(maxLineHeight, (maxAscent + maxDescent)); sz.y = wxMax(sz.y, maxLineHeight); sz.x += childSize.x; descent = maxDescent; // FIXME: this won't change the original values. // Should we be calling GetRangeSize above instead of using cached values? #if 0 if ((flags & wxRICHTEXT_CACHE_SIZE) && (rangeToUse == child->GetRange())) { child->SetCachedSize(childSize); child->SetDescent(childDescent); } #endif if (partialExtents) { int lastSize; if (partialExtents->GetCount() > 0) lastSize = (*partialExtents)[partialExtents->GetCount()-1]; else lastSize = 0; partialExtents->Add(childSize.x + lastSize); } } else if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, context, flags, wxPoint(position.x + sz.x, position.y), parentSize, p)) { if (childDescent == 0) { maxLineHeight = wxMax(maxLineHeight, childSize.y); } else { maxDescent = wxMax(maxDescent, childDescent); maxAscent = wxMax(maxAscent, (childSize.y - childDescent)); } maxLineHeight = wxMax(maxLineHeight, (maxAscent + maxDescent)); sz.y = wxMax(sz.y, maxLineHeight); sz.x += childSize.x; descent = maxDescent; if ((flags & wxRICHTEXT_CACHE_SIZE) && (rangeToUse == child->GetRange())) { child->SetCachedSize(childSize); child->SetDescent(childDescent); } if (partialExtents) { int lastSize; if (partialExtents->GetCount() > 0) lastSize = (*partialExtents)[partialExtents->GetCount()-1]; else lastSize = 0; size_t i; for (i = 0; i < childExtents.GetCount(); i++) { partialExtents->Add(childExtents[i] + lastSize); } } } } if (p) p->Clear(); } node = node->GetNext(); } size = sz; } else { // Use formatted data, with line breaks wxSize sz; // We're going to loop through each line, and then for each line, // call GetRangeSize for the fragment that comprises that line. // Only we have to do that multiple times within the line, because // the line may be broken into pieces. For now ignore line break commands // (so we can assume that getting the unformatted size for a fragment // within a line is the actual size) wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst(); while (node) { wxRichTextLine* line = node->GetData(); wxRichTextRange lineRange = line->GetAbsoluteRange(); if (!lineRange.IsOutside(range)) { int maxDescent = 0; int maxAscent = 0; int maxLineHeight = 0; int maxLineWidth = 0; wxRichTextObjectList::compatibility_iterator node2 = m_children.GetFirst(); while (node2) { wxRichTextObject* child = node2->GetData(); if ((!child->IsFloating() || !wxRichTextBuffer::GetFloatingLayoutMode()) && !child->GetRange().IsOutside(lineRange)) { wxRichTextRange rangeToUse = lineRange; rangeToUse.LimitTo(child->GetRange()); if (child->IsTopLevel()) rangeToUse = child->GetOwnRange(); wxSize childSize; int childDescent = 0; if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, context, flags, wxPoint(position.x + sz.x, position.y), parentSize)) { if (childDescent == 0) { // Assume that if descent is zero, this child can occupy the full line height // and does not need space for the line's maximum descent. So we influence // the overall max line height only. maxLineHeight = wxMax(maxLineHeight, childSize.y); } else { maxAscent = wxMax(maxAscent, (childSize.y - childDescent)); maxDescent = wxMax(maxAscent, childDescent); } maxLineHeight = wxMax(maxLineHeight, (maxAscent + maxDescent)); maxLineWidth += childSize.x; } } node2 = node2->GetNext(); } descent = wxMax(descent, maxDescent); // Increase size by a line (TODO: paragraph spacing) sz.y += maxLineHeight; sz.x = wxMax(sz.x, maxLineWidth); } node = node->GetNext(); } size = sz; } return true; } /// Finds the absolute position and row height for the given character position bool wxRichTextParagraph::FindPosition(wxDC& dc, wxRichTextDrawingContext& context, long index, wxPoint& pt, int* height, bool forceLineStart) { if (index == -1) { wxRichTextLine* line = ((wxRichTextParagraphLayoutBox*)GetParent())->GetLineAtPosition(0); if (line) *height = line->GetSize().y; else *height = dc.GetCharHeight(); // -1 means 'the start of the buffer'. pt = GetPosition(); if (line) pt = pt + line->GetPosition(); return true; } // The final position in a paragraph is taken to mean the position // at the start of the next paragraph. if (index == GetRange().GetEnd()) { wxRichTextParagraphLayoutBox* parent = wxDynamicCast(GetParent(), wxRichTextParagraphLayoutBox); wxASSERT( parent != NULL ); // Find the height at the next paragraph, if any wxRichTextLine* line = parent->GetLineAtPosition(index + 1); if (line) { *height = line->GetSize().y; pt = line->GetAbsolutePosition(); } else { *height = dc.GetCharHeight(); int indent = ConvertTenthsMMToPixels(dc, m_attributes.GetLeftIndent()); pt = wxPoint(indent, GetCachedSize().y); } return true; } if (index < GetRange().GetStart() || index > GetRange().GetEnd()) return false; wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst(); while (node) { wxRichTextLine* line = node->GetData(); wxRichTextRange lineRange = line->GetAbsoluteRange(); if (index >= lineRange.GetStart() && index <= lineRange.GetEnd()) { // If this is the last point in the line, and we're forcing the // returned value to be the start of the next line, do the required // thing. if (index == lineRange.GetEnd() && forceLineStart) { if (node->GetNext()) { wxRichTextLine* nextLine = node->GetNext()->GetData(); *height = nextLine->GetSize().y; pt = nextLine->GetAbsolutePosition(); return true; } } pt.y = line->GetPosition().y + GetPosition().y; wxRichTextRange r(lineRange.GetStart(), index); wxSize rangeSize; int descent = 0; // We find the size of the line up to this point, // then we can add this size to the line start position and // paragraph start position to find the actual position. if (GetRangeSize(r, rangeSize, descent, dc, context, wxRICHTEXT_UNFORMATTED, line->GetPosition()+ GetPosition())) { pt.x = line->GetPosition().x + GetPosition().x + rangeSize.x; *height = line->GetSize().y; return true; } } node = node->GetNext(); } return false; } /// Hit-testing: returns a flag indicating hit test details, plus /// information about position int wxRichTextParagraph::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int flags) { if (!IsShown()) return wxRICHTEXT_HITTEST_NONE; // If we're in the top-level container, then we can return // a suitable hit test code even if the point is outside the container area, // so that we can position the caret sensibly even if we don't // click on valid content. If we're not at the top-level, and the point // is not within this paragraph object, then we don't want to stop more // precise hit-testing from working prematurely, so return immediately. // NEW STRATEGY: use the parent boundary to test whether we're in the // right region, not the paragraph, since the paragraph may be positioned // some way in from where the user clicks. { long tmpPos; wxRichTextObject* tempObj, *tempContextObj; if (GetParent() && GetParent()->wxRichTextObject::HitTest(dc, context, pt, tmpPos, & tempObj, & tempContextObj, flags) == wxRICHTEXT_HITTEST_NONE) return wxRICHTEXT_HITTEST_NONE; } wxRichTextObjectList::compatibility_iterator objNode = m_children.GetFirst(); while (objNode) { wxRichTextObject* child = objNode->GetData(); // Don't recurse if we have wxRICHTEXT_HITTEST_NO_NESTED_OBJECTS, // and also, if this seems composite but actually is marked as atomic, // don't recurse. if (child->IsTopLevel() && ((flags & wxRICHTEXT_HITTEST_NO_NESTED_OBJECTS) == 0) && (! (((flags & wxRICHTEXT_HITTEST_HONOUR_ATOMIC) != 0) && child->IsAtomic()))) { { int hitTest = child->HitTest(dc, context, pt, textPosition, obj, contextObj); if (hitTest != wxRICHTEXT_HITTEST_NONE) return hitTest; } } objNode = objNode->GetNext(); } wxPoint paraPos = GetPosition(); wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst(); while (node) { wxRichTextLine* line = node->GetData(); wxPoint linePos = paraPos + line->GetPosition(); wxSize lineSize = line->GetSize(); wxRichTextRange lineRange = line->GetAbsoluteRange(); if (pt.y <= linePos.y + lineSize.y) { if (pt.x < linePos.x) { textPosition = lineRange.GetStart(); *obj = FindObjectAtPosition(textPosition); *contextObj = GetContainer(); return wxRICHTEXT_HITTEST_BEFORE|wxRICHTEXT_HITTEST_OUTSIDE; } else if (pt.x >= (linePos.x + lineSize.x)) { textPosition = lineRange.GetEnd(); *obj = FindObjectAtPosition(textPosition); *contextObj = GetContainer(); return wxRICHTEXT_HITTEST_AFTER|wxRICHTEXT_HITTEST_OUTSIDE; } else { #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS wxArrayInt partialExtents; wxSize paraSize; int paraDescent; // This calculates the partial text extents GetRangeSize(lineRange, paraSize, paraDescent, dc, context, wxRICHTEXT_UNFORMATTED, linePos, wxDefaultSize, & partialExtents); int lastX = linePos.x; size_t i; for (i = 0; i < partialExtents.GetCount(); i++) { int nextX = partialExtents[i] + linePos.x; if (pt.x >= lastX && pt.x <= nextX) { textPosition = i + lineRange.GetStart(); // minus 1? *obj = FindObjectAtPosition(textPosition); *contextObj = GetContainer(); // So now we know it's between i-1 and i. // Let's see if we can be more precise about // which side of the position it's on. int midPoint = (nextX + lastX)/2; if (pt.x >= midPoint) return wxRICHTEXT_HITTEST_AFTER; else return wxRICHTEXT_HITTEST_BEFORE; } lastX = nextX; } #else long i; int lastX = linePos.x; for (i = lineRange.GetStart(); i <= lineRange.GetEnd(); i++) { wxSize childSize; int descent = 0; wxRichTextRange rangeToUse(lineRange.GetStart(), i); GetRangeSize(rangeToUse, childSize, descent, dc, context, wxRICHTEXT_UNFORMATTED, linePos); int nextX = childSize.x + linePos.x; if (pt.x >= lastX && pt.x <= nextX) { textPosition = i; *obj = FindObjectAtPosition(textPosition); *contextObj = GetContainer(); // So now we know it's between i-1 and i. // Let's see if we can be more precise about // which side of the position it's on. int midPoint = (nextX + lastX)/2; if (pt.x >= midPoint) return wxRICHTEXT_HITTEST_AFTER; else return wxRICHTEXT_HITTEST_BEFORE; } else { lastX = nextX; } } #endif } } node = node->GetNext(); } return wxRICHTEXT_HITTEST_NONE; } /// Split an object at this position if necessary, and return /// the previous object, or NULL if inserting at beginning. wxRichTextObject* wxRichTextParagraph::SplitAt(long pos, wxRichTextObject** previousObject) { wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextObject* child = node->GetData(); if (pos == child->GetRange().GetStart()) { if (previousObject) { if (node->GetPrevious()) *previousObject = node->GetPrevious()->GetData(); else *previousObject = NULL; } return child; } if (child->GetRange().Contains(pos)) { // This should create a new object, transferring part of // the content to the old object and the rest to the new object. wxRichTextObject* newObject = child->DoSplit(pos); // If we couldn't split this object, just insert in front of it. if (!newObject) { // Maybe this is an empty string, try the next one // return child; } else { // Insert the new object after 'child' if (node->GetNext()) m_children.Insert(node->GetNext(), newObject); else m_children.Append(newObject); newObject->SetParent(this); if (previousObject) *previousObject = child; return newObject; } } node = node->GetNext(); } if (previousObject) *previousObject = NULL; return NULL; } /// Move content to a list from obj on void wxRichTextParagraph::MoveToList(wxRichTextObject* obj, wxList& list) { wxRichTextObjectList::compatibility_iterator node = m_children.Find(obj); while (node) { wxRichTextObject* child = node->GetData(); list.Append(child); wxRichTextObjectList::compatibility_iterator oldNode = node; node = node->GetNext(); m_children.DeleteNode(oldNode); } } /// Add content back from list void wxRichTextParagraph::MoveFromList(wxList& list) { for (wxList::compatibility_iterator node = list.GetFirst(); node; node = node->GetNext()) { AppendChild((wxRichTextObject*) node->GetData()); } } /// Calculate range void wxRichTextParagraph::CalculateRange(long start, long& end) { wxRichTextCompositeObject::CalculateRange(start, end); // Add one for end of paragraph end ++; m_range.SetRange(start, end); } /// Find the object at the given position wxRichTextObject* wxRichTextParagraph::FindObjectAtPosition(long position) { wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextObject* obj = node->GetData(); if (obj->GetRange().Contains(position) || obj->GetRange().GetStart() == position || obj->GetRange().GetEnd() == position) return obj; node = node->GetNext(); } return NULL; } /// Get the plain text searching from the start or end of the range. /// The resulting string may be shorter than the range given. bool wxRichTextParagraph::GetContiguousPlainText(wxString& text, const wxRichTextRange& range, bool fromStart) { text = wxEmptyString; if (fromStart) { wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextObject* obj = node->GetData(); if (!obj->GetRange().IsOutside(range)) { wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText); if (textObj) { text += textObj->GetTextForRange(range); } else { text += wxT(" "); } } node = node->GetNext(); } } else { wxRichTextObjectList::compatibility_iterator node = m_children.GetLast(); while (node) { wxRichTextObject* obj = node->GetData(); if (!obj->GetRange().IsOutside(range)) { wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText); if (textObj) { text = textObj->GetTextForRange(range) + text; } else { text = wxT(" ") + text; } } node = node->GetPrevious(); } } return true; } /// Find a suitable wrap position. bool wxRichTextParagraph::FindWrapPosition(const wxRichTextRange& range, wxDC& dc, wxRichTextDrawingContext& context, int availableSpace, long& wrapPosition, wxArrayInt* partialExtents) { if (range.GetLength() <= 0) return false; // Find the first position where the line exceeds the available space. wxSize sz; long breakPosition = range.GetEnd(); #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS if (partialExtents && partialExtents->GetCount() >= (size_t) (GetRange().GetLength()-1)) // the final position in a paragraph is the newline { int widthBefore; if (range.GetStart() > GetRange().GetStart()) widthBefore = (*partialExtents)[range.GetStart() - GetRange().GetStart() - 1]; else widthBefore = 0; size_t i; for (i = (size_t) range.GetStart(); i <= (size_t) range.GetEnd(); i++) { int widthFromStartOfThisRange = (*partialExtents)[i - GetRange().GetStart()] - widthBefore; if (widthFromStartOfThisRange > availableSpace) { breakPosition = i-1; break; } } } else #endif { // Binary chop for speed long minPos = range.GetStart(); long maxPos = range.GetEnd(); while (true) { if (minPos == maxPos) { int descent = 0; GetRangeSize(wxRichTextRange(range.GetStart(), minPos), sz, descent, dc, context, wxRICHTEXT_UNFORMATTED); if (sz.x > availableSpace) breakPosition = minPos - 1; break; } else if ((maxPos - minPos) == 1) { int descent = 0; GetRangeSize(wxRichTextRange(range.GetStart(), minPos), sz, descent, dc, context, wxRICHTEXT_UNFORMATTED); if (sz.x > availableSpace) breakPosition = minPos - 1; else { GetRangeSize(wxRichTextRange(range.GetStart(), maxPos), sz, descent, dc, context, wxRICHTEXT_UNFORMATTED); if (sz.x > availableSpace) breakPosition = maxPos-1; } break; } else { long nextPos = minPos + ((maxPos - minPos) / 2); int descent = 0; GetRangeSize(wxRichTextRange(range.GetStart(), nextPos), sz, descent, dc, context, wxRICHTEXT_UNFORMATTED); if (sz.x > availableSpace) { maxPos = nextPos; } else { minPos = nextPos; } } } } // Now we know the last position on the line. // Let's try to find a word break. wxString plainText; if (GetContiguousPlainText(plainText, wxRichTextRange(range.GetStart(), breakPosition), false)) { int newLinePos = plainText.Find(wxRichTextLineBreakChar); if (newLinePos != wxNOT_FOUND) { breakPosition = wxMax(0, range.GetStart() + newLinePos); } else { int spacePos = plainText.Find(wxT(' '), true); int tabPos = plainText.Find(wxT('\t'), true); int pos = wxMax(spacePos, tabPos); if (pos != wxNOT_FOUND) { int positionsFromEndOfString = plainText.length() - pos - 1; breakPosition = breakPosition - positionsFromEndOfString; } } } wrapPosition = breakPosition; return true; } /// Get the bullet text for this paragraph. wxString wxRichTextParagraph::GetBulletText() { if (GetAttributes().GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_NONE || (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_BITMAP)) return wxEmptyString; int number = GetAttributes().GetBulletNumber(); wxString text; if ((GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ARABIC) || (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE)) { text.Printf(wxT("%d"), number); } else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_LETTERS_UPPER) { // TODO: Unicode, and also check if number > 26 text.Printf(wxT("%c"), (wxChar) (number+64)); } else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_LETTERS_LOWER) { // TODO: Unicode, and also check if number > 26 text.Printf(wxT("%c"), (wxChar) (number+96)); } else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ROMAN_UPPER) { text = wxRichTextDecimalToRoman(number); } else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ROMAN_LOWER) { text = wxRichTextDecimalToRoman(number); text.MakeLower(); } else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_SYMBOL) { text = GetAttributes().GetBulletText(); } if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE) { // The outline style relies on the text being computed statically, // since it depends on other levels points (e.g. 1.2.1.1). So normally the bullet text // should be stored in the attributes; if not, just use the number for this // level, as previously computed. if (!GetAttributes().GetBulletText().IsEmpty()) text = GetAttributes().GetBulletText(); } if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_PARENTHESES) { text = wxT("(") + text + wxT(")"); } else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_RIGHT_PARENTHESIS) { text = text + wxT(")"); } if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_PERIOD) { text += wxT("."); } return text; } /// Allocate or reuse a line object wxRichTextLine* wxRichTextParagraph::AllocateLine(int pos) { if (pos < (int) m_cachedLines.GetCount()) { wxRichTextLine* line = m_cachedLines.Item(pos)->GetData(); line->Init(this); return line; } else { wxRichTextLine* line = new wxRichTextLine(this); m_cachedLines.Append(line); return line; } } /// Clear remaining unused line objects, if any bool wxRichTextParagraph::ClearUnusedLines(int lineCount) { int cachedLineCount = m_cachedLines.GetCount(); if ((int) cachedLineCount > lineCount) { for (int i = 0; i < (int) (cachedLineCount - lineCount); i ++) { wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetLast(); wxRichTextLine* line = node->GetData(); m_cachedLines.Erase(node); delete line; } } return true; } /// Get combined attributes of the base style, paragraph style and character style. We use this to dynamically /// retrieve the actual style. wxRichTextAttr wxRichTextParagraph::GetCombinedAttributes(const wxRichTextAttr& contentStyle, bool includingBoxAttr) const { wxRichTextAttr attr; wxRichTextParagraphLayoutBox* buf = wxDynamicCast(GetParent(), wxRichTextParagraphLayoutBox); if (buf) { attr = buf->GetBasicStyle(); if (!includingBoxAttr) { attr.GetTextBoxAttr().Reset(); // The background colour will be painted by the container, and we don't // want to unnecessarily overwrite the background when we're drawing text // because this may erase the guideline (which appears just under the text // if there's no padding). attr.SetFlags(attr.GetFlags() & ~wxTEXT_ATTR_BACKGROUND_COLOUR); } wxRichTextApplyStyle(attr, GetAttributes()); } else attr = GetAttributes(); wxRichTextApplyStyle(attr, contentStyle); return attr; } /// Get combined attributes of the base style and paragraph style. wxRichTextAttr wxRichTextParagraph::GetCombinedAttributes(bool includingBoxAttr) const { wxRichTextAttr attr; wxRichTextParagraphLayoutBox* buf = wxDynamicCast(GetParent(), wxRichTextParagraphLayoutBox); if (buf) { attr = buf->GetBasicStyle(); if (!includingBoxAttr) attr.GetTextBoxAttr().Reset(); wxRichTextApplyStyle(attr, GetAttributes()); } else attr = GetAttributes(); return attr; } // Create default tabstop array void wxRichTextParagraph::InitDefaultTabs() { // create a default tab list at 10 mm each. for (int i = 0; i < 20; ++i) { sm_defaultTabs.Add(i*100); } } // Clear default tabstop array void wxRichTextParagraph::ClearDefaultTabs() { sm_defaultTabs.Clear(); } void wxRichTextParagraph::LayoutFloat(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& parentRect, int style, wxRichTextFloatCollector* floatCollector) { wxTextAttrDimensionConverter converter(dc, GetBuffer() ? GetBuffer()->GetScale() : 1.0, parentRect.GetSize()); wxRichTextObjectList::compatibility_iterator node = GetChildren().GetFirst(); while (node) { wxRichTextObject* anchored = node->GetData(); if (anchored && anchored->IsFloating() && !floatCollector->HasFloat(anchored)) { int x = 0; wxRichTextAttr parentAttr(GetAttributes()); AdjustAttributes(parentAttr, context); #if 1 // 27-09-2012 wxRect availableSpace = GetParent()->GetAvailableContentArea(dc, context, rect); anchored->LayoutToBestSize(dc, context, GetBuffer(), parentAttr, anchored->GetAttributes(), parentRect, availableSpace, style); wxSize size = anchored->GetCachedSize(); #else wxSize size; int descent = 0; anchored->GetRangeSize(anchored->GetRange(), size, descent, dc, context, style); #endif int offsetY = 0; if (anchored->GetAttributes().GetTextBoxAttr().GetTop().IsValid()) offsetY = converter.GetPixels(anchored->GetAttributes().GetTextBoxAttr().GetTop(), wxVERTICAL); int pos = floatCollector->GetFitPosition(anchored->GetAttributes().GetTextBoxAttr().GetFloatMode(), rect.y + offsetY, size.y); /* Update the offset */ int newOffsetY = pos - rect.y; if (newOffsetY != offsetY) { if (anchored->GetAttributes().GetTextBoxAttr().GetTop().GetUnits() == wxTEXT_ATTR_UNITS_PIXELS) { // We unscaled in GetPixels, so apply scale again. anchored->GetAttributes().GetTextBoxAttr().GetTop().SetValue(int((double(newOffsetY) * converter.GetScale()) + 0.5)); } else { newOffsetY = converter.ConvertPixelsToTenthsMM(newOffsetY); anchored->GetAttributes().GetTextBoxAttr().GetTop().SetValue(newOffsetY, wxTEXT_ATTR_UNITS_TENTHS_MM); } } if (anchored->GetAttributes().GetTextBoxAttr().GetFloatMode() == wxTEXT_BOX_ATTR_FLOAT_LEFT) x = rect.x; else if (anchored->GetAttributes().GetTextBoxAttr().GetFloatMode() == wxTEXT_BOX_ATTR_FLOAT_RIGHT) x = rect.x + rect.width - size.x; //anchored->SetPosition(wxPoint(x, pos)); anchored->Move(wxPoint(x, pos)); // should move children anchored->SetCachedSize(size); floatCollector->CollectFloat(this, anchored); } node = node->GetNext(); } } // Get the first position from pos that has a line break character. long wxRichTextParagraph::GetFirstLineBreakPosition(long pos) { wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextObject* obj = node->GetData(); if (pos >= obj->GetRange().GetStart() && pos <= obj->GetRange().GetEnd()) { wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText); if (textObj) { long breakPos = textObj->GetFirstLineBreakPosition(pos); if (breakPos > -1) return breakPos; } } node = node->GetNext(); } return -1; } /*! * wxRichTextLine * This object represents a line in a paragraph, and stores * offsets from the start of the paragraph representing the * start and end positions of the line. */ wxRichTextLine::wxRichTextLine(wxRichTextParagraph* parent) { Init(parent); } /// Initialisation void wxRichTextLine::Init(wxRichTextParagraph* parent) { m_parent = parent; m_range.SetRange(-1, -1); m_pos = wxPoint(0, 0); m_size = wxSize(0, 0); m_descent = 0; #if wxRICHTEXT_USE_OPTIMIZED_LINE_DRAWING m_objectSizes.Clear(); #endif } /// Copy void wxRichTextLine::Copy(const wxRichTextLine& obj) { m_range = obj.m_range; #if wxRICHTEXT_USE_OPTIMIZED_LINE_DRAWING m_objectSizes = obj.m_objectSizes; #endif } /// Get the absolute object position wxPoint wxRichTextLine::GetAbsolutePosition() const { return m_parent->GetPosition() + m_pos; } /// Get the absolute range wxRichTextRange wxRichTextLine::GetAbsoluteRange() const { wxRichTextRange range(m_range.GetStart() + m_parent->GetRange().GetStart(), 0); range.SetEnd(range.GetStart() + m_range.GetLength()-1); return range; } /*! * wxRichTextPlainText * This object represents a single piece of text. */ IMPLEMENT_DYNAMIC_CLASS(wxRichTextPlainText, wxRichTextObject) wxRichTextPlainText::wxRichTextPlainText(const wxString& text, wxRichTextObject* parent, wxRichTextAttr* style): wxRichTextObject(parent) { if (style) SetAttributes(*style); m_text = text; } #define USE_KERNING_FIX 1 // If insufficient tabs are defined, this is the tab width used #define WIDTH_FOR_DEFAULT_TABS 50 /// Draw the item bool wxRichTextPlainText::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int WXUNUSED(style)) { wxRichTextParagraph* para = wxDynamicCast(GetParent(), wxRichTextParagraph); wxASSERT (para != NULL); wxRichTextAttr textAttr(para ? para->GetCombinedAttributes(GetAttributes(), false /* no box attributes */) : GetAttributes()); AdjustAttributes(textAttr, context); // Let's make the assumption for now that for content in a paragraph, including // text, we never have a discontinuous selection. So we only deal with a // single range. wxRichTextRange selectionRange; if (selection.IsValid()) { wxRichTextRangeArray selectionRanges = selection.GetSelectionForObject(this); if (selectionRanges.GetCount() > 0) selectionRange = selectionRanges[0]; else selectionRange = wxRICHTEXT_NO_SELECTION; } else selectionRange = wxRICHTEXT_NO_SELECTION; int offset = GetRange().GetStart(); wxString str = m_text; if (context.HasVirtualText(this)) { if (!context.GetVirtualText(this, str) || str.Length() != m_text.Length()) str = m_text; } // Replace line break characters with spaces wxString toRemove = wxRichTextLineBreakChar; str.Replace(toRemove, wxT(" ")); if (textAttr.HasTextEffects() && (textAttr.GetTextEffects() & (wxTEXT_ATTR_EFFECT_CAPITALS|wxTEXT_ATTR_EFFECT_SMALL_CAPITALS))) str.MakeUpper(); long len = range.GetLength(); wxString stringChunk = str.Mid(range.GetStart() - offset, (size_t) len); // Test for the optimized situations where all is selected, or none // is selected. wxFont textFont(GetBuffer()->GetFontTable().FindFont(textAttr)); wxCheckSetFont(dc, textFont); int charHeight = dc.GetCharHeight(); int x, y; if ( textFont.IsOk() ) { if (textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SMALL_CAPITALS)) { textFont.SetPointSize((int) (textFont.GetPointSize()*0.75)); wxCheckSetFont(dc, textFont); charHeight = dc.GetCharHeight(); } if ( textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUPERSCRIPT) ) { if (textFont.IsUsingSizeInPixels()) { double size = static_cast(textFont.GetPixelSize().y) / wxSCRIPT_MUL_FACTOR; textFont.SetPixelSize(wxSize(0, static_cast(size))); x = rect.x; y = rect.y; } else { double size = static_cast(textFont.GetPointSize()) / wxSCRIPT_MUL_FACTOR; textFont.SetPointSize(static_cast(size)); x = rect.x; y = rect.y; } wxCheckSetFont(dc, textFont); } else if ( textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUBSCRIPT) ) { if (textFont.IsUsingSizeInPixels()) { double size = static_cast(textFont.GetPixelSize().y) / wxSCRIPT_MUL_FACTOR; textFont.SetPixelSize(wxSize(0, static_cast(size))); x = rect.x; int sub_height = static_cast(static_cast(charHeight) / wxSCRIPT_MUL_FACTOR); y = rect.y + (rect.height - sub_height + (descent - m_descent)); } else { double size = static_cast(textFont.GetPointSize()) / wxSCRIPT_MUL_FACTOR; textFont.SetPointSize(static_cast(size)); x = rect.x; int sub_height = static_cast(static_cast(charHeight) / wxSCRIPT_MUL_FACTOR); y = rect.y + (rect.height - sub_height + (descent - m_descent)); } wxCheckSetFont(dc, textFont); } else { x = rect.x; y = rect.y + (rect.height - charHeight - (descent - m_descent)); } } else { x = rect.x; y = rect.y + (rect.height - charHeight - (descent - m_descent)); } // TODO: new selection code // (a) All selected. if (selectionRange.GetStart() <= range.GetStart() && selectionRange.GetEnd() >= range.GetEnd()) { DrawTabbedString(dc, textAttr, rect, stringChunk, x, y, true); } // (b) None selected. else if (selectionRange.GetEnd() < range.GetStart() || selectionRange.GetStart() > range.GetEnd()) { // Draw all unselected DrawTabbedString(dc, textAttr, rect, stringChunk, x, y, false); } else { // (c) Part selected, part not // Let's draw unselected chunk, selected chunk, then unselected chunk. dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT); // 1. Initial unselected chunk, if any, up until start of selection. if (selectionRange.GetStart() > range.GetStart() && selectionRange.GetStart() <= range.GetEnd()) { int r1 = range.GetStart(); int s1 = selectionRange.GetStart()-1; int fragmentLen = s1 - r1 + 1; if (fragmentLen < 0) { wxLogDebug(wxT("Mid(%d, %d"), (int)(r1 - offset), (int)fragmentLen); } wxString stringFragment = str.Mid(r1 - offset, fragmentLen); DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, false); #if USE_KERNING_FIX if (stringChunk.Find(wxT("\t")) == wxNOT_FOUND) { // Compensate for kerning difference wxString stringFragment2(str.Mid(r1 - offset, fragmentLen+1)); wxString stringFragment3(str.Mid(r1 - offset + fragmentLen, 1)); wxCoord w1, h1, w2, h2, w3, h3; dc.GetTextExtent(stringFragment, & w1, & h1); dc.GetTextExtent(stringFragment2, & w2, & h2); dc.GetTextExtent(stringFragment3, & w3, & h3); int kerningDiff = (w1 + w3) - w2; x = x - kerningDiff; } #endif } // 2. Selected chunk, if any. if (selectionRange.GetEnd() >= range.GetStart()) { int s1 = wxMax(selectionRange.GetStart(), range.GetStart()); int s2 = wxMin(selectionRange.GetEnd(), range.GetEnd()); int fragmentLen = s2 - s1 + 1; if (fragmentLen < 0) { wxLogDebug(wxT("Mid(%d, %d"), (int)(s1 - offset), (int)fragmentLen); } wxString stringFragment = str.Mid(s1 - offset, fragmentLen); DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, true); #if USE_KERNING_FIX if (stringChunk.Find(wxT("\t")) == wxNOT_FOUND) { // Compensate for kerning difference wxString stringFragment2(str.Mid(s1 - offset, fragmentLen+1)); wxString stringFragment3(str.Mid(s1 - offset + fragmentLen, 1)); wxCoord w1, h1, w2, h2, w3, h3; dc.GetTextExtent(stringFragment, & w1, & h1); dc.GetTextExtent(stringFragment2, & w2, & h2); dc.GetTextExtent(stringFragment3, & w3, & h3); int kerningDiff = (w1 + w3) - w2; x = x - kerningDiff; } #endif } // 3. Remaining unselected chunk, if any if (selectionRange.GetEnd() < range.GetEnd()) { int s2 = wxMin(selectionRange.GetEnd()+1, range.GetEnd()); int r2 = range.GetEnd(); int fragmentLen = r2 - s2 + 1; if (fragmentLen < 0) { wxLogDebug(wxT("Mid(%d, %d"), (int)(s2 - offset), (int)fragmentLen); } wxString stringFragment = str.Mid(s2 - offset, fragmentLen); DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, false); } } return true; } bool wxRichTextPlainText::DrawTabbedString(wxDC& dc, const wxRichTextAttr& attr, const wxRect& rect,wxString& str, wxCoord& x, wxCoord& y, bool selected) { bool hasTabs = (str.Find(wxT('\t')) != wxNOT_FOUND); wxArrayInt tabArray; int tabCount; if (hasTabs) { if (attr.GetTabs().IsEmpty()) tabArray = wxRichTextParagraph::GetDefaultTabs(); else tabArray = attr.GetTabs(); tabCount = tabArray.GetCount(); for (int i = 0; i < tabCount; ++i) { int pos = tabArray[i]; pos = ConvertTenthsMMToPixels(dc, pos); tabArray[i] = pos; } } else tabCount = 0; int nextTabPos = -1; int tabPos = -1; wxCoord w, h; if (selected) { wxColour highlightColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT)); wxColour highlightTextColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT)); wxCheckSetBrush(dc, wxBrush(highlightColour)); wxCheckSetPen(dc, wxPen(highlightColour)); dc.SetTextForeground(highlightTextColour); dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT); } else { dc.SetTextForeground(attr.GetTextColour()); if (attr.HasFlag(wxTEXT_ATTR_BACKGROUND_COLOUR) && attr.GetBackgroundColour().IsOk()) { dc.SetBackgroundMode(wxBRUSHSTYLE_SOLID); dc.SetTextBackground(attr.GetBackgroundColour()); } else dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT); } wxCoord x_orig = GetParent()->GetPosition().x; while (hasTabs) { // the string has a tab // break up the string at the Tab wxString stringChunk = str.BeforeFirst(wxT('\t')); str = str.AfterFirst(wxT('\t')); dc.GetTextExtent(stringChunk, & w, & h); tabPos = x + w; bool not_found = true; for (int i = 0; i < tabCount && not_found; ++i) { nextTabPos = tabArray.Item(i) + x_orig; // Find the next tab position. // Even if we're at the end of the tab array, we must still draw the chunk. if (nextTabPos > tabPos || (i == (tabCount - 1))) { if (nextTabPos <= tabPos) { int defaultTabWidth = ConvertTenthsMMToPixels(dc, WIDTH_FOR_DEFAULT_TABS); nextTabPos = tabPos + defaultTabWidth; } not_found = false; if (selected) { w = nextTabPos - x; wxRect selRect(x, rect.y, w, rect.GetHeight()); dc.DrawRectangle(selRect); } dc.DrawText(stringChunk, x, y); if (attr.HasTextEffects() && (attr.GetTextEffects() & wxTEXT_ATTR_EFFECT_STRIKETHROUGH)) { wxPen oldPen = dc.GetPen(); wxCheckSetPen(dc, wxPen(attr.GetTextColour(), 1)); dc.DrawLine(x, (int) (y+(h/2)+0.5), x+w, (int) (y+(h/2)+0.5)); wxCheckSetPen(dc, oldPen); } x = nextTabPos; } } hasTabs = (str.Find(wxT('\t')) != wxNOT_FOUND); } if (!str.IsEmpty()) { dc.GetTextExtent(str, & w, & h); if (selected) { wxRect selRect(x, rect.y, w, rect.GetHeight()); dc.DrawRectangle(selRect); } dc.DrawText(str, x, y); if (attr.HasTextEffects() && (attr.GetTextEffects() & wxTEXT_ATTR_EFFECT_STRIKETHROUGH)) { wxPen oldPen = dc.GetPen(); wxCheckSetPen(dc, wxPen(attr.GetTextColour(), 1)); dc.DrawLine(x, (int) (y+(h/2)+0.5), x+w, (int) (y+(h/2)+0.5)); wxCheckSetPen(dc, oldPen); } x += w; } return true; } /// Lay the item out bool wxRichTextPlainText::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& WXUNUSED(rect), const wxRect& WXUNUSED(parentRect), int WXUNUSED(style)) { // Only lay out if we haven't already cached the size if (m_size.x == -1) GetRangeSize(GetRange(), m_size, m_descent, dc, context, 0, wxPoint(0, 0)); m_maxSize = m_size; // Eventually we want to have a reasonable estimate of minimum size. m_minSize = wxSize(0, 0); return true; } // Adjusts the attributes for virtual attribute provision, collapsed borders, etc. bool wxRichTextPlainText::AdjustAttributes(wxRichTextAttr& attr, wxRichTextDrawingContext& context) { wxRichTextObject::AdjustAttributes(attr, context); if (!attr.HasTextColour()) { wxRichTextBuffer* buf = GetBuffer(); if (buf && buf->GetBasicStyle().HasTextColour()) attr.SetTextColour(buf->GetBasicStyle().GetTextColour()); else attr.SetTextColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); } return true; } /// Copy void wxRichTextPlainText::Copy(const wxRichTextPlainText& obj) { wxRichTextObject::Copy(obj); m_text = obj.m_text; } /// Get/set the object size for the given range. Returns false if the range /// is invalid for this object. bool wxRichTextPlainText::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int WXUNUSED(flags), const wxPoint& position, const wxSize& WXUNUSED(parentSize), wxArrayInt* partialExtents) const { if (!range.IsWithin(GetRange())) return false; wxRichTextParagraph* para = wxDynamicCast(GetParent(), wxRichTextParagraph); wxASSERT (para != NULL); int relativeX = position.x - GetParent()->GetPosition().x; wxRichTextAttr textAttr(para ? para->GetCombinedAttributes(GetAttributes()) : GetAttributes()); ((wxRichTextObject*) this)->AdjustAttributes(textAttr, context); // Always assume unformatted text, since at this level we have no knowledge // of line breaks - and we don't need it, since we'll calculate size within // formatted text by doing it in chunks according to the line ranges bool bScript(false); wxFont font(GetBuffer()->GetFontTable().FindFont(textAttr)); if (font.IsOk()) { if ( textAttr.HasTextEffects() && ( (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUPERSCRIPT) || (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUBSCRIPT) ) ) { wxFont textFont = font; if (textFont.IsUsingSizeInPixels()) { double size = static_cast(textFont.GetPixelSize().y) / wxSCRIPT_MUL_FACTOR; textFont.SetPixelSize(wxSize(0, static_cast(size))); } else { double size = static_cast(textFont.GetPointSize()) / wxSCRIPT_MUL_FACTOR; textFont.SetPointSize(static_cast(size)); } wxCheckSetFont(dc, textFont); bScript = true; } else if (textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SMALL_CAPITALS)) { wxFont textFont = font; textFont.SetPointSize((int) (textFont.GetPointSize()*0.75)); wxCheckSetFont(dc, textFont); bScript = true; } else { wxCheckSetFont(dc, font); } } bool haveDescent = false; int startPos = range.GetStart() - GetRange().GetStart(); long len = range.GetLength(); wxString str(m_text); if (context.HasVirtualText(this)) { if (!context.GetVirtualText(this, str) || str.Length() != m_text.Length()) str = m_text; } wxString toReplace = wxRichTextLineBreakChar; str.Replace(toReplace, wxT(" ")); wxString stringChunk = str.Mid(startPos, (size_t) len); if (textAttr.HasTextEffects() && (textAttr.GetTextEffects() & (wxTEXT_ATTR_EFFECT_CAPITALS|wxTEXT_ATTR_EFFECT_SMALL_CAPITALS))) stringChunk.MakeUpper(); wxCoord w, h; int width = 0; if (stringChunk.Find(wxT('\t')) != wxNOT_FOUND) { // the string has a tab wxArrayInt tabArray; if (textAttr.GetTabs().IsEmpty()) tabArray = wxRichTextParagraph::GetDefaultTabs(); else tabArray = textAttr.GetTabs(); int tabCount = tabArray.GetCount(); for (int i = 0; i < tabCount; ++i) { int pos = tabArray[i]; pos = ((wxRichTextPlainText*) this)->ConvertTenthsMMToPixels(dc, pos); tabArray[i] = pos; } int nextTabPos = -1; while (stringChunk.Find(wxT('\t')) >= 0) { int absoluteWidth = 0; // the string has a tab // break up the string at the Tab wxString stringFragment = stringChunk.BeforeFirst(wxT('\t')); stringChunk = stringChunk.AfterFirst(wxT('\t')); if (partialExtents) { int oldWidth; if (partialExtents->GetCount() > 0) oldWidth = (*partialExtents)[partialExtents->GetCount()-1]; else oldWidth = 0; // Add these partial extents wxArrayInt p; dc.GetPartialTextExtents(stringFragment, p); size_t j; for (j = 0; j < p.GetCount(); j++) partialExtents->Add(oldWidth + p[j]); if (partialExtents->GetCount() > 0) absoluteWidth = (*partialExtents)[(*partialExtents).GetCount()-1] + relativeX; else absoluteWidth = relativeX; } else { dc.GetTextExtent(stringFragment, & w, & h); width += w; absoluteWidth = width + relativeX; haveDescent = true; } bool notFound = true; for (int i = 0; i < tabCount && notFound; ++i) { nextTabPos = tabArray.Item(i); // Find the next tab position. // Even if we're at the end of the tab array, we must still process the chunk. if (nextTabPos > absoluteWidth || (i == (tabCount - 1))) { if (nextTabPos <= absoluteWidth) { int defaultTabWidth = ((wxRichTextPlainText*) this)->ConvertTenthsMMToPixels(dc, WIDTH_FOR_DEFAULT_TABS); nextTabPos = absoluteWidth + defaultTabWidth; } notFound = false; width = nextTabPos - relativeX; if (partialExtents) partialExtents->Add(width); } } } } if (!stringChunk.IsEmpty()) { if (partialExtents) { int oldWidth; if (partialExtents->GetCount() > 0) oldWidth = (*partialExtents)[partialExtents->GetCount()-1]; else oldWidth = 0; // Add these partial extents wxArrayInt p; dc.GetPartialTextExtents(stringChunk, p); size_t j; for (j = 0; j < p.GetCount(); j++) partialExtents->Add(oldWidth + p[j]); } else { dc.GetTextExtent(stringChunk, & w, & h, & descent); width += w; haveDescent = true; } } if (partialExtents) { int charHeight = dc.GetCharHeight(); if ((*partialExtents).GetCount() > 0) w = (*partialExtents)[partialExtents->GetCount()-1]; else w = 0; size = wxSize(w, charHeight); } else { size = wxSize(width, dc.GetCharHeight()); } if (!haveDescent) dc.GetTextExtent(wxT("X"), & w, & h, & descent); if ( bScript ) dc.SetFont(font); return true; } /// Do a split, returning an object containing the second part, and setting /// the first part in 'this'. wxRichTextObject* wxRichTextPlainText::DoSplit(long pos) { long index = pos - GetRange().GetStart(); if (index < 0 || index >= (int) m_text.length()) return NULL; wxString firstPart = m_text.Mid(0, index); wxString secondPart = m_text.Mid(index); m_text = firstPart; wxRichTextPlainText* newObject = new wxRichTextPlainText(secondPart); newObject->SetAttributes(GetAttributes()); newObject->SetProperties(GetProperties()); newObject->SetRange(wxRichTextRange(pos, GetRange().GetEnd())); GetRange().SetEnd(pos-1); return newObject; } /// Calculate range void wxRichTextPlainText::CalculateRange(long start, long& end) { end = start + m_text.length() - 1; m_range.SetRange(start, end); } /// Delete range bool wxRichTextPlainText::DeleteRange(const wxRichTextRange& range) { wxRichTextRange r = range; r.LimitTo(GetRange()); if (r.GetStart() == GetRange().GetStart() && r.GetEnd() == GetRange().GetEnd()) { m_text.Empty(); return true; } long startIndex = r.GetStart() - GetRange().GetStart(); long len = r.GetLength(); m_text = m_text.Mid(0, startIndex) + m_text.Mid(startIndex+len); return true; } /// Get text for the given range. wxString wxRichTextPlainText::GetTextForRange(const wxRichTextRange& range) const { wxRichTextRange r = range; r.LimitTo(GetRange()); long startIndex = r.GetStart() - GetRange().GetStart(); long len = r.GetLength(); return m_text.Mid(startIndex, len); } /// Returns true if this object can merge itself with the given one. bool wxRichTextPlainText::CanMerge(wxRichTextObject* object, wxRichTextDrawingContext& context) const { // JACS 2013-01-27 if (!context.GetVirtualAttributesEnabled()) { return object->GetClassInfo() == wxCLASSINFO(wxRichTextPlainText) && (m_text.empty() || (wxTextAttrEq(GetAttributes(), object->GetAttributes()) && m_properties == object->GetProperties())); } else { wxRichTextPlainText* otherObj = wxDynamicCast(object, wxRichTextPlainText); if (!otherObj || m_text.empty()) return false; if (!wxTextAttrEq(GetAttributes(), object->GetAttributes()) || !(m_properties == object->GetProperties())) return false; // Check if differing virtual attributes makes it impossible to merge // these strings. bool hasVirtualAttr1 = context.HasVirtualAttributes((wxRichTextObject*) this); bool hasVirtualAttr2 = context.HasVirtualAttributes((wxRichTextObject*) object); if (!hasVirtualAttr1 && !hasVirtualAttr2) return true; else if (hasVirtualAttr1 != hasVirtualAttr2) return false; else { wxRichTextAttr virtualAttr1 = context.GetVirtualAttributes((wxRichTextObject*) this); wxRichTextAttr virtualAttr2 = context.GetVirtualAttributes((wxRichTextObject*) object); return virtualAttr1 == virtualAttr2; } } } /// Returns true if this object merged itself with the given one. /// The calling code will then delete the given object. bool wxRichTextPlainText::Merge(wxRichTextObject* object, wxRichTextDrawingContext& WXUNUSED(context)) { wxRichTextPlainText* textObject = wxDynamicCast(object, wxRichTextPlainText); wxASSERT( textObject != NULL ); if (textObject) { m_text += textObject->GetText(); wxRichTextApplyStyle(m_attributes, textObject->GetAttributes()); return true; } else return false; } bool wxRichTextPlainText::CanSplit(wxRichTextDrawingContext& context) const { // If this object has any virtual attributes at all, whether for the whole object // or individual ones, we should try splitting it by calling Split. // Must be more than one character in order to be able to split. return m_text.Length() > 1 && context.HasVirtualAttributes((wxRichTextObject*) this); } wxRichTextObject* wxRichTextPlainText::Split(wxRichTextDrawingContext& context) { int count = context.GetVirtualSubobjectAttributesCount(this); if (count > 0 && GetParent()) { wxRichTextCompositeObject* parent = wxDynamicCast(GetParent(), wxRichTextCompositeObject); wxRichTextObjectList::compatibility_iterator node = parent->GetChildren().Find(this); if (node) { const wxRichTextAttr emptyAttr; wxRichTextObjectList::compatibility_iterator next = node->GetNext(); wxArrayInt positions; wxRichTextAttrArray attributes; if (context.GetVirtualSubobjectAttributes(this, positions, attributes) && positions.GetCount() > 0) { wxASSERT(positions.GetCount() == attributes.GetCount()); // We will gather up runs of text with the same virtual attributes int len = m_text.Length(); int i = 0; // runStart and runEnd represent the accumulated run with a consistent attribute // that hasn't yet been appended int runStart = -1; int runEnd = -1; wxRichTextAttr currentAttr; wxString text = m_text; wxRichTextPlainText* lastPlainText = this; for (i = 0; i < (int) positions.GetCount(); i++) { int pos = positions[i]; wxASSERT(pos >= 0 && pos < len); if (pos >= 0 && pos < len) { const wxRichTextAttr& attr = attributes[i]; if (pos == 0) { runStart = 0; currentAttr = attr; } // Check if there was a gap from the last known attribute and this. // In that case, we need to do something with the span of non-attributed text. else if ((pos-1) > runEnd) { if (runEnd == -1) { // We hadn't processed anything previously, so the previous run is from the text start // to just before this position. The current attribute remains empty. runStart = 0; runEnd = pos-1; } else { // If the previous attribute matches the gap's attribute (i.e., no attributes) // then just extend the run. if (currentAttr.IsDefault()) { runEnd = pos-1; } else { // We need to add an object, or reuse the existing one. if (runStart == 0) { lastPlainText = this; SetText(text.Mid(runStart, runEnd - runStart + 1)); } else { wxRichTextPlainText* obj = new wxRichTextPlainText; lastPlainText = obj; obj->SetAttributes(GetAttributes()); obj->SetProperties(GetProperties()); obj->SetParent(parent); obj->SetText(text.Mid(runStart, runEnd - runStart + 1)); if (next) parent->GetChildren().Insert(next, obj); else parent->GetChildren().Append(obj); } runStart = runEnd+1; runEnd = pos-1; currentAttr = emptyAttr; } } } wxASSERT(runEnd == pos-1); // Now we only have to deal with the previous run if (currentAttr == attr) { // If we still have the same attributes, then we // simply increase the run size. runEnd = pos; } else { if (runEnd >= 0) { // We need to add an object, or reuse the existing one. if (runStart == 0) { lastPlainText = this; SetText(text.Mid(runStart, runEnd - runStart + 1)); } else { wxRichTextPlainText* obj = new wxRichTextPlainText; lastPlainText = obj; obj->SetAttributes(GetAttributes()); obj->SetProperties(GetProperties()); obj->SetParent(parent); obj->SetText(text.Mid(runStart, runEnd - runStart + 1)); if (next) parent->GetChildren().Insert(next, obj); else parent->GetChildren().Append(obj); } } runStart = pos; runEnd = pos; currentAttr = attr; } } } // We may still have a run to add, and possibly a no-attribute text fragment after that. // If the whole string was already a single attribute (the run covers the whole string), don't split. if ((runStart != -1) && !(runStart == 0 && runEnd == (len-1))) { // If the current attribute is empty, merge the run with the next fragment // which by definition (because it's not specified) has empty attributes. if (currentAttr.IsDefault()) runEnd = (len-1); if (runEnd < (len-1)) { // We need to add an object, or reuse the existing one. if (runStart == 0) { lastPlainText = this; SetText(text.Mid(runStart, runEnd - runStart + 1)); } else { wxRichTextPlainText* obj = new wxRichTextPlainText; lastPlainText = obj; obj->SetAttributes(GetAttributes()); obj->SetProperties(GetProperties()); obj->SetParent(parent); obj->SetText(text.Mid(runStart, runEnd - runStart + 1)); if (next) parent->GetChildren().Insert(next, obj); else parent->GetChildren().Append(obj); } runStart = runEnd+1; runEnd = (len-1); } // Now the last, non-attributed fragment at the end, if any if ((runStart < len) && !(runStart == 0 && runEnd == (len-1))) { wxASSERT(runStart != 0); wxRichTextPlainText* obj = new wxRichTextPlainText; obj->SetAttributes(GetAttributes()); obj->SetProperties(GetProperties()); obj->SetParent(parent); obj->SetText(text.Mid(runStart, runEnd - runStart + 1)); if (next) parent->GetChildren().Insert(next, obj); else parent->GetChildren().Append(obj); lastPlainText = obj; } } return lastPlainText; } } } return this; } /// Dump to output stream for debugging void wxRichTextPlainText::Dump(wxTextOutputStream& stream) { wxRichTextObject::Dump(stream); stream << m_text << wxT("\n"); } /// Get the first position from pos that has a line break character. long wxRichTextPlainText::GetFirstLineBreakPosition(long pos) { int i; int len = m_text.length(); int startPos = pos - m_range.GetStart(); for (i = startPos; i < len; i++) { wxChar ch = m_text[i]; if (ch == wxRichTextLineBreakChar) { return i + m_range.GetStart(); } } return -1; } /*! * wxRichTextBuffer * This is a kind of box, used to represent the whole buffer */ IMPLEMENT_DYNAMIC_CLASS(wxRichTextBuffer, wxRichTextParagraphLayoutBox) wxList wxRichTextBuffer::sm_handlers; wxList wxRichTextBuffer::sm_drawingHandlers; wxRichTextFieldTypeHashMap wxRichTextBuffer::sm_fieldTypes; wxRichTextRenderer* wxRichTextBuffer::sm_renderer = NULL; int wxRichTextBuffer::sm_bulletRightMargin = 20; float wxRichTextBuffer::sm_bulletProportion = (float) 0.3; bool wxRichTextBuffer::sm_floatingLayoutMode = true; /// Initialisation void wxRichTextBuffer::Init() { m_commandProcessor = new wxCommandProcessor; m_styleSheet = NULL; m_modified = false; m_batchedCommandDepth = 0; m_batchedCommand = NULL; m_suppressUndo = 0; m_handlerFlags = 0; m_scale = 1.0; m_dimensionScale = 1.0; m_fontScale = 1.0; SetMargins(4); } /// Initialisation wxRichTextBuffer::~wxRichTextBuffer() { delete m_commandProcessor; delete m_batchedCommand; ClearStyleStack(); ClearEventHandlers(); } void wxRichTextBuffer::ResetAndClearCommands() { Reset(); GetCommandProcessor()->ClearCommands(); Modify(false); Invalidate(wxRICHTEXT_ALL); } void wxRichTextBuffer::Copy(const wxRichTextBuffer& obj) { wxRichTextParagraphLayoutBox::Copy(obj); m_styleSheet = obj.m_styleSheet; m_modified = obj.m_modified; m_batchedCommandDepth = 0; if (m_batchedCommand) delete m_batchedCommand; m_batchedCommand = NULL; m_suppressUndo = obj.m_suppressUndo; m_invalidRange = obj.m_invalidRange; m_dimensionScale = obj.m_dimensionScale; m_fontScale = obj.m_fontScale; } /// Push style sheet to top of stack bool wxRichTextBuffer::PushStyleSheet(wxRichTextStyleSheet* styleSheet) { if (m_styleSheet) styleSheet->InsertSheet(m_styleSheet); SetStyleSheet(styleSheet); return true; } /// Pop style sheet from top of stack wxRichTextStyleSheet* wxRichTextBuffer::PopStyleSheet() { if (m_styleSheet) { wxRichTextStyleSheet* oldSheet = m_styleSheet; m_styleSheet = oldSheet->GetNextSheet(); oldSheet->Unlink(); return oldSheet; } else return NULL; } /// Submit command to insert paragraphs bool wxRichTextBuffer::InsertParagraphsWithUndo(long pos, const wxRichTextParagraphLayoutBox& paragraphs, wxRichTextCtrl* ctrl, int flags) { return ctrl->GetFocusObject()->InsertParagraphsWithUndo(this, pos, paragraphs, ctrl, flags); } /// Submit command to insert paragraphs bool wxRichTextParagraphLayoutBox::InsertParagraphsWithUndo(wxRichTextBuffer* buffer, long pos, const wxRichTextParagraphLayoutBox& paragraphs, wxRichTextCtrl* ctrl, int WXUNUSED(flags)) { wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, buffer, this, ctrl, false); action->GetNewParagraphs() = paragraphs; action->SetPosition(pos); wxRichTextRange range = wxRichTextRange(pos, pos + paragraphs.GetOwnRange().GetEnd() - 1); if (!paragraphs.GetPartialParagraph()) range.SetEnd(range.GetEnd()+1); // Set the range we'll need to delete in Undo action->SetRange(range); buffer->SubmitAction(action); return true; } /// Submit command to insert the given text bool wxRichTextBuffer::InsertTextWithUndo(long pos, const wxString& text, wxRichTextCtrl* ctrl, int flags) { if (ctrl) return ctrl->GetFocusObject()->InsertTextWithUndo(this, pos, text, ctrl, flags); else return wxRichTextParagraphLayoutBox::InsertTextWithUndo(this, pos, text, ctrl, flags); } /// Submit command to insert the given text bool wxRichTextParagraphLayoutBox::InsertTextWithUndo(wxRichTextBuffer* buffer, long pos, const wxString& text, wxRichTextCtrl* ctrl, int flags) { wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, buffer, this, ctrl, false); wxRichTextAttr* p = NULL; wxRichTextAttr paraAttr; if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE) { // Get appropriate paragraph style paraAttr = GetStyleForNewParagraph(buffer, pos, false, false); if (!paraAttr.IsDefault()) p = & paraAttr; } action->GetNewParagraphs().AddParagraphs(text, p); int length = action->GetNewParagraphs().GetOwnRange().GetLength(); if (!text.empty() && text.Last() != wxT('\n')) { // Don't count the newline when undoing length --; action->GetNewParagraphs().SetPartialParagraph(true); } else if (!text.empty() && text.Last() == wxT('\n')) length --; action->SetPosition(pos); // Set the range we'll need to delete in Undo action->SetRange(wxRichTextRange(pos, pos + length - 1)); buffer->SubmitAction(action); return true; } /// Submit command to insert the given text bool wxRichTextBuffer::InsertNewlineWithUndo(long pos, wxRichTextCtrl* ctrl, int flags) { return ctrl->GetFocusObject()->InsertNewlineWithUndo(this, pos, ctrl, flags); } /// Submit command to insert the given text bool wxRichTextParagraphLayoutBox::InsertNewlineWithUndo(wxRichTextBuffer* buffer, long pos, wxRichTextCtrl* ctrl, int flags) { wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, buffer, this, ctrl, false); wxRichTextAttr* p = NULL; wxRichTextAttr paraAttr; if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE) { paraAttr = GetStyleForNewParagraph(buffer, pos, false, true /* look for next paragraph style */); if (!paraAttr.IsDefault()) p = & paraAttr; } wxRichTextAttr attr(buffer->GetDefaultStyle()); // Don't include box attributes such as margins attr.GetTextBoxAttr().Reset(); wxRichTextParagraph* newPara = new wxRichTextParagraph(wxEmptyString, this, & attr); action->GetNewParagraphs().AppendChild(newPara); action->GetNewParagraphs().UpdateRanges(); action->GetNewParagraphs().SetPartialParagraph(false); wxRichTextParagraph* para = GetParagraphAtPosition(pos, false); long pos1 = pos; if (p) newPara->SetAttributes(*p); if (flags & wxRICHTEXT_INSERT_INTERACTIVE) { if (para && para->GetRange().GetEnd() == pos) pos1 ++; // Now see if we need to number the paragraph. if (newPara->GetAttributes().HasBulletNumber()) { wxRichTextAttr numberingAttr; if (FindNextParagraphNumber(para, numberingAttr)) wxRichTextApplyStyle(newPara->GetAttributes(), (const wxRichTextAttr&) numberingAttr); } } action->SetPosition(pos); // Use the default character style if (!buffer->GetDefaultStyle().IsDefault() && newPara->GetChildren().GetFirst()) { // Check whether the default style merely reflects the paragraph/basic style, // in which case don't apply it. wxRichTextAttr defaultStyle(buffer->GetDefaultStyle()); defaultStyle.GetTextBoxAttr().Reset(); wxRichTextAttr toApply; if (para) { wxRichTextAttr combinedAttr = para->GetCombinedAttributes(true /* include box attributes */); wxRichTextAttr newAttr; // This filters out attributes that are accounted for by the current // paragraph/basic style wxRichTextApplyStyle(toApply, defaultStyle, & combinedAttr); } else toApply = defaultStyle; if (!toApply.IsDefault()) newPara->GetChildren().GetFirst()->GetData()->SetAttributes(toApply); } // Set the range we'll need to delete in Undo action->SetRange(wxRichTextRange(pos1, pos1)); buffer->SubmitAction(action); return true; } /// Submit command to insert the given image bool wxRichTextBuffer::InsertImageWithUndo(long pos, const wxRichTextImageBlock& imageBlock, wxRichTextCtrl* ctrl, int flags, const wxRichTextAttr& textAttr) { return ctrl->GetFocusObject()->InsertImageWithUndo(this, pos, imageBlock, ctrl, flags, textAttr); } /// Submit command to insert the given image bool wxRichTextParagraphLayoutBox::InsertImageWithUndo(wxRichTextBuffer* buffer, long pos, const wxRichTextImageBlock& imageBlock, wxRichTextCtrl* ctrl, int flags, const wxRichTextAttr& textAttr) { wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Image"), wxRICHTEXT_INSERT, buffer, this, ctrl, false); wxRichTextAttr* p = NULL; wxRichTextAttr paraAttr; if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE) { paraAttr = GetStyleForNewParagraph(buffer, pos); if (!paraAttr.IsDefault()) p = & paraAttr; } wxRichTextAttr attr(buffer->GetDefaultStyle()); // Don't include box attributes such as margins attr.GetTextBoxAttr().Reset(); wxRichTextParagraph* newPara = new wxRichTextParagraph(this, & attr); if (p) newPara->SetAttributes(*p); wxRichTextImage* imageObject = new wxRichTextImage(imageBlock, newPara); newPara->AppendChild(imageObject); imageObject->SetAttributes(textAttr); action->GetNewParagraphs().AppendChild(newPara); action->GetNewParagraphs().UpdateRanges(); action->GetNewParagraphs().SetPartialParagraph(true); action->SetPosition(pos); // Set the range we'll need to delete in Undo action->SetRange(wxRichTextRange(pos, pos)); buffer->SubmitAction(action); return true; } // Insert an object with no change of it wxRichTextObject* wxRichTextBuffer::InsertObjectWithUndo(long pos, wxRichTextObject *object, wxRichTextCtrl* ctrl, int flags) { return ctrl->GetFocusObject()->InsertObjectWithUndo(this, pos, object, ctrl, flags); } // Insert an object with no change of it wxRichTextObject* wxRichTextParagraphLayoutBox::InsertObjectWithUndo(wxRichTextBuffer* buffer, long pos, wxRichTextObject *object, wxRichTextCtrl* ctrl, int flags) { wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Object"), wxRICHTEXT_INSERT, buffer, this, ctrl, false); wxRichTextAttr* p = NULL; wxRichTextAttr paraAttr; if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE) { paraAttr = GetStyleForNewParagraph(buffer, pos); if (!paraAttr.IsDefault()) p = & paraAttr; } wxRichTextAttr attr(buffer->GetDefaultStyle()); // Don't include box attributes such as margins attr.GetTextBoxAttr().Reset(); wxRichTextParagraph* newPara = new wxRichTextParagraph(this, & attr); if (p) newPara->SetAttributes(*p); newPara->AppendChild(object); action->GetNewParagraphs().AppendChild(newPara); action->GetNewParagraphs().UpdateRanges(); action->GetNewParagraphs().SetPartialParagraph(true); action->SetPosition(pos); // Set the range we'll need to delete in Undo action->SetRange(wxRichTextRange(pos, pos)); buffer->SubmitAction(action); wxRichTextObject* obj = GetLeafObjectAtPosition(pos); return obj; } wxRichTextField* wxRichTextParagraphLayoutBox::InsertFieldWithUndo(wxRichTextBuffer* buffer, long pos, const wxString& fieldType, const wxRichTextProperties& properties, wxRichTextCtrl* ctrl, int flags, const wxRichTextAttr& textAttr) { wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Field"), wxRICHTEXT_INSERT, buffer, this, ctrl, false); wxRichTextAttr* p = NULL; wxRichTextAttr paraAttr; if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE) { paraAttr = GetStyleForNewParagraph(buffer, pos); if (!paraAttr.IsDefault()) p = & paraAttr; } wxRichTextAttr attr(buffer->GetDefaultStyle()); // Don't include box attributes such as margins attr.GetTextBoxAttr().Reset(); wxRichTextParagraph* newPara = new wxRichTextParagraph(this, & attr); if (p) newPara->SetAttributes(*p); wxRichTextField* fieldObject = new wxRichTextField(); fieldObject->wxRichTextObject::SetProperties(properties); fieldObject->SetFieldType(fieldType); fieldObject->SetAttributes(textAttr); newPara->AppendChild(fieldObject); action->GetNewParagraphs().AppendChild(newPara); action->GetNewParagraphs().UpdateRanges(); action->GetNewParagraphs().SetPartialParagraph(true); action->SetPosition(pos); // Set the range we'll need to delete in Undo action->SetRange(wxRichTextRange(pos, pos)); buffer->SubmitAction(action); wxRichTextField* obj = wxDynamicCast(GetLeafObjectAtPosition(pos), wxRichTextField); return obj; } bool wxRichTextParagraphLayoutBox::SetObjectPropertiesWithUndo(wxRichTextObject& obj, const wxRichTextProperties& properties, wxRichTextObject* objToSet) { wxRichTextBuffer* buffer = GetBuffer(); wxCHECK_MSG(buffer, false, wxT("Invalid buffer")); wxRichTextCtrl* rtc = buffer->GetRichTextCtrl(); wxCHECK_MSG(rtc, false, wxT("Invalid rtc")); wxRichTextAction* action = NULL; wxRichTextObject* clone = NULL; // The object on which to set properties will usually be 'obj', but use objToSet if it's valid. // This is necessary e.g. on setting a wxRichTextCell's properties, when obj will be the parent table if (objToSet == NULL) objToSet = &obj; if (rtc->SuppressingUndo()) objToSet->SetProperties(properties); else { clone = obj.Clone(); objToSet->SetProperties(properties); // The 'true' parameter in the next line says "Ignore first time"; otherwise the objects are prematurely switched action = new wxRichTextAction(NULL, _("Change Properties"), wxRICHTEXT_CHANGE_OBJECT, buffer, obj.GetParentContainer(), rtc, true); action->SetOldAndNewObjects(& obj, clone); action->SetPosition(obj.GetRange().GetStart()); action->SetRange(obj.GetRange()); buffer->SubmitAction(action); } return true; } /// Get the style that is appropriate for a new paragraph at this position. /// If the previous paragraph has a paragraph style name, look up the next-paragraph /// style. wxRichTextAttr wxRichTextParagraphLayoutBox::GetStyleForNewParagraph(wxRichTextBuffer* buffer, long pos, bool caretPosition, bool lookUpNewParaStyle) const { wxRichTextParagraph* para = GetParagraphAtPosition(pos, caretPosition); if (para) { wxRichTextAttr attr; bool foundAttributes = false; // Look for a matching paragraph style if (lookUpNewParaStyle && !para->GetAttributes().GetParagraphStyleName().IsEmpty() && buffer->GetStyleSheet()) { wxRichTextParagraphStyleDefinition* paraDef = buffer->GetStyleSheet()->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName()); if (paraDef) { // If we're not at the end of the paragraph, then we apply THIS style, and not the designated next style. if (para->GetRange().GetEnd() == pos && !paraDef->GetNextStyle().IsEmpty()) { wxRichTextParagraphStyleDefinition* nextParaDef = buffer->GetStyleSheet()->FindParagraphStyle(paraDef->GetNextStyle()); if (nextParaDef) { foundAttributes = true; attr = nextParaDef->GetStyleMergedWithBase(buffer->GetStyleSheet()); } } // If we didn't find the 'next style', use this style instead. if (!foundAttributes) { foundAttributes = true; attr = paraDef->GetStyleMergedWithBase(buffer->GetStyleSheet()); } } } // Also apply list style if present if (lookUpNewParaStyle && !para->GetAttributes().GetListStyleName().IsEmpty() && buffer->GetStyleSheet()) { wxRichTextListStyleDefinition* listDef = buffer->GetStyleSheet()->FindListStyle(para->GetAttributes().GetListStyleName()); if (listDef) { int thisIndent = para->GetAttributes().GetLeftIndent(); int thisLevel = para->GetAttributes().HasOutlineLevel() ? para->GetAttributes().GetOutlineLevel() : listDef->FindLevelForIndent(thisIndent); // Apply the overall list style, and item style for this level wxRichTextAttr listStyle(listDef->GetCombinedStyleForLevel(thisLevel, buffer->GetStyleSheet())); wxRichTextApplyStyle(attr, listStyle); attr.SetOutlineLevel(thisLevel); if (para->GetAttributes().HasBulletNumber()) attr.SetBulletNumber(para->GetAttributes().GetBulletNumber()); } } if (!foundAttributes) { attr = para->GetAttributes(); int flags = attr.GetFlags(); // Eliminate character styles flags &= ( (~ wxTEXT_ATTR_FONT) | (~ wxTEXT_ATTR_TEXT_COLOUR) | (~ wxTEXT_ATTR_BACKGROUND_COLOUR) ); attr.SetFlags(flags); } return attr; } else return wxRichTextAttr(); } /// Submit command to delete this range bool wxRichTextBuffer::DeleteRangeWithUndo(const wxRichTextRange& range, wxRichTextCtrl* ctrl) { return ctrl->GetFocusObject()->DeleteRangeWithUndo(range, ctrl, this); } /// Submit command to delete this range bool wxRichTextParagraphLayoutBox::DeleteRangeWithUndo(const wxRichTextRange& range, wxRichTextCtrl* ctrl, wxRichTextBuffer* buffer) { wxRichTextAction* action = new wxRichTextAction(NULL, _("Delete"), wxRICHTEXT_DELETE, buffer, this, ctrl); action->SetPosition(ctrl->GetCaretPosition()); // Set the range to delete action->SetRange(range); // Copy the fragment that we'll need to restore in Undo CopyFragment(range, action->GetOldParagraphs()); // See if we're deleting a paragraph marker, in which case we need to // make a note not to copy the attributes from the 2nd paragraph to the 1st. if (range.GetStart() == range.GetEnd()) { wxRichTextParagraph* para = GetParagraphAtPosition(range.GetStart()); if (para && para->GetRange().GetEnd() == range.GetEnd()) { wxRichTextParagraph* nextPara = GetParagraphAtPosition(range.GetStart()+1); if (nextPara && nextPara != para) { action->GetOldParagraphs().GetChildren().GetFirst()->GetData()->SetAttributes(nextPara->GetAttributes()); action->GetOldParagraphs().GetAttributes().SetFlags(action->GetOldParagraphs().GetAttributes().GetFlags() | wxTEXT_ATTR_KEEP_FIRST_PARA_STYLE); } } } buffer->SubmitAction(action); return true; } /// Collapse undo/redo commands bool wxRichTextBuffer::BeginBatchUndo(const wxString& cmdName) { if (m_batchedCommandDepth == 0) { wxASSERT(m_batchedCommand == NULL); if (m_batchedCommand) { GetCommandProcessor()->Store(m_batchedCommand); } m_batchedCommand = new wxRichTextCommand(cmdName); } m_batchedCommandDepth ++; return true; } /// Collapse undo/redo commands bool wxRichTextBuffer::EndBatchUndo() { m_batchedCommandDepth --; wxASSERT(m_batchedCommandDepth >= 0); wxASSERT(m_batchedCommand != NULL); if (m_batchedCommandDepth == 0) { GetCommandProcessor()->Store(m_batchedCommand); m_batchedCommand = NULL; } return true; } /// Submit immediately, or delay according to whether collapsing is on bool wxRichTextBuffer::SubmitAction(wxRichTextAction* action) { if (action && !action->GetNewParagraphs().IsEmpty()) PrepareContent(action->GetNewParagraphs()); if (BatchingUndo() && m_batchedCommand && !SuppressingUndo()) { if (!action->GetIgnoreFirstTime()) { wxRichTextCommand* cmd = new wxRichTextCommand(action->GetName()); cmd->AddAction(action); cmd->Do(); cmd->GetActions().Clear(); delete cmd; } m_batchedCommand->AddAction(action); } else { wxRichTextCommand* cmd = new wxRichTextCommand(action->GetName()); cmd->AddAction(action); // Only store it if we're not suppressing undo. if (!action->GetIgnoreFirstTime()) { return GetCommandProcessor()->Submit(cmd, !SuppressingUndo()); } else if (!SuppressingUndo()) { GetCommandProcessor()->Store(cmd); // Just store it, without Do()ing anything } } return true; } /// Begin suppressing undo/redo commands. bool wxRichTextBuffer::BeginSuppressUndo() { m_suppressUndo ++; return true; } /// End suppressing undo/redo commands. bool wxRichTextBuffer::EndSuppressUndo() { m_suppressUndo --; return true; } /// Begin using a style bool wxRichTextBuffer::BeginStyle(const wxRichTextAttr& style) { wxRichTextAttr newStyle(GetDefaultStyle()); newStyle.GetTextBoxAttr().Reset(); // Save the old default style m_attributeStack.Append((wxObject*) new wxRichTextAttr(newStyle)); wxRichTextApplyStyle(newStyle, style); newStyle.SetFlags(style.GetFlags()|newStyle.GetFlags()); SetDefaultStyle(newStyle); return true; } /// End the style bool wxRichTextBuffer::EndStyle() { if (!m_attributeStack.GetFirst()) { wxLogDebug(_("Too many EndStyle calls!")); return false; } wxList::compatibility_iterator node = m_attributeStack.GetLast(); wxRichTextAttr* attr = (wxRichTextAttr*)node->GetData(); m_attributeStack.Erase(node); SetDefaultStyle(*attr); delete attr; return true; } /// End all styles bool wxRichTextBuffer::EndAllStyles() { while (m_attributeStack.GetCount() != 0) EndStyle(); return true; } /// Clear the style stack void wxRichTextBuffer::ClearStyleStack() { for (wxList::compatibility_iterator node = m_attributeStack.GetFirst(); node; node = node->GetNext()) delete (wxRichTextAttr*) node->GetData(); m_attributeStack.Clear(); } /// Begin using bold bool wxRichTextBuffer::BeginBold() { wxRichTextAttr attr; attr.SetFontWeight(wxFONTWEIGHT_BOLD); return BeginStyle(attr); } /// Begin using italic bool wxRichTextBuffer::BeginItalic() { wxRichTextAttr attr; attr.SetFontStyle(wxFONTSTYLE_ITALIC); return BeginStyle(attr); } /// Begin using underline bool wxRichTextBuffer::BeginUnderline() { wxRichTextAttr attr; attr.SetFontUnderlined(true); return BeginStyle(attr); } /// Begin using point size bool wxRichTextBuffer::BeginFontSize(int pointSize) { wxRichTextAttr attr; attr.SetFontSize(pointSize); return BeginStyle(attr); } /// Begin using this font bool wxRichTextBuffer::BeginFont(const wxFont& font) { wxRichTextAttr attr; attr.SetFont(font); return BeginStyle(attr); } /// Begin using this colour bool wxRichTextBuffer::BeginTextColour(const wxColour& colour) { wxRichTextAttr attr; attr.SetFlags(wxTEXT_ATTR_TEXT_COLOUR); attr.SetTextColour(colour); return BeginStyle(attr); } /// Begin using alignment bool wxRichTextBuffer::BeginAlignment(wxTextAttrAlignment alignment) { wxRichTextAttr attr; attr.SetFlags(wxTEXT_ATTR_ALIGNMENT); attr.SetAlignment(alignment); return BeginStyle(attr); } /// Begin left indent bool wxRichTextBuffer::BeginLeftIndent(int leftIndent, int leftSubIndent) { wxRichTextAttr attr; attr.SetFlags(wxTEXT_ATTR_LEFT_INDENT); attr.SetLeftIndent(leftIndent, leftSubIndent); return BeginStyle(attr); } /// Begin right indent bool wxRichTextBuffer::BeginRightIndent(int rightIndent) { wxRichTextAttr attr; attr.SetFlags(wxTEXT_ATTR_RIGHT_INDENT); attr.SetRightIndent(rightIndent); return BeginStyle(attr); } /// Begin paragraph spacing bool wxRichTextBuffer::BeginParagraphSpacing(int before, int after) { long flags = 0; if (before != 0) flags |= wxTEXT_ATTR_PARA_SPACING_BEFORE; if (after != 0) flags |= wxTEXT_ATTR_PARA_SPACING_AFTER; wxRichTextAttr attr; attr.SetFlags(flags); attr.SetParagraphSpacingBefore(before); attr.SetParagraphSpacingAfter(after); return BeginStyle(attr); } /// Begin line spacing bool wxRichTextBuffer::BeginLineSpacing(int lineSpacing) { wxRichTextAttr attr; attr.SetFlags(wxTEXT_ATTR_LINE_SPACING); attr.SetLineSpacing(lineSpacing); return BeginStyle(attr); } /// Begin numbered bullet bool wxRichTextBuffer::BeginNumberedBullet(int bulletNumber, int leftIndent, int leftSubIndent, int bulletStyle) { wxRichTextAttr attr; attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT); attr.SetBulletStyle(bulletStyle); attr.SetBulletNumber(bulletNumber); attr.SetLeftIndent(leftIndent, leftSubIndent); return BeginStyle(attr); } /// Begin symbol bullet bool wxRichTextBuffer::BeginSymbolBullet(const wxString& symbol, int leftIndent, int leftSubIndent, int bulletStyle) { wxRichTextAttr attr; attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT); attr.SetBulletStyle(bulletStyle); attr.SetLeftIndent(leftIndent, leftSubIndent); attr.SetBulletText(symbol); return BeginStyle(attr); } /// Begin standard bullet bool wxRichTextBuffer::BeginStandardBullet(const wxString& bulletName, int leftIndent, int leftSubIndent, int bulletStyle) { wxRichTextAttr attr; attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT); attr.SetBulletStyle(bulletStyle); attr.SetLeftIndent(leftIndent, leftSubIndent); attr.SetBulletName(bulletName); return BeginStyle(attr); } /// Begin named character style bool wxRichTextBuffer::BeginCharacterStyle(const wxString& characterStyle) { if (GetStyleSheet()) { wxRichTextCharacterStyleDefinition* def = GetStyleSheet()->FindCharacterStyle(characterStyle); if (def) { wxRichTextAttr attr = def->GetStyleMergedWithBase(GetStyleSheet()); return BeginStyle(attr); } } return false; } /// Begin named paragraph style bool wxRichTextBuffer::BeginParagraphStyle(const wxString& paragraphStyle) { if (GetStyleSheet()) { wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(paragraphStyle); if (def) { wxRichTextAttr attr = def->GetStyleMergedWithBase(GetStyleSheet()); return BeginStyle(attr); } } return false; } /// Begin named list style bool wxRichTextBuffer::BeginListStyle(const wxString& listStyle, int level, int number) { if (GetStyleSheet()) { wxRichTextListStyleDefinition* def = GetStyleSheet()->FindListStyle(listStyle); if (def) { wxRichTextAttr attr(def->GetCombinedStyleForLevel(level)); attr.SetBulletNumber(number); return BeginStyle(attr); } } return false; } /// Begin URL bool wxRichTextBuffer::BeginURL(const wxString& url, const wxString& characterStyle) { wxRichTextAttr attr; if (!characterStyle.IsEmpty() && GetStyleSheet()) { wxRichTextCharacterStyleDefinition* def = GetStyleSheet()->FindCharacterStyle(characterStyle); if (def) { attr = def->GetStyleMergedWithBase(GetStyleSheet()); } } attr.SetURL(url); return BeginStyle(attr); } /// Adds a handler to the end void wxRichTextBuffer::AddHandler(wxRichTextFileHandler *handler) { sm_handlers.Append(handler); } /// Inserts a handler at the front void wxRichTextBuffer::InsertHandler(wxRichTextFileHandler *handler) { sm_handlers.Insert( handler ); } /// Removes a handler bool wxRichTextBuffer::RemoveHandler(const wxString& name) { wxRichTextFileHandler *handler = FindHandler(name); if (handler) { sm_handlers.DeleteObject(handler); delete handler; return true; } else return false; } /// Finds a handler by filename or, if supplied, type wxRichTextFileHandler *wxRichTextBuffer::FindHandlerFilenameOrType(const wxString& filename, wxRichTextFileType imageType) { if (imageType != wxRICHTEXT_TYPE_ANY) return FindHandler(imageType); else if (!filename.IsEmpty()) { wxString path, file, ext; wxFileName::SplitPath(filename, & path, & file, & ext); return FindHandler(ext, imageType); } else return NULL; } /// Finds a handler by name wxRichTextFileHandler* wxRichTextBuffer::FindHandler(const wxString& name) { wxList::compatibility_iterator node = sm_handlers.GetFirst(); while (node) { wxRichTextFileHandler *handler = (wxRichTextFileHandler*)node->GetData(); if (handler->GetName().Lower() == name.Lower()) return handler; node = node->GetNext(); } return NULL; } /// Finds a handler by extension and type wxRichTextFileHandler* wxRichTextBuffer::FindHandler(const wxString& extension, wxRichTextFileType type) { wxList::compatibility_iterator node = sm_handlers.GetFirst(); while (node) { wxRichTextFileHandler *handler = (wxRichTextFileHandler*)node->GetData(); if ( handler->GetExtension().Lower() == extension.Lower() && (type == wxRICHTEXT_TYPE_ANY || handler->GetType() == type) ) return handler; node = node->GetNext(); } return 0; } /// Finds a handler by type wxRichTextFileHandler* wxRichTextBuffer::FindHandler(wxRichTextFileType type) { wxList::compatibility_iterator node = sm_handlers.GetFirst(); while (node) { wxRichTextFileHandler *handler = (wxRichTextFileHandler *)node->GetData(); if (handler->GetType() == type) return handler; node = node->GetNext(); } return NULL; } void wxRichTextBuffer::InitStandardHandlers() { if (!FindHandler(wxRICHTEXT_TYPE_TEXT)) AddHandler(new wxRichTextPlainTextHandler); } void wxRichTextBuffer::CleanUpHandlers() { wxList::compatibility_iterator node = sm_handlers.GetFirst(); while (node) { wxRichTextFileHandler* handler = (wxRichTextFileHandler*)node->GetData(); wxList::compatibility_iterator next = node->GetNext(); delete handler; node = next; } sm_handlers.Clear(); } wxString wxRichTextBuffer::GetExtWildcard(bool combine, bool save, wxArrayInt* types) { if (types) types->Clear(); wxString wildcard; wxList::compatibility_iterator node = GetHandlers().GetFirst(); int count = 0; while (node) { wxRichTextFileHandler* handler = (wxRichTextFileHandler*) node->GetData(); if (handler->IsVisible() && ((save && handler->CanSave()) || (!save && handler->CanLoad()))) { if (combine) { if (count > 0) wildcard += wxT(";"); wildcard += wxT("*.") + handler->GetExtension(); } else { if (count > 0) wildcard += wxT("|"); wildcard += handler->GetName(); wildcard += wxT(" "); wildcard += _("files"); wildcard += wxT(" (*."); wildcard += handler->GetExtension(); wildcard += wxT(")|*."); wildcard += handler->GetExtension(); if (types) types->Add(handler->GetType()); } count ++; } node = node->GetNext(); } if (combine) wildcard = wxT("(") + wildcard + wxT(")|") + wildcard; return wildcard; } #if wxUSE_FFILE && wxUSE_STREAMS /// Load a file bool wxRichTextBuffer::LoadFile(const wxString& filename, wxRichTextFileType type) { wxRichTextFileHandler* handler = FindHandlerFilenameOrType(filename, type); if (handler) { SetDefaultStyle(wxRichTextAttr()); handler->SetFlags(GetHandlerFlags()); bool success = handler->LoadFile(this, filename); Invalidate(wxRICHTEXT_ALL); return success; } else return false; } /// Save a file bool wxRichTextBuffer::SaveFile(const wxString& filename, wxRichTextFileType type) { wxRichTextFileHandler* handler = FindHandlerFilenameOrType(filename, type); if (handler) { handler->SetFlags(GetHandlerFlags()); return handler->SaveFile(this, filename); } else return false; } #endif // wxUSE_FFILE && wxUSE_STREAMS #if wxUSE_STREAMS /// Load from a stream bool wxRichTextBuffer::LoadFile(wxInputStream& stream, wxRichTextFileType type) { wxRichTextFileHandler* handler = FindHandler(type); if (handler) { SetDefaultStyle(wxRichTextAttr()); handler->SetFlags(GetHandlerFlags()); bool success = handler->LoadFile(this, stream); Invalidate(wxRICHTEXT_ALL); return success; } else return false; } /// Save to a stream bool wxRichTextBuffer::SaveFile(wxOutputStream& stream, wxRichTextFileType type) { wxRichTextFileHandler* handler = FindHandler(type); if (handler) { handler->SetFlags(GetHandlerFlags()); return handler->SaveFile(this, stream); } else return false; } #endif // wxUSE_STREAMS /// Copy the range to the clipboard bool wxRichTextBuffer::CopyToClipboard(const wxRichTextRange& range) { bool success = false; wxRichTextParagraphLayoutBox* container = this; if (GetRichTextCtrl()) container = GetRichTextCtrl()->GetFocusObject(); #if wxUSE_CLIPBOARD && wxUSE_DATAOBJ if (!wxTheClipboard->IsOpened() && wxTheClipboard->Open()) { wxTheClipboard->Clear(); // Add composite object wxDataObjectComposite* compositeObject = new wxDataObjectComposite(); { wxString text = container->GetTextForRange(range); #ifdef __WXMSW__ text = wxTextFile::Translate(text, wxTextFileType_Dos); #endif compositeObject->Add(new wxTextDataObject(text), false /* not preferred */); } // Add rich text buffer data object. This needs the XML handler to be present. if (FindHandler(wxRICHTEXT_TYPE_XML)) { wxRichTextBuffer* richTextBuf = new wxRichTextBuffer; container->CopyFragment(range, *richTextBuf); compositeObject->Add(new wxRichTextBufferDataObject(richTextBuf), true /* preferred */); } if (wxTheClipboard->SetData(compositeObject)) success = true; wxTheClipboard->Close(); } #else wxUnusedVar(range); #endif return success; } /// Paste the clipboard content to the buffer bool wxRichTextBuffer::PasteFromClipboard(long position) { bool success = false; wxRichTextParagraphLayoutBox* container = this; if (GetRichTextCtrl()) container = GetRichTextCtrl()->GetFocusObject(); #if wxUSE_CLIPBOARD && wxUSE_DATAOBJ if (CanPasteFromClipboard()) { if (wxTheClipboard->Open()) { if (wxTheClipboard->IsSupported(wxDataFormat(wxRichTextBufferDataObject::GetRichTextBufferFormatId()))) { wxRichTextBufferDataObject data; wxTheClipboard->GetData(data); wxRichTextBuffer* richTextBuffer = data.GetRichTextBuffer(); if (richTextBuffer) { container->InsertParagraphsWithUndo(this, position+1, *richTextBuffer, GetRichTextCtrl(), 0); if (GetRichTextCtrl()) GetRichTextCtrl()->ShowPosition(position + richTextBuffer->GetOwnRange().GetEnd()); delete richTextBuffer; } } else if (wxTheClipboard->IsSupported(wxDF_TEXT) #if wxUSE_UNICODE || wxTheClipboard->IsSupported(wxDF_UNICODETEXT) #endif ) { wxTextDataObject data; wxTheClipboard->GetData(data); wxString text(data.GetText()); #ifdef __WXMSW__ wxString text2; text2.Alloc(text.Length()+1); size_t i; for (i = 0; i < text.Length(); i++) { wxChar ch = text[i]; if (ch != wxT('\r')) text2 += ch; } #else wxString text2 = text; #endif container->InsertTextWithUndo(this, position+1, text2, GetRichTextCtrl(), wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE); if (GetRichTextCtrl()) GetRichTextCtrl()->ShowPosition(position + text2.Length()); success = true; } else if (wxTheClipboard->IsSupported(wxDF_BITMAP)) { wxBitmapDataObject data; wxTheClipboard->GetData(data); wxBitmap bitmap(data.GetBitmap()); wxImage image(bitmap.ConvertToImage()); wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Image"), wxRICHTEXT_INSERT, this, container, GetRichTextCtrl(), false); action->GetNewParagraphs().AddImage(image); if (action->GetNewParagraphs().GetChildCount() == 1) action->GetNewParagraphs().SetPartialParagraph(true); action->SetPosition(position+1); // Set the range we'll need to delete in Undo action->SetRange(wxRichTextRange(position+1, position+1)); SubmitAction(action); success = true; } wxTheClipboard->Close(); } } #else wxUnusedVar(position); #endif return success; } /// Can we paste from the clipboard? bool wxRichTextBuffer::CanPasteFromClipboard() const { bool canPaste = false; #if wxUSE_CLIPBOARD && wxUSE_DATAOBJ if (!wxTheClipboard->IsOpened() && wxTheClipboard->Open()) { if (wxTheClipboard->IsSupported(wxDF_TEXT) #if wxUSE_UNICODE || wxTheClipboard->IsSupported(wxDF_UNICODETEXT) #endif || wxTheClipboard->IsSupported(wxDataFormat(wxRichTextBufferDataObject::GetRichTextBufferFormatId())) || wxTheClipboard->IsSupported(wxDF_BITMAP)) { canPaste = true; } wxTheClipboard->Close(); } #endif return canPaste; } /// Dumps contents of buffer for debugging purposes void wxRichTextBuffer::Dump() { wxString text; { wxStringOutputStream stream(& text); wxTextOutputStream textStream(stream); Dump(textStream); } wxLogDebug(text); } /// Add an event handler bool wxRichTextBuffer::AddEventHandler(wxEvtHandler* handler) { m_eventHandlers.Append(handler); return true; } /// Remove an event handler bool wxRichTextBuffer::RemoveEventHandler(wxEvtHandler* handler, bool deleteHandler) { wxList::compatibility_iterator node = m_eventHandlers.Find(handler); if (node) { m_eventHandlers.Erase(node); if (deleteHandler) delete handler; return true; } else return false; } /// Clear event handlers void wxRichTextBuffer::ClearEventHandlers() { m_eventHandlers.Clear(); } /// Send event to event handlers. If sendToAll is true, will send to all event handlers, /// otherwise will stop at the first successful one. bool wxRichTextBuffer::SendEvent(wxEvent& event, bool sendToAll) { bool success = false; for (wxList::compatibility_iterator node = m_eventHandlers.GetFirst(); node; node = node->GetNext()) { wxEvtHandler* handler = (wxEvtHandler*) node->GetData(); if (handler->ProcessEvent(event)) { success = true; if (!sendToAll) return true; } } return success; } /// Set style sheet and notify of the change bool wxRichTextBuffer::SetStyleSheetAndNotify(wxRichTextStyleSheet* sheet) { wxRichTextStyleSheet* oldSheet = GetStyleSheet(); wxWindowID winid = wxID_ANY; if (GetRichTextCtrl()) winid = GetRichTextCtrl()->GetId(); wxRichTextEvent event(wxEVT_RICHTEXT_STYLESHEET_REPLACING, winid); event.SetEventObject(GetRichTextCtrl()); event.SetContainer(GetRichTextCtrl()->GetFocusObject()); event.SetOldStyleSheet(oldSheet); event.SetNewStyleSheet(sheet); event.Allow(); if (SendEvent(event) && !event.IsAllowed()) { if (sheet != oldSheet) delete sheet; return false; } if (oldSheet && oldSheet != sheet) delete oldSheet; SetStyleSheet(sheet); event.SetEventType(wxEVT_RICHTEXT_STYLESHEET_REPLACED); event.SetOldStyleSheet(NULL); event.Allow(); return SendEvent(event); } /// Set renderer, deleting old one void wxRichTextBuffer::SetRenderer(wxRichTextRenderer* renderer) { if (sm_renderer) delete sm_renderer; sm_renderer = renderer; } /// Hit-testing: returns a flag indicating hit test details, plus /// information about position int wxRichTextBuffer::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int flags) { int ret = wxRichTextParagraphLayoutBox::HitTest(dc, context, pt, textPosition, obj, contextObj, flags); if (ret != wxRICHTEXT_HITTEST_NONE) { return ret; } else { textPosition = m_ownRange.GetEnd()-1; *obj = this; *contextObj = this; return wxRICHTEXT_HITTEST_AFTER|wxRICHTEXT_HITTEST_OUTSIDE; } } void wxRichTextBuffer::SetFontScale(double fontScale) { m_fontScale = fontScale; m_fontTable.SetFontScale(fontScale); } void wxRichTextBuffer::SetDimensionScale(double dimScale) { m_dimensionScale = dimScale; } bool wxRichTextStdRenderer::DrawStandardBullet(wxRichTextParagraph* paragraph, wxDC& dc, const wxRichTextAttr& bulletAttr, const wxRect& rect) { if (bulletAttr.GetTextColour().IsOk()) { wxCheckSetPen(dc, wxPen(bulletAttr.GetTextColour())); wxCheckSetBrush(dc, wxBrush(bulletAttr.GetTextColour())); } else { wxCheckSetPen(dc, *wxBLACK_PEN); wxCheckSetBrush(dc, *wxBLACK_BRUSH); } wxFont font; if (bulletAttr.HasFont()) { font = paragraph->GetBuffer()->GetFontTable().FindFont(bulletAttr); } else font = (*wxNORMAL_FONT); wxCheckSetFont(dc, font); int charHeight = dc.GetCharHeight(); int bulletWidth = (int) (((float) charHeight) * wxRichTextBuffer::GetBulletProportion()); int bulletHeight = bulletWidth; int x = rect.x; // Calculate the top position of the character (as opposed to the whole line height) int y = rect.y + (rect.height - charHeight); // Calculate where the bullet should be positioned y = y + (charHeight+1)/2 - (bulletHeight+1)/2; // The margin between a bullet and text. int margin = paragraph->ConvertTenthsMMToPixels(dc, wxRichTextBuffer::GetBulletRightMargin()); if (bulletAttr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_RIGHT) x = rect.x + rect.width - bulletWidth - margin; else if (bulletAttr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_CENTRE) x = x + (rect.width)/2 - bulletWidth/2; if (bulletAttr.GetBulletName() == wxT("standard/square")) { dc.DrawRectangle(x, y, bulletWidth, bulletHeight); } else if (bulletAttr.GetBulletName() == wxT("standard/diamond")) { wxPoint pts[5]; pts[0].x = x; pts[0].y = y + bulletHeight/2; pts[1].x = x + bulletWidth/2; pts[1].y = y; pts[2].x = x + bulletWidth; pts[2].y = y + bulletHeight/2; pts[3].x = x + bulletWidth/2; pts[3].y = y + bulletHeight; dc.DrawPolygon(4, pts); } else if (bulletAttr.GetBulletName() == wxT("standard/triangle")) { wxPoint pts[3]; pts[0].x = x; pts[0].y = y; pts[1].x = x + bulletWidth; pts[1].y = y + bulletHeight/2; pts[2].x = x; pts[2].y = y + bulletHeight; dc.DrawPolygon(3, pts); } else if (bulletAttr.GetBulletName() == wxT("standard/circle-outline")) { wxCheckSetBrush(dc, *wxTRANSPARENT_BRUSH); dc.DrawEllipse(x, y, bulletWidth, bulletHeight); } else // "standard/circle", and catch-all { dc.DrawEllipse(x, y, bulletWidth, bulletHeight); } return true; } bool wxRichTextStdRenderer::DrawTextBullet(wxRichTextParagraph* paragraph, wxDC& dc, const wxRichTextAttr& attr, const wxRect& rect, const wxString& text) { if (!text.empty()) { wxFont font; if ((attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_SYMBOL) && !attr.GetBulletFont().IsEmpty() && attr.HasFont()) { wxRichTextAttr fontAttr; if (attr.HasFontPixelSize()) fontAttr.SetFontPixelSize(attr.GetFontSize()); else fontAttr.SetFontPointSize(attr.GetFontSize()); fontAttr.SetFontStyle(attr.GetFontStyle()); fontAttr.SetFontWeight(attr.GetFontWeight()); fontAttr.SetFontUnderlined(attr.GetFontUnderlined()); fontAttr.SetFontFaceName(attr.GetBulletFont()); font = paragraph->GetBuffer()->GetFontTable().FindFont(fontAttr); } else if (attr.HasFont()) font = paragraph->GetBuffer()->GetFontTable().FindFont(attr); else font = (*wxNORMAL_FONT); wxCheckSetFont(dc, font); if (attr.GetTextColour().IsOk()) dc.SetTextForeground(attr.GetTextColour()); dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT); int charHeight = dc.GetCharHeight(); wxCoord tw, th; dc.GetTextExtent(text, & tw, & th); int x = rect.x; // Calculate the top position of the character (as opposed to the whole line height) int y = rect.y + (rect.height - charHeight); // The margin between a bullet and text. int margin = paragraph->ConvertTenthsMMToPixels(dc, wxRichTextBuffer::GetBulletRightMargin()); if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_RIGHT) x = (rect.x + rect.width) - tw - margin; else if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_CENTRE) x = x + (rect.width)/2 - tw/2; dc.DrawText(text, x, y); return true; } else return false; } bool wxRichTextStdRenderer::DrawBitmapBullet(wxRichTextParagraph* WXUNUSED(paragraph), wxDC& WXUNUSED(dc), const wxRichTextAttr& WXUNUSED(attr), const wxRect& WXUNUSED(rect)) { // Currently unimplemented. The intention is to store bitmaps by name in a media store associated // with the buffer. The store will allow retrieval from memory, disk or other means. return false; } /// Enumerate the standard bullet names currently supported bool wxRichTextStdRenderer::EnumerateStandardBulletNames(wxArrayString& bulletNames) { bulletNames.Add(wxTRANSLATE("standard/circle")); bulletNames.Add(wxTRANSLATE("standard/circle-outline")); bulletNames.Add(wxTRANSLATE("standard/square")); bulletNames.Add(wxTRANSLATE("standard/diamond")); bulletNames.Add(wxTRANSLATE("standard/triangle")); return true; } /*! * wxRichTextBox */ IMPLEMENT_DYNAMIC_CLASS(wxRichTextBox, wxRichTextParagraphLayoutBox) wxRichTextBox::wxRichTextBox(wxRichTextObject* parent): wxRichTextParagraphLayoutBox(parent) { } /// Draw the item bool wxRichTextBox::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style) { if (!IsShown()) return true; // TODO: if the active object in the control, draw an indication. // We need to add the concept of active object, and not just focus object, // so we can apply commands (properties, delete, ...) to objects such as text boxes and images. // Ultimately we would like to be able to interactively resize an active object // using drag handles. return wxRichTextParagraphLayoutBox::Draw(dc, context, range, selection, rect, descent, style); } /// Copy void wxRichTextBox::Copy(const wxRichTextBox& obj) { wxRichTextParagraphLayoutBox::Copy(obj); } // Edit properties via a GUI bool wxRichTextBox::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer) { wxRichTextObjectPropertiesDialog boxDlg(this, wxGetTopLevelParent(parent), wxID_ANY, _("Box Properties")); boxDlg.SetAttributes(GetAttributes()); if (boxDlg.ShowModal() == wxID_OK) { // By passing wxRICHTEXT_SETSTYLE_RESET, indeterminate attributes set by the user will be set as // indeterminate in the object. boxDlg.ApplyStyle(buffer->GetRichTextCtrl(), wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_RESET); return true; } else return false; } /*! * wxRichTextField */ IMPLEMENT_DYNAMIC_CLASS(wxRichTextField, wxRichTextParagraphLayoutBox) wxRichTextField::wxRichTextField(const wxString& fieldType, wxRichTextObject* parent): wxRichTextParagraphLayoutBox(parent) { SetFieldType(fieldType); } /// Draw the item bool wxRichTextField::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style) { if (!IsShown()) return true; wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType()); if (fieldType && fieldType->Draw(this, dc, context, range, selection, rect, descent, style)) return true; // Fallback; but don't draw guidelines. style &= ~wxRICHTEXT_DRAW_GUIDELINES; return wxRichTextParagraphLayoutBox::Draw(dc, context, range, selection, rect, descent, style); } bool wxRichTextField::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& parentRect, int style) { wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType()); if (fieldType && fieldType->Layout(this, dc, context, rect, parentRect, style)) return true; // Fallback return wxRichTextParagraphLayoutBox::Layout(dc, context, rect, parentRect, style); } bool wxRichTextField::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, const wxPoint& position, const wxSize& parentSize, wxArrayInt* partialExtents) const { wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType()); if (fieldType) return fieldType->GetRangeSize((wxRichTextField*) this, range, size, descent, dc, context, flags, position, parentSize, partialExtents); return wxRichTextParagraphLayoutBox::GetRangeSize(range, size, descent, dc, context, flags, position, parentSize, partialExtents); } /// Calculate range void wxRichTextField::CalculateRange(long start, long& end) { if (IsTopLevel()) wxRichTextParagraphLayoutBox::CalculateRange(start, end); else wxRichTextObject::CalculateRange(start, end); } /// Copy void wxRichTextField::Copy(const wxRichTextField& obj) { wxRichTextParagraphLayoutBox::Copy(obj); UpdateField(GetBuffer()); } // Edit properties via a GUI bool wxRichTextField::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer) { wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType()); if (fieldType) return fieldType->EditProperties(this, parent, buffer); return false; } bool wxRichTextField::CanEditProperties() const { wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType()); if (fieldType) return fieldType->CanEditProperties((wxRichTextField*) this); return false; } wxString wxRichTextField::GetPropertiesMenuLabel() const { wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType()); if (fieldType) return fieldType->GetPropertiesMenuLabel((wxRichTextField*) this); return wxEmptyString; } bool wxRichTextField::UpdateField(wxRichTextBuffer* buffer) { wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType()); if (fieldType) return fieldType->UpdateField(buffer, (wxRichTextField*) this); return false; } bool wxRichTextField::IsTopLevel() const { wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType()); if (fieldType) return fieldType->IsTopLevel((wxRichTextField*) this); return true; } IMPLEMENT_CLASS(wxRichTextFieldType, wxObject) IMPLEMENT_CLASS(wxRichTextFieldTypeStandard, wxRichTextFieldType) wxRichTextFieldTypeStandard::wxRichTextFieldTypeStandard(const wxString& name, const wxString& label, int displayStyle) { Init(); SetName(name); SetLabel(label); SetDisplayStyle(displayStyle); } wxRichTextFieldTypeStandard::wxRichTextFieldTypeStandard(const wxString& name, const wxBitmap& bitmap, int displayStyle) { Init(); SetName(name); SetBitmap(bitmap); SetDisplayStyle(displayStyle); } void wxRichTextFieldTypeStandard::Init() { m_displayStyle = wxRICHTEXT_FIELD_STYLE_RECTANGLE; m_font = wxFont(6, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL); m_textColour = *wxWHITE; m_borderColour = *wxBLACK; m_backgroundColour = *wxBLACK; m_verticalPadding = 1; m_horizontalPadding = 3; m_horizontalMargin = 2; m_verticalMargin = 0; } void wxRichTextFieldTypeStandard::Copy(const wxRichTextFieldTypeStandard& field) { wxRichTextFieldType::Copy(field); m_label = field.m_label; m_displayStyle = field.m_displayStyle; m_font = field.m_font; m_textColour = field.m_textColour; m_borderColour = field.m_borderColour; m_backgroundColour = field.m_backgroundColour; m_verticalPadding = field.m_verticalPadding; m_horizontalPadding = field.m_horizontalPadding; m_horizontalMargin = field.m_horizontalMargin; m_bitmap = field.m_bitmap; } bool wxRichTextFieldTypeStandard::Draw(wxRichTextField* obj, wxDC& dc, wxRichTextDrawingContext& WXUNUSED(context), const wxRichTextRange& WXUNUSED(range), const wxRichTextSelection& selection, const wxRect& rect, int descent, int WXUNUSED(style)) { if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_COMPOSITE) return false; // USe default composite drawing else // if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_RECTANGLE || m_displayStyle == wxRICHTEXT_FIELD_STYLE_NOBORDER) { int borderSize = 1; wxPen borderPen(m_borderColour, 1, wxSOLID); wxBrush backgroundBrush(m_backgroundColour); wxColour textColour(m_textColour); if (selection.WithinSelection(obj->GetRange().GetStart(), obj)) { wxColour highlightColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT)); wxColour highlightTextColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT)); borderPen = wxPen(highlightTextColour, 1, wxSOLID); backgroundBrush = wxBrush(highlightColour); wxCheckSetBrush(dc, backgroundBrush); wxCheckSetPen(dc, wxPen(highlightColour, 1, wxSOLID)); dc.DrawRectangle(rect); } if (m_displayStyle != wxRICHTEXT_FIELD_STYLE_NO_BORDER) borderSize = 0; // objectRect is the area where the content is drawn, after margins around it have been taken into account wxRect objectRect = wxRect(wxPoint(rect.x + m_horizontalMargin, rect.y + wxMax(0, rect.height - descent - obj->GetCachedSize().y)), wxSize(obj->GetCachedSize().x - 2*m_horizontalMargin - borderSize, obj->GetCachedSize().y)); // clientArea is where the text is actually written wxRect clientArea = objectRect; if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_RECTANGLE) { dc.SetPen(borderPen); dc.SetBrush(backgroundBrush); dc.DrawRoundedRectangle(objectRect, 4.0); } else if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_START_TAG) { int arrowLength = objectRect.height/2; clientArea.width -= (arrowLength - m_horizontalPadding); wxPoint pts[5]; pts[0].x = objectRect.x; pts[0].y = objectRect.y; pts[1].x = objectRect.x + objectRect.width - arrowLength; pts[1].y = objectRect.y; pts[2].x = objectRect.x + objectRect.width; pts[2].y = objectRect.y + (objectRect.height/2); pts[3].x = objectRect.x + objectRect.width - arrowLength; pts[3].y = objectRect.y + objectRect.height; pts[4].x = objectRect.x; pts[4].y = objectRect.y + objectRect.height; dc.SetPen(borderPen); dc.SetBrush(backgroundBrush); dc.DrawPolygon(5, pts); } else if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_END_TAG) { int arrowLength = objectRect.height/2; clientArea.width -= (arrowLength - m_horizontalPadding); clientArea.x += (arrowLength - m_horizontalPadding); wxPoint pts[5]; pts[0].x = objectRect.x + objectRect.width; pts[0].y = objectRect.y; pts[1].x = objectRect.x + arrowLength; pts[1].y = objectRect.y; pts[2].x = objectRect.x; pts[2].y = objectRect.y + (objectRect.height/2); pts[3].x = objectRect.x + arrowLength; pts[3].y = objectRect.y + objectRect.height; pts[4].x = objectRect.x + objectRect.width; pts[4].y = objectRect.y + objectRect.height; dc.SetPen(borderPen); dc.SetBrush(backgroundBrush); dc.DrawPolygon(5, pts); } if (m_bitmap.IsOk()) { int x = clientArea.x + (clientArea.width - m_bitmap.GetWidth())/2; int y = clientArea.y + m_verticalPadding; dc.DrawBitmap(m_bitmap, x, y, true); if (selection.WithinSelection(obj->GetRange().GetStart(), obj)) { wxCheckSetBrush(dc, *wxBLACK_BRUSH); wxCheckSetPen(dc, *wxBLACK_PEN); dc.SetLogicalFunction(wxINVERT); dc.DrawRectangle(wxRect(x, y, m_bitmap.GetWidth(), m_bitmap.GetHeight())); dc.SetLogicalFunction(wxCOPY); } } else { wxString label(m_label); if (label.IsEmpty()) label = wxT("??"); int w, h, maxDescent; dc.SetFont(m_font); dc.GetTextExtent(m_label, & w, &h, & maxDescent); dc.SetTextForeground(textColour); int x = clientArea.x + (clientArea.width - w)/2; int y = clientArea.y + (clientArea.height - (h - maxDescent))/2; dc.DrawText(m_label, x, y); } } return true; } bool wxRichTextFieldTypeStandard::Layout(wxRichTextField* obj, wxDC& dc, wxRichTextDrawingContext& context, const wxRect& WXUNUSED(rect), const wxRect& WXUNUSED(parentRect), int style) { if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_COMPOSITE) return false; // USe default composite layout wxSize size = GetSize(obj, dc, context, style); obj->SetCachedSize(size); obj->SetMinSize(size); obj->SetMaxSize(size); return true; } bool wxRichTextFieldTypeStandard::GetRangeSize(wxRichTextField* obj, const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, const wxPoint& position, const wxSize& parentSize, wxArrayInt* partialExtents) const { if (IsTopLevel(obj)) return obj->wxRichTextParagraphLayoutBox::GetRangeSize(range, size, descent, dc, context, flags, position, parentSize); else { wxSize sz = GetSize(obj, dc, context, 0); if (partialExtents) { int lastSize; if (partialExtents->GetCount() > 0) lastSize = (*partialExtents)[partialExtents->GetCount()-1]; else lastSize = 0; partialExtents->Add(lastSize + sz.x); } size = sz; return true; } } wxSize wxRichTextFieldTypeStandard::GetSize(wxRichTextField* WXUNUSED(obj), wxDC& dc, wxRichTextDrawingContext& WXUNUSED(context), int WXUNUSED(style)) const { int borderSize = 1; int w = 0, h = 0, maxDescent = 0; wxSize sz; if (m_bitmap.IsOk()) { w = m_bitmap.GetWidth(); h = m_bitmap.GetHeight(); sz = wxSize(w + m_horizontalMargin*2, h + m_verticalMargin*2); } else { wxString label(m_label); if (label.IsEmpty()) label = wxT("??"); dc.SetFont(m_font); dc.GetTextExtent(label, & w, &h, & maxDescent); sz = wxSize(w + m_horizontalPadding*2 + m_horizontalMargin*2, h + m_verticalPadding *2 + m_verticalMargin*2); } if (m_displayStyle != wxRICHTEXT_FIELD_STYLE_NO_BORDER) { sz.x += borderSize*2; sz.y += borderSize*2; } if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_START_TAG || m_displayStyle == wxRICHTEXT_FIELD_STYLE_END_TAG) { // Add space for the arrow sz.x += (sz.y/2 - m_horizontalPadding); } return sz; } IMPLEMENT_DYNAMIC_CLASS(wxRichTextCell, wxRichTextBox) wxRichTextCell::wxRichTextCell(wxRichTextObject* parent): wxRichTextBox(parent) { } /// Draw the item bool wxRichTextCell::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style) { return wxRichTextBox::Draw(dc, context, range, selection, rect, descent, style); } int wxRichTextCell::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int flags) { int ret = wxRichTextParagraphLayoutBox::HitTest(dc, context, pt, textPosition, obj, contextObj, flags); if (ret != wxRICHTEXT_HITTEST_NONE) { return ret; } else { textPosition = m_ownRange.GetEnd()-1; *obj = this; *contextObj = this; return wxRICHTEXT_HITTEST_AFTER|wxRICHTEXT_HITTEST_OUTSIDE; } } // Adjusts the attributes for virtual attribute provision, collapsed borders, etc. bool wxRichTextCell::AdjustAttributes(wxRichTextAttr& attr, wxRichTextDrawingContext& context) { wxRichTextObject::AdjustAttributes(attr, context); wxRichTextTable* table = wxDynamicCast(GetParent(), wxRichTextTable); if (IsShown() && table && table->GetAttributes().GetTextBoxAttr().HasCollapseBorders() && table->GetAttributes().GetTextBoxAttr().GetCollapseBorders() == wxTEXT_BOX_ATTR_COLLAPSE_FULL) { // Collapse borders: // (1) Reset left and top for all cells unless there is no table border there; // (2) for bottom and right, reset if at edge of table and there are no table borders, // otherwise use this cell's border if present, otherwise adjacent border if not. // Takes into account spanning by checking if adjacent cells are shown. int row, col; if (table->GetCellRowColumnPosition(GetRange().GetStart(), row, col)) { if (col == 0) { // Only remove the cell border on the left edge if we have a table border if (table->GetAttributes().GetTextBoxAttr().GetBorder().GetLeft().IsValid()) attr.GetTextBoxAttr().GetBorder().GetLeft().Reset(); } else attr.GetTextBoxAttr().GetBorder().GetLeft().Reset(); if (row == 0) { // Only remove the cell border on the top edge if we have a table border if (table->GetAttributes().GetTextBoxAttr().GetBorder().GetTop().IsValid()) attr.GetTextBoxAttr().GetBorder().GetTop().Reset(); } else attr.GetTextBoxAttr().GetBorder().GetTop().Reset(); // Compute right border // We need to explicity look at the spans, not just whether // the cell is visible, because that doesn't tell us which // cell to look at for border information. wxRichTextCell* adjacentCellRight = NULL; int nextCol = col + GetColSpan(); if (nextCol >= table->GetColumnCount()) { // Do nothing - at edge of table } else { wxRichTextCell* nextRightCell = table->GetCell(row, nextCol); if (nextRightCell->IsShown()) { adjacentCellRight = nextRightCell; } else { // Must be hidden by a rowspan above. Go hunting for it. int r; for (r = row-1; r >= 0; r--) { nextRightCell = table->GetCell(r, nextCol); if (nextRightCell->IsShown()) { adjacentCellRight = nextRightCell; break; } } } } // If no adjacent cell (either because they were hidden or at the edge of the table) // then we must reset the border, if there's a right table border. if (!adjacentCellRight) { if (table->GetAttributes().GetTextBoxAttr().GetBorder().GetRight().IsValid()) attr.GetTextBoxAttr().GetBorder().GetRight().Reset(); } else { if (!attr.GetTextBoxAttr().GetBorder().GetRight().IsValid() || attr.GetTextBoxAttr().GetBorder().GetRight().GetWidth().GetValue() == 0) { attr.GetTextBoxAttr().GetBorder().GetRight() = adjacentCellRight->GetAttributes().GetTextBoxAttr().GetBorder().GetLeft(); } } // Compute bottom border wxRichTextCell* adjacentCellBelow = NULL; int nextRow = row + GetRowSpan(); if (nextRow >= table->GetRowCount()) { // Do nothing - at edge of table } else { wxRichTextCell* nextBottomCell = table->GetCell(nextRow, col); if (nextBottomCell->IsShown()) { adjacentCellBelow = nextBottomCell; } else { // Must be hidden by a colspan to the left. Go hunting for it. int c; for (c = col-1; c >= 0; c--) { nextBottomCell = table->GetCell(nextRow, c); if (nextBottomCell->IsShown()) { adjacentCellBelow = nextBottomCell; break; } } } } // If no adjacent cell (either because they were hidden or at the edge of the table) // then we must reset the border, if there's a bottom table border. if (!adjacentCellBelow) { if (table->GetAttributes().GetTextBoxAttr().GetBorder().GetBottom().IsValid()) attr.GetTextBoxAttr().GetBorder().GetBottom().Reset(); } else { if (!attr.GetTextBoxAttr().GetBorder().GetBottom().IsValid() || attr.GetTextBoxAttr().GetBorder().GetBottom().GetWidth().GetValue() == 0) { attr.GetTextBoxAttr().GetBorder().GetBottom() = adjacentCellBelow->GetAttributes().GetTextBoxAttr().GetBorder().GetTop(); } } } } return true; } /// Copy void wxRichTextCell::Copy(const wxRichTextCell& obj) { wxRichTextBox::Copy(obj); } // Edit properties via a GUI bool wxRichTextCell::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer) { // We need to gather common attributes for all selected cells. wxRichTextTable* table = wxDynamicCast(GetParent(), wxRichTextTable); bool multipleCells = false; wxRichTextAttr attr; if (table && buffer && buffer->GetRichTextCtrl() && buffer->GetRichTextCtrl()->GetSelection().IsValid() && buffer->GetRichTextCtrl()->GetSelection().GetContainer() == GetParent()) { wxRichTextAttr clashingAttr, absentAttr; const wxRichTextSelection& sel = buffer->GetRichTextCtrl()->GetSelection(); size_t i; int selectedCellCount = 0; for (i = 0; i < sel.GetCount(); i++) { const wxRichTextRange& range = sel[i]; wxRichTextCell* cell = table->GetCell(range.GetStart()); if (cell) { wxRichTextAttr cellStyle = cell->GetAttributes(); CollectStyle(attr, cellStyle, clashingAttr, absentAttr); selectedCellCount ++; } } multipleCells = selectedCellCount > 1; } else { attr = GetAttributes(); } wxString caption; if (multipleCells) caption = _("Multiple Cell Properties"); else caption = _("Cell Properties"); // We don't want position and floating controls for a cell. wxRichTextSizePage::ShowPositionControls(false); wxRichTextSizePage::ShowFloatingControls(false); wxRichTextSizePage::ShowAlignmentControls(true); wxRichTextObjectPropertiesDialog cellDlg(this, wxGetTopLevelParent(parent), wxID_ANY, caption); cellDlg.SetAttributes(attr); bool ok = (cellDlg.ShowModal() == wxID_OK); wxRichTextSizePage::ShowPositionControls(true); wxRichTextSizePage::ShowFloatingControls(true); if (ok) { if (multipleCells) { const wxRichTextSelection& sel = buffer->GetRichTextCtrl()->GetSelection(); // Apply the style; we interpret indeterminate attributes as 'don't touch this attribute' // since it may represent clashing attributes across multiple objects. table->SetCellStyle(sel, attr); } else // For a single object, indeterminate attributes set by the user should be reflected in the // actual object style, so pass the wxRICHTEXT_SETSTYLE_RESET flag to assign // the style directly instead of applying (which ignores indeterminate attributes, // leaving them as they were). cellDlg.ApplyStyle(buffer->GetRichTextCtrl(), wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_RESET); return true; } else return false; } // The next 2 methods return span values. Note that the default is 1, not 0 int wxRichTextCell::GetColSpan() const { int span = 1; if (GetProperties().HasProperty(wxT("colspan"))) { span = GetProperties().GetPropertyLong(wxT("colspan")); } return span; } int wxRichTextCell::GetRowSpan() const { int span = 1; if (GetProperties().HasProperty(wxT("rowspan"))) { span = GetProperties().GetPropertyLong(wxT("rowspan")); } return span; } WX_DEFINE_OBJARRAY(wxRichTextObjectPtrArrayArray) IMPLEMENT_DYNAMIC_CLASS(wxRichTextTable, wxRichTextBox) wxRichTextTable::wxRichTextTable(wxRichTextObject* parent): wxRichTextBox(parent) { m_rowCount = 0; m_colCount = 0; } // Draws the object. bool wxRichTextTable::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style) { wxRichTextBox::Draw(dc, context, range, selection, rect, descent, style); // If the table is not collapsed (in which case the outer table box provides the border), // draw the overall border again using cell borders in case it has been overwritten by // adjacent cell borders of different colours. if (!GetAttributes().GetTextBoxAttr().HasCollapseBorders() || GetAttributes().GetTextBoxAttr().GetCollapseBorders() != wxTEXT_BOX_ATTR_COLLAPSE_FULL) { int colCount = GetColumnCount(); int rowCount = GetRowCount(); int col, row; for (col = 0; col < colCount; col++) { for (row = 0; row < rowCount; row++) { if (row == 0 || row == (rowCount-1) || col == 0 || col == (colCount-1)) { wxRichTextCell* cell = GetCell(row, col); if (cell && cell->IsShown() && !cell->GetRange().IsOutside(range)) { wxRect childRect(cell->GetPosition(), cell->GetCachedSize()); wxRichTextAttr attr(cell->GetAttributes()); cell->AdjustAttributes(attr, context); if (row != 0) attr.GetTextBoxAttr().GetBorder().GetTop().Reset(); if (row != (rowCount-1)) attr.GetTextBoxAttr().GetBorder().GetBottom().Reset(); if (col != 0) attr.GetTextBoxAttr().GetBorder().GetLeft().Reset(); if (col != (colCount-1)) attr.GetTextBoxAttr().GetBorder().GetRight().Reset(); if (attr.GetTextBoxAttr().GetBorder().IsValid()) { wxRect boxRect(cell->GetPosition(), cell->GetCachedSize()); wxRect marginRect = boxRect; wxRect contentRect, borderRect, paddingRect, outlineRect; cell->GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect); cell->DrawBorder(dc, GetBuffer(), attr.GetTextBoxAttr().GetBorder(), borderRect); } } } } } } return true; } // Helper function for Layout() that clears the space needed by a cell with rowspan > 1 int GetRowspanDisplacement(const wxRichTextTable* table, int row, int col, int paddingX, const wxArrayInt& colWidths) { // If one or more cells above-left of this one has rowspan > 1, the affected cells below it // will have been hidden and have width 0. As a result they are ignored by the layout algorithm, // and all cells to their right are effectively shifted left. As a result there's no hole for // the spanning cell to fill. // So search back along the current row for hidden cells. However there's also the annoying issue of a // rowspanning cell that also has colspam. So we can't rely on the rowspanning cell being directly above // the first hidden one we come to. We also can't rely on a cell being hidden only by one type of span; // there's nothing to stop a cell being hidden by colspan, and then again hidden from above by rowspan. // The answer is to look above each hidden cell in turn, which I think covers all bases. int deltaX = 0; for (int prevcol = 0; prevcol < col; ++prevcol) { if (!table->GetCell(row, prevcol)->IsShown()) { // We've found a hidden cell. If it's hidden because of colspan to its left, it's // already been taken into account; but not if there's a rowspanning cell above for (int prevrow = row-1; prevrow >= 0; --prevrow) { wxRichTextCell* cell = table->GetCell(prevrow, prevcol); if (cell && cell->IsShown()) { int rowSpan = cell->GetRowSpan(); if (rowSpan > 1 && rowSpan > (row-prevrow)) { // There is a rowspanning cell above above the hidden one, so we need // to right-shift the index cell by this column's width. Furthermore, // if the cell also colspans, we need to shift by all affected columns for (int colSpan = 0; colSpan < cell->GetColSpan(); ++colSpan) deltaX += (colWidths[prevcol+colSpan] + paddingX); break; } } } } } return deltaX; } // Helper function for Layout() that expands any cell with rowspan > 1 void ExpandCellsWithRowspan(const wxRichTextTable* table, int paddingY, int& bottomY, wxDC& dc, wxRichTextDrawingContext& context, const wxRect& availableSpace, int style) { // This is called when the table's cell layout is otherwise complete. // For any cell with rowspan > 1, expand downwards into the row(s) below. // Start by finding the current 'y' of the top of each row, plus the bottom of the available area for cells. // Deduce this from the top of a visible cell in the row below. (If none are visible, the row will be invisible anyway and can be ignored.) const int rowCount = table->GetRowCount(); const int colCount = table->GetColumnCount(); wxArrayInt rowTops; rowTops.Add(0, rowCount+1); int row; for (row = 0; row < rowCount; ++row) { for (int column = 0; column < colCount; ++column) { wxRichTextCell* cell = table->GetCell(row, column); if (cell && cell->IsShown()) { rowTops[row] = cell->GetPosition().y; break; } } } rowTops[rowCount] = bottomY + paddingY; // The table bottom, which was passed to us bool needsRelay = false; for (row = 0; row < rowCount-1; ++row) // -1 as the bottom row can't rowspan { for (int col = 0; col < colCount; ++col) { wxRichTextCell* cell = table->GetCell(row, col); if (cell && cell->IsShown()) { int span = cell->GetRowSpan(); if (span > 1) { span = wxMin(span, rowCount-row); // Don't try to span below the table! if (span < 2) continue; int availableHeight = rowTops[row+span] - rowTops[row] - paddingY; wxSize newSize = wxSize(cell->GetCachedSize().GetWidth(), availableHeight); wxRect availableCellSpace = wxRect(cell->GetPosition(), newSize); cell->Invalidate(wxRICHTEXT_ALL); cell->Layout(dc, context, availableCellSpace, availableSpace, style); // Ensure there's room in the span to display its contents, else it'll overwrite lower rows int overhang = cell->GetCachedSize().GetHeight() - availableHeight; cell->SetCachedSize(newSize); if (overhang > 0) { // There are 3 things to get right: // 1) The easiest is the rows below the span: they need to be downshifted by the overhang, and so does the table bottom itself // 2) The rows within the span, including the one holding this cell, need to be deepened by their share of the overhang // e.g. if rowspan == 3, each row should increase in depth by 1/3rd of the overhang. // 3) The cell with the rowspan shouldn't be touched in 2); its height will be set to the whole span later. int deltaY = overhang / span; int spare = overhang % span; // Each row in the span needs to by deepened by its share of the overhang (give the first row any spare). // This is achieved by increasing the value stored in the following row's rowTops for (int spannedRows = 0; spannedRows < span; ++spannedRows) { rowTops[row+spannedRows+1] += ((deltaY * (spannedRows+1)) + (spannedRows == 0 ? spare:0)); } // Any rows below the span need shifting down for (int rowsBelow = row + span+1; rowsBelow <= rowCount; ++rowsBelow) { rowTops[rowsBelow] += overhang; } needsRelay = true; } } } } } if (!needsRelay) return; // There were overflowing rowspanning cells, so layout yet again to make the increased row depths show for (row = 0; row < rowCount; ++row) { for (int col = 0; col < colCount; ++col) { wxRichTextCell* cell = table->GetCell(row, col); if (cell && cell->IsShown()) { wxPoint position(cell->GetPosition().x, rowTops[row]); // GetRowspan() will usually return 1, but may be greater wxSize size(cell->GetCachedSize().GetWidth(), rowTops[row + cell->GetRowSpan()] - rowTops[row] - paddingY); wxRect availableCellSpace = wxRect(position, size); cell->Invalidate(wxRICHTEXT_ALL); cell->Layout(dc, context, availableCellSpace, availableSpace, style); cell->SetCachedSize(size); } } bottomY = rowTops[rowCount] - paddingY; } } // Lays the object out. rect is the space available for layout. Often it will // be the specified overall space for this object, if trying to constrain // layout to a particular size, or it could be the total space available in the // parent. rect is the overall size, so we must subtract margins and padding. // to get the actual available space. bool wxRichTextTable::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& WXUNUSED(parentRect), int style) { SetPosition(rect.GetPosition()); // The meaty bit. Calculate sizes of all cells and rows. Try to use // minimum size if within alloted size, then divide up remaining size // between rows/cols. double scale = 1.0; wxRichTextBuffer* buffer = GetBuffer(); if (buffer) scale = buffer->GetScale(); wxRect availableSpace = GetAvailableContentArea(dc, context, rect); wxTextAttrDimensionConverter converter(dc, scale, availableSpace.GetSize()); wxRichTextAttr attr(GetAttributes()); AdjustAttributes(attr, context); bool tableHasPercentWidth = (attr.GetTextBoxAttr().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE); // If we have no fixed table size, and assuming we're not pushed for // space, then we don't have to try to stretch the table to fit the contents. bool stretchToFitTableWidth = tableHasPercentWidth; int tableWidth = rect.width; if (attr.GetTextBoxAttr().GetWidth().IsValid() && !tableHasPercentWidth) { tableWidth = converter.GetPixels(attr.GetTextBoxAttr().GetWidth(), wxHORIZONTAL); // Fixed table width, so we do want to stretch columns out if necessary. stretchToFitTableWidth = true; // Shouldn't be able to exceed the size passed to this function tableWidth = wxMin(rect.width, tableWidth); } // Get internal padding int paddingLeft = 0, paddingTop = 0; if (attr.GetTextBoxAttr().GetPadding().GetLeft().IsValid()) paddingLeft = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetLeft(), wxHORIZONTAL); if (attr.GetTextBoxAttr().GetPadding().GetTop().IsValid()) paddingTop = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetTop(), wxVERTICAL); // Assume that left and top padding are also used for inter-cell padding. int paddingX = paddingLeft; int paddingY = paddingTop; int totalLeftMargin = 0, totalRightMargin = 0, totalTopMargin = 0, totalBottomMargin = 0; GetTotalMargin(dc, buffer, attr, totalLeftMargin, totalRightMargin, totalTopMargin, totalBottomMargin); // Internal table width - the area for content int internalTableWidth = tableWidth - totalLeftMargin - totalRightMargin; int rowCount = m_cells.GetCount(); if (m_colCount == 0 || rowCount == 0) { wxRect overallRect(rect.x, rect.y, totalLeftMargin + totalRightMargin, totalTopMargin + totalBottomMargin); SetCachedSize(overallRect.GetSize()); // Zero content size SetMinSize(overallRect.GetSize()); SetMaxSize(GetMinSize()); return true; } // The final calculated widths wxArrayInt colWidths; colWidths.Add(0, m_colCount); wxArrayInt absoluteColWidths; absoluteColWidths.Add(0, m_colCount); wxArrayInt percentageColWidths; percentageColWidths.Add(0, m_colCount); // The required width of a column calculated from the content, in case we don't specify any widths wxArrayInt maxUnspecifiedColumnWidths; maxUnspecifiedColumnWidths.Add(0, m_colCount); wxArrayInt maxColWidths; maxColWidths.Add(0, m_colCount); wxArrayInt minColWidths; minColWidths.Add(0, m_colCount); // Separately record the minimum width of columns with // nowrap cells wxArrayInt minColWidthsNoWrap; minColWidthsNoWrap.Add(0, m_colCount); wxSize tableSize(tableWidth, 0); int i, j, k; for (i = 0; i < m_colCount; i++) { absoluteColWidths[i] = 0; percentageColWidths[i] = -1; colWidths[i] = 0; maxColWidths[i] = 0; minColWidths[i] = 0; minColWidthsNoWrap[i] = 0; } // (0) Determine which cells are visible according to spans // 1 2 3 4 5 // __________________ // | | | | | 1 // |------| |----| // |------| | | 2 // |------| | | 3 // |------------------| // |__________________| 4 // To calculate cell visibility: // First find all spanning cells. Build an array of span records with start x, y and end x, y. // Then for each cell, test whether we're within one of those cells, and unless we're at the start of // that cell, hide the cell. // We can also use this array to match the size of spanning cells to the grid. Or just do // this when we iterate through all cells. // 0.1: add spanning cells to an array wxRichTextRectArray rectArray; for (j = 0; j < m_rowCount; j++) { for (i = 0; i < m_colCount; i++) { wxRichTextCell* cell = GetCell(j, i); int colSpan = cell->GetColSpan(); int rowSpan = cell->GetRowSpan(); if (colSpan > 1 || rowSpan > 1) { rectArray.Add(wxRect(i, j, colSpan, rowSpan)); } } } // 0.2: find which cells are subsumed by a spanning cell for (j = 0; j < m_rowCount; j++) { for (i = 0; i < m_colCount; i++) { wxRichTextCell* cell = GetCell(j, i); if (rectArray.GetCount() == 0) { cell->Show(true); } else { int colSpan = cell->GetColSpan(); int rowSpan = cell->GetRowSpan(); if (colSpan > 1 || rowSpan > 1) { // Assume all spanning cells are shown cell->Show(true); } else { bool shown = true; for (k = 0; k < (int) rectArray.GetCount(); k++) { if (rectArray[k].Contains(wxPoint(i, j))) { shown = false; break; } } cell->Show(shown); } } } } // Find the first spanned cell in each row that spans the most columns and doesn't // overlap with a spanned cell starting at a previous column position. // This means we need to keep an array of rects so we can check. However // it does also mean that some spans simply may not be taken into account // where there are different spans happening on different rows. In these cases, // they will simply be as wide as their constituent columns. // (1) Do an initial layout for all cells to get minimum and maximum size, and get // the absolute or percentage width of each column. for (j = 0; j < m_rowCount; j++) { int visibleCellCount = 0; for (i = 0; i < m_colCount; i++) { wxRichTextBox* cell = GetCell(j, i); if (cell->IsShown()) { visibleCellCount ++; } } // Cell width percentages are for the overall cell width, so ignore margins and // only take into account table margins and inter-cell padding. int availableWidthForPercentageCellWidths = internalTableWidth - ((visibleCellCount-1) * paddingX); wxTextAttrDimensionConverter converter(dc, scale, wxSize(availableWidthForPercentageCellWidths, 0)); for (i = 0; i < m_colCount; i++) { wxRichTextCell* cell = GetCell(j, i); if (cell->IsShown()) { int colSpan = cell->GetColSpan(); // Lay out cell to find min/max widths cell->Invalidate(wxRICHTEXT_ALL); cell->Layout(dc, context, availableSpace, availableSpace, style); if (colSpan == 1) { int absoluteCellWidth = -1; int percentageCellWidth = -1; if (cell->GetAttributes().GetTextBoxAttr().GetWidth().IsValid()) { int w = converter.GetPixels(cell->GetAttributes().GetTextBoxAttr().GetWidth(), wxHORIZONTAL); if (cell->GetAttributes().GetTextBoxAttr().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE) { percentageCellWidth = w; } else { absoluteCellWidth = w; } // Override absolute width with minimum width if necessary if (cell->GetMinSize().x > 0 && absoluteCellWidth != -1 && cell->GetMinSize().x > absoluteCellWidth) absoluteCellWidth = cell->GetMinSize().x; } else maxUnspecifiedColumnWidths[i] = wxMax(cell->GetMaxSize().x, maxUnspecifiedColumnWidths[i]); if (absoluteCellWidth != -1) { if (absoluteCellWidth > absoluteColWidths[i]) absoluteColWidths[i] = absoluteCellWidth; } if (percentageCellWidth != -1) { if (percentageCellWidth > percentageColWidths[i]) percentageColWidths[i] = percentageCellWidth; } if (colSpan == 1 && cell->GetMinSize().x && cell->GetMinSize().x > minColWidths[i]) minColWidths[i] = cell->GetMinSize().x; if (colSpan == 1 && cell->GetMaxSize().x && cell->GetMaxSize().x > maxColWidths[i]) maxColWidths[i] = cell->GetMaxSize().x; if (cell->GetAttributes().GetTextBoxAttr().HasWhitespaceMode() && (cell->GetAttributes().GetTextBoxAttr().GetWhitespaceMode() == wxTEXT_BOX_ATTR_WHITESPACE_NO_WRAP)) { if (cell->GetMaxSize().x > minColWidthsNoWrap[i]) minColWidthsNoWrap[i] = cell->GetMaxSize().x; } } } } } // (2) Allocate initial column widths from absolute values and proportions for (i = 0; i < m_colCount; i++) { if (absoluteColWidths[i] > 0) { colWidths[i] = absoluteColWidths[i]; } else if (percentageColWidths[i] > 0) { colWidths[i] = percentageColWidths[i]; } } // (3) Process absolute or proportional widths of spanning columns, // now that we know what our fixed column widths are going to be. // Spanned cells will try to adjust columns so the span will fit. // Even existing fixed column widths can be expanded if necessary. // Actually, currently fixed columns widths aren't adjusted; instead, // the algorithm favours earlier rows and adjusts unspecified column widths // the first time only. After that, we can't know whether the column has been // specified explicitly or not. (We could make a note if necessary.) for (j = 0; j < m_rowCount; j++) { int visibleCellCount = 0; for (i = 0; i < m_colCount; i++) { wxRichTextBox* cell = GetCell(j, i); if (cell->IsShown()) { visibleCellCount ++; } } // Cell width percentages are for the overall cell width, so ignore margins and // only take into account table margins and inter-cell padding. int availableWidthForPercentageCellWidths = internalTableWidth - ((visibleCellCount-1) * paddingX); wxTextAttrDimensionConverter converter(dc, scale, wxSize(availableWidthForPercentageCellWidths, 0)); for (i = 0; i < m_colCount; i++) { wxRichTextCell* cell = GetCell(j, i); if (cell->IsShown()) { int colSpan = cell->GetColSpan(); if (colSpan > 1) { int spans = wxMin(colSpan, m_colCount - i); int cellWidth = 0; if (spans > 0) { if (cell->GetAttributes().GetTextBoxAttr().GetWidth().IsValid()) { cellWidth = converter.GetPixels(cell->GetAttributes().GetTextBoxAttr().GetWidth(), wxHORIZONTAL); // Override absolute width with minimum width if necessary if (cell->GetMinSize().x > 0 && cellWidth != -1 && cell->GetMinSize().x > cellWidth) cellWidth = cell->GetMinSize().x; } else { // Do we want to do this? It's the only chance we get to // use the cell's min/max sizes, so we need to work out // how we're going to balance the unspecified spanning cell // width with the possibility more-constrained constituent cell widths. // Say there's a tiny bitmap giving it a max width of 10 pixels. We // don't want to constraint all the spanned columns to fit into this cell. // OK, let's say that if any of the constituent columns don't fit, // then we simply stop constraining the columns; instead, we'll just fit the spanning // cells to the columns later. cellWidth = cell->GetMinSize().x; if (cell->GetMaxSize().x > cellWidth) cellWidth = cell->GetMaxSize().x; } // Subtract the padding between cells int spanningWidth = cellWidth; spanningWidth -= paddingX * (spans-1); if (spanningWidth > 0) { // Now share the spanning width between columns within that span // TODO: take into account min widths of columns within the span int spanningWidthLeft = spanningWidth; int stretchColCount = 0; for (k = i; k < (i+spans); k++) { if (colWidths[k] > 0) // absolute or proportional width has been specified spanningWidthLeft -= colWidths[k]; else stretchColCount ++; } // Now divide what's left between the remaining columns int colShare = 0; if (stretchColCount > 0) colShare = spanningWidthLeft / stretchColCount; int colShareRemainder = spanningWidthLeft - (colShare * stretchColCount); // If fixed-width columns are currently too big, then we'll later // stretch the spanned cell to fit. if (spanningWidthLeft > 0) { for (k = i; k < (i+spans); k++) { if (colWidths[k] <= 0) // absolute or proportional width has not been specified { int newWidth = colShare; if (k == (i+spans-1)) newWidth += colShareRemainder; // ensure all pixels are filled colWidths[k] = newWidth; } } } } } } } } } // (4) Next, share any remaining space out between columns that have not yet been calculated. // TODO: take into account min widths of columns within the span int tableWidthMinusPadding = internalTableWidth - (m_colCount-1)*paddingX; int widthLeft = tableWidthMinusPadding; int stretchColCount = 0; bool relaxConstraints = false; size_t phase; for (phase = 0; phase < 2; phase ++) { widthLeft = tableWidthMinusPadding; stretchColCount = 0; for (i = 0; i < m_colCount; i++) { // Subtract min width from width left, then // add the colShare to the min width if (colWidths[i] > 0) // absolute or proportional width has been specified widthLeft -= colWidths[i]; else { int minColWidth = wxMax(minColWidths[i], minColWidthsNoWrap[i]); // If we're at phase 2, it means we had insufficient space, so // this time, don't take maxUnspecifiedColumnWidths into account // since we will simply apportion the remaining space. if (phase == 0) minColWidth = wxMax(minColWidth, maxUnspecifiedColumnWidths[i]); if (minColWidth > 0) widthLeft -= minColWidth; // Don't allow this to stretch if we're not wrapping; give the space // to other columns instead. if (minColWidthsNoWrap[i] == 0) stretchColCount ++; } } if (widthLeft >= 0) break; else if (phase == 0) { relaxConstraints = true; // If there was insufficient space, we're now shrinking to fit the available width. stretchToFitTableWidth = true; } } // Now divide what's left between the remaining columns int colShare = 0; if (stretchColCount > 0) colShare = widthLeft / stretchColCount; int colShareRemainder = widthLeft - (colShare * stretchColCount); // Check if any columns will go below their minimum width. If so, give // up and size columns equally to avoid rendering problems. if (colShare < 0) { for (i = 0; i < m_colCount; i++) { int w = colWidths[i]; if (w == 0) w = wxMax(minColWidths[i], minColWidthsNoWrap[i]); if ((w + colShare) < minColWidths[i]) { stretchColCount = 0; break; } } } // Check we don't have enough space, in which case shrink all columns, overriding // any absolute/proportional widths // TODO: actually we would like to divide up the shrinkage according to size. // How do we calculate the proportions that will achieve this? // Could first choose an arbitrary value for stretching cells, and then calculate // factors to multiply each width by. // TODO: want to record this fact and pass to an iteration that tries e.g. min widths bool shareEqually = false; if (widthLeft < 0 || (stretchToFitTableWidth && (stretchColCount == 0))) { if (stretchColCount == 0) { // No columns to stretch or squash, so give up and divide space equally colShare = tableWidthMinusPadding / m_colCount; colShareRemainder = tableWidthMinusPadding - (colShare * m_colCount); shareEqually = true; } for (i = 0; i < m_colCount; i++) { colWidths[i] = 0; } } // We have to adjust the columns if either we need to shrink the // table to fit the parent/table width, or we explicitly set the // table width and need to stretch out the table. for (i = 0; i < m_colCount; i++) { if (colWidths[i] <= 0) // absolute or proportional width has not been specified { if (widthLeft < 0 || stretchToFitTableWidth) { int minColWidth = wxMax(minColWidths[i], minColWidthsNoWrap[i]); // Don't use a value for unspecified widths if we have insufficient space, // unless it's a nowrap cell which is likely to be small. // Actually this code is useless because if minColWidthsNoWrap exists, // it'll be the same value as maxUnspecifiedColumnWidths. if (!relaxConstraints) minColWidth = wxMax(minColWidth, maxUnspecifiedColumnWidths[i]); if (minColWidth > 0 && !shareEqually) colWidths[i] = minColWidth; // Don't allocate extra space if not wrapping since we assume a tight fit. // Unless shareEqually forces us to distribute space because we didn't have any // stretchable columns. if ((minColWidthsNoWrap[i] == 0) || shareEqually) colWidths[i] += colShare; if (i == (m_colCount-1)) colWidths[i] += colShareRemainder; // ensure all pixels are filled } else { // We're not stretching or shrinking, so calculate the column width // consistent with how we calculated the remaining table width previously. int minColWidth = wxMax(minColWidths[i], minColWidthsNoWrap[i]); minColWidth = wxMax(minColWidth, maxUnspecifiedColumnWidths[i]); colWidths[i] = minColWidth; } } } /* So, now we've laid out the table to fit into the given space and have used specified widths and minimum widths. Now we need to consider how we will try to take maximum width into account. */ // (??) TODO: take max width into account // (6) Lay out all cells again with the current values int maxRight = 0; int y = availableSpace.y; for (j = 0; j < m_rowCount; j++) { int x = availableSpace.x; // TODO: take into account centering etc. int maxCellHeight = 0; int maxSpecifiedCellHeight = 0; wxArrayInt actualWidths; actualWidths.Add(0, m_colCount); wxTextAttrDimensionConverter converter(dc, scale); for (i = 0; i < m_colCount; i++) { wxRichTextCell* cell = GetCell(j, i); if (cell->IsShown()) { // Get max specified cell height // Don't handle percentages for height if (cell->GetAttributes().GetTextBoxAttr().GetHeight().IsValid() && cell->GetAttributes().GetTextBoxAttr().GetHeight().GetUnits() != wxTEXT_ATTR_UNITS_PERCENTAGE) { int h = converter.GetPixels(cell->GetAttributes().GetTextBoxAttr().GetHeight()); if (h > maxSpecifiedCellHeight) maxSpecifiedCellHeight = h; } if (colWidths[i] > 0) // absolute or proportional width has been specified { int colSpan = cell->GetColSpan(); wxRect availableCellSpace; // Take into account spans if (colSpan > 1) { // Calculate the size of this spanning cell from its constituent columns int xx = 0; int spans = wxMin(colSpan, m_colCount - i); for (k = i; k < (i+spans); k++) { if (k != i) xx += paddingX; xx += colWidths[k]; } availableCellSpace = wxRect(x, y, xx, -1); } else availableCellSpace = wxRect(x, y, colWidths[i], -1); // Store actual width so we can force cell to be the appropriate width on the final loop actualWidths[i] = availableCellSpace.GetWidth(); // We now need to shift right by the width of any rowspanning cells above-left of us int deltaX = GetRowspanDisplacement(this, j, i, paddingX, colWidths); availableCellSpace.SetX(availableCellSpace.GetX() + deltaX); // Lay out cell cell->Invalidate(wxRICHTEXT_ALL); cell->Layout(dc, context, availableCellSpace, availableSpace, style); // TODO: use GetCachedSize().x to compute 'natural' size x += (availableCellSpace.GetWidth() + paddingX); if ((cell->GetCachedSize().y > maxCellHeight) && (cell->GetRowSpan() < 2)) maxCellHeight = cell->GetCachedSize().y; } } } maxCellHeight = wxMax(maxCellHeight, maxSpecifiedCellHeight); for (i = 0; i < m_colCount; i++) { wxRichTextCell* cell = GetCell(j, i); if (cell->IsShown()) { wxRect availableCellSpace = wxRect(cell->GetPosition(), wxSize(actualWidths[i], maxCellHeight)); // Lay out cell with new height cell->Invalidate(wxRICHTEXT_ALL); cell->Layout(dc, context, availableCellSpace, availableSpace, style); // Make sure the cell size really is the appropriate size, // not the calculated box size cell->SetCachedSize(wxSize(actualWidths[i], maxCellHeight)); maxRight = wxMax(maxRight, cell->GetPosition().x + cell->GetCachedSize().x); } } y += maxCellHeight; if (j < (m_rowCount-1)) y += paddingY; } // Finally we need to expand any cell with rowspan > 1. We couldn't earlier; lower rows' heights weren't known ExpandCellsWithRowspan(this, paddingY, y, dc, context, availableSpace, style); // We need to add back the margins etc. { wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect; contentRect = wxRect(wxPoint(0, 0), wxSize(maxRight - availableSpace.x, y - availableSpace.y)); GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect); SetCachedSize(marginRect.GetSize()); } // TODO: calculate max size { SetMaxSize(GetCachedSize()); } // TODO: calculate min size { SetMinSize(GetCachedSize()); } return true; } // Adjusts the attributes for virtual attribute provision, collapsed borders, etc. bool wxRichTextTable::AdjustAttributes(wxRichTextAttr& attr, wxRichTextDrawingContext& context) { wxRichTextObject::AdjustAttributes(attr, context); if (attr.GetTextBoxAttr().HasCollapseBorders() && attr.GetTextBoxAttr().GetCollapseBorders() == wxTEXT_BOX_ATTR_COLLAPSE_FULL) { // Padding between the table border and the table cells no longer // applies in collapsed mode. attr.GetTextBoxAttr().GetPadding().Reset(); } return true; } // Finds the absolute position and row height for the given character position bool wxRichTextTable::FindPosition(wxDC& dc, wxRichTextDrawingContext& context, long index, wxPoint& pt, int* height, bool forceLineStart) { wxRichTextCell* child = GetCell(index+1); if (child) { // Find the position at the start of the child cell, since the table doesn't // have any caret position of its own. return child->FindPosition(dc, context, -1, pt, height, forceLineStart); } else return false; } // Get the cell at the given character position (in the range of the table). wxRichTextCell* wxRichTextTable::GetCell(long pos) const { int row = 0, col = 0; if (GetCellRowColumnPosition(pos, row, col)) { return GetCell(row, col); } else return NULL; } // Get the row/column for a given character position bool wxRichTextTable::GetCellRowColumnPosition(long pos, int& row, int& col) const { if (m_colCount == 0 || m_rowCount == 0) return false; row = (int) (pos / m_colCount); col = pos - (row * m_colCount); wxASSERT(row < m_rowCount && col < m_colCount); if (row < m_rowCount && col < m_colCount) return true; else return false; } // Calculate range, taking row/cell ordering into account instead of relying // on list ordering. void wxRichTextTable::CalculateRange(long start, long& end) { long current = start; long lastEnd = current; if (IsTopLevel()) { current = 0; lastEnd = 0; } int i, j; for (i = 0; i < m_rowCount; i++) { for (j = 0; j < m_colCount; j++) { wxRichTextCell* child = GetCell(i, j); if (child) { long childEnd = 0; child->CalculateRange(current, childEnd); lastEnd = childEnd; current = childEnd + 1; } } } // A top-level object always has a range of size 1, // because its children don't count at this level. end = start; m_range.SetRange(start, start); // An object with no children has zero length if (m_children.GetCount() == 0) lastEnd --; m_ownRange.SetRange(0, lastEnd); } // Gets the range size. bool wxRichTextTable::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, const wxPoint& position, const wxSize& parentSize, wxArrayInt* partialExtents) const { return wxRichTextBox::GetRangeSize(range, size, descent, dc, context, flags, position, parentSize, partialExtents); } // Deletes content in the given range. bool wxRichTextTable::DeleteRange(const wxRichTextRange& WXUNUSED(range)) { // TODO: implement deletion of cells return true; } // Gets any text in this object for the given range. wxString wxRichTextTable::GetTextForRange(const wxRichTextRange& range) const { return wxRichTextBox::GetTextForRange(range); } // Copies this object. void wxRichTextTable::Copy(const wxRichTextTable& obj) { wxRichTextBox::Copy(obj); ClearTable(); m_rowCount = obj.m_rowCount; m_colCount = obj.m_colCount; m_cells.Add(wxRichTextObjectPtrArray(), m_rowCount); int i, j; for (i = 0; i < m_rowCount; i++) { wxRichTextObjectPtrArray& colArray = m_cells[i]; for (j = 0; j < m_colCount; j++) { wxRichTextCell* cell = wxDynamicCast(obj.GetCell(i, j)->Clone(), wxRichTextCell); AppendChild(cell); colArray.Add(cell); } } } void wxRichTextTable::ClearTable() { m_cells.Clear(); DeleteChildren(); m_rowCount = 0; m_colCount = 0; } bool wxRichTextTable::CreateTable(int rows, int cols) { ClearTable(); wxRichTextAttr cellattr; cellattr.SetTextColour(GetBasicStyle().GetTextColour()); m_rowCount = rows; m_colCount = cols; m_cells.Add(wxRichTextObjectPtrArray(), rows); int i, j; for (i = 0; i < rows; i++) { wxRichTextObjectPtrArray& colArray = m_cells[i]; for (j = 0; j < cols; j++) { wxRichTextCell* cell = new wxRichTextCell; cell->GetAttributes() = cellattr; AppendChild(cell); cell->AddParagraph(wxEmptyString); colArray.Add(cell); } } return true; } wxRichTextCell* wxRichTextTable::GetCell(int row, int col) const { wxASSERT(row < m_rowCount); wxASSERT(col < m_colCount); if (row < m_rowCount && col < m_colCount) { wxRichTextObjectPtrArray& colArray = m_cells[row]; wxRichTextObject* obj = colArray[col]; return wxDynamicCast(obj, wxRichTextCell); } else return NULL; } // Returns a selection object specifying the selections between start and end character positions. // For example, a table would deduce what cells (of range length 1) are selected when dragging across the table. wxRichTextSelection wxRichTextTable::GetSelection(long start, long end) const { wxRichTextSelection selection; selection.SetContainer((wxRichTextTable*) this); if (start > end) { long tmp = end; end = start; start = tmp; } wxASSERT( start >= 0 && end < (m_colCount * m_rowCount)); if (end >= (m_colCount * m_rowCount)) return selection; // We need to find the rectangle of cells that is described by the rectangle // with start, end as the diagonal. Make sure we don't add cells that are // not currenty visible because they are overlapped by spanning cells. /* -------------------------- | 0 | 1 | 2 | 3 | 4 | -------------------------- | 5 | 6 | 7 | 8 | 9 | -------------------------- | 10 | 11 | 12 | 13 | 14 | -------------------------- | 15 | 16 | 17 | 18 | 19 | -------------------------- Let's say we select 6 -> 18. Left and right edge cols of rectangle are 1 and 3 inclusive. Find least/greatest to find which is left and which is right. Top and bottom edge rows are 1 and 3 inclusive. Again, find least/greatest to find top and bottom. Now go through rows from 1 to 3 and only add cells that are (a) within above column range and (b) shown. */ int leftCol = start - m_colCount * int(start/m_colCount); int rightCol = end - m_colCount * int(end/m_colCount); int topRow = int(start/m_colCount); int bottomRow = int(end/m_colCount); if (leftCol > rightCol) { int tmp = rightCol; rightCol = leftCol; leftCol = tmp; } if (topRow > bottomRow) { int tmp = bottomRow; bottomRow = topRow; topRow = tmp; } int i, j; for (i = topRow; i <= bottomRow; i++) { for (j = leftCol; j <= rightCol; j++) { wxRichTextCell* cell = GetCell(i, j); if (cell && cell->IsShown()) selection.Add(cell->GetRange()); } } return selection; } // Sets the attributes for the cells specified by the selection. bool wxRichTextTable::SetCellStyle(const wxRichTextSelection& selection, const wxRichTextAttr& style, int flags) { if (selection.GetContainer() != this) return false; wxRichTextBuffer* buffer = GetBuffer(); bool haveControl = (buffer && buffer->GetRichTextCtrl() != NULL); bool withUndo = haveControl && ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0); if (withUndo) buffer->BeginBatchUndo(_("Set Cell Style")); wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst(); while (node) { wxRichTextCell* cell = wxDynamicCast(node->GetData(), wxRichTextCell); if (cell && selection.WithinSelection(cell->GetRange().GetStart())) SetStyle(cell, style, flags); node = node->GetNext(); } // Do action, or delay it until end of batch. if (withUndo) buffer->EndBatchUndo(); return true; } wxPosition wxRichTextTable::GetFocusedCell() const { wxPosition position(-1, -1); const wxRichTextObject* focus = GetBuffer()->GetRichTextCtrl()->GetFocusObject(); for (int row = 0; row < GetRowCount(); ++row) { for (int col = 0; col < GetColumnCount(); ++col) { if (GetCell(row, col) == focus) { position.SetRow(row); position.SetCol(col); return position; } } } return position; } int wxRichTextTable::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int flags) { for (int row = 0; row < GetRowCount(); ++row) { for (int col = 0; col < GetColumnCount(); ++col) { wxRichTextCell* cell = GetCell(row, col); if (cell->wxRichTextObject::HitTest(dc, context, pt, textPosition, obj, contextObj, flags) != wxRICHTEXT_HITTEST_NONE) { return cell->HitTest(dc, context, pt, textPosition, obj, contextObj, flags); } } } return wxRICHTEXT_HITTEST_NONE; } bool wxRichTextTable::DeleteRows(int startRow, int noRows) { wxASSERT((startRow + noRows) <= m_rowCount); if ((startRow + noRows) > m_rowCount) return false; wxCHECK_MSG(noRows != m_rowCount, false, "Trying to delete all the cells in a table"); wxRichTextBuffer* buffer = GetBuffer(); wxRichTextCtrl* rtc = buffer->GetRichTextCtrl(); wxRichTextAction* action = NULL; wxRichTextTable* clone = NULL; if (!rtc->SuppressingUndo()) { // Create a clone containing the current state of the table. It will be used to Undo the action clone = wxStaticCast(this->Clone(), wxRichTextTable); clone->SetParent(GetParent()); action = new wxRichTextAction(NULL, _("Delete Row"), wxRICHTEXT_CHANGE_OBJECT, buffer, this, rtc); action->SetObject(this); action->SetPosition(GetRange().GetStart()); } int i, j; for (i = startRow; i < (startRow+noRows); i++) { wxRichTextObjectPtrArray& colArray = m_cells[startRow]; for (j = 0; j < (int) colArray.GetCount(); j++) { wxRichTextObject* cell = colArray[j]; RemoveChild(cell, true); } // Keep deleting at the same position, since we move all // the others up m_cells.RemoveAt(startRow); } m_rowCount = m_rowCount - noRows; if (!rtc->SuppressingUndo()) { buffer->SubmitAction(action); // Finally store the original-state clone; doing so earlier would cause various failures action->StoreObject(clone); } return true; } bool wxRichTextTable::DeleteColumns(int startCol, int noCols) { wxASSERT((startCol + noCols) <= m_colCount); if ((startCol + noCols) > m_colCount) return false; wxCHECK_MSG(noCols != m_colCount, false, "Trying to delete all the cells in a table"); wxRichTextBuffer* buffer = GetBuffer(); wxRichTextCtrl* rtc = buffer->GetRichTextCtrl(); wxRichTextAction* action = NULL; wxRichTextTable* clone = NULL; if (!rtc->SuppressingUndo()) { // Create a clone containing the current state of the table. It will be used to Undo the action clone = wxStaticCast(this->Clone(), wxRichTextTable); clone->SetParent(GetParent()); action = new wxRichTextAction(NULL, _("Delete Column"), wxRICHTEXT_CHANGE_OBJECT, buffer, this, rtc); action->SetObject(this); action->SetPosition(GetRange().GetStart()); } bool deleteRows = (noCols == m_colCount); int i, j; for (i = 0; i < m_rowCount; i++) { wxRichTextObjectPtrArray& colArray = m_cells[deleteRows ? 0 : i]; for (j = 0; j < noCols; j++) { wxRichTextObject* cell = colArray[startCol]; RemoveChild(cell, true); colArray.RemoveAt(startCol); } if (deleteRows) m_cells.RemoveAt(0); } if (deleteRows) m_rowCount = 0; m_colCount = m_colCount - noCols; if (!rtc->SuppressingUndo()) { buffer->SubmitAction(action); // Finally store the original-state clone; doing so earlier would cause various failures action->StoreObject(clone); } return true; } bool wxRichTextTable::AddRows(int startRow, int noRows, const wxRichTextAttr& attr) { wxASSERT(startRow <= m_rowCount); if (startRow > m_rowCount) return false; wxRichTextBuffer* buffer = GetBuffer(); wxRichTextAction* action = NULL; wxRichTextTable* clone = NULL; if (!buffer->GetRichTextCtrl()->SuppressingUndo()) { // Create a clone containing the current state of the table. It will be used to Undo the action clone = wxStaticCast(this->Clone(), wxRichTextTable); clone->SetParent(GetParent()); action = new wxRichTextAction(NULL, _("Add Row"), wxRICHTEXT_CHANGE_OBJECT, buffer, this, buffer->GetRichTextCtrl()); action->SetObject(this); action->SetPosition(GetRange().GetStart()); } wxRichTextAttr cellattr = attr; if (!cellattr.GetTextColour().IsOk()) cellattr.SetTextColour(buffer->GetBasicStyle().GetTextColour()); int i, j; for (i = 0; i < noRows; i++) { int idx; if (startRow == m_rowCount) { m_cells.Add(wxRichTextObjectPtrArray()); idx = m_cells.GetCount() - 1; } else { m_cells.Insert(wxRichTextObjectPtrArray(), startRow+i); idx = startRow+i; } wxRichTextObjectPtrArray& colArray = m_cells[idx]; for (j = 0; j < m_colCount; j++) { wxRichTextCell* cell = new wxRichTextCell; cell->GetAttributes() = cellattr; AppendChild(cell); cell->AddParagraph(wxEmptyString); colArray.Add(cell); } } m_rowCount = m_rowCount + noRows; if (!buffer->GetRichTextCtrl()->SuppressingUndo()) { buffer->SubmitAction(action); // Finally store the original-state clone; doing so earlier would cause various failures action->StoreObject(clone); } return true; } bool wxRichTextTable::AddColumns(int startCol, int noCols, const wxRichTextAttr& attr) { wxASSERT(startCol <= m_colCount); if (startCol > m_colCount) return false; wxRichTextBuffer* buffer = GetBuffer(); wxRichTextAction* action = NULL; wxRichTextTable* clone = NULL; if (!buffer->GetRichTextCtrl()->SuppressingUndo()) { // Create a clone containing the current state of the table. It will be used to Undo the action clone = wxStaticCast(this->Clone(), wxRichTextTable); clone->SetParent(GetParent()); action = new wxRichTextAction(NULL, _("Add Column"), wxRICHTEXT_CHANGE_OBJECT, buffer, this, buffer->GetRichTextCtrl()); action->SetObject(this); action->SetPosition(GetRange().GetStart()); } wxRichTextAttr cellattr = attr; if (!cellattr.GetTextColour().IsOk()) cellattr.SetTextColour(buffer->GetBasicStyle().GetTextColour()); int i, j; for (i = 0; i < m_rowCount; i++) { wxRichTextObjectPtrArray& colArray = m_cells[i]; for (j = 0; j < noCols; j++) { wxRichTextCell* cell = new wxRichTextCell; cell->GetAttributes() = cellattr; AppendChild(cell); cell->AddParagraph(wxEmptyString); if (startCol == m_colCount) colArray.Add(cell); else colArray.Insert(cell, startCol+j); } } m_colCount = m_colCount + noCols; if (!buffer->GetRichTextCtrl()->SuppressingUndo()) { buffer->SubmitAction(action); // Finally store the original-state clone; doing so earlier would cause various failures action->StoreObject(clone); } return true; } // Edit properties via a GUI bool wxRichTextTable::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer) { wxRichTextObjectPropertiesDialog boxDlg(this, wxGetTopLevelParent(parent), wxID_ANY, _("Table Properties")); boxDlg.SetAttributes(GetAttributes()); if (boxDlg.ShowModal() == wxID_OK) { boxDlg.ApplyStyle(buffer->GetRichTextCtrl(), wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_RESET); return true; } else return false; } bool wxRichTextTableBlock::ComputeBlockForSelection(wxRichTextTable* table, wxRichTextCtrl* ctrl, bool requireCellSelection) { if (!ctrl) return false; ColStart() = 0; ColEnd() = table->GetColumnCount()-1; RowStart() = 0; RowEnd() = table->GetRowCount()-1; wxRichTextSelection selection = ctrl->GetSelection(); if (selection.IsValid() && selection.GetContainer() == table) { // Start with an invalid block and increase. wxRichTextTableBlock selBlock(-1, -1, -1, -1); wxRichTextRangeArray ranges = selection.GetRanges(); int row, col; for (row = 0; row < table->GetRowCount(); row++) { for (col = 0; col < table->GetColumnCount(); col++) { if (selection.WithinSelection(table->GetCell(row, col)->GetRange().GetStart())) { if (selBlock.ColStart() == -1) selBlock.ColStart() = col; if (selBlock.ColEnd() == -1) selBlock.ColEnd() = col; if (col < selBlock.ColStart()) selBlock.ColStart() = col; if (col > selBlock.ColEnd()) selBlock.ColEnd() = col; if (selBlock.RowStart() == -1) selBlock.RowStart() = row; if (selBlock.RowEnd() == -1) selBlock.RowEnd() = row; if (row < selBlock.RowStart()) selBlock.RowStart() = row; if (row > selBlock.RowEnd()) selBlock.RowEnd() = row; } } } if (selBlock.RowStart() != -1 && selBlock.RowEnd() != -1 && selBlock.ColStart() != -1 && selBlock.ColEnd() != -1) (*this) = selBlock; } else { // See if a whole cell's contents is selected, in which case we can treat the cell as selected. // wxRTC lacks the ability to select a single cell. wxRichTextCell* cell = wxDynamicCast(ctrl->GetFocusObject(), wxRichTextCell); if (cell && (!requireCellSelection || (ctrl->HasSelection() && ctrl->GetSelectionRange() == cell->GetOwnRange()))) { int row, col; if (table->GetCellRowColumnPosition(cell->GetRange().GetStart(), row, col)) { RowStart() = row; RowEnd() = row; ColStart() = col; ColEnd() = col; } } } return true; } // Does this block represent the whole table? bool wxRichTextTableBlock::IsWholeTable(wxRichTextTable* table) const { return (ColStart() == 0 && RowStart() == 0 && ColEnd() == (table->GetColumnCount()-1) && RowEnd() == (table->GetRowCount()-1)); } // Returns the cell focused in the table, if any wxRichTextCell* wxRichTextTableBlock::GetFocusedCell(wxRichTextCtrl* ctrl) { if (!ctrl) return NULL; wxRichTextCell* cell = wxDynamicCast(ctrl->GetFocusObject(), wxRichTextCell); return cell; } /* * Module to initialise and clean up handlers */ class wxRichTextModule: public wxModule { DECLARE_DYNAMIC_CLASS(wxRichTextModule) public: wxRichTextModule() {} bool OnInit() { wxRichTextBuffer::SetRenderer(new wxRichTextStdRenderer); wxRichTextBuffer::InitStandardHandlers(); wxRichTextParagraph::InitDefaultTabs(); wxRichTextXMLHandler::RegisterNodeName(wxT("text"), wxT("wxRichTextPlainText")); wxRichTextXMLHandler::RegisterNodeName(wxT("symbol"), wxT("wxRichTextPlainText")); wxRichTextXMLHandler::RegisterNodeName(wxT("image"), wxT("wxRichTextImage")); wxRichTextXMLHandler::RegisterNodeName(wxT("paragraph"), wxT("wxRichTextParagraph")); wxRichTextXMLHandler::RegisterNodeName(wxT("paragraphlayout"), wxT("wxRichTextParagraphLayoutBox")); wxRichTextXMLHandler::RegisterNodeName(wxT("textbox"), wxT("wxRichTextBox")); wxRichTextXMLHandler::RegisterNodeName(wxT("cell"), wxT("wxRichTextCell")); wxRichTextXMLHandler::RegisterNodeName(wxT("table"), wxT("wxRichTextTable")); wxRichTextXMLHandler::RegisterNodeName(wxT("field"), wxT("wxRichTextField")); return true; } void OnExit() { wxRichTextBuffer::CleanUpHandlers(); wxRichTextBuffer::CleanUpDrawingHandlers(); wxRichTextBuffer::CleanUpFieldTypes(); wxRichTextXMLHandler::ClearNodeToClassMap(); wxRichTextDecimalToRoman(-1); wxRichTextParagraph::ClearDefaultTabs(); wxRichTextCtrl::ClearAvailableFontNames(); wxRichTextBuffer::SetRenderer(NULL); } }; IMPLEMENT_DYNAMIC_CLASS(wxRichTextModule, wxModule) // If the richtext lib is dynamically loaded after the app has already started // (such as from wxPython) then the built-in module system will not init this // module. Provide this function to do it manually. void wxRichTextModuleInit() { wxModule* module = new wxRichTextModule; wxModule::RegisterModule(module); wxModule::InitializeModules(); } /*! * Commands for undo/redo * */ wxRichTextCommand::wxRichTextCommand(const wxString& name, wxRichTextCommandId id, wxRichTextBuffer* buffer, wxRichTextParagraphLayoutBox* container, wxRichTextCtrl* ctrl, bool ignoreFirstTime): wxCommand(true, name) { /* wxRichTextAction* action = */ new wxRichTextAction(this, name, id, buffer, container, ctrl, ignoreFirstTime); } wxRichTextCommand::wxRichTextCommand(const wxString& name): wxCommand(true, name) { } wxRichTextCommand::~wxRichTextCommand() { ClearActions(); } void wxRichTextCommand::AddAction(wxRichTextAction* action) { if (!m_actions.Member(action)) m_actions.Append(action); } bool wxRichTextCommand::Do() { for (wxList::compatibility_iterator node = m_actions.GetFirst(); node; node = node->GetNext()) { wxRichTextAction* action = (wxRichTextAction*) node->GetData(); action->Do(); } return true; } bool wxRichTextCommand::Undo() { for (wxList::compatibility_iterator node = m_actions.GetLast(); node; node = node->GetPrevious()) { wxRichTextAction* action = (wxRichTextAction*) node->GetData(); action->Undo(); } return true; } void wxRichTextCommand::ClearActions() { WX_CLEAR_LIST(wxList, m_actions); } /*! * Individual action * */ wxRichTextAction::wxRichTextAction(wxRichTextCommand* cmd, const wxString& name, wxRichTextCommandId id, wxRichTextBuffer* buffer, wxRichTextParagraphLayoutBox* container, wxRichTextCtrl* ctrl, bool ignoreFirstTime) { m_buffer = buffer; m_object = NULL; m_containerAddress.Create(buffer, container); m_ignoreThis = ignoreFirstTime; m_cmdId = id; m_position = -1; m_ctrl = ctrl; m_name = name; m_newParagraphs.SetDefaultStyle(buffer->GetDefaultStyle()); m_newParagraphs.SetBasicStyle(buffer->GetBasicStyle()); if (cmd) cmd->AddAction(this); } wxRichTextAction::~wxRichTextAction() { if (m_object) delete m_object; } // Returns the container that this action refers to, using the container address and top-level buffer. wxRichTextParagraphLayoutBox* wxRichTextAction::GetContainer() const { wxRichTextParagraphLayoutBox* container = wxDynamicCast(GetContainerAddress().GetObject(m_buffer), wxRichTextParagraphLayoutBox); return container; } void wxRichTextAction::CalculateRefreshOptimizations(wxArrayInt& optimizationLineCharPositions, wxArrayInt& optimizationLineYPositions) { // Store a list of line start character and y positions so we can figure out which area // we need to refresh #if wxRICHTEXT_USE_OPTIMIZED_DRAWING wxRichTextParagraphLayoutBox* container = GetContainer(); wxASSERT(container != NULL); if (!container) return; // 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->IsDirty() && m_ctrl) // can only do optimisation if the buffer is already laid out correctly { wxSize clientSize = m_ctrl->GetUnscaledSize(m_ctrl->GetClientSize()); wxPoint firstVisiblePt = m_ctrl->GetUnscaledPoint(m_ctrl->GetFirstVisiblePoint()); int lastY = firstVisiblePt.y + clientSize.y; wxRichTextParagraph* para = container->GetParagraphAtPosition(GetRange().GetStart()); wxRichTextObjectList::compatibility_iterator node = container->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 = wxRichTextLineList::compatibility_iterator(); node = wxRichTextObjectList::compatibility_iterator(); } 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 } bool wxRichTextAction::Do() { m_buffer->Modify(true); wxRichTextParagraphLayoutBox* container = GetContainer(); wxASSERT(container != NULL); if (!container) return false; switch (m_cmdId) { 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 CalculateRefreshOptimizations(optimizationLineCharPositions, optimizationLineYPositions); #endif container->InsertFragment(GetRange().GetStart(), m_newParagraphs); container->UpdateRanges(); // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object, // Layout() would stop prematurely at the top level. container->InvalidateHierarchy(wxRichTextRange(wxMax(0, GetRange().GetStart()-1), GetRange().GetEnd())); long newCaretPosition = GetPosition() + m_newParagraphs.GetOwnRange().GetLength(); // Character position to caret position newCaretPosition --; // Don't take into account the last newline if (m_newParagraphs.GetPartialParagraph()) newCaretPosition --; else if (m_newParagraphs.GetChildren().GetCount() > 1) { wxRichTextObject* p = (wxRichTextObject*) m_newParagraphs.GetChildren().GetLast()->GetData(); if (p->GetRange().GetLength() == 1) newCaretPosition --; } newCaretPosition = wxMin(newCaretPosition, (container->GetOwnRange().GetEnd()-1)); UpdateAppearance(newCaretPosition, true /* send update event */, & optimizationLineCharPositions, & optimizationLineYPositions, true /* do */); wxRichTextEvent cmdEvent( wxEVT_RICHTEXT_CONTENT_INSERTED, m_ctrl ? m_ctrl->GetId() : -1); cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer); cmdEvent.SetRange(GetRange()); cmdEvent.SetPosition(GetRange().GetStart()); cmdEvent.SetContainer(container); m_buffer->SendEvent(cmdEvent); break; } case wxRICHTEXT_DELETE: { wxArrayInt optimizationLineCharPositions; wxArrayInt optimizationLineYPositions; #if wxRICHTEXT_USE_OPTIMIZED_DRAWING CalculateRefreshOptimizations(optimizationLineCharPositions, optimizationLineYPositions); #endif // Check if the current object focus needs to be changed before deletion of content if (m_ctrl) { wxRichTextObject* c = m_ctrl->GetFocusObject(); while (c) { if (c == container) { m_ctrl->StoreFocusObject(container); break; } c = c->GetParent(); } } container->DeleteRange(GetRange()); container->UpdateRanges(); // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object, // Layout() would stop prematurely at the top level. container->InvalidateHierarchy(wxRichTextRange(GetRange().GetStart(), GetRange().GetStart())); long caretPos = GetRange().GetStart()-1; if (caretPos >= container->GetOwnRange().GetEnd()) caretPos --; UpdateAppearance(caretPos, true /* send update event */, & optimizationLineCharPositions, & optimizationLineYPositions, true /* do */); wxRichTextEvent cmdEvent( wxEVT_RICHTEXT_CONTENT_DELETED, m_ctrl ? m_ctrl->GetId() : -1); cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer); cmdEvent.SetRange(GetRange()); cmdEvent.SetPosition(GetRange().GetStart()); cmdEvent.SetContainer(container); m_buffer->SendEvent(cmdEvent); break; } case wxRICHTEXT_CHANGE_STYLE: case wxRICHTEXT_CHANGE_PROPERTIES: { ApplyParagraphs(GetNewParagraphs()); // Invalidate the whole buffer if there were floating objects if (wxRichTextBuffer::GetFloatingLayoutMode() && container->GetFloatingObjectCount() > 0) m_buffer->InvalidateHierarchy(wxRICHTEXT_ALL); else { // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object, // Layout() would stop prematurely at the top level. container->InvalidateHierarchy(GetRange()); } UpdateAppearance(GetPosition()); wxRichTextEvent cmdEvent( m_cmdId == wxRICHTEXT_CHANGE_STYLE ? wxEVT_RICHTEXT_STYLE_CHANGED : wxEVT_RICHTEXT_PROPERTIES_CHANGED, m_ctrl ? m_ctrl->GetId() : -1); cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer); cmdEvent.SetRange(GetRange()); cmdEvent.SetPosition(GetRange().GetStart()); cmdEvent.SetContainer(container); m_buffer->SendEvent(cmdEvent); break; } case wxRICHTEXT_CHANGE_ATTRIBUTES: { wxRichTextObject* obj = m_objectAddress.GetObject(m_buffer); // container->GetChildAtPosition(GetRange().GetStart()); if (obj) { wxRichTextAttr oldAttr = obj->GetAttributes(); obj->GetAttributes() = m_attributes; m_attributes = oldAttr; } // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object, // Layout() would stop prematurely at the top level. // Invalidate the whole buffer if there were floating objects if (wxRichTextBuffer::GetFloatingLayoutMode() && container->GetFloatingObjectCount() > 0) m_buffer->InvalidateHierarchy(wxRICHTEXT_ALL); else container->InvalidateHierarchy(GetRange()); UpdateAppearance(GetPosition(), true); wxRichTextEvent cmdEvent( wxEVT_RICHTEXT_STYLE_CHANGED, m_ctrl ? m_ctrl->GetId() : -1); cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer); cmdEvent.SetRange(GetRange()); cmdEvent.SetPosition(GetRange().GetStart()); cmdEvent.SetContainer(container); m_buffer->SendEvent(cmdEvent); break; } case wxRICHTEXT_CHANGE_OBJECT: { wxRichTextObject* obj = m_objectAddress.GetObject(m_buffer); if (obj && m_object && m_ctrl) { // The plan is to swap the current object with the stored, previous-state, clone // We can't get 'node' from the containing buffer (as it doesn't directly store objects) // so use the parent paragraph wxRichTextParagraph* para = wxDynamicCast(obj->GetParent(), wxRichTextParagraph); wxCHECK_MSG(para, false, "Invalid parent paragraph"); // The stored object, m_object, may have a stale parent paragraph. This would cause // a crash during layout, so use obj's parent para, which should be the correct one. // (An alternative would be to return the parent too from m_objectAddress.GetObject(), // or to set obj's parent there before returning) m_object->SetParent(para); wxRichTextObjectList::compatibility_iterator node = para->GetChildren().Find(obj); if (node) { wxRichTextObject* obj = node->GetData(); node->SetData(m_object); m_object = obj; } } // We can't rely on the current focus-object remaining valid, if it's e.g. a table's cell. // And we can't cope with this in the calling code: a user may later click in the cell // before deciding to Undo() or Redo(). So play safe and set focus to the buffer. if (m_ctrl) m_ctrl->SetFocusObject(m_buffer, false); // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object, // Layout() would stop prematurely at the top level. // Invalidate the whole buffer if there were floating objects if (wxRichTextBuffer::GetFloatingLayoutMode() && container->GetFloatingObjectCount() > 0) m_buffer->InvalidateHierarchy(wxRICHTEXT_ALL); else container->InvalidateHierarchy(GetRange()); UpdateAppearance(GetPosition(), true); // TODO: send new kind of modification event break; } default: break; } return true; } bool wxRichTextAction::Undo() { m_buffer->Modify(true); wxRichTextParagraphLayoutBox* container = GetContainer(); wxASSERT(container != NULL); if (!container) return false; switch (m_cmdId) { case wxRICHTEXT_INSERT: { wxArrayInt optimizationLineCharPositions; wxArrayInt optimizationLineYPositions; #if wxRICHTEXT_USE_OPTIMIZED_DRAWING CalculateRefreshOptimizations(optimizationLineCharPositions, optimizationLineYPositions); #endif // Check if the current object focus needs to be changed before deletion of content if (m_ctrl) { wxRichTextObject* c = m_ctrl->GetFocusObject(); while (c) { if (c == container) { m_ctrl->StoreFocusObject(container); break; } c = c->GetParent(); } } container->DeleteRange(GetRange()); container->UpdateRanges(); // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object, // Layout() would stop prematurely at the top level. container->InvalidateHierarchy(wxRichTextRange(GetRange().GetStart(), GetRange().GetStart())); long newCaretPosition = GetPosition() - 1; UpdateAppearance(newCaretPosition, true, /* send update event */ & optimizationLineCharPositions, & optimizationLineYPositions, false /* undo */); wxRichTextEvent cmdEvent( wxEVT_RICHTEXT_CONTENT_DELETED, m_ctrl ? m_ctrl->GetId() : -1); cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer); cmdEvent.SetRange(GetRange()); cmdEvent.SetPosition(GetRange().GetStart()); cmdEvent.SetContainer(container); m_buffer->SendEvent(cmdEvent); break; } case wxRICHTEXT_DELETE: { wxArrayInt optimizationLineCharPositions; wxArrayInt optimizationLineYPositions; #if wxRICHTEXT_USE_OPTIMIZED_DRAWING CalculateRefreshOptimizations(optimizationLineCharPositions, optimizationLineYPositions); #endif container->InsertFragment(GetRange().GetStart(), m_oldParagraphs); container->UpdateRanges(); // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object, // Layout() would stop prematurely at the top level. container->InvalidateHierarchy(GetRange()); UpdateAppearance(GetPosition(), true, /* send update event */ & optimizationLineCharPositions, & optimizationLineYPositions, false /* undo */); wxRichTextEvent cmdEvent( wxEVT_RICHTEXT_CONTENT_INSERTED, m_ctrl ? m_ctrl->GetId() : -1); cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer); cmdEvent.SetRange(GetRange()); cmdEvent.SetPosition(GetRange().GetStart()); cmdEvent.SetContainer(container); m_buffer->SendEvent(cmdEvent); break; } case wxRICHTEXT_CHANGE_STYLE: case wxRICHTEXT_CHANGE_PROPERTIES: { ApplyParagraphs(GetOldParagraphs()); // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object, // Layout() would stop prematurely at the top level. container->InvalidateHierarchy(GetRange()); UpdateAppearance(GetPosition()); wxRichTextEvent cmdEvent( m_cmdId == wxRICHTEXT_CHANGE_STYLE ? wxEVT_RICHTEXT_STYLE_CHANGED : wxEVT_RICHTEXT_PROPERTIES_CHANGED, m_ctrl ? m_ctrl->GetId() : -1); cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer); cmdEvent.SetRange(GetRange()); cmdEvent.SetPosition(GetRange().GetStart()); cmdEvent.SetContainer(container); m_buffer->SendEvent(cmdEvent); break; } case wxRICHTEXT_CHANGE_ATTRIBUTES: case wxRICHTEXT_CHANGE_OBJECT: { return Do(); } default: break; } return true; } /// Update the control appearance void wxRichTextAction::UpdateAppearance(long caretPosition, bool sendUpdateEvent, wxArrayInt* optimizationLineCharPositions, wxArrayInt* optimizationLineYPositions, bool isDoCmd) { wxRichTextParagraphLayoutBox* container = GetContainer(); wxASSERT(container != NULL); if (!container) return; if (m_ctrl) { m_ctrl->SetFocusObject(container); m_ctrl->SetCaretPosition(caretPosition); if (!m_ctrl->IsFrozen()) { wxRect containerRect = container->GetRect(); m_ctrl->LayoutContent(); // Refresh everything if there were floating objects or the container changed size // (we can't yet optimize in these cases, since more complex interaction with other content occurs) if ((wxRichTextBuffer::GetFloatingLayoutMode() && container->GetFloatingObjectCount() > 0) || (container->GetParent() && containerRect != container->GetRect())) { m_ctrl->Refresh(false); } else #if wxRICHTEXT_USE_OPTIMIZED_DRAWING // Find refresh rectangle if we are in a position to optimise refresh if ((m_cmdId == wxRICHTEXT_INSERT || m_cmdId == wxRICHTEXT_DELETE) && optimizationLineCharPositions) { size_t i; wxSize clientSize = m_ctrl->GetUnscaledSize(m_ctrl->GetClientSize()); wxPoint firstVisiblePt = m_ctrl->GetUnscaledPoint(m_ctrl->GetFirstVisiblePoint()); // Start/end positions int firstY = 0; int lastY = firstVisiblePt.y + clientSize.y; bool foundEnd = false; // position offset - how many characters were inserted int positionOffset = GetRange().GetLength(); // Determine whether this is Do or Undo, and adjust positionOffset accordingly if ((m_cmdId == wxRICHTEXT_DELETE && isDoCmd) || (m_cmdId == wxRICHTEXT_INSERT && !isDoCmd)) positionOffset = - positionOffset; // 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. long pos = GetRange().GetStart(); wxRichTextParagraph* para = container->GetParagraphAtPosition(pos, false /* is not caret pos */); // Since we support floating layout, we should redraw the whole para instead of just // the first line touching the invalid range. if (para) { // In case something was drawn above the paragraph, // such as a line break, allow a little extra. firstY = para->GetPosition().y - 4; } wxRichTextObjectList::compatibility_iterator node = container->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 = wxRichTextLineList::compatibility_iterator(); node = wxRichTextObjectList::compatibility_iterator(); } // Detect last line in the buffer else if (!node2->GetNext() && para->GetRange().Contains(container->GetOwnRange().GetEnd())) { // If deleting text, make sure we refresh below as well as above if (positionOffset >= 0) { foundEnd = true; lastY = pt.y + line->GetSize().y; } node2 = wxRichTextLineList::compatibility_iterator(); node = wxRichTextObjectList::compatibility_iterator(); break; } else { // 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 + line->GetSize().y; node2 = wxRichTextLineList::compatibility_iterator(); node = wxRichTextObjectList::compatibility_iterator(); break; } } } if (node2) node2 = node2->GetNext(); } if (node) node = node->GetNext(); } firstY = wxMax(firstVisiblePt.y, firstY); if (!foundEnd) lastY = firstVisiblePt.y + clientSize.y; // Convert to device coordinates wxRect rect(m_ctrl->GetPhysicalPoint(m_ctrl->GetScaledPoint(wxPoint(firstVisiblePt.x, firstY))), m_ctrl->GetScaledSize(wxSize(clientSize.x, lastY - firstY))); m_ctrl->RefreshRect(rect); } else #endif m_ctrl->Refresh(false); m_ctrl->PositionCaret(); // This causes styles to persist when doing programmatic // content creation except when Freeze/Thaw is used, so // disable this and check for the consequences. // m_ctrl->SetDefaultStyleToCursorStyle(); if (sendUpdateEvent) wxTextCtrl::SendTextUpdatedEvent(m_ctrl); } } } /// Replace the buffer paragraphs with the new ones. void wxRichTextAction::ApplyParagraphs(const wxRichTextParagraphLayoutBox& fragment) { wxRichTextParagraphLayoutBox* container = GetContainer(); wxASSERT(container != NULL); if (!container) return; wxRichTextObjectList::compatibility_iterator node = fragment.GetChildren().GetFirst(); while (node) { wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph); wxASSERT (para != NULL); // We'll replace the existing paragraph by finding the paragraph at this position, // delete its node data, and setting a copy as the new node data. // TODO: make more efficient by simply swapping old and new paragraph objects. wxRichTextParagraph* existingPara = container->GetParagraphAtPosition(para->GetRange().GetStart()); if (existingPara) { wxRichTextObjectList::compatibility_iterator bufferParaNode = container->GetChildren().Find(existingPara); if (bufferParaNode) { wxRichTextParagraph* newPara = new wxRichTextParagraph(*para); newPara->SetParent(container); bufferParaNode->SetData(newPara); delete existingPara; } } node = node->GetNext(); } } /*! * wxRichTextRange * This stores beginning and end positions for a range of data. */ WX_DEFINE_OBJARRAY(wxRichTextRangeArray); /// Limit this range to be within 'range' bool wxRichTextRange::LimitTo(const wxRichTextRange& range) { if (m_start < range.m_start) m_start = range.m_start; if (m_end > range.m_end) m_end = range.m_end; return true; } /*! * wxRichTextImage implementation * This object represents an image. */ IMPLEMENT_DYNAMIC_CLASS(wxRichTextImage, wxRichTextObject) wxRichTextImage::wxRichTextImage(const wxImage& image, wxRichTextObject* parent, wxRichTextAttr* charStyle): wxRichTextObject(parent) { Init(); m_imageBlock.MakeImageBlockDefaultQuality(image, wxBITMAP_TYPE_PNG); if (charStyle) SetAttributes(*charStyle); } wxRichTextImage::wxRichTextImage(const wxRichTextImageBlock& imageBlock, wxRichTextObject* parent, wxRichTextAttr* charStyle): wxRichTextObject(parent) { Init(); m_imageBlock = imageBlock; if (charStyle) SetAttributes(*charStyle); } wxRichTextImage::~wxRichTextImage() { } void wxRichTextImage::Init() { m_originalImageSize = wxSize(-1, -1); } /// Create a cached image at the required size bool wxRichTextImage::LoadImageCache(wxDC& dc, bool resetCache, const wxSize& parentSize) { if (!m_imageBlock.IsOk()) return false; // If we have an original image size, use that to compute the cached bitmap size // instead of loading the image each time. This way we can avoid loading // the image so long as the new cached bitmap size hasn't changed. wxImage image; if (resetCache || m_originalImageSize.GetWidth() <= 0 || m_originalImageSize.GetHeight() <= 0) { m_imageCache = wxNullBitmap; m_imageBlock.Load(image); if (!image.IsOk()) return false; m_originalImageSize = wxSize(image.GetWidth(), image.GetHeight()); } int width = m_originalImageSize.GetWidth(); int height = m_originalImageSize.GetHeight(); int parentWidth = 0; int parentHeight = 0; int maxWidth = -1; int maxHeight = -1; wxSize sz = parentSize; if (sz == wxDefaultSize) { if (GetParent() && GetParent()->GetParent()) sz = GetParent()->GetParent()->GetCachedSize(); } wxRichTextBuffer* buffer = GetBuffer(); if (sz != wxDefaultSize) { if (buffer) { // Find the actual space available when margin is taken into account wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect; marginRect = wxRect(0, 0, sz.x, sz.y); if (GetParent() && GetParent()->GetParent()) { buffer->GetBoxRects(dc, buffer, GetParent()->GetParent()->GetAttributes(), marginRect, borderRect, contentRect, paddingRect, outlineRect); sz = contentRect.GetSize(); } // Use a minimum size to stop images becoming very small parentWidth = wxMax(100, sz.GetWidth()); parentHeight = wxMax(100, sz.GetHeight()); if (buffer->GetRichTextCtrl()) // Start with a maximum width of the control size, even if not specified by the content, // to minimize the amount of picture overlapping the right-hand side maxWidth = parentWidth; } } wxTextAttrDimensionConverter converter(dc, buffer ? buffer->GetScale() : 1.0, wxSize(parentWidth, parentHeight)); if (GetAttributes().GetTextBoxAttr().GetWidth().IsValid() && GetAttributes().GetTextBoxAttr().GetWidth().GetValue() > 0) { int widthPixels = converter.GetPixels(GetAttributes().GetTextBoxAttr().GetWidth(), wxHORIZONTAL); if (widthPixels > 0) width = widthPixels; } // Limit to max width if (GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().IsValid() && GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetValue() > 0) { int mw = converter.GetPixels(GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth(), wxHORIZONTAL); // If we already have a smaller max width due to the constraints of the control size, // don't use the larger max width. if (mw > 0 && ((maxWidth == -1) || (mw < maxWidth))) maxWidth = mw; } if (maxWidth > 0 && width > maxWidth) width = maxWidth; // Preserve the aspect ratio if (width != m_originalImageSize.GetWidth()) height = (int) (float(m_originalImageSize.GetHeight()) * (float(width)/float(m_originalImageSize.GetWidth()))); if (GetAttributes().GetTextBoxAttr().GetHeight().IsValid() && GetAttributes().GetTextBoxAttr().GetHeight().GetValue() > 0) { int heightPixels = converter.GetPixels(GetAttributes().GetTextBoxAttr().GetHeight(), wxVERTICAL); if (heightPixels > 0) height = heightPixels; // Preserve the aspect ratio if (height != m_originalImageSize.GetHeight()) width = (int) (float(m_originalImageSize.GetWidth()) * (float(height)/float(m_originalImageSize.GetHeight()))); } // Limit to max height if (GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().IsValid() && GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetValue() > 0) { int mh = converter.GetPixels(GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight(), wxVERTICAL); if (mh > 0) maxHeight = mh; } if (maxHeight > 0 && height > maxHeight) { height = maxHeight; // Preserve the aspect ratio if (height != m_originalImageSize.GetHeight()) width = (int) (float(m_originalImageSize.GetWidth()) * (float(height)/float(m_originalImageSize.GetHeight()))); } // Prevent the use of zero size width = wxMax(1, width); height = wxMax(1, height); if (m_imageCache.IsOk() && m_imageCache.GetWidth() == width && m_imageCache.GetHeight() == height) { // Do nothing, we didn't need to change the image cache } else { if (!image.IsOk()) { m_imageBlock.Load(image); if (!image.IsOk()) return false; } if (image.GetWidth() == width && image.GetHeight() == height) m_imageCache = wxBitmap(image); else { // If the original width and height is small, e.g. 400 or below, // scale up and then down to improve image quality. This can make // a big difference, with not much performance hit. int upscaleThreshold = 400; wxImage img; if (image.GetWidth() <= upscaleThreshold || image.GetHeight() <= upscaleThreshold) { img = image.Scale(image.GetWidth()*2, image.GetHeight()*2); img.Rescale(width, height, wxIMAGE_QUALITY_HIGH); } else img = image.Scale(width, height, wxIMAGE_QUALITY_HIGH); m_imageCache = wxBitmap(img); } } return m_imageCache.IsOk(); } /// Draw the item bool wxRichTextImage::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& WXUNUSED(range), const wxRichTextSelection& selection, const wxRect& rect, int WXUNUSED(descent), int WXUNUSED(style)) { if (!IsShown()) return true; // Don't need cached size AFAIK // wxSize size = GetCachedSize(); if (!LoadImageCache(dc)) return false; wxRichTextAttr attr(GetAttributes()); AdjustAttributes(attr, context); DrawBoxAttributes(dc, GetBuffer(), attr, wxRect(rect.GetPosition(), GetCachedSize())); wxSize imageSize(m_imageCache.GetWidth(), m_imageCache.GetHeight()); wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect; marginRect = rect; // outer rectangle, will calculate contentRect GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect); dc.DrawBitmap(m_imageCache, contentRect.x, contentRect.y, true); if (selection.WithinSelection(GetRange().GetStart(), this)) { wxCheckSetBrush(dc, *wxBLACK_BRUSH); wxCheckSetPen(dc, *wxBLACK_PEN); dc.SetLogicalFunction(wxINVERT); dc.DrawRectangle(contentRect); dc.SetLogicalFunction(wxCOPY); } return true; } /// Lay the item out bool wxRichTextImage::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& WXUNUSED(parentRect), int WXUNUSED(style)) { if (!LoadImageCache(dc)) return false; wxSize imageSize(m_imageCache.GetWidth(), m_imageCache.GetHeight()); wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect; contentRect = wxRect(wxPoint(0,0), imageSize); wxRichTextAttr attr(GetAttributes()); AdjustAttributes(attr, context); GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect); wxSize overallSize = marginRect.GetSize(); SetCachedSize(overallSize); SetMaxSize(overallSize); SetMinSize(overallSize); SetPosition(rect.GetPosition()); return true; } /// Get/set the object size for the given range. Returns false if the range /// is invalid for this object. bool wxRichTextImage::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& WXUNUSED(descent), wxDC& dc, wxRichTextDrawingContext& context, int WXUNUSED(flags), const wxPoint& WXUNUSED(position), const wxSize& parentSize, wxArrayInt* partialExtents) const { if (!range.IsWithin(GetRange())) return false; if (!((wxRichTextImage*)this)->LoadImageCache(dc, false, parentSize)) { size.x = 0; size.y = 0; if (partialExtents) partialExtents->Add(0); return false; } wxRichTextAttr attr(GetAttributes()); ((wxRichTextObject*)this)->AdjustAttributes(attr, context); wxSize imageSize(m_imageCache.GetWidth(), m_imageCache.GetHeight()); wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect; contentRect = wxRect(wxPoint(0,0), imageSize); GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect); wxSize overallSize = marginRect.GetSize(); if (partialExtents) partialExtents->Add(overallSize.x); size = overallSize; return true; } // Get the 'natural' size for an object. For an image, it would be the // image size. wxTextAttrSize wxRichTextImage::GetNaturalSize() const { wxTextAttrSize size; if (GetImageCache().IsOk()) { size.SetWidth(GetImageCache().GetWidth(), wxTEXT_ATTR_UNITS_PIXELS); size.SetHeight(GetImageCache().GetHeight(), wxTEXT_ATTR_UNITS_PIXELS); } return size; } /// Copy void wxRichTextImage::Copy(const wxRichTextImage& obj) { wxRichTextObject::Copy(obj); m_imageBlock = obj.m_imageBlock; m_originalImageSize = obj.m_originalImageSize; } /// Edit properties via a GUI bool wxRichTextImage::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer) { wxRichTextObjectPropertiesDialog imageDlg(this, wxGetTopLevelParent(parent), wxID_ANY, _("Picture Properties")); imageDlg.SetAttributes(GetAttributes()); if (imageDlg.ShowModal() == wxID_OK) { // By passing wxRICHTEXT_SETSTYLE_RESET, indeterminate attributes set by the user will be set as // indeterminate in the object. imageDlg.ApplyStyle(buffer->GetRichTextCtrl(), wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_RESET); return true; } else return false; } /*! * Utilities * */ /// Compare two attribute objects bool wxTextAttrEq(const wxRichTextAttr& attr1, const wxRichTextAttr& attr2) { return (attr1 == attr2); } /// Compare tabs bool wxRichTextTabsEq(const wxArrayInt& tabs1, const wxArrayInt& tabs2) { if (tabs1.GetCount() != tabs2.GetCount()) return false; size_t i; for (i = 0; i < tabs1.GetCount(); i++) { if (tabs1[i] != tabs2[i]) return false; } return true; } bool wxRichTextApplyStyle(wxRichTextAttr& destStyle, const wxRichTextAttr& style, wxRichTextAttr* compareWith) { return destStyle.Apply(style, compareWith); } // Remove attributes bool wxRichTextRemoveStyle(wxRichTextAttr& destStyle, const wxRichTextAttr& style) { return destStyle.RemoveStyle(style); } /// Combine two bitlists, specifying the bits of interest with separate flags. bool wxRichTextCombineBitlists(int& valueA, int valueB, int& flagsA, int flagsB) { return wxRichTextAttr::CombineBitlists(valueA, valueB, flagsA, flagsB); } /// Compare two bitlists bool wxRichTextBitlistsEqPartial(int valueA, int valueB, int flags) { return wxRichTextAttr::BitlistsEqPartial(valueA, valueB, flags); } /// Split into paragraph and character styles bool wxRichTextSplitParaCharStyles(const wxRichTextAttr& style, wxRichTextAttr& parStyle, wxRichTextAttr& charStyle) { return wxRichTextAttr::SplitParaCharStyles(style, parStyle, charStyle); } /// Convert a decimal to Roman numerals wxString wxRichTextDecimalToRoman(long n) { static wxArrayInt decimalNumbers; static wxArrayString romanNumbers; // Clean up arrays if (n == -1) { decimalNumbers.Clear(); romanNumbers.Clear(); return wxEmptyString; } if (decimalNumbers.GetCount() == 0) { #define wxRichTextAddDecRom(n, r) decimalNumbers.Add(n); romanNumbers.Add(r); wxRichTextAddDecRom(1000, wxT("M")); wxRichTextAddDecRom(900, wxT("CM")); wxRichTextAddDecRom(500, wxT("D")); wxRichTextAddDecRom(400, wxT("CD")); wxRichTextAddDecRom(100, wxT("C")); wxRichTextAddDecRom(90, wxT("XC")); wxRichTextAddDecRom(50, wxT("L")); wxRichTextAddDecRom(40, wxT("XL")); wxRichTextAddDecRom(10, wxT("X")); wxRichTextAddDecRom(9, wxT("IX")); wxRichTextAddDecRom(5, wxT("V")); wxRichTextAddDecRom(4, wxT("IV")); wxRichTextAddDecRom(1, wxT("I")); } int i = 0; wxString roman; while (n > 0 && i < 13) { if (n >= decimalNumbers[i]) { n -= decimalNumbers[i]; roman += romanNumbers[i]; } else { i ++; } } if (roman.IsEmpty()) roman = wxT("0"); return roman; } /*! * wxRichTextFileHandler * Base class for file handlers */ IMPLEMENT_CLASS(wxRichTextFileHandler, wxObject) #if wxUSE_FFILE && wxUSE_STREAMS bool wxRichTextFileHandler::LoadFile(wxRichTextBuffer *buffer, const wxString& filename) { wxFFileInputStream stream(filename); if (stream.IsOk()) return LoadFile(buffer, stream); return false; } bool wxRichTextFileHandler::SaveFile(wxRichTextBuffer *buffer, const wxString& filename) { wxFFileOutputStream stream(filename); if (stream.IsOk()) return SaveFile(buffer, stream); return false; } #endif // wxUSE_FFILE && wxUSE_STREAMS /// Can we handle this filename (if using files)? By default, checks the extension. bool wxRichTextFileHandler::CanHandle(const wxString& filename) const { wxString path, file, ext; wxFileName::SplitPath(filename, & path, & file, & ext); return (ext.Lower() == GetExtension()); } /*! * wxRichTextTextHandler * Plain text handler */ IMPLEMENT_CLASS(wxRichTextPlainTextHandler, wxRichTextFileHandler) #if wxUSE_STREAMS bool wxRichTextPlainTextHandler::DoLoadFile(wxRichTextBuffer *buffer, wxInputStream& stream) { if (!stream.IsOk()) return false; wxString str; int lastCh = 0; while (!stream.Eof()) { int ch = stream.GetC(); if (!stream.Eof()) { if (ch == 10 && lastCh != 13) str += wxT('\n'); if (ch > 0 && ch != 10) str += wxChar(ch); lastCh = ch; } } buffer->ResetAndClearCommands(); buffer->Clear(); buffer->AddParagraphs(str); buffer->UpdateRanges(); return true; } bool wxRichTextPlainTextHandler::DoSaveFile(wxRichTextBuffer *buffer, wxOutputStream& stream) { if (!stream.IsOk()) return false; wxString text = buffer->GetText(); wxString newLine = wxRichTextLineBreakChar; text.Replace(newLine, wxT("\n")); wxCharBuffer buf = text.ToAscii(); stream.Write((const char*) buf, text.length()); return true; } #endif // wxUSE_STREAMS /* * Stores information about an image, in binary in-memory form */ wxRichTextImageBlock::wxRichTextImageBlock() { Init(); } wxRichTextImageBlock::wxRichTextImageBlock(const wxRichTextImageBlock& block):wxObject() { Init(); Copy(block); } wxRichTextImageBlock::~wxRichTextImageBlock() { wxDELETEA(m_data); } void wxRichTextImageBlock::Init() { m_data = NULL; m_dataSize = 0; m_imageType = wxBITMAP_TYPE_INVALID; } void wxRichTextImageBlock::Clear() { wxDELETEA(m_data); m_dataSize = 0; m_imageType = wxBITMAP_TYPE_INVALID; } // Load the original image into a memory block. // If the image is not a JPEG, we must convert it into a JPEG // to conserve space. // If it's not a JPEG we can make use of 'image', already scaled, so we don't have to // load the image a 2nd time. bool wxRichTextImageBlock::MakeImageBlock(const wxString& filename, wxBitmapType imageType, wxImage& image, bool convertToJPEG) { m_imageType = imageType; wxString filenameToRead(filename); bool removeFile = false; if (imageType == wxBITMAP_TYPE_INVALID) return false; // Could not determine image type if ((imageType != wxBITMAP_TYPE_JPEG) && convertToJPEG) { wxString tempFile = wxFileName::CreateTempFileName(_("image")); wxASSERT(!tempFile.IsEmpty()); image.SaveFile(tempFile, wxBITMAP_TYPE_JPEG); filenameToRead = tempFile; removeFile = true; m_imageType = wxBITMAP_TYPE_JPEG; } wxFile file; if (!file.Open(filenameToRead)) return false; m_dataSize = (size_t) file.Length(); file.Close(); if (m_data) delete[] m_data; m_data = ReadBlock(filenameToRead, m_dataSize); if (removeFile) wxRemoveFile(filenameToRead); return (m_data != NULL); } // Make an image block from the wxImage in the given // format. bool wxRichTextImageBlock::MakeImageBlock(wxImage& image, wxBitmapType imageType, int quality) { image.SetOption(wxT("quality"), quality); if (imageType == wxBITMAP_TYPE_INVALID) return false; // Could not determine image type return DoMakeImageBlock(image, imageType); } // Uses a const wxImage for efficiency, but can't set quality (only relevant for JPEG) bool wxRichTextImageBlock::MakeImageBlockDefaultQuality(const wxImage& image, wxBitmapType imageType) { if (imageType == wxBITMAP_TYPE_INVALID) return false; // Could not determine image type return DoMakeImageBlock(image, imageType); } // Makes the image block bool wxRichTextImageBlock::DoMakeImageBlock(const wxImage& image, wxBitmapType imageType) { wxMemoryOutputStream memStream; if (!image.SaveFile(memStream, imageType)) { return false; } unsigned char* block = new unsigned char[memStream.GetSize()]; if (!block) return false; if (m_data) delete[] m_data; m_data = block; m_imageType = imageType; m_dataSize = memStream.GetSize(); memStream.CopyTo(m_data, m_dataSize); return (m_data != NULL); } // Write to a file bool wxRichTextImageBlock::Write(const wxString& filename) { return WriteBlock(filename, m_data, m_dataSize); } void wxRichTextImageBlock::Copy(const wxRichTextImageBlock& block) { m_imageType = block.m_imageType; wxDELETEA(m_data); m_dataSize = block.m_dataSize; if (m_dataSize == 0) return; m_data = new unsigned char[m_dataSize]; unsigned int i; for (i = 0; i < m_dataSize; i++) m_data[i] = block.m_data[i]; } //// Operators void wxRichTextImageBlock::operator=(const wxRichTextImageBlock& block) { Copy(block); } // Load a wxImage from the block bool wxRichTextImageBlock::Load(wxImage& image) { if (!m_data) return false; // Read in the image. #if wxUSE_STREAMS wxMemoryInputStream mstream(m_data, m_dataSize); bool success = image.LoadFile(mstream, GetImageType()); #else wxString tempFile = wxFileName::CreateTempFileName(_("image")); wxASSERT(!tempFile.IsEmpty()); if (!WriteBlock(tempFile, m_data, m_dataSize)) { return false; } success = image.LoadFile(tempFile, GetImageType()); wxRemoveFile(tempFile); #endif return success; } // Write data in hex to a stream bool wxRichTextImageBlock::WriteHex(wxOutputStream& stream) { if (m_dataSize == 0) return true; int bufSize = 100000; if (int(2*m_dataSize) < bufSize) bufSize = 2*m_dataSize; char* buf = new char[bufSize+1]; int left = m_dataSize; int n, i, j; j = 0; while (left > 0) { if (left*2 > bufSize) { n = bufSize; left -= (bufSize/2); } else { n = left*2; left = 0; } char* b = buf; for (i = 0; i < (n/2); i++) { wxDecToHex(m_data[j], b, b+1); b += 2; j ++; } buf[n] = 0; stream.Write((const char*) buf, n); } delete[] buf; return true; } // Read data in hex from a stream bool wxRichTextImageBlock::ReadHex(wxInputStream& stream, int length, wxBitmapType imageType) { int dataSize = length/2; if (m_data) delete[] m_data; // create a null terminated temporary string: char str[3]; str[2] = '\0'; m_data = new unsigned char[dataSize]; int i; for (i = 0; i < dataSize; i ++) { str[0] = (char)stream.GetC(); str[1] = (char)stream.GetC(); m_data[i] = (unsigned char)wxHexToDec(str); } m_dataSize = dataSize; m_imageType = imageType; return true; } // Allocate and read from stream as a block of memory unsigned char* wxRichTextImageBlock::ReadBlock(wxInputStream& stream, size_t size) { unsigned char* block = new unsigned char[size]; if (!block) return NULL; stream.Read(block, size); return block; } unsigned char* wxRichTextImageBlock::ReadBlock(const wxString& filename, size_t size) { wxFileInputStream stream(filename); if (!stream.IsOk()) return NULL; return ReadBlock(stream, size); } // Write memory block to stream bool wxRichTextImageBlock::WriteBlock(wxOutputStream& stream, unsigned char* block, size_t size) { stream.Write((void*) block, size); return stream.IsOk(); } // Write memory block to file bool wxRichTextImageBlock::WriteBlock(const wxString& filename, unsigned char* block, size_t size) { wxFileOutputStream outStream(filename); if (!outStream.IsOk()) return false; return WriteBlock(outStream, block, size); } // Gets the extension for the block's type wxString wxRichTextImageBlock::GetExtension() const { wxImageHandler* handler = wxImage::FindHandler(GetImageType()); if (handler) return handler->GetExtension(); else return wxEmptyString; } #if wxUSE_DATAOBJ /*! * The data object for a wxRichTextBuffer */ const wxChar *wxRichTextBufferDataObject::ms_richTextBufferFormatId = wxT("wxRichText"); wxRichTextBufferDataObject::wxRichTextBufferDataObject(wxRichTextBuffer* richTextBuffer) { m_richTextBuffer = richTextBuffer; // this string should uniquely identify our format, but is otherwise // arbitrary m_formatRichTextBuffer.SetId(GetRichTextBufferFormatId()); SetFormat(m_formatRichTextBuffer); } wxRichTextBufferDataObject::~wxRichTextBufferDataObject() { delete m_richTextBuffer; } // after a call to this function, the richTextBuffer is owned by the caller and it // is responsible for deleting it! wxRichTextBuffer* wxRichTextBufferDataObject::GetRichTextBuffer() { wxRichTextBuffer* richTextBuffer = m_richTextBuffer; m_richTextBuffer = NULL; return richTextBuffer; } wxDataFormat wxRichTextBufferDataObject::GetPreferredFormat(Direction WXUNUSED(dir)) const { return m_formatRichTextBuffer; } size_t wxRichTextBufferDataObject::GetDataSize() const { if (!m_richTextBuffer) return 0; wxString bufXML; { wxStringOutputStream stream(& bufXML); if (!m_richTextBuffer->SaveFile(stream, wxRICHTEXT_TYPE_XML)) { wxLogError(wxT("Could not write the buffer to an XML stream.\nYou may have forgotten to add the XML file handler.")); return 0; } } #if wxUSE_UNICODE wxCharBuffer buffer = bufXML.mb_str(wxConvUTF8); return strlen(buffer) + 1; #else return bufXML.Length()+1; #endif } bool wxRichTextBufferDataObject::GetDataHere(void *pBuf) const { if (!pBuf || !m_richTextBuffer) return false; wxString bufXML; { wxStringOutputStream stream(& bufXML); if (!m_richTextBuffer->SaveFile(stream, wxRICHTEXT_TYPE_XML)) { wxLogError(wxT("Could not write the buffer to an XML stream.\nYou may have forgotten to add the XML file handler.")); return 0; } } #if wxUSE_UNICODE wxCharBuffer buffer = bufXML.mb_str(wxConvUTF8); size_t len = strlen(buffer); memcpy((char*) pBuf, (const char*) buffer, len); ((char*) pBuf)[len] = 0; #else size_t len = bufXML.Length(); memcpy((char*) pBuf, (const char*) bufXML.c_str(), len); ((char*) pBuf)[len] = 0; #endif return true; } bool wxRichTextBufferDataObject::SetData(size_t WXUNUSED(len), const void *buf) { wxDELETE(m_richTextBuffer); wxString bufXML((const char*) buf, wxConvUTF8); m_richTextBuffer = new wxRichTextBuffer; wxStringInputStream stream(bufXML); if (!m_richTextBuffer->LoadFile(stream, wxRICHTEXT_TYPE_XML)) { wxLogError(wxT("Could not read the buffer from an XML stream.\nYou may have forgotten to add the XML file handler.")); wxDELETE(m_richTextBuffer); return false; } return true; } #endif // wxUSE_DATAOBJ /* * wxRichTextFontTable * Manages quick access to a pool of fonts for rendering rich text */ WX_DECLARE_STRING_HASH_MAP_WITH_DECL(wxFont, wxRichTextFontTableHashMap, class WXDLLIMPEXP_RICHTEXT); class wxRichTextFontTableData: public wxObjectRefData { public: wxRichTextFontTableData() {} wxFont FindFont(const wxRichTextAttr& fontSpec, double fontScale); wxRichTextFontTableHashMap m_hashMap; }; wxFont wxRichTextFontTableData::FindFont(const wxRichTextAttr& fontSpec, double fontScale) { wxString facename(fontSpec.GetFontFaceName()); int fontSize = fontSpec.GetFontSize(); if (fontScale != 1.0) fontSize = (int) ((double(fontSize) * fontScale) + 0.5); wxString units; if (fontSpec.HasFontPixelSize() && !fontSpec.HasFontPointSize()) units = wxT("px"); else units = wxT("pt"); wxString spec = wxString::Format(wxT("%d-%s-%d-%d-%d-%d-%s-%d"), fontSize, units.c_str(), fontSpec.GetFontStyle(), fontSpec.GetFontWeight(), (int) fontSpec.GetFontUnderlined(), (int) fontSpec.GetFontStrikethrough(), facename.c_str(), (int) fontSpec.GetFontEncoding()); wxRichTextFontTableHashMap::iterator entry = m_hashMap.find(spec); if ( entry == m_hashMap.end() ) { if (fontSpec.HasFontPixelSize() && !fontSpec.HasFontPointSize()) { wxFont font(wxSize(0, fontSize), wxFONTFAMILY_DEFAULT, fontSpec.GetFontStyle(), fontSpec.GetFontWeight(), fontSpec.GetFontUnderlined(), facename); if (fontSpec.HasFontStrikethrough() && fontSpec.GetFontStrikethrough()) font.SetStrikethrough(true); m_hashMap[spec] = font; return font; } else { wxFont font(fontSize, wxFONTFAMILY_DEFAULT, fontSpec.GetFontStyle(), fontSpec.GetFontWeight(), fontSpec.GetFontUnderlined(), facename.c_str()); if (fontSpec.HasFontStrikethrough() && fontSpec.GetFontStrikethrough()) font.SetStrikethrough(true); m_hashMap[spec] = font; return font; } } else { return entry->second; } } IMPLEMENT_DYNAMIC_CLASS(wxRichTextFontTable, wxObject) wxRichTextFontTable::wxRichTextFontTable() { m_refData = new wxRichTextFontTableData; m_fontScale = 1.0; } wxRichTextFontTable::wxRichTextFontTable(const wxRichTextFontTable& table) : wxObject() { (*this) = table; } wxRichTextFontTable::~wxRichTextFontTable() { UnRef(); } bool wxRichTextFontTable::operator == (const wxRichTextFontTable& table) const { return (m_refData == table.m_refData); } void wxRichTextFontTable::operator= (const wxRichTextFontTable& table) { Ref(table); m_fontScale = table.m_fontScale; } wxFont wxRichTextFontTable::FindFont(const wxRichTextAttr& fontSpec) { wxRichTextFontTableData* data = (wxRichTextFontTableData*) m_refData; if (data) return data->FindFont(fontSpec, m_fontScale); else return wxFont(); } void wxRichTextFontTable::Clear() { wxRichTextFontTableData* data = (wxRichTextFontTableData*) m_refData; if (data) data->m_hashMap.clear(); } void wxRichTextFontTable::SetFontScale(double fontScale) { if (fontScale != m_fontScale) Clear(); m_fontScale = fontScale; } // wxTextBoxAttr void wxTextBoxAttr::Reset() { m_flags = 0; m_floatMode = wxTEXT_BOX_ATTR_FLOAT_NONE; m_clearMode = wxTEXT_BOX_ATTR_CLEAR_NONE; m_whitespaceMode = wxTEXT_BOX_ATTR_WHITESPACE_NONE; m_collapseMode = wxTEXT_BOX_ATTR_COLLAPSE_NONE; m_verticalAlignment = wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT_NONE; m_boxStyleName = wxEmptyString; m_margins.Reset(); m_padding.Reset(); m_position.Reset(); m_size.Reset(); m_minSize.Reset(); m_maxSize.Reset(); m_border.Reset(); m_outline.Reset(); } // Equality test bool wxTextBoxAttr::operator== (const wxTextBoxAttr& attr) const { return ( m_flags == attr.m_flags && m_floatMode == attr.m_floatMode && m_clearMode == attr.m_clearMode && m_whitespaceMode == attr.m_whitespaceMode && m_collapseMode == attr.m_collapseMode && m_verticalAlignment == attr.m_verticalAlignment && m_margins == attr.m_margins && m_padding == attr.m_padding && m_position == attr.m_position && m_size == attr.m_size && m_minSize == attr.m_minSize && m_maxSize == attr.m_maxSize && m_border == attr.m_border && m_outline == attr.m_outline && m_boxStyleName == attr.m_boxStyleName ); } // Partial equality test bool wxTextBoxAttr::EqPartial(const wxTextBoxAttr& attr, bool weakTest) const { if (!weakTest && ((!HasFloatMode() && attr.HasFloatMode()) || (!HasClearMode() && attr.HasClearMode()) || (!HasCollapseBorders() && attr.HasCollapseBorders()) || (!HasVerticalAlignment() && attr.HasVerticalAlignment()) || (!HasWhitespaceMode() && attr.HasWhitespaceMode()) || (!HasBoxStyleName() && attr.HasBoxStyleName()))) { return false; } if (attr.HasFloatMode() && HasFloatMode() && (GetFloatMode() != attr.GetFloatMode())) return false; if (attr.HasClearMode() && HasClearMode() && (GetClearMode() != attr.GetClearMode())) return false; if (attr.HasCollapseBorders() && HasCollapseBorders() && (attr.GetCollapseBorders() != GetCollapseBorders())) return false; if (attr.HasVerticalAlignment() && HasVerticalAlignment() && (attr.GetVerticalAlignment() != GetVerticalAlignment())) return false; if (attr.HasWhitespaceMode() && HasWhitespaceMode() && (GetWhitespaceMode() != attr.GetWhitespaceMode())) return false; if (attr.HasBoxStyleName() && HasBoxStyleName() && (attr.GetBoxStyleName() != GetBoxStyleName())) return false; // Position if (!m_position.EqPartial(attr.m_position, weakTest)) return false; // Size if (!m_size.EqPartial(attr.m_size, weakTest)) return false; if (!m_minSize.EqPartial(attr.m_minSize, weakTest)) return false; if (!m_maxSize.EqPartial(attr.m_maxSize, weakTest)) return false; // Margins if (!m_margins.EqPartial(attr.m_margins, weakTest)) return false; // Padding if (!m_padding.EqPartial(attr.m_padding, weakTest)) return false; // Border if (!GetBorder().EqPartial(attr.GetBorder(), weakTest)) return false; // Outline if (!GetOutline().EqPartial(attr.GetOutline(), weakTest)) return false; return true; } // Merges the given attributes. If compareWith // is non-NULL, then it will be used to mask out those attributes that are the same in style // and compareWith, for situations where we don't want to explicitly set inherited attributes. bool wxTextBoxAttr::Apply(const wxTextBoxAttr& attr, const wxTextBoxAttr* compareWith) { if (attr.HasFloatMode()) { if (!(compareWith && compareWith->HasFloatMode() && compareWith->GetFloatMode() == attr.GetFloatMode())) SetFloatMode(attr.GetFloatMode()); } if (attr.HasClearMode()) { if (!(compareWith && compareWith->HasClearMode() && compareWith->GetClearMode() == attr.GetClearMode())) SetClearMode(attr.GetClearMode()); } if (attr.HasCollapseBorders()) { if (!(compareWith && compareWith->HasCollapseBorders() && compareWith->GetCollapseBorders() == attr.GetCollapseBorders())) SetCollapseBorders(attr.GetCollapseBorders()); } if (attr.HasVerticalAlignment()) { if (!(compareWith && compareWith->HasVerticalAlignment() && compareWith->GetVerticalAlignment() == attr.GetVerticalAlignment())) SetVerticalAlignment(attr.GetVerticalAlignment()); } if (attr.HasWhitespaceMode()) { if (!(compareWith && compareWith->HasWhitespaceMode() && compareWith->GetWhitespaceMode() == attr.GetWhitespaceMode())) SetWhitespaceMode(attr.GetWhitespaceMode()); } if (attr.HasBoxStyleName()) { if (!(compareWith && compareWith->HasBoxStyleName() && compareWith->GetBoxStyleName() == attr.GetBoxStyleName())) SetBoxStyleName(attr.GetBoxStyleName()); } m_margins.Apply(attr.m_margins, compareWith ? (& compareWith->m_margins) : (const wxTextAttrDimensions*) NULL); m_padding.Apply(attr.m_padding, compareWith ? (& compareWith->m_padding) : (const wxTextAttrDimensions*) NULL); m_position.Apply(attr.m_position, compareWith ? (& compareWith->m_position) : (const wxTextAttrDimensions*) NULL); m_size.Apply(attr.m_size, compareWith ? (& compareWith->m_size) : (const wxTextAttrSize*) NULL); m_minSize.Apply(attr.m_minSize, compareWith ? (& compareWith->m_minSize) : (const wxTextAttrSize*) NULL); m_maxSize.Apply(attr.m_maxSize, compareWith ? (& compareWith->m_maxSize) : (const wxTextAttrSize*) NULL); m_border.Apply(attr.m_border, compareWith ? (& compareWith->m_border) : (const wxTextAttrBorders*) NULL); m_outline.Apply(attr.m_outline, compareWith ? (& compareWith->m_outline) : (const wxTextAttrBorders*) NULL); return true; } // Remove specified attributes from this object bool wxTextBoxAttr::RemoveStyle(const wxTextBoxAttr& attr) { if (attr.HasFloatMode()) RemoveFlag(wxTEXT_BOX_ATTR_FLOAT); if (attr.HasClearMode()) RemoveFlag(wxTEXT_BOX_ATTR_CLEAR); if (attr.HasCollapseBorders()) RemoveFlag(wxTEXT_BOX_ATTR_COLLAPSE_BORDERS); if (attr.HasVerticalAlignment()) RemoveFlag(wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT); if (attr.HasWhitespaceMode()) RemoveFlag(wxTEXT_BOX_ATTR_WHITESPACE); if (attr.HasBoxStyleName()) { SetBoxStyleName(wxEmptyString); RemoveFlag(wxTEXT_BOX_ATTR_BOX_STYLE_NAME); } m_margins.RemoveStyle(attr.m_margins); m_padding.RemoveStyle(attr.m_padding); m_position.RemoveStyle(attr.m_position); m_size.RemoveStyle(attr.m_size); m_minSize.RemoveStyle(attr.m_minSize); m_maxSize.RemoveStyle(attr.m_maxSize); m_border.RemoveStyle(attr.m_border); m_outline.RemoveStyle(attr.m_outline); return true; } // Collects the attributes that are common to a range of content, building up a note of // which attributes are absent in some objects and which clash in some objects. void wxTextBoxAttr::CollectCommonAttributes(const wxTextBoxAttr& attr, wxTextBoxAttr& clashingAttr, wxTextBoxAttr& absentAttr) { if (attr.HasFloatMode()) { if (!clashingAttr.HasFloatMode() && !absentAttr.HasFloatMode()) { if (HasFloatMode()) { if (GetFloatMode() != attr.GetFloatMode()) { clashingAttr.AddFlag(wxTEXT_BOX_ATTR_FLOAT); RemoveFlag(wxTEXT_BOX_ATTR_FLOAT); } } else SetFloatMode(attr.GetFloatMode()); } } else absentAttr.AddFlag(wxTEXT_BOX_ATTR_FLOAT); if (attr.HasClearMode()) { if (!clashingAttr.HasClearMode() && !absentAttr.HasClearMode()) { if (HasClearMode()) { if (GetClearMode() != attr.GetClearMode()) { clashingAttr.AddFlag(wxTEXT_BOX_ATTR_CLEAR); RemoveFlag(wxTEXT_BOX_ATTR_CLEAR); } } else SetClearMode(attr.GetClearMode()); } } else absentAttr.AddFlag(wxTEXT_BOX_ATTR_CLEAR); if (attr.HasCollapseBorders()) { if (!clashingAttr.HasCollapseBorders() && !absentAttr.HasCollapseBorders()) { if (HasCollapseBorders()) { if (GetCollapseBorders() != attr.GetCollapseBorders()) { clashingAttr.AddFlag(wxTEXT_BOX_ATTR_COLLAPSE_BORDERS); RemoveFlag(wxTEXT_BOX_ATTR_COLLAPSE_BORDERS); } } else SetCollapseBorders(attr.GetCollapseBorders()); } } else absentAttr.AddFlag(wxTEXT_BOX_ATTR_COLLAPSE_BORDERS); if (attr.HasVerticalAlignment()) { if (!clashingAttr.HasVerticalAlignment() && !absentAttr.HasVerticalAlignment()) { if (HasVerticalAlignment()) { if (GetVerticalAlignment() != attr.GetVerticalAlignment()) { clashingAttr.AddFlag(wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT); RemoveFlag(wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT); } } else SetVerticalAlignment(attr.GetVerticalAlignment()); } } else absentAttr.AddFlag(wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT); if (attr.HasWhitespaceMode()) { if (!clashingAttr.HasWhitespaceMode() && !absentAttr.HasWhitespaceMode()) { if (HasWhitespaceMode()) { if (GetWhitespaceMode() != attr.GetWhitespaceMode()) { clashingAttr.AddFlag(wxTEXT_BOX_ATTR_WHITESPACE); RemoveFlag(wxTEXT_BOX_ATTR_WHITESPACE); } } else SetWhitespaceMode(attr.GetWhitespaceMode()); } } else absentAttr.AddFlag(wxTEXT_BOX_ATTR_WHITESPACE); if (attr.HasBoxStyleName()) { if (!clashingAttr.HasBoxStyleName() && !absentAttr.HasBoxStyleName()) { if (HasBoxStyleName()) { if (GetBoxStyleName() != attr.GetBoxStyleName()) { clashingAttr.AddFlag(wxTEXT_BOX_ATTR_BOX_STYLE_NAME); RemoveFlag(wxTEXT_BOX_ATTR_BOX_STYLE_NAME); } } else SetBoxStyleName(attr.GetBoxStyleName()); } } else absentAttr.AddFlag(wxTEXT_BOX_ATTR_BOX_STYLE_NAME); m_margins.CollectCommonAttributes(attr.m_margins, clashingAttr.m_margins, absentAttr.m_margins); m_padding.CollectCommonAttributes(attr.m_padding, clashingAttr.m_padding, absentAttr.m_padding); m_position.CollectCommonAttributes(attr.m_position, clashingAttr.m_position, absentAttr.m_position); m_size.CollectCommonAttributes(attr.m_size, clashingAttr.m_size, absentAttr.m_size); m_minSize.CollectCommonAttributes(attr.m_minSize, clashingAttr.m_minSize, absentAttr.m_minSize); m_maxSize.CollectCommonAttributes(attr.m_maxSize, clashingAttr.m_maxSize, absentAttr.m_maxSize); m_border.CollectCommonAttributes(attr.m_border, clashingAttr.m_border, absentAttr.m_border); m_outline.CollectCommonAttributes(attr.m_outline, clashingAttr.m_outline, absentAttr.m_outline); } bool wxTextBoxAttr::IsDefault() const { return GetFlags() == 0 && m_border.IsDefault() && m_outline.IsDefault() && !m_size.IsValid() && !m_minSize.IsValid() && !m_maxSize.IsValid() && !m_position.IsValid() && !m_padding.IsValid() && !m_margins.IsValid(); } // wxRichTextAttr void wxRichTextAttr::Copy(const wxRichTextAttr& attr) { wxTextAttr::Copy(attr); m_textBoxAttr = attr.m_textBoxAttr; } bool wxRichTextAttr::operator==(const wxRichTextAttr& attr) const { if (!(wxTextAttr::operator==(attr))) return false; return (m_textBoxAttr == attr.m_textBoxAttr); } // Partial equality test bool wxRichTextAttr::EqPartial(const wxRichTextAttr& attr, bool weakTest) const { if (!(wxTextAttr::EqPartial(attr, weakTest))) return false; return m_textBoxAttr.EqPartial(attr.m_textBoxAttr, weakTest); } // Merges the given attributes. If compareWith // is non-NULL, then it will be used to mask out those attributes that are the same in style // and compareWith, for situations where we don't want to explicitly set inherited attributes. bool wxRichTextAttr::Apply(const wxRichTextAttr& style, const wxRichTextAttr* compareWith) { wxTextAttr::Apply(style, compareWith); return m_textBoxAttr.Apply(style.m_textBoxAttr, compareWith ? (& compareWith->m_textBoxAttr) : (const wxTextBoxAttr*) NULL); } // Remove specified attributes from this object bool wxRichTextAttr::RemoveStyle(const wxRichTextAttr& attr) { wxTextAttr::RemoveStyle(*this, attr); return m_textBoxAttr.RemoveStyle(attr.m_textBoxAttr); } // Collects the attributes that are common to a range of content, building up a note of // which attributes are absent in some objects and which clash in some objects. void wxRichTextAttr::CollectCommonAttributes(const wxRichTextAttr& attr, wxRichTextAttr& clashingAttr, wxRichTextAttr& absentAttr) { wxTextAttrCollectCommonAttributes(*this, attr, clashingAttr, absentAttr); m_textBoxAttr.CollectCommonAttributes(attr.m_textBoxAttr, clashingAttr.m_textBoxAttr, absentAttr.m_textBoxAttr); } // Partial equality test bool wxTextAttrBorder::EqPartial(const wxTextAttrBorder& border, bool weakTest) const { if (!weakTest && ((!HasStyle() && border.HasStyle()) || (!HasColour() && border.HasColour()) || (!HasWidth() && border.HasWidth()))) { return false; } if (border.HasStyle() && HasStyle() && (border.GetStyle() != GetStyle())) return false; if (border.HasColour() && HasColour() && (border.GetColourLong() != GetColourLong())) return false; if (border.HasWidth() && HasWidth() && !(border.GetWidth() == GetWidth())) return false; return true; } // Apply border to 'this', but not if the same as compareWith bool wxTextAttrBorder::Apply(const wxTextAttrBorder& border, const wxTextAttrBorder* compareWith) { if (border.HasStyle()) { if (!(compareWith && (border.GetStyle() == compareWith->GetStyle()))) SetStyle(border.GetStyle()); } if (border.HasColour()) { if (!(compareWith && (border.GetColourLong() == compareWith->GetColourLong()))) SetColour(border.GetColourLong()); } if (border.HasWidth()) { if (!(compareWith && (border.GetWidth() == compareWith->GetWidth()))) SetWidth(border.GetWidth()); } return true; } // Remove specified attributes from this object bool wxTextAttrBorder::RemoveStyle(const wxTextAttrBorder& attr) { if (attr.HasStyle() && HasStyle()) SetFlags(GetFlags() & ~wxTEXT_BOX_ATTR_BORDER_STYLE); if (attr.HasColour() && HasColour()) SetFlags(GetFlags() & ~wxTEXT_BOX_ATTR_BORDER_COLOUR); if (attr.HasWidth() && HasWidth()) m_borderWidth.Reset(); return true; } // Collects the attributes that are common to a range of content, building up a note of // which attributes are absent in some objects and which clash in some objects. void wxTextAttrBorder::CollectCommonAttributes(const wxTextAttrBorder& attr, wxTextAttrBorder& clashingAttr, wxTextAttrBorder& absentAttr) { if (attr.HasStyle()) { if (!clashingAttr.HasStyle() && !absentAttr.HasStyle()) { if (HasStyle()) { if (GetStyle() != attr.GetStyle()) { clashingAttr.AddFlag(wxTEXT_BOX_ATTR_BORDER_STYLE); RemoveFlag(wxTEXT_BOX_ATTR_BORDER_STYLE); } } else SetStyle(attr.GetStyle()); } } else absentAttr.AddFlag(wxTEXT_BOX_ATTR_BORDER_STYLE); if (attr.HasColour()) { if (!clashingAttr.HasColour() && !absentAttr.HasColour()) { if (HasColour()) { if (GetColour() != attr.GetColour()) { clashingAttr.AddFlag(wxTEXT_BOX_ATTR_BORDER_COLOUR); RemoveFlag(wxTEXT_BOX_ATTR_BORDER_COLOUR); } } else SetColour(attr.GetColourLong()); } } else absentAttr.AddFlag(wxTEXT_BOX_ATTR_BORDER_COLOUR); m_borderWidth.CollectCommonAttributes(attr.m_borderWidth, clashingAttr.m_borderWidth, absentAttr.m_borderWidth); } // Partial equality test bool wxTextAttrBorders::EqPartial(const wxTextAttrBorders& borders, bool weakTest) const { return m_left.EqPartial(borders.m_left, weakTest) && m_right.EqPartial(borders.m_right, weakTest) && m_top.EqPartial(borders.m_top, weakTest) && m_bottom.EqPartial(borders.m_bottom, weakTest); } // Apply border to 'this', but not if the same as compareWith bool wxTextAttrBorders::Apply(const wxTextAttrBorders& borders, const wxTextAttrBorders* compareWith) { m_left.Apply(borders.m_left, compareWith ? (& compareWith->m_left) : (const wxTextAttrBorder*) NULL); m_right.Apply(borders.m_right, compareWith ? (& compareWith->m_right) : (const wxTextAttrBorder*) NULL); m_top.Apply(borders.m_top, compareWith ? (& compareWith->m_top) : (const wxTextAttrBorder*) NULL); m_bottom.Apply(borders.m_bottom, compareWith ? (& compareWith->m_bottom) : (const wxTextAttrBorder*) NULL); return true; } // Remove specified attributes from this object bool wxTextAttrBorders::RemoveStyle(const wxTextAttrBorders& attr) { m_left.RemoveStyle(attr.m_left); m_right.RemoveStyle(attr.m_right); m_top.RemoveStyle(attr.m_top); m_bottom.RemoveStyle(attr.m_bottom); return true; } // Collects the attributes that are common to a range of content, building up a note of // which attributes are absent in some objects and which clash in some objects. void wxTextAttrBorders::CollectCommonAttributes(const wxTextAttrBorders& attr, wxTextAttrBorders& clashingAttr, wxTextAttrBorders& absentAttr) { m_left.CollectCommonAttributes(attr.m_left, clashingAttr.m_left, absentAttr.m_left); m_right.CollectCommonAttributes(attr.m_right, clashingAttr.m_right, absentAttr.m_right); m_top.CollectCommonAttributes(attr.m_top, clashingAttr.m_top, absentAttr.m_top); m_bottom.CollectCommonAttributes(attr.m_bottom, clashingAttr.m_bottom, absentAttr.m_bottom); } // Set style of all borders void wxTextAttrBorders::SetStyle(int style) { m_left.SetStyle(style); m_right.SetStyle(style); m_top.SetStyle(style); m_bottom.SetStyle(style); } // Set colour of all borders void wxTextAttrBorders::SetColour(unsigned long colour) { m_left.SetColour(colour); m_right.SetColour(colour); m_top.SetColour(colour); m_bottom.SetColour(colour); } void wxTextAttrBorders::SetColour(const wxColour& colour) { m_left.SetColour(colour); m_right.SetColour(colour); m_top.SetColour(colour); m_bottom.SetColour(colour); } // Set width of all borders void wxTextAttrBorders::SetWidth(const wxTextAttrDimension& width) { m_left.SetWidth(width); m_right.SetWidth(width); m_top.SetWidth(width); m_bottom.SetWidth(width); } // Partial equality test bool wxTextAttrDimension::EqPartial(const wxTextAttrDimension& dim, bool weakTest) const { if (!weakTest && !IsValid() && dim.IsValid()) return false; if (dim.IsValid() && IsValid() && !((*this) == dim)) return false; else return true; } bool wxTextAttrDimension::Apply(const wxTextAttrDimension& dim, const wxTextAttrDimension* compareWith) { if (dim.IsValid()) { if (!(compareWith && dim == (*compareWith))) (*this) = dim; } return true; } // Collects the attributes that are common to a range of content, building up a note of // which attributes are absent in some objects and which clash in some objects. void wxTextAttrDimension::CollectCommonAttributes(const wxTextAttrDimension& attr, wxTextAttrDimension& clashingAttr, wxTextAttrDimension& absentAttr) { if (attr.IsValid()) { if (!clashingAttr.IsValid() && !absentAttr.IsValid()) { if (IsValid()) { if (!((*this) == attr)) { clashingAttr.SetValid(true); SetValid(false); } } else (*this) = attr; } } else absentAttr.SetValid(true); } wxTextAttrDimensionConverter::wxTextAttrDimensionConverter(wxDC& dc, double scale, const wxSize& parentSize) { m_ppi = dc.GetPPI().x; m_scale = scale; m_parentSize = parentSize; } wxTextAttrDimensionConverter::wxTextAttrDimensionConverter(int ppi, double scale, const wxSize& parentSize) { m_ppi = ppi; m_scale = scale; m_parentSize = parentSize; } int wxTextAttrDimensionConverter::ConvertTenthsMMToPixels(int units) const { return wxRichTextObject::ConvertTenthsMMToPixels(m_ppi, units, m_scale); } int wxTextAttrDimensionConverter::ConvertPixelsToTenthsMM(int pixels) const { return wxRichTextObject::ConvertPixelsToTenthsMM(m_ppi, pixels, m_scale); } int wxTextAttrDimensionConverter::GetPixels(const wxTextAttrDimension& dim, int direction) const { if (dim.GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM) return ConvertTenthsMMToPixels(dim.GetValue()); // Incorporates scaling else { double pixelsDouble = 0.0; if (dim.GetUnits() == wxTEXT_ATTR_UNITS_PIXELS) pixelsDouble = (double) dim.GetValue(); else if (dim.GetUnits() == wxTEXT_ATTR_UNITS_POINTS) pixelsDouble = (double(dim.GetValue()) * (double(m_ppi)/72.0)); else if (dim.GetUnits() == wxTEXT_ATTR_UNITS_HUNDREDTHS_POINT) pixelsDouble = ((double(dim.GetValue())/100.0) * (double(m_ppi)/72.0)); else if (dim.GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE) { wxASSERT(m_parentSize != wxDefaultSize); if (direction == wxHORIZONTAL) pixelsDouble = (double(m_parentSize.x) * double(dim.GetValue()) / 100.0); else pixelsDouble = (double(m_parentSize.y) * double(dim.GetValue()) / 100.0); } else { wxASSERT(false); return 0; } // Scaling is used in e.g. printing if (m_scale != 1.0) pixelsDouble /= m_scale; int pixelsInt = int(pixelsDouble + 0.5); // If the result is very small, make it at least one pixel in size. if (pixelsInt == 0 && dim.GetValue() > 0) pixelsInt = 1; return pixelsInt; } } int wxTextAttrDimensionConverter::GetTenthsMM(const wxTextAttrDimension& dim) const { if (dim.GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM) return dim.GetValue(); else if (dim.GetUnits() == wxTEXT_ATTR_UNITS_PIXELS) return ConvertPixelsToTenthsMM(dim.GetValue()); else if (dim.GetUnits() == wxTEXT_ATTR_UNITS_POINTS) return (int) ((double(dim.GetValue())/0.28346456692913384) + 0.5); else if (dim.GetUnits() == wxTEXT_ATTR_UNITS_HUNDREDTHS_POINT) return (int) ((double(dim.GetValue())/28.346456692913384) + 0.5); else { wxASSERT(false); return 0; } } // Partial equality test bool wxTextAttrDimensions::EqPartial(const wxTextAttrDimensions& dims, bool weakTest) const { if (!m_left.EqPartial(dims.m_left, weakTest)) return false; if (!m_right.EqPartial(dims.m_right, weakTest)) return false; if (!m_top.EqPartial(dims.m_top, weakTest)) return false; if (!m_bottom.EqPartial(dims.m_bottom, weakTest)) return false; return true; } // Apply border to 'this', but not if the same as compareWith bool wxTextAttrDimensions::Apply(const wxTextAttrDimensions& dims, const wxTextAttrDimensions* compareWith) { m_left.Apply(dims.m_left, compareWith ? (& compareWith->m_left) : (const wxTextAttrDimension*) NULL); m_right.Apply(dims.m_right, compareWith ? (& compareWith->m_right): (const wxTextAttrDimension*) NULL); m_top.Apply(dims.m_top, compareWith ? (& compareWith->m_top): (const wxTextAttrDimension*) NULL); m_bottom.Apply(dims.m_bottom, compareWith ? (& compareWith->m_bottom): (const wxTextAttrDimension*) NULL); return true; } // Remove specified attributes from this object bool wxTextAttrDimensions::RemoveStyle(const wxTextAttrDimensions& attr) { if (attr.m_left.IsValid()) m_left.Reset(); if (attr.m_right.IsValid()) m_right.Reset(); if (attr.m_top.IsValid()) m_top.Reset(); if (attr.m_bottom.IsValid()) m_bottom.Reset(); return true; } // Collects the attributes that are common to a range of content, building up a note of // which attributes are absent in some objects and which clash in some objects. void wxTextAttrDimensions::CollectCommonAttributes(const wxTextAttrDimensions& attr, wxTextAttrDimensions& clashingAttr, wxTextAttrDimensions& absentAttr) { m_left.CollectCommonAttributes(attr.m_left, clashingAttr.m_left, absentAttr.m_left); m_right.CollectCommonAttributes(attr.m_right, clashingAttr.m_right, absentAttr.m_right); m_top.CollectCommonAttributes(attr.m_top, clashingAttr.m_top, absentAttr.m_top); m_bottom.CollectCommonAttributes(attr.m_bottom, clashingAttr.m_bottom, absentAttr.m_bottom); } // Partial equality test bool wxTextAttrSize::EqPartial(const wxTextAttrSize& size, bool weakTest) const { if (!m_width.EqPartial(size.m_width, weakTest)) return false; if (!m_height.EqPartial(size.m_height, weakTest)) return false; return true; } // Apply border to 'this', but not if the same as compareWith bool wxTextAttrSize::Apply(const wxTextAttrSize& size, const wxTextAttrSize* compareWith) { m_width.Apply(size.m_width, compareWith ? (& compareWith->m_width) : (const wxTextAttrDimension*) NULL); m_height.Apply(size.m_height, compareWith ? (& compareWith->m_height): (const wxTextAttrDimension*) NULL); return true; } // Remove specified attributes from this object bool wxTextAttrSize::RemoveStyle(const wxTextAttrSize& attr) { if (attr.m_width.IsValid()) m_width.Reset(); if (attr.m_height.IsValid()) m_height.Reset(); return true; } // Collects the attributes that are common to a range of content, building up a note of // which attributes are absent in some objects and which clash in some objects. void wxTextAttrSize::CollectCommonAttributes(const wxTextAttrSize& attr, wxTextAttrSize& clashingAttr, wxTextAttrSize& absentAttr) { m_width.CollectCommonAttributes(attr.m_width, clashingAttr.m_width, absentAttr.m_width); m_height.CollectCommonAttributes(attr.m_height, clashingAttr.m_height, absentAttr.m_height); } // Collects the attributes that are common to a range of content, building up a note of // which attributes are absent in some objects and which clash in some objects. void wxTextAttrCollectCommonAttributes(wxTextAttr& currentStyle, const wxTextAttr& attr, wxTextAttr& clashingAttr, wxTextAttr& absentAttr) { absentAttr.SetFlags(absentAttr.GetFlags() | (~attr.GetFlags() & wxTEXT_ATTR_ALL)); absentAttr.SetTextEffectFlags(absentAttr.GetTextEffectFlags() | (~attr.GetTextEffectFlags() & 0xFFFF)); long forbiddenFlags = clashingAttr.GetFlags()|absentAttr.GetFlags(); // If different font size units are being used, this is a clash. if (((attr.GetFlags() & wxTEXT_ATTR_FONT_SIZE) | (currentStyle.GetFlags() & wxTEXT_ATTR_FONT_SIZE)) == wxTEXT_ATTR_FONT_SIZE) { currentStyle.SetFontSize(0); currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_SIZE); clashingAttr.AddFlag(wxTEXT_ATTR_FONT_SIZE); } else { if (attr.HasFontPointSize() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_POINT_SIZE)) { if (currentStyle.HasFontPointSize()) { if (currentStyle.GetFontSize() != attr.GetFontSize()) { // Clash of attr - mark as such clashingAttr.AddFlag(wxTEXT_ATTR_FONT_POINT_SIZE); currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_POINT_SIZE); } } else currentStyle.SetFontSize(attr.GetFontSize()); } else if (!attr.HasFontPointSize() && currentStyle.HasFontPointSize()) { clashingAttr.AddFlag(wxTEXT_ATTR_FONT_POINT_SIZE); currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_POINT_SIZE); } if (attr.HasFontPixelSize() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_PIXEL_SIZE)) { if (currentStyle.HasFontPixelSize()) { if (currentStyle.GetFontSize() != attr.GetFontSize()) { // Clash of attr - mark as such clashingAttr.AddFlag(wxTEXT_ATTR_FONT_PIXEL_SIZE); currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_PIXEL_SIZE); } } else currentStyle.SetFontPixelSize(attr.GetFontSize()); } else if (!attr.HasFontPixelSize() && currentStyle.HasFontPixelSize()) { clashingAttr.AddFlag(wxTEXT_ATTR_FONT_PIXEL_SIZE); currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_PIXEL_SIZE); } } if (attr.HasFontItalic() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_ITALIC)) { if (currentStyle.HasFontItalic()) { if (currentStyle.GetFontStyle() != attr.GetFontStyle()) { // Clash of attr - mark as such clashingAttr.AddFlag(wxTEXT_ATTR_FONT_ITALIC); currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_ITALIC); } } else currentStyle.SetFontStyle(attr.GetFontStyle()); } else if (!attr.HasFontItalic() && currentStyle.HasFontItalic()) { clashingAttr.AddFlag(wxTEXT_ATTR_FONT_ITALIC); currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_ITALIC); } if (attr.HasFontFamily() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_FAMILY)) { if (currentStyle.HasFontFamily()) { if (currentStyle.GetFontFamily() != attr.GetFontFamily()) { // Clash of attr - mark as such clashingAttr.AddFlag(wxTEXT_ATTR_FONT_FAMILY); currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_FAMILY); } } else currentStyle.SetFontFamily(attr.GetFontFamily()); } else if (!attr.HasFontFamily() && currentStyle.HasFontFamily()) { clashingAttr.AddFlag(wxTEXT_ATTR_FONT_FAMILY); currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_FAMILY); } if (attr.HasFontWeight() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_WEIGHT)) { if (currentStyle.HasFontWeight()) { if (currentStyle.GetFontWeight() != attr.GetFontWeight()) { // Clash of attr - mark as such clashingAttr.AddFlag(wxTEXT_ATTR_FONT_WEIGHT); currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_WEIGHT); } } else currentStyle.SetFontWeight(attr.GetFontWeight()); } else if (!attr.HasFontWeight() && currentStyle.HasFontWeight()) { clashingAttr.AddFlag(wxTEXT_ATTR_FONT_WEIGHT); currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_WEIGHT); } if (attr.HasFontFaceName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_FACE)) { if (currentStyle.HasFontFaceName()) { wxString faceName1(currentStyle.GetFontFaceName()); wxString faceName2(attr.GetFontFaceName()); if (faceName1 != faceName2) { // Clash of attr - mark as such clashingAttr.AddFlag(wxTEXT_ATTR_FONT_FACE); currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_FACE); } } else currentStyle.SetFontFaceName(attr.GetFontFaceName()); } else if (!attr.HasFontFaceName() && currentStyle.HasFontFaceName()) { clashingAttr.AddFlag(wxTEXT_ATTR_FONT_FACE); currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_FACE); } if (attr.HasFontUnderlined() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_UNDERLINE)) { if (currentStyle.HasFontUnderlined()) { if (currentStyle.GetFontUnderlined() != attr.GetFontUnderlined()) { // Clash of attr - mark as such clashingAttr.AddFlag(wxTEXT_ATTR_FONT_UNDERLINE); currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_UNDERLINE); } } else currentStyle.SetFontUnderlined(attr.GetFontUnderlined()); } else if (!attr.HasFontUnderlined() && currentStyle.HasFontUnderlined()) { clashingAttr.AddFlag(wxTEXT_ATTR_FONT_UNDERLINE); currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_UNDERLINE); } if (attr.HasFontStrikethrough() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_STRIKETHROUGH)) { if (currentStyle.HasFontStrikethrough()) { if (currentStyle.GetFontStrikethrough() != attr.GetFontStrikethrough()) { // Clash of attr - mark as such clashingAttr.AddFlag(wxTEXT_ATTR_FONT_STRIKETHROUGH); currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_STRIKETHROUGH); } } else currentStyle.SetFontStrikethrough(attr.GetFontStrikethrough()); } else if (!attr.HasFontStrikethrough() && currentStyle.HasFontStrikethrough()) { clashingAttr.AddFlag(wxTEXT_ATTR_FONT_STRIKETHROUGH); currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_STRIKETHROUGH); } if (attr.HasTextColour() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_TEXT_COLOUR)) { if (currentStyle.HasTextColour()) { if (currentStyle.GetTextColour() != attr.GetTextColour()) { // Clash of attr - mark as such clashingAttr.AddFlag(wxTEXT_ATTR_TEXT_COLOUR); currentStyle.RemoveFlag(wxTEXT_ATTR_TEXT_COLOUR); } } else currentStyle.SetTextColour(attr.GetTextColour()); } else if (!attr.HasTextColour() && currentStyle.HasTextColour()) { clashingAttr.AddFlag(wxTEXT_ATTR_TEXT_COLOUR); currentStyle.RemoveFlag(wxTEXT_ATTR_TEXT_COLOUR); } if (attr.HasBackgroundColour() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BACKGROUND_COLOUR)) { if (currentStyle.HasBackgroundColour()) { if (currentStyle.GetBackgroundColour() != attr.GetBackgroundColour()) { // Clash of attr - mark as such clashingAttr.AddFlag(wxTEXT_ATTR_BACKGROUND_COLOUR); currentStyle.RemoveFlag(wxTEXT_ATTR_BACKGROUND_COLOUR); } } else currentStyle.SetBackgroundColour(attr.GetBackgroundColour()); } else if (!attr.HasBackgroundColour() && currentStyle.HasBackgroundColour()) { clashingAttr.AddFlag(wxTEXT_ATTR_BACKGROUND_COLOUR); currentStyle.RemoveFlag(wxTEXT_ATTR_BACKGROUND_COLOUR); } if (attr.HasAlignment() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_ALIGNMENT)) { if (currentStyle.HasAlignment()) { if (currentStyle.GetAlignment() != attr.GetAlignment()) { // Clash of attr - mark as such clashingAttr.AddFlag(wxTEXT_ATTR_ALIGNMENT); currentStyle.RemoveFlag(wxTEXT_ATTR_ALIGNMENT); } } else currentStyle.SetAlignment(attr.GetAlignment()); } else if (!attr.HasAlignment() && currentStyle.HasAlignment()) { clashingAttr.AddFlag(wxTEXT_ATTR_ALIGNMENT); currentStyle.RemoveFlag(wxTEXT_ATTR_ALIGNMENT); } if (attr.HasTabs() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_TABS)) { if (currentStyle.HasTabs()) { if (!wxRichTextTabsEq(currentStyle.GetTabs(), attr.GetTabs())) { // Clash of attr - mark as such clashingAttr.AddFlag(wxTEXT_ATTR_TABS); currentStyle.RemoveFlag(wxTEXT_ATTR_TABS); } } else currentStyle.SetTabs(attr.GetTabs()); } else if (!attr.HasTabs() && currentStyle.HasTabs()) { clashingAttr.AddFlag(wxTEXT_ATTR_TABS); currentStyle.RemoveFlag(wxTEXT_ATTR_TABS); } if (attr.HasLeftIndent() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_LEFT_INDENT)) { if (currentStyle.HasLeftIndent()) { if (currentStyle.GetLeftIndent() != attr.GetLeftIndent() || currentStyle.GetLeftSubIndent() != attr.GetLeftSubIndent()) { // Clash of attr - mark as such clashingAttr.AddFlag(wxTEXT_ATTR_LEFT_INDENT); currentStyle.RemoveFlag(wxTEXT_ATTR_LEFT_INDENT); } } else currentStyle.SetLeftIndent(attr.GetLeftIndent(), attr.GetLeftSubIndent()); } else if (!attr.HasLeftIndent() && currentStyle.HasLeftIndent()) { clashingAttr.AddFlag(wxTEXT_ATTR_LEFT_INDENT); currentStyle.RemoveFlag(wxTEXT_ATTR_LEFT_INDENT); } if (attr.HasRightIndent() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_RIGHT_INDENT)) { if (currentStyle.HasRightIndent()) { if (currentStyle.GetRightIndent() != attr.GetRightIndent()) { // Clash of attr - mark as such clashingAttr.AddFlag(wxTEXT_ATTR_RIGHT_INDENT); currentStyle.RemoveFlag(wxTEXT_ATTR_RIGHT_INDENT); } } else currentStyle.SetRightIndent(attr.GetRightIndent()); } else if (!attr.HasRightIndent() && currentStyle.HasRightIndent()) { clashingAttr.AddFlag(wxTEXT_ATTR_RIGHT_INDENT); currentStyle.RemoveFlag(wxTEXT_ATTR_RIGHT_INDENT); } if (attr.HasParagraphSpacingAfter() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_PARA_SPACING_AFTER)) { if (currentStyle.HasParagraphSpacingAfter()) { if (currentStyle.GetParagraphSpacingAfter() != attr.GetParagraphSpacingAfter()) { // Clash of attr - mark as such clashingAttr.AddFlag(wxTEXT_ATTR_PARA_SPACING_AFTER); currentStyle.RemoveFlag(wxTEXT_ATTR_PARA_SPACING_AFTER); } } else currentStyle.SetParagraphSpacingAfter(attr.GetParagraphSpacingAfter()); } else if (!attr.HasParagraphSpacingAfter() && currentStyle.HasParagraphSpacingAfter()) { clashingAttr.AddFlag(wxTEXT_ATTR_PARA_SPACING_AFTER); currentStyle.RemoveFlag(wxTEXT_ATTR_PARA_SPACING_AFTER); } if (attr.HasParagraphSpacingBefore() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_PARA_SPACING_BEFORE)) { if (currentStyle.HasParagraphSpacingBefore()) { if (currentStyle.GetParagraphSpacingBefore() != attr.GetParagraphSpacingBefore()) { // Clash of attr - mark as such clashingAttr.AddFlag(wxTEXT_ATTR_PARA_SPACING_BEFORE); currentStyle.RemoveFlag(wxTEXT_ATTR_PARA_SPACING_BEFORE); } } else currentStyle.SetParagraphSpacingBefore(attr.GetParagraphSpacingBefore()); } else if (!attr.HasParagraphSpacingBefore() && currentStyle.HasParagraphSpacingBefore()) { clashingAttr.AddFlag(wxTEXT_ATTR_PARA_SPACING_BEFORE); currentStyle.RemoveFlag(wxTEXT_ATTR_PARA_SPACING_BEFORE); } if (attr.HasLineSpacing() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_LINE_SPACING)) { if (currentStyle.HasLineSpacing()) { if (currentStyle.GetLineSpacing() != attr.GetLineSpacing()) { // Clash of attr - mark as such clashingAttr.AddFlag(wxTEXT_ATTR_LINE_SPACING); currentStyle.RemoveFlag(wxTEXT_ATTR_LINE_SPACING); } } else currentStyle.SetLineSpacing(attr.GetLineSpacing()); } else if (!attr.HasLineSpacing() && currentStyle.HasLineSpacing()) { clashingAttr.AddFlag(wxTEXT_ATTR_LINE_SPACING); currentStyle.RemoveFlag(wxTEXT_ATTR_LINE_SPACING); } if (attr.HasCharacterStyleName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_CHARACTER_STYLE_NAME)) { if (currentStyle.HasCharacterStyleName()) { if (currentStyle.GetCharacterStyleName() != attr.GetCharacterStyleName()) { // Clash of attr - mark as such clashingAttr.AddFlag(wxTEXT_ATTR_CHARACTER_STYLE_NAME); currentStyle.RemoveFlag(wxTEXT_ATTR_CHARACTER_STYLE_NAME); } } else currentStyle.SetCharacterStyleName(attr.GetCharacterStyleName()); } else if (!attr.HasCharacterStyleName() && currentStyle.HasCharacterStyleName()) { clashingAttr.AddFlag(wxTEXT_ATTR_CHARACTER_STYLE_NAME); currentStyle.RemoveFlag(wxTEXT_ATTR_CHARACTER_STYLE_NAME); } if (attr.HasParagraphStyleName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_PARAGRAPH_STYLE_NAME)) { if (currentStyle.HasParagraphStyleName()) { if (currentStyle.GetParagraphStyleName() != attr.GetParagraphStyleName()) { // Clash of attr - mark as such clashingAttr.AddFlag(wxTEXT_ATTR_PARAGRAPH_STYLE_NAME); currentStyle.RemoveFlag(wxTEXT_ATTR_PARAGRAPH_STYLE_NAME); } } else currentStyle.SetParagraphStyleName(attr.GetParagraphStyleName()); } else if (!attr.HasParagraphStyleName() && currentStyle.HasParagraphStyleName()) { clashingAttr.AddFlag(wxTEXT_ATTR_PARAGRAPH_STYLE_NAME); currentStyle.RemoveFlag(wxTEXT_ATTR_PARAGRAPH_STYLE_NAME); } if (attr.HasListStyleName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_LIST_STYLE_NAME)) { if (currentStyle.HasListStyleName()) { if (currentStyle.GetListStyleName() != attr.GetListStyleName()) { // Clash of attr - mark as such clashingAttr.AddFlag(wxTEXT_ATTR_LIST_STYLE_NAME); currentStyle.RemoveFlag(wxTEXT_ATTR_LIST_STYLE_NAME); } } else currentStyle.SetListStyleName(attr.GetListStyleName()); } else if (!attr.HasListStyleName() && currentStyle.HasListStyleName()) { clashingAttr.AddFlag(wxTEXT_ATTR_LIST_STYLE_NAME); currentStyle.RemoveFlag(wxTEXT_ATTR_LIST_STYLE_NAME); } if (attr.HasBulletStyle() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BULLET_STYLE)) { if (currentStyle.HasBulletStyle()) { if (currentStyle.GetBulletStyle() != attr.GetBulletStyle()) { // Clash of attr - mark as such clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_STYLE); currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_STYLE); } } else currentStyle.SetBulletStyle(attr.GetBulletStyle()); } else if (!attr.HasBulletStyle() && currentStyle.HasBulletStyle()) { clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_STYLE); currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_STYLE); } if (attr.HasBulletNumber() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BULLET_NUMBER)) { if (currentStyle.HasBulletNumber()) { if (currentStyle.GetBulletNumber() != attr.GetBulletNumber()) { // Clash of attr - mark as such clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_NUMBER); currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_NUMBER); } } else currentStyle.SetBulletNumber(attr.GetBulletNumber()); } else if (!attr.HasBulletNumber() && currentStyle.HasBulletNumber()) { clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_NUMBER); currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_NUMBER); } if (attr.HasBulletText() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BULLET_TEXT)) { if (currentStyle.HasBulletText()) { if (currentStyle.GetBulletText() != attr.GetBulletText()) { // Clash of attr - mark as such clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_TEXT); currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_TEXT); } } else { currentStyle.SetBulletText(attr.GetBulletText()); currentStyle.SetBulletFont(attr.GetBulletFont()); } } else if (!attr.HasBulletText() && currentStyle.HasBulletText()) { clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_TEXT); currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_TEXT); } if (attr.HasBulletName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BULLET_NAME)) { if (currentStyle.HasBulletName()) { if (currentStyle.GetBulletName() != attr.GetBulletName()) { // Clash of attr - mark as such clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_NAME); currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_NAME); } } else { currentStyle.SetBulletName(attr.GetBulletName()); } } else if (!attr.HasBulletName() && currentStyle.HasBulletName()) { clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_NAME); currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_NAME); } if (attr.HasURL() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_URL)) { if (currentStyle.HasURL()) { if (currentStyle.GetURL() != attr.GetURL()) { // Clash of attr - mark as such clashingAttr.AddFlag(wxTEXT_ATTR_URL); currentStyle.RemoveFlag(wxTEXT_ATTR_URL); } } else { currentStyle.SetURL(attr.GetURL()); } } else if (!attr.HasURL() && currentStyle.HasURL()) { clashingAttr.AddFlag(wxTEXT_ATTR_URL); currentStyle.RemoveFlag(wxTEXT_ATTR_URL); } if (attr.HasTextEffects() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_EFFECTS)) { if (currentStyle.HasTextEffects()) { // We need to find the bits in the new attr that are different: // just look at those bits that are specified by the new attr. // We need to remove the bits and flags that are not common between current attr // and new attr. In so doing we need to take account of the styles absent from one or more of the // previous styles. int currentRelevantTextEffects = currentStyle.GetTextEffects() & attr.GetTextEffectFlags(); int newRelevantTextEffects = attr.GetTextEffects() & attr.GetTextEffectFlags(); if (currentRelevantTextEffects != newRelevantTextEffects) { // Find the text effects that were different, using XOR int differentEffects = currentRelevantTextEffects ^ newRelevantTextEffects; // Clash of attr - mark as such clashingAttr.SetTextEffectFlags(clashingAttr.GetTextEffectFlags() | differentEffects); currentStyle.SetTextEffectFlags(currentStyle.GetTextEffectFlags() & ~differentEffects); } } else { currentStyle.SetTextEffects(attr.GetTextEffects()); currentStyle.SetTextEffectFlags(attr.GetTextEffectFlags()); } // Mask out the flags and values that cannot be common because they were absent in one or more objecrs // that we've looked at so far currentStyle.SetTextEffects(currentStyle.GetTextEffects() & ~absentAttr.GetTextEffectFlags()); currentStyle.SetTextEffectFlags(currentStyle.GetTextEffectFlags() & ~absentAttr.GetTextEffectFlags()); if (currentStyle.GetTextEffectFlags() == 0) currentStyle.RemoveFlag(wxTEXT_ATTR_EFFECTS); } else if (!attr.HasTextEffects() && currentStyle.HasTextEffects()) { clashingAttr.AddFlag(wxTEXT_ATTR_EFFECTS); currentStyle.RemoveFlag(wxTEXT_ATTR_EFFECTS); } if (attr.HasOutlineLevel() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_OUTLINE_LEVEL)) { if (currentStyle.HasOutlineLevel()) { if (currentStyle.GetOutlineLevel() != attr.GetOutlineLevel()) { // Clash of attr - mark as such clashingAttr.AddFlag(wxTEXT_ATTR_OUTLINE_LEVEL); currentStyle.RemoveFlag(wxTEXT_ATTR_OUTLINE_LEVEL); } } else currentStyle.SetOutlineLevel(attr.GetOutlineLevel()); } else if (!attr.HasOutlineLevel() && currentStyle.HasOutlineLevel()) { clashingAttr.AddFlag(wxTEXT_ATTR_OUTLINE_LEVEL); currentStyle.RemoveFlag(wxTEXT_ATTR_OUTLINE_LEVEL); } } WX_DEFINE_OBJARRAY(wxRichTextVariantArray); WX_DEFINE_OBJARRAY(wxRichTextAttrArray); WX_DEFINE_OBJARRAY(wxRichTextRectArray); IMPLEMENT_DYNAMIC_CLASS(wxRichTextProperties, wxObject) bool wxRichTextProperties::operator==(const wxRichTextProperties& props) const { if (m_properties.GetCount() != props.GetCount()) return false; size_t i; for (i = 0; i < m_properties.GetCount(); i++) { const wxVariant& var1 = m_properties[i]; int idx = props.Find(var1.GetName()); if (idx == -1) return false; const wxVariant& var2 = props.m_properties[idx]; if (!(var1 == var2)) return false; } return true; } wxArrayString wxRichTextProperties::GetPropertyNames() const { wxArrayString arr; size_t i; for (i = 0; i < m_properties.GetCount(); i++) { arr.Add(m_properties[i].GetName()); } return arr; } int wxRichTextProperties::Find(const wxString& name) const { size_t i; for (i = 0; i < m_properties.GetCount(); i++) { if (m_properties[i].GetName() == name) return (int) i; } return -1; } bool wxRichTextProperties::Remove(const wxString& name) { int idx = Find(name); if (idx != -1) { m_properties.RemoveAt(idx); return true; } else return false; } wxVariant* wxRichTextProperties::FindOrCreateProperty(const wxString& name) { int idx = Find(name); if (idx == wxNOT_FOUND) SetProperty(name, wxString()); idx = Find(name); if (idx != wxNOT_FOUND) { return & (*this)[idx]; } else return NULL; } const wxVariant& wxRichTextProperties::GetProperty(const wxString& name) const { static const wxVariant nullVariant; int idx = Find(name); if (idx != -1) return m_properties[idx]; else return nullVariant; } wxString wxRichTextProperties::GetPropertyString(const wxString& name) const { return GetProperty(name).GetString(); } long wxRichTextProperties::GetPropertyLong(const wxString& name) const { return GetProperty(name).GetLong(); } bool wxRichTextProperties::GetPropertyBool(const wxString& name) const { return GetProperty(name).GetBool(); } double wxRichTextProperties::GetPropertyDouble(const wxString& name) const { return GetProperty(name).GetDouble(); } void wxRichTextProperties::SetProperty(const wxVariant& variant) { wxASSERT(!variant.GetName().IsEmpty()); int idx = Find(variant.GetName()); if (idx == -1) m_properties.Add(variant); else m_properties[idx] = variant; } void wxRichTextProperties::SetProperty(const wxString& name, const wxVariant& variant) { int idx = Find(name); wxVariant var(variant); var.SetName(name); if (idx == -1) m_properties.Add(var); else m_properties[idx] = var; } void wxRichTextProperties::SetProperty(const wxString& name, const wxString& value) { SetProperty(name, wxVariant(value, name)); } void wxRichTextProperties::SetProperty(const wxString& name, long value) { SetProperty(name, wxVariant(value, name)); } void wxRichTextProperties::SetProperty(const wxString& name, double value) { SetProperty(name, wxVariant(value, name)); } void wxRichTextProperties::SetProperty(const wxString& name, bool value) { SetProperty(name, wxVariant(value, name)); } void wxRichTextProperties::RemoveProperties(const wxRichTextProperties& properties) { size_t i; for (i = 0; i < properties.GetCount(); i++) { wxString name = properties.GetProperties()[i].GetName(); if (HasProperty(name)) Remove(name); } } void wxRichTextProperties::MergeProperties(const wxRichTextProperties& properties) { size_t i; for (i = 0; i < properties.GetCount(); i++) { SetProperty(properties.GetProperties()[i]); } } wxRichTextObject* wxRichTextObjectAddress::GetObject(wxRichTextParagraphLayoutBox* topLevelContainer) const { if (m_address.GetCount() == 0) return topLevelContainer; wxRichTextCompositeObject* p = topLevelContainer; size_t i = 0; while (p && i < m_address.GetCount()) { int pos = m_address[i]; wxASSERT(pos >= 0 && pos < (int) p->GetChildren().GetCount()); if (pos < 0 || pos >= (int) p->GetChildren().GetCount()) return NULL; wxRichTextObject* p1 = p->GetChild(pos); if (i == (m_address.GetCount()-1)) return p1; p = wxDynamicCast(p1, wxRichTextCompositeObject); i ++; } return NULL; } bool wxRichTextObjectAddress::Create(wxRichTextParagraphLayoutBox* topLevelContainer, wxRichTextObject* obj) { m_address.Clear(); if (topLevelContainer == obj) return true; wxRichTextObject* o = obj; while (o) { wxRichTextCompositeObject* p = wxDynamicCast(o->GetParent(), wxRichTextCompositeObject); if (!p) return false; int pos = p->GetChildren().IndexOf(o); if (pos == -1) return false; m_address.Insert(pos, 0); if (p == topLevelContainer) return true; o = p; } return false; } // Equality test bool wxRichTextSelection::operator==(const wxRichTextSelection& sel) const { if (m_container != sel.m_container) return false; if (m_ranges.GetCount() != sel.m_ranges.GetCount()) return false; size_t i; for (i = 0; i < m_ranges.GetCount(); i++) if (!(m_ranges[i] == sel.m_ranges[i])) return false; return true; } // Get the selections appropriate to the specified object, if any; returns wxRICHTEXT_NO_SELECTION if none // or none at the level of the object's container. wxRichTextRangeArray wxRichTextSelection::GetSelectionForObject(wxRichTextObject* obj) const { if (IsValid()) { wxRichTextParagraphLayoutBox* container = obj->GetParentContainer(); if (container == m_container) return m_ranges; container = obj->GetContainer(); while (container) { if (container->GetParent()) { // If we found that our object's container is within the range of // a selection higher up, then assume the whole original object // is also selected. wxRichTextParagraphLayoutBox* parentContainer = container->GetParentContainer(); if (parentContainer == m_container) { if (WithinSelection(container->GetRange().GetStart(), m_ranges)) { wxRichTextRangeArray ranges; ranges.Add(obj->GetRange()); return ranges; } } container = parentContainer; } else { container = NULL; break; } } } return wxRichTextRangeArray(); } // Is the given position within the selection? bool wxRichTextSelection::WithinSelection(long pos, wxRichTextObject* obj) const { if (!IsValid()) return false; else { wxRichTextRangeArray selectionRanges = GetSelectionForObject(obj); return WithinSelection(pos, selectionRanges); } } // Is the given position within the selection range? bool wxRichTextSelection::WithinSelection(long pos, const wxRichTextRangeArray& ranges) { size_t i; for (i = 0; i < ranges.GetCount(); i++) { const wxRichTextRange& range = ranges[i]; if (pos >= range.GetStart() && pos <= range.GetEnd()) return true; } return false; } // Is the given range completely within the selection range? bool wxRichTextSelection::WithinSelection(const wxRichTextRange& range, const wxRichTextRangeArray& ranges) { size_t i; for (i = 0; i < ranges.GetCount(); i++) { const wxRichTextRange& eachRange = ranges[i]; if (range.IsWithin(eachRange)) return true; } return false; } IMPLEMENT_CLASS(wxRichTextDrawingHandler, wxObject) IMPLEMENT_CLASS(wxRichTextDrawingContext, wxObject) wxRichTextDrawingContext::wxRichTextDrawingContext(wxRichTextBuffer* buffer) { Init(); m_buffer = buffer; if (m_buffer && m_buffer->GetRichTextCtrl()) EnableVirtualAttributes(m_buffer->GetRichTextCtrl()->GetVirtualAttributesEnabled()); } bool wxRichTextDrawingContext::HasVirtualAttributes(wxRichTextObject* obj) const { if (!GetVirtualAttributesEnabled()) return false; wxList::compatibility_iterator node = m_buffer->GetDrawingHandlers().GetFirst(); while (node) { wxRichTextDrawingHandler *handler = (wxRichTextDrawingHandler*)node->GetData(); if (handler->HasVirtualAttributes(obj)) return true; node = node->GetNext(); } return false; } wxRichTextAttr wxRichTextDrawingContext::GetVirtualAttributes(wxRichTextObject* obj) const { wxRichTextAttr attr; if (!GetVirtualAttributesEnabled()) return attr; // We apply all handlers, so we can may combine several different attributes wxList::compatibility_iterator node = m_buffer->GetDrawingHandlers().GetFirst(); while (node) { wxRichTextDrawingHandler *handler = (wxRichTextDrawingHandler*)node->GetData(); if (handler->HasVirtualAttributes(obj)) { bool success = handler->GetVirtualAttributes(attr, obj); wxASSERT(success); wxUnusedVar(success); } node = node->GetNext(); } return attr; } bool wxRichTextDrawingContext::ApplyVirtualAttributes(wxRichTextAttr& attr, wxRichTextObject* obj) const { if (!GetVirtualAttributesEnabled()) return false; if (HasVirtualAttributes(obj)) { wxRichTextAttr a(GetVirtualAttributes(obj)); attr.Apply(a); return true; } else return false; } int wxRichTextDrawingContext::GetVirtualSubobjectAttributesCount(wxRichTextObject* obj) const { if (!GetVirtualAttributesEnabled()) return 0; wxList::compatibility_iterator node = m_buffer->GetDrawingHandlers().GetFirst(); while (node) { wxRichTextDrawingHandler *handler = (wxRichTextDrawingHandler*)node->GetData(); int count = handler->GetVirtualSubobjectAttributesCount(obj); if (count > 0) return count; node = node->GetNext(); } return 0; } int wxRichTextDrawingContext::GetVirtualSubobjectAttributes(wxRichTextObject* obj, wxArrayInt& positions, wxRichTextAttrArray& attributes) const { if (!GetVirtualAttributesEnabled()) return 0; wxList::compatibility_iterator node = m_buffer->GetDrawingHandlers().GetFirst(); while (node) { wxRichTextDrawingHandler *handler = (wxRichTextDrawingHandler*)node->GetData(); if (handler->GetVirtualSubobjectAttributes(obj, positions, attributes)) return positions.GetCount(); node = node->GetNext(); } return 0; } bool wxRichTextDrawingContext::HasVirtualText(const wxRichTextPlainText* obj) const { if (!GetVirtualAttributesEnabled()) return false; wxList::compatibility_iterator node = m_buffer->GetDrawingHandlers().GetFirst(); while (node) { wxRichTextDrawingHandler *handler = (wxRichTextDrawingHandler*)node->GetData(); if (handler->HasVirtualText(obj)) return true; node = node->GetNext(); } return false; } bool wxRichTextDrawingContext::GetVirtualText(const wxRichTextPlainText* obj, wxString& text) const { if (!GetVirtualAttributesEnabled()) return false; wxList::compatibility_iterator node = m_buffer->GetDrawingHandlers().GetFirst(); while (node) { wxRichTextDrawingHandler *handler = (wxRichTextDrawingHandler*)node->GetData(); if (handler->GetVirtualText(obj, text)) return true; node = node->GetNext(); } return false; } /// Adds a handler to the end void wxRichTextBuffer::AddDrawingHandler(wxRichTextDrawingHandler *handler) { sm_drawingHandlers.Append(handler); } /// Inserts a handler at the front void wxRichTextBuffer::InsertDrawingHandler(wxRichTextDrawingHandler *handler) { sm_drawingHandlers.Insert( handler ); } /// Removes a handler bool wxRichTextBuffer::RemoveDrawingHandler(const wxString& name) { wxRichTextDrawingHandler *handler = FindDrawingHandler(name); if (handler) { sm_drawingHandlers.DeleteObject(handler); delete handler; return true; } else return false; } wxRichTextDrawingHandler* wxRichTextBuffer::FindDrawingHandler(const wxString& name) { wxList::compatibility_iterator node = sm_drawingHandlers.GetFirst(); while (node) { wxRichTextDrawingHandler *handler = (wxRichTextDrawingHandler*)node->GetData(); if (handler->GetName().Lower() == name.Lower()) return handler; node = node->GetNext(); } return NULL; } void wxRichTextBuffer::CleanUpDrawingHandlers() { wxList::compatibility_iterator node = sm_drawingHandlers.GetFirst(); while (node) { wxRichTextDrawingHandler* handler = (wxRichTextDrawingHandler*)node->GetData(); wxList::compatibility_iterator next = node->GetNext(); delete handler; node = next; } sm_drawingHandlers.Clear(); } void wxRichTextBuffer::AddFieldType(wxRichTextFieldType *fieldType) { sm_fieldTypes[fieldType->GetName()] = fieldType; } bool wxRichTextBuffer::RemoveFieldType(const wxString& name) { wxRichTextFieldTypeHashMap::iterator it = sm_fieldTypes.find(name); if (it == sm_fieldTypes.end()) return false; else { wxRichTextFieldType* fieldType = it->second; sm_fieldTypes.erase(it); delete fieldType; return true; } } wxRichTextFieldType *wxRichTextBuffer::FindFieldType(const wxString& name) { wxRichTextFieldTypeHashMap::iterator it = sm_fieldTypes.find(name); if (it == sm_fieldTypes.end()) return NULL; else return it->second; } void wxRichTextBuffer::CleanUpFieldTypes() { wxRichTextFieldTypeHashMap::iterator it; for( it = sm_fieldTypes.begin(); it != sm_fieldTypes.end(); ++it ) { wxRichTextFieldType* fieldType = it->second; delete fieldType; } sm_fieldTypes.clear(); } #endif // wxUSE_RICHTEXT