Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
15793 lines (13329 sloc) 549 KB
/////////////////////////////////////////////////////////////////////////////
// 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/math.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/richtext/bitmaps/image_placeholder24x24.xpm"
#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, wxRichTextObject** contextObj, 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, wxRichTextObject** contextObj, 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 function
* 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())
{
// Our object will fit before this object
if (array[i]->startY - last >= height)
{
// If we are fitting after another object, add a pixel since last
// is the bottom of another object.
if (last != start)
last ++;
return last;
}
last = array[i]->endY;
i++;
}
// If we are fitting after another object, add a pixel since last
// is the bottom of another object.
if (last != start)
last ++;
return last;
}
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
{
wxFAIL_MSG("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;
wxFAIL_MSG("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();
if (obj->IsTopLevel())
r = obj->GetOwnRange();
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& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, 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)
{
if (array[i]->anchor->IsTopLevel())
{
int result = array[i]->anchor->HitTest(dc, context, pt, textPosition, obj, contextObj, 0);
if (result != wxRICHTEXT_HITTEST_NONE)
{
return result;
}
}
textPosition = array[i]->anchor->GetRange().GetStart();
* obj = array[i]->anchor;
* contextObj = array[i]->anchor->GetParentContainer();
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, wxRichTextObject** contextObj, int flags)
{
int ret = HitTestFloat(m_left, dc, context, pt, textPosition, obj, contextObj, flags);
if (ret == wxRICHTEXT_HITTEST_NONE)
{
ret = HitTestFloat(m_right, dc, context, pt, textPosition, obj, contextObj, 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.
*/
wxIMPLEMENT_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);
if (attr.GetTextBoxAttr().GetShadow().IsValid())
{
wxTextAttrDimensionConverter converter(dc, buffer ? buffer->GetScale() : 1.0);
wxColour shadowColour;
if (attr.GetTextBoxAttr().GetShadow().HasColour())
shadowColour = attr.GetTextBoxAttr().GetShadow().GetColour();
else
shadowColour = *wxLIGHT_GREY;
if (attr.GetTextBoxAttr().GetShadow().GetOpacity().IsValid())
{
// Let's pretend our background is always white. Calculate a colour value
// from this and the opacity.
double p = attr.GetTextBoxAttr().GetShadow().GetOpacity().GetValue() / 100.0;
shadowColour.Set(wxRound((1.0 - p)*255 + p*shadowColour.Red()),
wxRound((1.0 - p)*255 + p*shadowColour.Green()),
wxRound((1.0 - p)*255 + p*shadowColour.Blue()));
}
wxRect shadowRect = borderRect;
if (attr.GetTextBoxAttr().GetShadow().GetOffsetX().IsValid())
{
int pxX = converter.GetPixels(attr.GetTextBoxAttr().GetShadow().GetOffsetX());
shadowRect.x += pxX;
}
if (attr.GetTextBoxAttr().GetShadow().GetOffsetY().IsValid())
{
int pxY = converter.GetPixels(attr.GetTextBoxAttr().GetShadow().GetOffsetY());
shadowRect.y += pxY;
}
if (attr.GetTextBoxAttr().GetShadow().GetSpread().IsValid())
{
int pxSpread = converter.GetPixels(attr.GetTextBoxAttr().GetShadow().GetSpread());
shadowRect.x -= pxSpread;
shadowRect.y -= pxSpread;
shadowRect.width += 2*pxSpread;
shadowRect.height += 2*pxSpread;
}
dc.SetPen(*wxTRANSPARENT_PEN);
dc.SetBrush(wxBrush(shadowColour));
if (attr.GetTextBoxAttr().HasCornerRadius() && attr.GetTextBoxAttr().GetCornerRadius().GetValue() > 0)
{
wxTextAttrDimensionConverter radConverter(dc, buffer ? buffer->GetScale() : 1.0);
int cornerRadius = radConverter.GetPixels(attr.GetTextBoxAttr().GetCornerRadius());
if (cornerRadius > 0)
{
dc.DrawRoundedRectangle(shadowRect, cornerRadius);
}
else
dc.DrawRectangle(shadowRect);
}
else
dc.DrawRectangle(shadowRect);
// If there's no box colour, draw over the shadow in the nearest available colour
if (!attr.HasBackgroundColour())
{
wxColour bgColour;
if (obj)
{
wxRichTextCompositeObject* composite = obj->GetParentContainer();
if (composite && composite->GetAttributes().HasBackgroundColour())
bgColour = composite->GetAttributes().GetBackgroundColour();
}
if (!bgColour.IsOk() && buffer)
bgColour = buffer->GetAttributes().GetBackgroundColour();
if (!bgColour.IsOk())
bgColour = *wxWHITE;
dc.SetBrush(wxBrush(bgColour));
dc.DrawRectangle(borderRect);
}
}
// 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);
pen.SetJoin(wxJOIN_MITER);
wxBrush brush(colour);
dc.SetPen(pen);
dc.SetBrush(brush);
if (attr.GetTextBoxAttr().HasCornerRadius() && attr.GetTextBoxAttr().GetCornerRadius().GetValue() > 0)
{
wxTextAttrDimensionConverter converter(dc, buffer ? buffer->GetScale() : 1.0);
int cornerRadius = converter.GetPixels(attr.GetTextBoxAttr().GetCornerRadius());
if (cornerRadius > 0)
{
dc.DrawRoundedRectangle(borderRect, cornerRadius);
}
}
else
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, attr, editBorderAttr.GetTextBoxAttr().GetBorder(), borderRect, flags);
}
if (attr.GetTextBoxAttr().GetBorder().IsValid())
DrawBorder(dc, buffer, attr, attr.GetTextBoxAttr().GetBorder(), borderRect);
if (attr.GetTextBoxAttr().GetOutline().IsValid())
DrawBorder(dc, buffer, attr, attr.GetTextBoxAttr().GetOutline(), outlineRect);
return true;
}
// Draw a border
bool wxRichTextObject::DrawBorder(wxDC& dc, wxRichTextBuffer* buffer, const wxRichTextAttr& attr, const wxTextAttrBorders& borders, const wxRect& rect, int WXUNUSED(flags))
{
int borderLeft = 0, borderRight = 0, borderTop = 0, borderBottom = 0;
wxTextAttrDimensionConverter converter(dc, buffer ? buffer->GetScale() : 1.0);
// If we have a corner radius, assume all borders are the same, and draw a rounded outline.
if (attr.GetTextBoxAttr().HasCornerRadius() && borders.GetLeft().IsValid() && (borders.GetLeft().GetWidth().GetValue() > 0) && borders.GetLeft().GetStyle() != wxTEXT_BOX_ATTR_BORDER_NONE)
{
int cornerRadius = converter.GetPixels(attr.GetTextBoxAttr().GetCornerRadius());
if (cornerRadius > 0)
{
borderLeft = converter.GetPixels(borders.GetLeft().GetWidth());
// Compensate for border thickness, since the rectangle borders are centred on the rect
wxRect rect2(rect);
if (borderLeft > 1)
{
int inc = (int) ((double(borderLeft) / 2.0) + 0.5);
rect2.x += inc;
rect2.y += inc;
rect2.width -= (2*inc - 1);
rect2.height -= (2*inc - 1);
}
wxColour col(borders.GetLeft().GetColour());
wxPenStyle penStyle = wxPENSTYLE_SOLID;
if (borders.GetLeft().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DOTTED)
penStyle = wxPENSTYLE_DOT;
else if (borders.GetLeft().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DASHED)
penStyle = wxPENSTYLE_LONG_DASH;
wxPen pen(col, borderLeft, penStyle);
dc.SetPen(pen);
dc.SetBrush(*wxTRANSPARENT_BRUSH);
dc.DrawRoundedRectangle(rect2, cornerRadius);
return true;
}
}
// Don't do this, since it can mess up cell drawing in tables when
// there are inconsistencies between rectangle and line drawing.
#if 0
// Draw the border in one go if all the borders are the same
if (borders.GetLeft().IsValid() && (borders.GetLeft().GetWidth().GetValue() > 0) && borders.GetTop().IsValid() && borders.GetRight().IsValid() &&borders.GetBottom().IsValid() &&
(borders.GetLeft() == borders.GetTop()) && (borders.GetLeft() == borders.GetRight()) && (borders.GetLeft() == borders.GetBottom()))
{
borderLeft = converter.GetPixels(borders.GetLeft().GetWidth());
// Compensate for border thickness, since the rectangle borders are centred on the rect
wxRect rect2(rect);
if (borderLeft > 1)
{
int inc = (int) ((double(borderLeft) / 2.0) + 0.5);
rect2.x += inc;
rect2.y += inc;
rect2.width -= (2*inc - 1);
rect2.height -= (2*inc - 1);
}
wxColour col(borders.GetLeft().GetColour());
wxPenStyle penStyle = wxPENSTYLE_SOLID;
if (borders.GetLeft().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DOTTED)
penStyle = wxPENSTYLE_DOT;
else if (borders.GetLeft().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DASHED)
penStyle = wxPENSTYLE_LONG_DASH;
wxPen pen(col, borderLeft, penStyle);
pen.SetJoin(wxJOIN_MITER);
dc.SetPen(pen);
dc.SetBrush(*wxTRANSPARENT_BRUSH);
dc.DrawRectangle(rect);
return true;
}
#endif
if (borders.GetLeft().IsValid() && (borders.GetLeft().GetWidth().GetValue() > 0) && (borders.GetLeft().GetStyle() != wxTEXT_BOX_ATTR_BORDER_NONE))
{
borderLeft = converter.GetPixels(borders.GetLeft().GetWidth());
wxColour col(borders.GetLeft().GetColour());
wxPenStyle penStyle = wxPENSTYLE_SOLID;
if (borders.GetLeft().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DOTTED)
penStyle = wxPENSTYLE_DOT;
else if (borders.GetLeft().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DASHED)
penStyle = wxPENSTYLE_LONG_DASH;
if (borderLeft == 1 || penStyle != wxPENSTYLE_SOLID)
{
wxPen pen(col, borderLeft, penStyle);
dc.SetPen(pen);
// Note that the last point is not drawn, at least on GTK+ and Windows.
// On Mac, we must compensate.
int inc = 0;
#ifdef __WXMAC__
inc = 1;
#endif
dc.DrawLine(rect.x, rect.y, rect.x, rect.y + rect.height - inc);
}
else
{
wxPen pen(col);
pen.SetJoin(wxJOIN_MITER);
wxBrush brush(col);
dc.SetPen(pen);
dc.SetBrush(brush);
dc.DrawRectangle(rect.x, rect.y, borderLeft, rect.height);
}
}
if (borders.GetRight().IsValid() && (borders.GetRight().GetWidth().GetValue() > 0) && (borders.GetRight().GetStyle() != wxTEXT_BOX_ATTR_BORDER_NONE))
{
borderRight = converter.GetPixels(borders.GetRight().GetWidth());
wxColour col(borders.GetRight().GetColour());
wxPenStyle penStyle = wxPENSTYLE_SOLID;
if (borders.GetRight().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DOTTED)
penStyle = wxPENSTYLE_DOT;
else if (borders.GetRight().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DASHED)
penStyle = wxPENSTYLE_LONG_DASH;
if (borderRight == 1 || penStyle != wxPENSTYLE_SOLID)
{
wxPen pen(col, borderRight, penStyle);
dc.SetPen(pen);
// See note above.
int inc = 0;
#ifdef __WXMAC__
inc = 1;
#endif
dc.DrawLine(rect.x + rect.width - 1, rect.y, rect.x + rect.width - 1, rect.y + rect.height - inc);
}
else
{
wxPen pen(col);
pen.SetJoin(wxJOIN_MITER);
wxBrush brush(col);
dc.SetPen(pen);
dc.SetBrush(brush);
dc.DrawRectangle(rect.x + rect.width - borderRight, rect.y, borderRight, rect.height);
}
}
if (borders.GetTop().IsValid() && (borders.GetTop().GetWidth().GetValue() > 0) && (borders.GetTop().GetStyle() != wxTEXT_BOX_ATTR_BORDER_NONE))
{
borderTop = converter.GetPixels(borders.GetTop().GetWidth());
wxColour col(borders.GetTop().GetColour());
wxPenStyle penStyle = wxPENSTYLE_SOLID;
if (borders.GetTop().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DOTTED)
penStyle = wxPENSTYLE_DOT;
else if (borders.GetTop().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DASHED)
penStyle = wxPENSTYLE_LONG_DASH;
if (borderTop == 1 || penStyle != wxPENSTYLE_SOLID)
{
wxPen pen(col, borderTop, penStyle);
dc.SetPen(pen);
// See note above.
int inc = 0;
#ifdef __WXMAC__
inc = 1;
#endif
dc.DrawLine(rect.x, rect.y, rect.x + rect.width - inc, rect.y);
}
else
{
wxPen pen(col);
pen.SetJoin(wxJOIN_MITER);
wxBrush brush(col);
dc.SetPen(pen);
dc.SetBrush(brush);
dc.DrawRectangle(rect.x, rect.y, rect.width, borderTop);
}
}
if (borders.GetBottom().IsValid() && (borders.GetBottom().GetWidth().GetValue() > 0) && (borders.GetBottom().GetStyle() != wxTEXT_BOX_ATTR_BORDER_NONE))
{
borderBottom = converter.GetPixels(borders.GetBottom().GetWidth());
wxColour col(borders.GetBottom().GetColour());
wxPenStyle penStyle = wxPENSTYLE_SOLID;
if (borders.GetBottom().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DOTTED)
penStyle = wxPENSTYLE_DOT;
else if (borders.GetBottom().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DASHED)
penStyle = wxPENSTYLE_LONG_DASH;
if (borderBottom == 1 || penStyle != wxPENSTYLE_SOLID)
{
wxPen pen(col, borderBottom, penStyle);
dc.SetPen(pen);
// See note above.
int inc = 0;
#ifdef __WXMAC__
inc = 1;
#endif
dc.DrawLine(rect.x, rect.y + rect.height - 1, rect.x + rect.width - inc, rect.y + rect.height - 1);
}
else
{
wxPen pen(col);
pen.SetJoin(wxJOIN_MITER);
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);
#if 0
wxRect originalAvailableRect = availableChildRect;
#endif
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)
{
if (!attr.HasAlignment() || attr.GetAlignment() == wxTEXT_ALIGNMENT_LEFT)
{
// 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);
Layout(dc, context, availableChildRect, availableContainerSpace, style);
}
#if 0
// 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);
#endif
}
/*
__________________
| ____________ |
| | | |
*/
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.
*/
wxIMPLEMENT_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 between 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))
{
if (child->IsTopLevel())
childRange = child->GetOwnRange();
else
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)
{
wxRichTextObject::Dump(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())
{
// Not sure why we did this, but it stops updated layout happening for floating objects.
#if 0
if (wxRichTextBuffer::GetFloatingLayoutMode() && child->IsFloating() && GetBuffer()->GetFloatCollector() && GetBuffer()->GetFloatCollector()->HasFloat(child))
{
// Don't invalidate subhierarchy if we've already been laid out
}
else
#endif
{
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.
*/
wxIMPLEMENT_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, contextObj, flags);
if (ret != wxRICHTEXT_HITTEST_NONE)
{
return ret;
}
}
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)
{
context.SetLayingOut(false);
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()))))
{
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->IsShown() && !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)
{
context.SetLayingOut(true);
Move(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 or if we're asked to
if (!GetParent() || (style & wxRICHTEXT_FIXED_WIDTH))
attr.GetTextBoxAttr().GetWidth().SetValue(rect.GetWidth(), wxTEXT_ATTR_UNITS_PIXELS);
// Fix the height to the passed rect height if we're asked to
if (style & wxRICHTEXT_FIXED_HEIGHT)
attr.GetTextBoxAttr().GetHeight().SetValue(rect.GetHeight(), wxTEXT_ATTR_UNITS_PIXELS);
// Don't pass fixed width/height styles to children
style &= ~(wxRICHTEXT_FIXED_WIDTH|wxRICHTEXT_FIXED_HEIGHT);
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, start quick layout.
if (!hasVerticalAlignment && formatRect && child->GetPosition().y > rect.GetBottom())
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.
// Lay out paragraphs until they are (and were) not affected
// by floating objects from above the paragraphs.
if (wxRichTextBuffer::GetFloatingLayoutMode())
{
while (node)
{
child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
if (child)
{
int oldImpactedByFloats = child->GetImpactedByFloatingObjects();
child->SetImpactedByFloatingObjects(-1);
child->LayoutToBestSize(dc, context, GetBuffer(),
attr, child->GetAttributes(), availableSpace, rect, style&~wxRICHTEXT_LAYOUT_SPECIFIED_RECT);
availableSpace.y += child->GetCachedSize().y;
maxWidth = wxMax(maxWidth, child->GetCachedSize().x);
maxMinWidth = wxMax(maxMinWidth, child->GetMinSize().x);
maxMaxWidth = wxMax(maxMaxWidth, child->GetMaxSize().x);
int newImpactedByFloats = child->GetImpactedByFloatingObjects();
// We can stop laying out if this paragraph is unaffected by floating
// objects, and was previously too.
if (oldImpactedByFloats == 0 && newImpactedByFloats == 0)
{
node = node->GetNext();
break;
}
}
node = node->GetNext();
}
}
int inc = 0;
if (node)
{
child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
inc = availableSpace.y - child->GetPosition().y;
}
while (node)
{
wxRichTextParagraph* nodeChild = wxDynamicCast(node->GetData(), wxRichTextParagraph);
if (nodeChild)
{
if (nodeChild->GetLines().GetCount() == 0)
{
nodeChild->SetImpactedByFloatingObjects(-1);
// 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
nodeChild->LayoutToBestSize(dc, context, GetBuffer(),
attr, nodeChild->GetAttributes(), availableSpace, rect, style&~wxRICHTEXT_LAYOUT_SPECIFIED_RECT);
}
else
{
if (wxRichTextBuffer::GetFloatingLayoutMode() && GetFloatCollector())
GetFloatCollector()->CollectFloat(nodeChild);
nodeChild->Move(wxPoint(nodeChild->GetPosition().x, nodeChild->GetPosition().y + inc));
}
availableSpace.y += nodeChild->GetCachedSize().y;
maxWidth = wxMax(maxWidth, nodeChild->GetCachedSize().x);
maxMinWidth = wxMax(maxMinWidth, nodeChild->GetMinSize().x);
maxMaxWidth = wxMax(maxMaxWidth, nodeChild->GetMaxSize().x);
}
node = node->GetNext();
}
break;
}
}
node = node->GetNext();
}
int maxContentHeight = 0;
node = m_children.GetLast();
if (node && node->GetData()->IsShown())
{
wxRichTextObject* child = node->GetData();
maxHeight = child->GetPosition().y - (GetPosition().y + topMargin) + child->GetCachedSize().y;
maxContentHeight = maxHeight;
}
else
maxHeight = 0; // topMargin + bottomMargin;
// Check the bottom edge of any floating object
if (wxRichTextBuffer::GetFloatingLayoutMode() && GetFloatCollector() && GetFloatCollector()->HasFloats())
{
// The floating objects are positioned relative to entire buffer, not this box
int maxFloatHeight = GetFloatCollector()->GetLastRectBottom() - GetPosition().y - topMargin;
if (maxFloatHeight > maxHeight)
maxHeight = maxFloatHeight;
}
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 = GetCachedSize().y - topMargin - bottomMargin - maxContentHeight;
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 childNode = m_children.GetFirst();
while (childNode)
{
wxRichTextParagraph* child = wxDynamicCast(childNode->GetData(), wxRichTextParagraph);
if (child)
child->Move(wxPoint(child->GetPosition().x, child->GetPosition().y + yOffset));
childNode = childNode->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.clear();
}
}
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();
wxRichTextProperties originalProperties = para->GetProperties();
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());
para->SetProperties(firstPara->GetProperties());
}
// 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* searchPara = wxDynamicCast(i->GetData(), wxRichTextParagraph);
wxASSERT( searchPara != NULL );
finalPara = (wxRichTextParagraph*) searchPara->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());
finalPara->SetProperties(firstPara->GetProperties());
}
else if (finalPara && finalPara != para)
{
finalPara->SetAttributes(originalAttr);
finalPara->SetProperties(originalProperties);
}
return true;
}
}
else
{
// Append
wxRichTextObjectList::compatibility_iterator i = fragment.GetChildren().GetFirst();
while (i)
{
wxRichTextParagraph* searchPara = wxDynamicCast(i->GetData(), wxRichTextParagraph);
wxASSERT( searchPara != NULL );
AppendChild(searchPara->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 unusedEnd;
fragment.CalculateRange(0, unusedEnd);
// 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;
if (child->IsTopLevel())
childRange = child->GetOwnRange();
else
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());
textAttr.SetFlags(textAttr.GetFlags() & ~wxTEXT_ATTR_PARAGRAPH);
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