+ wxRichTextParagraphStyleDefinition* paraDef = styleSheet->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName());
+ wxRichTextListStyleDefinition* listDef = styleSheet->FindListStyle(para->GetAttributes().GetListStyleName());
+ if (paraDef && !listDef)
+ {
+ para->GetAttributes() = paraDef->GetStyleMergedWithBase(styleSheet);
+ foundCount ++;
+ }
+ else if (listDef && !paraDef)
+ {
+ // Set overall style defined for the list style definition
+ para->GetAttributes() = listDef->GetStyleMergedWithBase(styleSheet);
+
+ // Apply the style for this level
+ wxRichTextApplyStyle(para->GetAttributes(), * listDef->GetLevelAttributes(listDef->FindLevelForIndent(currentIndent)));
+ foundCount ++;
+ }
+ else if (listDef && paraDef)
+ {
+ // Combines overall list style, style for level, and paragraph style
+ para->GetAttributes() = listDef->CombineWithParagraphStyle(currentIndent, paraDef->GetStyleMergedWithBase(styleSheet));
+ foundCount ++;
+ }
+ }
+ else if (para->GetAttributes().GetParagraphStyleName().IsEmpty() && !para->GetAttributes().GetListStyleName().IsEmpty())
+ {
+ int currentIndent = para->GetAttributes().GetLeftIndent();
+
+ wxRichTextListStyleDefinition* listDef = styleSheet->FindListStyle(para->GetAttributes().GetListStyleName());
+
+ // Overall list definition style
+ para->GetAttributes() = listDef->GetStyleMergedWithBase(styleSheet);
+
+ // Style for this level
+ wxRichTextApplyStyle(para->GetAttributes(), * listDef->GetLevelAttributes(listDef->FindLevelForIndent(currentIndent)));
+
+ foundCount ++;
+ }
+ else if (!para->GetAttributes().GetParagraphStyleName().IsEmpty() && para->GetAttributes().GetListStyleName().IsEmpty())
+ {
+ wxRichTextParagraphStyleDefinition* def = styleSheet->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName());
+ if (def)
+ {
+ para->GetAttributes() = def->GetStyleMergedWithBase(styleSheet);
+ foundCount ++;
+ }
+ }
+
+ if (outline != -1)
+ para->GetAttributes().SetOutlineLevel(outline);
+ if (num != -1)
+ para->GetAttributes().SetBulletNumber(num);
+ }
+
+ node = node->GetNext();
+ }
+ return foundCount != 0;
+}
+
+/// Set list style
+bool wxRichTextParagraphLayoutBox::SetListStyle(const wxRichTextRange& range, wxRichTextListStyleDefinition* def, int flags, int startFrom, int specifiedLevel)
+{
+ wxRichTextBuffer* buffer = GetBuffer();
+ wxRichTextStyleSheet* styleSheet = buffer->GetStyleSheet();
+
+ bool withUndo = ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0);
+ // bool applyMinimal = ((flags & wxRICHTEXT_SETSTYLE_OPTIMIZE) != 0);
+ bool specifyLevel = ((flags & wxRICHTEXT_SETSTYLE_SPECIFY_LEVEL) != 0);
+ bool renumber = ((flags & wxRICHTEXT_SETSTYLE_RENUMBER) != 0);
+
+ // Current number, if numbering
+ int n = startFrom;
+
+ wxASSERT (!specifyLevel || (specifyLevel && (specifiedLevel >= 0)));
+
+ // If we are associated with a control, make undoable; otherwise, apply immediately
+ // to the data.
+
+ bool haveControl = (buffer->GetRichTextCtrl() != NULL);
+
+ wxRichTextAction* action = NULL;
+
+ if (haveControl && withUndo)
+ {
+ action = new wxRichTextAction(NULL, _("Change List Style"), wxRICHTEXT_CHANGE_STYLE, buffer, this, buffer->GetRichTextCtrl());
+ action->SetRange(range);
+ action->SetPosition(buffer->GetRichTextCtrl()->GetCaretPosition());
+ }
+
+ wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
+ while (node)
+ {
+ wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
+ // wxASSERT (para != NULL);
+
+ if (para && para->GetChildCount() > 0)
+ {
+ // Stop searching if we're beyond the range of interest
+ if (para->GetRange().GetStart() > range.GetEnd())
+ break;
+
+ if (!para->GetRange().IsOutside(range))
+ {
+ // We'll be using a copy of the paragraph to make style changes,
+ // not updating the buffer directly.
+ wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL);
+
+ if (haveControl && withUndo)
+ {
+ newPara = new wxRichTextParagraph(*para);
+ action->GetNewParagraphs().AppendChild(newPara);
+
+ // Also store the old ones for Undo
+ action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para));
+ }
+ else
+ newPara = para;
+
+ if (def)
+ {
+ int thisIndent = newPara->GetAttributes().GetLeftIndent();
+ int thisLevel = specifyLevel ? specifiedLevel : def->FindLevelForIndent(thisIndent);
+
+ // How is numbering going to work?
+ // If we are renumbering, or numbering for the first time, we need to keep
+ // track of the number for each level. But we might be simply applying a different
+ // list style.
+ // In Word, applying a style to several paragraphs, even if at different levels,
+ // reverts the level back to the same one. So we could do the same here.
+ // Renumbering will need to be done when we promote/demote a paragraph.
+
+ // Apply the overall list style, and item style for this level
+ wxRichTextAttr listStyle(def->GetCombinedStyleForLevel(thisLevel, styleSheet));
+ wxRichTextApplyStyle(newPara->GetAttributes(), listStyle);
+
+ // Now we need to do numbering
+ // Preserve the existing list item continuation bullet style, if any
+ if (para->GetAttributes().HasBulletStyle() && (para->GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_CONTINUATION))
+ newPara->GetAttributes().SetBulletStyle(newPara->GetAttributes().GetBulletStyle()|wxTEXT_ATTR_BULLET_STYLE_CONTINUATION);
+ else
+ {
+ // Now we need to do numbering
+ if (renumber)
+ {
+ newPara->GetAttributes().SetBulletNumber(n);
+ }
+
+ n ++;
+ }
+ }
+ else if (!newPara->GetAttributes().GetListStyleName().IsEmpty())
+ {
+ // if def is NULL, remove list style, applying any associated paragraph style
+ // to restore the attributes
+
+ newPara->GetAttributes().SetListStyleName(wxEmptyString);
+ newPara->GetAttributes().SetLeftIndent(0, 0);
+ newPara->GetAttributes().SetBulletText(wxEmptyString);
+
+ // Eliminate the main list-related attributes
+ newPara->GetAttributes().SetFlags(newPara->GetAttributes().GetFlags() & ~wxTEXT_ATTR_LEFT_INDENT & ~wxTEXT_ATTR_BULLET_STYLE & ~wxTEXT_ATTR_BULLET_NUMBER & ~wxTEXT_ATTR_BULLET_TEXT & wxTEXT_ATTR_LIST_STYLE_NAME);
+
+ if (styleSheet && !newPara->GetAttributes().GetParagraphStyleName().IsEmpty())
+ {
+ wxRichTextParagraphStyleDefinition* def = styleSheet->FindParagraphStyle(newPara->GetAttributes().GetParagraphStyleName());
+ if (def)
+ {
+ newPara->GetAttributes() = def->GetStyleMergedWithBase(styleSheet);
+ }
+ }
+ }
+ }
+ }
+
+ node = node->GetNext();
+ }
+
+ // Do action, or delay it until end of batch.
+ if (haveControl && withUndo)
+ buffer->SubmitAction(action);
+
+ return true;
+}
+
+bool wxRichTextParagraphLayoutBox::SetListStyle(const wxRichTextRange& range, const wxString& defName, int flags, int startFrom, int specifiedLevel)
+{
+ wxRichTextBuffer* buffer = GetBuffer();
+ if (buffer && buffer->GetStyleSheet())
+ {
+ wxRichTextListStyleDefinition* def = buffer->GetStyleSheet()->FindListStyle(defName);
+ if (def)
+ return SetListStyle(range, def, flags, startFrom, specifiedLevel);
+ }
+ return false;
+}
+
+/// Clear list for given range
+bool wxRichTextParagraphLayoutBox::ClearListStyle(const wxRichTextRange& range, int flags)
+{
+ return SetListStyle(range, NULL, flags);
+}
+
+/// Number/renumber any list elements in the given range
+bool wxRichTextParagraphLayoutBox::NumberList(const wxRichTextRange& range, wxRichTextListStyleDefinition* def, int flags, int startFrom, int specifiedLevel)
+{
+ return DoNumberList(range, range, 0, def, flags, startFrom, specifiedLevel);
+}
+
+/// Number/renumber any list elements in the given range. Also do promotion or demotion of items, if specified
+bool wxRichTextParagraphLayoutBox::DoNumberList(const wxRichTextRange& range, const wxRichTextRange& promotionRange, int promoteBy,
+ wxRichTextListStyleDefinition* def, int flags, int startFrom, int specifiedLevel)
+{
+ wxRichTextBuffer* buffer = GetBuffer();
+ wxRichTextStyleSheet* styleSheet = buffer->GetStyleSheet();
+
+ bool withUndo = ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0);
+ // bool applyMinimal = ((flags & wxRICHTEXT_SETSTYLE_OPTIMIZE) != 0);
+#if wxDEBUG_LEVEL
+ bool specifyLevel = ((flags & wxRICHTEXT_SETSTYLE_SPECIFY_LEVEL) != 0);
+#endif
+
+ bool renumber = ((flags & wxRICHTEXT_SETSTYLE_RENUMBER) != 0);
+
+ // Max number of levels
+ const int maxLevels = 10;
+
+ // The level we're looking at now
+ int currentLevel = -1;
+
+ // The item number for each level
+ int levels[maxLevels];
+ int i;
+
+ // Reset all numbering
+ for (i = 0; i < maxLevels; i++)
+ {
+ if (startFrom != -1)
+ levels[i] = startFrom-1;
+ else if (renumber) // start again
+ levels[i] = 0;
+ else
+ levels[i] = -1; // start from the number we found, if any
+ }
+
+#if wxDEBUG_LEVEL
+ wxASSERT(!specifyLevel || (specifyLevel && (specifiedLevel >= 0)));
+#endif
+
+ // If we are associated with a control, make undoable; otherwise, apply immediately
+ // to the data.
+
+ bool haveControl = (buffer->GetRichTextCtrl() != NULL);
+
+ wxRichTextAction* action = NULL;
+
+ if (haveControl && withUndo)
+ {
+ action = new wxRichTextAction(NULL, _("Renumber List"), wxRICHTEXT_CHANGE_STYLE, buffer, this, buffer->GetRichTextCtrl());
+ action->SetRange(range);
+ action->SetPosition(buffer->GetRichTextCtrl()->GetCaretPosition());
+ }
+
+ wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
+ while (node)
+ {
+ wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
+ // wxASSERT (para != NULL);
+
+ if (para && para->GetChildCount() > 0)
+ {
+ // Stop searching if we're beyond the range of interest
+ if (para->GetRange().GetStart() > range.GetEnd())
+ break;
+
+ if (!para->GetRange().IsOutside(range))
+ {
+ // We'll be using a copy of the paragraph to make style changes,
+ // not updating the buffer directly.
+ wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL);
+
+ if (haveControl && withUndo)
+ {
+ newPara = new wxRichTextParagraph(*para);
+ action->GetNewParagraphs().AppendChild(newPara);
+
+ // Also store the old ones for Undo
+ action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para));
+ }
+ else
+ newPara = para;
+
+ wxRichTextListStyleDefinition* defToUse = def;
+ if (!defToUse)
+ {
+ if (styleSheet && !newPara->GetAttributes().GetListStyleName().IsEmpty())
+ defToUse = styleSheet->FindListStyle(newPara->GetAttributes().GetListStyleName());
+ }
+
+ if (defToUse)
+ {
+ int thisIndent = newPara->GetAttributes().GetLeftIndent();
+ int thisLevel = defToUse->FindLevelForIndent(thisIndent);
+
+ // If we've specified a level to apply to all, change the level.
+ if (specifiedLevel != -1)
+ thisLevel = specifiedLevel;
+
+ // Do promotion if specified
+ if ((promoteBy != 0) && !para->GetRange().IsOutside(promotionRange))
+ {
+ thisLevel = thisLevel - promoteBy;
+ if (thisLevel < 0)
+ thisLevel = 0;
+ if (thisLevel > 9)
+ thisLevel = 9;
+ }
+
+ // Apply the overall list style, and item style for this level
+ wxRichTextAttr listStyle(defToUse->GetCombinedStyleForLevel(thisLevel, styleSheet));
+ wxRichTextApplyStyle(newPara->GetAttributes(), listStyle);
+
+ // Preserve the existing list item continuation bullet style, if any
+ if (para->GetAttributes().HasBulletStyle() && (para->GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_CONTINUATION))
+ newPara->GetAttributes().SetBulletStyle(newPara->GetAttributes().GetBulletStyle()|wxTEXT_ATTR_BULLET_STYLE_CONTINUATION);
+
+ // OK, we've (re)applied the style, now let's get the numbering right.
+
+ if (currentLevel == -1)
+ currentLevel = thisLevel;
+
+ // Same level as before, do nothing except increment level's number afterwards
+ if (currentLevel == thisLevel)
+ {
+ }
+ // A deeper level: start renumbering all levels after current level
+ else if (thisLevel > currentLevel)
+ {
+ for (i = currentLevel+1; i <= thisLevel; i++)
+ {
+ levels[i] = 0;
+ }
+ currentLevel = thisLevel;
+ }
+ else if (thisLevel < currentLevel)
+ {
+ currentLevel = thisLevel;
+ }
+
+ // Use the current numbering if -1 and we have a bullet number already
+ if (levels[currentLevel] == -1)
+ {
+ if (newPara->GetAttributes().HasBulletNumber())
+ levels[currentLevel] = newPara->GetAttributes().GetBulletNumber();
+ else
+ levels[currentLevel] = 1;
+ }
+ else
+ {
+ if (!(para->GetAttributes().HasBulletStyle() && (para->GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_CONTINUATION)))
+ levels[currentLevel] ++;
+ }
+
+ newPara->GetAttributes().SetBulletNumber(levels[currentLevel]);
+
+ // Create the bullet text if an outline list
+ if (listStyle.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE)
+ {
+ wxString text;
+ for (i = 0; i <= currentLevel; i++)
+ {
+ if (!text.IsEmpty())
+ text += wxT(".");
+ text += wxString::Format(wxT("%d"), levels[i]);
+ }
+ newPara->GetAttributes().SetBulletText(text);
+ }
+ }
+ }
+ }
+
+ node = node->GetNext();
+ }
+
+ // Do action, or delay it until end of batch.
+ if (haveControl && withUndo)
+ buffer->SubmitAction(action);
+
+ return true;
+}
+
+bool wxRichTextParagraphLayoutBox::NumberList(const wxRichTextRange& range, const wxString& defName, int flags, int startFrom, int specifiedLevel)
+{
+ wxRichTextBuffer* buffer = GetBuffer();
+ if (buffer->GetStyleSheet())
+ {
+ wxRichTextListStyleDefinition* def = NULL;
+ if (!defName.IsEmpty())
+ def = buffer->GetStyleSheet()->FindListStyle(defName);
+ return NumberList(range, def, flags, startFrom, specifiedLevel);
+ }
+ return false;
+}
+
+/// Promote the list items within the given range. promoteBy can be a positive or negative number, e.g. 1 or -1
+bool wxRichTextParagraphLayoutBox::PromoteList(int promoteBy, const wxRichTextRange& range, wxRichTextListStyleDefinition* def, int flags, int specifiedLevel)
+{
+ // TODO
+ // One strategy is to first work out the range within which renumbering must occur. Then could pass these two ranges
+ // to NumberList with a flag indicating promotion is required within one of the ranges.
+ // Find first and last paragraphs in range. Then for first, calculate new indentation and look back until we find
+ // a paragraph that either has no list style, or has one that is different or whose indentation is less.
+ // We start renumbering from the para after that different para we found. We specify that the numbering of that
+ // list position will start from 1.
+ // Similarly, we look after the last para in the promote range for an indentation that is less (or no list style).
+ // We can end the renumbering at this point.
+
+ // For now, only renumber within the promotion range.
+
+ return DoNumberList(range, range, promoteBy, def, flags, 1, specifiedLevel);
+}
+
+bool wxRichTextParagraphLayoutBox::PromoteList(int promoteBy, const wxRichTextRange& range, const wxString& defName, int flags, int specifiedLevel)
+{
+ wxRichTextBuffer* buffer = GetBuffer();
+ if (buffer->GetStyleSheet())
+ {
+ wxRichTextListStyleDefinition* def = NULL;
+ if (!defName.IsEmpty())
+ def = buffer->GetStyleSheet()->FindListStyle(defName);
+ return PromoteList(promoteBy, range, def, flags, specifiedLevel);
+ }
+ return false;
+}
+
+/// Fills in the attributes for numbering a paragraph after previousParagraph. It also finds the
+/// position of the paragraph that it had to start looking from.
+bool wxRichTextParagraphLayoutBox::FindNextParagraphNumber(wxRichTextParagraph* previousParagraph, wxRichTextAttr& attr) const
+{
+ // Search for a paragraph that isn't a continuation paragraph (no bullet)
+ while (previousParagraph && previousParagraph->GetAttributes().HasBulletStyle() && previousParagraph->GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_CONTINUATION)
+ {
+ wxRichTextObjectList::compatibility_iterator node = ((wxRichTextCompositeObject*) previousParagraph->GetParent())->GetChildren().Find(previousParagraph);
+ if (node)
+ {
+ node = node->GetPrevious();
+ if (node)
+ previousParagraph = wxDynamicCast(node->GetData(), wxRichTextParagraph);
+ else
+ previousParagraph = NULL;
+ }
+ else
+ previousParagraph = NULL;
+ }
+
+ if (!previousParagraph->GetAttributes().HasFlag(wxTEXT_ATTR_BULLET_STYLE) || previousParagraph->GetAttributes().GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_NONE)
+ return false;
+
+ wxRichTextBuffer* buffer = GetBuffer();
+ wxRichTextStyleSheet* styleSheet = buffer->GetStyleSheet();
+ if (styleSheet && !previousParagraph->GetAttributes().GetListStyleName().IsEmpty())
+ {
+ wxRichTextListStyleDefinition* def = styleSheet->FindListStyle(previousParagraph->GetAttributes().GetListStyleName());
+ if (def)
+ {
+ // int thisIndent = previousParagraph->GetAttributes().GetLeftIndent();
+ // int thisLevel = def->FindLevelForIndent(thisIndent);
+
+ bool isOutline = (previousParagraph->GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE) != 0;
+
+ attr.SetFlags(previousParagraph->GetAttributes().GetFlags() & (wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_BULLET_NUMBER|wxTEXT_ATTR_BULLET_TEXT|wxTEXT_ATTR_BULLET_NAME));
+ if (previousParagraph->GetAttributes().HasBulletName())
+ attr.SetBulletName(previousParagraph->GetAttributes().GetBulletName());
+ attr.SetBulletStyle(previousParagraph->GetAttributes().GetBulletStyle());
+ attr.SetListStyleName(previousParagraph->GetAttributes().GetListStyleName());
+
+ int nextNumber = previousParagraph->GetAttributes().GetBulletNumber() + 1;
+ attr.SetBulletNumber(nextNumber);
+
+ if (isOutline)
+ {
+ wxString text = previousParagraph->GetAttributes().GetBulletText();
+ if (!text.IsEmpty())
+ {
+ int pos = text.Find(wxT('.'), true);
+ if (pos != wxNOT_FOUND)
+ {
+ text = text.Mid(0, text.Length() - pos - 1);
+ }
+ else
+ text = wxEmptyString;
+ if (!text.IsEmpty())
+ text += wxT(".");
+ text += wxString::Format(wxT("%d"), nextNumber);
+ attr.SetBulletText(text);
+ }
+ }
+
+ return true;
+ }
+ else
+ return false;
+ }
+ else
+ return false;
+}
+
+/*!
+ * wxRichTextParagraph
+ * This object represents a single paragraph (or in a straight text editor, a line).
+ */
+
+IMPLEMENT_DYNAMIC_CLASS(wxRichTextParagraph, wxRichTextCompositeObject)
+
+wxArrayInt wxRichTextParagraph::sm_defaultTabs;
+
+wxRichTextParagraph::wxRichTextParagraph(wxRichTextObject* parent, wxRichTextAttr* style):
+ wxRichTextCompositeObject(parent)
+{
+ if (style)
+ SetAttributes(*style);
+}
+
+wxRichTextParagraph::wxRichTextParagraph(const wxString& text, wxRichTextObject* parent, wxRichTextAttr* paraStyle, wxRichTextAttr* charStyle):
+ wxRichTextCompositeObject(parent)
+{
+ if (paraStyle)
+ SetAttributes(*paraStyle);
+
+ AppendChild(new wxRichTextPlainText(text, this, charStyle));
+}
+
+wxRichTextParagraph::~wxRichTextParagraph()
+{
+ ClearLines();
+}
+
+/// Draw the item
+bool wxRichTextParagraph::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int WXUNUSED(descent), int style)
+{
+ if (!IsShown())
+ return true;
+
+ // Currently we don't merge these attributes with the parent, but we
+ // should consider whether we should (e.g. if we set a border colour
+ // for all paragraphs). But generally box attributes are likely to be
+ // different for different objects.
+ wxRect paraRect = GetRect();
+ wxRichTextAttr attr = GetCombinedAttributes();
+ context.ApplyVirtualAttributes(attr, this);
+
+ DrawBoxAttributes(dc, GetBuffer(), attr, paraRect);
+
+ // Draw the bullet, if any
+ if ((attr.GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_NONE) == 0 && (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_CONTINUATION) == 0)
+ {
+ if (attr.GetLeftSubIndent() != 0)
+ {
+ int spaceBeforePara = ConvertTenthsMMToPixels(dc, attr.GetParagraphSpacingBefore());
+ int leftIndent = ConvertTenthsMMToPixels(dc, attr.GetLeftIndent());
+
+ wxRichTextAttr bulletAttr(attr);
+
+ // Combine with the font of the first piece of content, if one is specified
+ if (GetChildren().GetCount() > 0)
+ {
+ wxRichTextObject* firstObj = (wxRichTextObject*) GetChildren().GetFirst()->GetData();
+ if (!firstObj->IsFloatable() && firstObj->GetAttributes().HasFont())
+ {
+ wxRichTextApplyStyle(bulletAttr, firstObj->GetAttributes());
+ }
+ }
+
+ // Get line height from first line, if any
+ wxRichTextLine* line = m_cachedLines.GetFirst() ? (wxRichTextLine* ) m_cachedLines.GetFirst()->GetData() : NULL;
+
+ wxPoint linePos;
+ int lineHeight wxDUMMY_INITIALIZE(0);
+ if (line)
+ {
+ lineHeight = line->GetSize().y;
+ linePos = line->GetPosition() + GetPosition();
+ }
+ else
+ {
+ wxFont font;
+ if (bulletAttr.HasFont() && GetBuffer())
+ font = GetBuffer()->GetFontTable().FindFont(bulletAttr);
+ else
+ font = (*wxNORMAL_FONT);
+
+ wxCheckSetFont(dc, font);
+
+ lineHeight = dc.GetCharHeight();
+ linePos = GetPosition();
+ linePos.y += spaceBeforePara;
+ }
+
+ wxRect bulletRect(GetPosition().x + leftIndent, linePos.y, linePos.x - (GetPosition().x + leftIndent), lineHeight);
+
+ if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_BITMAP)
+ {
+ if (wxRichTextBuffer::GetRenderer())
+ wxRichTextBuffer::GetRenderer()->DrawBitmapBullet(this, dc, bulletAttr, bulletRect);
+ }
+ else if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_STANDARD)
+ {
+ if (wxRichTextBuffer::GetRenderer())
+ wxRichTextBuffer::GetRenderer()->DrawStandardBullet(this, dc, bulletAttr, bulletRect);
+ }
+ else
+ {
+ wxString bulletText = GetBulletText();
+
+ if (!bulletText.empty() && wxRichTextBuffer::GetRenderer())
+ wxRichTextBuffer::GetRenderer()->DrawTextBullet(this, dc, bulletAttr, bulletRect, bulletText);
+ }
+ }
+ }
+
+ // Draw the range for each line, one object at a time.
+
+ wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
+ while (node)
+ {
+ wxRichTextLine* line = node->GetData();
+ wxRichTextRange lineRange = line->GetAbsoluteRange();
+
+ // Lines are specified relative to the paragraph
+
+ wxPoint linePosition = line->GetPosition() + GetPosition();
+
+ // Don't draw if off the screen
+ if (((style & wxRICHTEXT_DRAW_IGNORE_CACHE) != 0) || ((linePosition.y + line->GetSize().y) >= rect.y && linePosition.y <= rect.y + rect.height))
+ {
+ wxPoint objectPosition = linePosition;
+ int maxDescent = line->GetDescent();
+
+ // Loop through objects until we get to the one within range
+ wxRichTextObjectList::compatibility_iterator node2 = m_children.GetFirst();
+
+ int i = 0;
+ while (node2)
+ {
+ wxRichTextObject* child = node2->GetData();
+
+ if (!child->IsFloating() && child->GetRange().GetLength() > 0 && !child->GetRange().IsOutside(lineRange) && !lineRange.IsOutside(range))
+ {
+ // Draw this part of the line at the correct position
+ wxRichTextRange objectRange(child->GetRange());
+ objectRange.LimitTo(lineRange);
+
+ wxSize objectSize;
+ if (child->IsTopLevel())
+ {
+ objectSize = child->GetCachedSize();
+ objectRange = child->GetOwnRange();
+ }
+ else
+ {
+#if wxRICHTEXT_USE_OPTIMIZED_LINE_DRAWING && wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
+ if (i < (int) line->GetObjectSizes().GetCount())
+ {
+ objectSize.x = line->GetObjectSizes()[(size_t) i];
+ }
+ else
+#endif
+ {
+ int descent = 0;
+ child->GetRangeSize(objectRange, objectSize, descent, dc, context, wxRICHTEXT_UNFORMATTED, objectPosition);
+ }
+ }
+
+ // Use the child object's width, but the whole line's height
+ wxRect childRect(objectPosition, wxSize(objectSize.x, line->GetSize().y));
+ child->Draw(dc, context, objectRange, selection, childRect, maxDescent, style);
+
+ objectPosition.x += objectSize.x;
+ i ++;
+ }
+ else if (child->GetRange().GetStart() > lineRange.GetEnd())
+ // Can break out of inner loop now since we've passed this line's range
+ break;
+
+ node2 = node2->GetNext();
+ }
+ }
+
+ node = node->GetNext();
+ }
+
+ return true;
+}
+
+// Get the range width using partial extents calculated for the whole paragraph.
+static int wxRichTextGetRangeWidth(const wxRichTextParagraph& para, const wxRichTextRange& range, const wxArrayInt& partialExtents)
+{
+ wxASSERT(partialExtents.GetCount() >= (size_t) range.GetLength());
+
+ if (partialExtents.GetCount() < (size_t) range.GetLength())
+ return 0;
+
+ int leftMostPos = 0;
+ if (range.GetStart() - para.GetRange().GetStart() > 0)
+ leftMostPos = partialExtents[range.GetStart() - para.GetRange().GetStart() - 1];
+
+ int rightMostPos = partialExtents[range.GetEnd() - para.GetRange().GetStart()];
+
+ int w = rightMostPos - leftMostPos;
+
+ return w;
+}
+
+/// Lay the item out
+bool wxRichTextParagraph::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& parentRect, int style)
+{
+ // Deal with floating objects firstly before the normal layout
+ wxRichTextBuffer* buffer = GetBuffer();
+ wxASSERT(buffer);
+ wxRichTextFloatCollector* collector = GetContainer()->GetFloatCollector();
+ wxASSERT(collector);
+ LayoutFloat(dc, context, rect, style, collector);
+
+ wxRichTextAttr attr = GetCombinedAttributes();
+ context.ApplyVirtualAttributes(attr, this);
+
+ // ClearLines();
+
+ // Increase the size of the paragraph due to spacing
+ int spaceBeforePara = ConvertTenthsMMToPixels(dc, attr.GetParagraphSpacingBefore());
+ int spaceAfterPara = ConvertTenthsMMToPixels(dc, attr.GetParagraphSpacingAfter());
+ int leftIndent = ConvertTenthsMMToPixels(dc, attr.GetLeftIndent());
+ int leftSubIndent = ConvertTenthsMMToPixels(dc, attr.GetLeftSubIndent());
+ int rightIndent = ConvertTenthsMMToPixels(dc, attr.GetRightIndent());
+
+ int lineSpacing = 0;
+
+ // Let's assume line spacing of 10 is normal, 15 is 1.5, 20 is 2, etc.
+ if (attr.HasLineSpacing() && attr.GetLineSpacing() > 0 && attr.GetFont().IsOk())
+ {
+ wxCheckSetFont(dc, attr.GetFont());
+ lineSpacing = (int) (double(dc.GetCharHeight()) * (double(attr.GetLineSpacing())/10.0 - 1.0));
+ }
+
+ // Start position for each line relative to the paragraph
+ int startPositionFirstLine = leftIndent;
+ int startPositionSubsequentLines = leftIndent + leftSubIndent;
+
+ // If we have a bullet in this paragraph, the start position for the first line's text
+ // is actually leftIndent + leftSubIndent.
+ if (attr.GetBulletStyle() != wxTEXT_ATTR_BULLET_STYLE_NONE)
+ startPositionFirstLine = startPositionSubsequentLines;
+
+ long lastEndPos = GetRange().GetStart()-1;
+ long lastCompletedEndPos = lastEndPos;
+
+ int currentWidth = 0;
+ SetPosition(rect.GetPosition());
+
+ wxPoint currentPosition(0, spaceBeforePara); // We will calculate lines relative to paragraph
+ int lineHeight = 0;
+ int maxWidth = 0;
+ int maxHeight = currentPosition.y;
+ int maxAscent = 0;
+ int maxDescent = 0;
+ int lineCount = 0;
+ int lineAscent = 0;
+ int lineDescent = 0;
+
+ wxRichTextObjectList::compatibility_iterator node;
+
+#if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
+ wxUnusedVar(style);
+ wxArrayInt partialExtents;
+
+ wxSize paraSize;
+ int paraDescent = 0;
+
+ // This calculates the partial text extents
+ GetRangeSize(GetRange(), paraSize, paraDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_CACHE_SIZE, rect.GetPosition(), & partialExtents);
+#else
+ node = m_children.GetFirst();
+ while (node)
+ {
+ wxRichTextObject* child = node->GetData();
+
+ //child->SetCachedSize(wxDefaultSize);
+ child->Layout(dc, context, rect, style);
+
+ node = node->GetNext();
+ }
+#endif
+
+ // Split up lines
+
+ // We may need to go back to a previous child, in which case create the new line,
+ // find the child corresponding to the start position of the string, and
+ // continue.
+
+ wxRect availableRect;
+
+ node = m_children.GetFirst();
+ while (node)
+ {
+ wxRichTextObject* child = node->GetData();
+
+ // If floating, ignore. We already laid out floats.
+ // Also ignore if empty object, except if we haven't got any
+ // size yet.
+ if (child->IsFloating() || !child->IsShown() ||
+ (child->GetRange().GetLength() == 0 && maxHeight > spaceBeforePara)
+ )
+ {
+ node = node->GetNext();
+ continue;
+ }
+
+ // If this is e.g. a composite text box, it will need to be laid out itself.
+ // But if just a text fragment or image, for example, this will
+ // do nothing. NB: won't we need to set the position after layout?
+ // since for example if position is dependent on vertical line size, we
+ // can't tell the position until the size is determined. So possibly introduce
+ // another layout phase.
+
+ // We may only be looking at part of a child, if we searched back for wrapping
+ // and found a suitable point some way into the child. So get the size for the fragment
+ // if necessary.
+
+ long nextBreakPos = GetFirstLineBreakPosition(lastEndPos+1);
+ long lastPosToUse = child->GetRange().GetEnd();
+ bool lineBreakInThisObject = (nextBreakPos > -1 && nextBreakPos <= child->GetRange().GetEnd());
+
+ if (lineBreakInThisObject)
+ lastPosToUse = nextBreakPos;
+
+ wxSize childSize;
+ int childDescent = 0;
+
+ int startOffset = (lineCount == 0 ? startPositionFirstLine : startPositionSubsequentLines);
+ availableRect = wxRect(rect.x + startOffset, rect.y + currentPosition.y,
+ rect.width - startOffset - rightIndent, rect.height);
+
+ if (child->IsTopLevel())
+ {
+ wxSize oldSize = child->GetCachedSize();
+
+ child->Invalidate(wxRICHTEXT_ALL);
+ child->SetPosition(wxPoint(0, 0));
+
+ // Lays out the object first with a given amount of space, and then if no width was specified in attr,
+ // lays out the object again using the minimum size
+ // The position will be determined by its location in its line,
+ // and not by the child's actual position.
+ child->LayoutToBestSize(dc, context, buffer,
+ attr, child->GetAttributes(), availableRect, parentRect, style);
+
+ if (oldSize != child->GetCachedSize())
+ {
+ partialExtents.Clear();
+
+ // Recalculate the partial text extents since the child object changed size
+ GetRangeSize(GetRange(), paraSize, paraDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_CACHE_SIZE, wxPoint(0,0), & partialExtents);
+ }
+ }
+
+ // Problem: we need to layout composites here for which we need the available width,
+ // but we can't get the available width without using the float collector which
+ // needs to know the object height.
+
+ if ((nextBreakPos == -1) && (lastEndPos == child->GetRange().GetStart() - 1)) // i.e. we want to get the whole thing
+ {
+ childSize = child->GetCachedSize();
+ childDescent = child->GetDescent();
+ }
+ else
+ {
+#if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
+ // Get height only, then the width using the partial extents
+ GetRangeSize(wxRichTextRange(lastEndPos+1, lastPosToUse), childSize, childDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_HEIGHT_ONLY);
+ childSize.x = wxRichTextGetRangeWidth(*this, wxRichTextRange(lastEndPos+1, lastPosToUse), partialExtents);
+#else
+ GetRangeSize(wxRichTextRange(lastEndPos+1, lastPosToUse), childSize, childDescent, dc, context, wxRICHTEXT_UNFORMATTED, rect.GetPosition());
+#endif
+ }
+
+ bool doLoop = true;
+ int loopIterations = 0;
+
+ // If there are nested objects that need to lay themselves out, we have to do this in a
+ // loop because the height of the object may well depend on the available width.
+ // And because of floating object positioning, the available width depends on the
+ // height of the object and whether it will clash with the floating objects.
+ // So, we see whether the available width changes due to the presence of floating images.
+ // If it does, then we'll use the new restricted width to find the object height again.
+ // If this causes another restriction in the available width, we'll try again, until
+ // either we lose patience or the available width settles down.
+ do
+ {
+ loopIterations ++;
+
+ wxRect oldAvailableRect = availableRect;
+
+ // Available width depends on the floating objects and the line height.
+ // Note: the floating objects may be placed vertically along the two sides of
+ // buffer, so we may have different available line widths with different
+ // [startY, endY]. So, we can't determine how wide the available
+ // space is until we know the exact line height.
+ if (childDescent == 0)
+ {
+ lineHeight = wxMax(lineHeight, childSize.y);
+ lineDescent = maxDescent;
+ lineAscent = maxAscent;
+ }
+ else
+ {
+ lineDescent = wxMax(childDescent, maxDescent);
+ lineAscent = wxMax(childSize.y-childDescent, maxAscent);
+ }
+ lineHeight = wxMax(lineHeight, (lineDescent + lineAscent));
+ wxRect floatAvailableRect = collector->GetAvailableRect(rect.y + currentPosition.y, rect.y + currentPosition.y + lineHeight);
+
+ // Adjust availableRect to the space that is available when taking floating objects into account.
+
+ if (floatAvailableRect.x + startOffset > availableRect.x)
+ {
+ int newX = floatAvailableRect.x + startOffset;
+ int newW = availableRect.width - (newX - availableRect.x);
+ availableRect.x = newX;
+ availableRect.width = newW;
+ }
+
+ if (floatAvailableRect.width < availableRect.width)
+ availableRect.width = floatAvailableRect.width;
+
+ currentPosition.x = availableRect.x - rect.x;
+
+ if (child->IsTopLevel() && loopIterations <= 20)
+ {
+ if (availableRect != oldAvailableRect)
+ {
+ wxSize oldSize = child->GetCachedSize();
+
+ // Lays out the object first with a given amount of space, and then if no width was specified in attr,
+ // lays out the object again using the minimum size
+ child->Invalidate(wxRICHTEXT_ALL);
+ child->LayoutToBestSize(dc, context, buffer,
+ attr, child->GetAttributes(), availableRect, parentRect, style);
+ childSize = child->GetCachedSize();
+ childDescent = child->GetDescent();
+
+ if (oldSize != child->GetCachedSize())
+ {
+ partialExtents.Clear();
+
+ // Recalculate the partial text extents since the child object changed size
+ GetRangeSize(GetRange(), paraSize, paraDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_CACHE_SIZE, wxPoint(0,0), & partialExtents);
+ }
+
+ // Go around the loop finding the available rect for the given floating objects
+ }
+ else
+ doLoop = false;
+ }
+ else
+ doLoop = false;
+ }
+ while (doLoop);
+
+ if (child->IsTopLevel())
+ {
+ // We can move it to the correct position at this point
+ // TODO: probably need to add margin
+ child->Move(GetPosition() + wxPoint(currentWidth + (wxMax(leftIndent, leftIndent + leftSubIndent)), currentPosition.y));
+ }
+
+ // Cases:
+ // 1) There was a line break BEFORE the natural break
+ // 2) There was a line break AFTER the natural break
+ // 3) It's the last line
+ // 4) The child still fits (carry on) - 'else' clause
+
+ if ((lineBreakInThisObject && (childSize.x + currentWidth <= availableRect.width))
+ ||
+ (childSize.x + currentWidth > availableRect.width)
+ ||
+ ((childSize.x + currentWidth <= availableRect.width) && !node->GetNext())
+
+ )
+ {
+ long wrapPosition = 0;
+ if ((childSize.x + currentWidth <= availableRect.width) && !node->GetNext() && !lineBreakInThisObject)
+ wrapPosition = child->GetRange().GetEnd();
+ else
+
+ // Find a place to wrap. This may walk back to previous children,
+ // for example if a word spans several objects.
+ // Note: one object must contains only one wxTextAtrr, so the line height will not
+ // change inside one object. Thus, we can pass the remain line width to the
+ // FindWrapPosition function.
+ if (!FindWrapPosition(wxRichTextRange(lastCompletedEndPos+1, child->GetRange().GetEnd()), dc, context, availableRect.width, wrapPosition, & partialExtents))
+ {
+ // If the function failed, just cut it off at the end of this child.
+ wrapPosition = child->GetRange().GetEnd();
+ }
+
+ // FindWrapPosition can still return a value that will put us in an endless wrapping loop
+ if (wrapPosition <= lastCompletedEndPos)
+ wrapPosition = wxMax(lastCompletedEndPos+1,child->GetRange().GetEnd());
+
+ // Line end position shouldn't be the same as the end, or greater.
+ if (wrapPosition >= GetRange().GetEnd())
+ wrapPosition = wxMax(0, GetRange().GetEnd()-1);
+
+ // wxLogDebug(wxT("Split at %ld"), wrapPosition);
+
+ // Let's find the actual size of the current line now
+ wxSize actualSize;
+ wxRichTextRange actualRange(lastCompletedEndPos+1, wrapPosition);
+
+ childDescent = 0;
+
+#if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
+ if (!child->IsEmpty())
+ {
+ // Get height only, then the width using the partial extents
+ GetRangeSize(actualRange, actualSize, childDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_HEIGHT_ONLY);
+ actualSize.x = wxRichTextGetRangeWidth(*this, actualRange, partialExtents);
+ }
+ else
+#endif
+ GetRangeSize(actualRange, actualSize, childDescent, dc, context, wxRICHTEXT_UNFORMATTED);
+
+ currentWidth = actualSize.x;
+
+ // The descent for the whole line at this point, is the correct max descent
+ maxDescent = childDescent;
+ // Maximum ascent
+ maxAscent = actualSize.y-childDescent;
+
+ // lineHeight is given by the height for the whole line, since it will
+ // take into account ascend/descend.
+ lineHeight = actualSize.y;
+
+ if (lineHeight == 0 && buffer)
+ {
+ wxFont font(buffer->GetFontTable().FindFont(attr));
+ wxCheckSetFont(dc, font);
+ lineHeight = dc.GetCharHeight();
+ }
+
+ if (maxDescent == 0)
+ {
+ int w, h;
+ dc.GetTextExtent(wxT("X"), & w, &h, & maxDescent);
+ }
+
+ // Add a new line
+ wxRichTextLine* line = AllocateLine(lineCount);
+
+ // Set relative range so we won't have to change line ranges when paragraphs are moved
+ line->SetRange(wxRichTextRange(actualRange.GetStart() - GetRange().GetStart(), actualRange.GetEnd() - GetRange().GetStart()));
+ line->SetPosition(currentPosition);
+ line->SetSize(wxSize(currentWidth, lineHeight));
+ line->SetDescent(maxDescent);
+
+ maxHeight = currentPosition.y + lineHeight;
+
+ // Now move down a line. TODO: add margins, spacing
+ currentPosition.y += lineHeight;
+ currentPosition.y += lineSpacing;
+ maxDescent = 0;
+ maxAscent = 0;
+ maxWidth = wxMax(maxWidth, currentWidth+startOffset);
+ currentWidth = 0;
+
+ lineCount ++;
+
+ // TODO: account for zero-length objects
+ // wxASSERT(wrapPosition > lastCompletedEndPos);
+
+ lastEndPos = wrapPosition;
+ lastCompletedEndPos = lastEndPos;
+
+ lineHeight = 0;
+
+ if (wrapPosition < GetRange().GetEnd()-1)
+ {
+ // May need to set the node back to a previous one, due to searching back in wrapping
+ wxRichTextObject* childAfterWrapPosition = FindObjectAtPosition(wrapPosition+1);
+ if (childAfterWrapPosition)
+ node = m_children.Find(childAfterWrapPosition);
+ else
+ node = node->GetNext();
+ }
+ else
+ node = node->GetNext();
+
+ // Apply paragraph styles such as alignment to the wrapped line
+ ApplyParagraphStyle(line, attr, availableRect, dc);
+ }
+ else
+ {
+ // We still fit, so don't add a line, and keep going
+ currentWidth += childSize.x;
+
+ if (childDescent == 0)
+ {
+ // An object with a zero descend value wants to take up the whole
+ // height regardless of baseline
+ lineHeight = wxMax(lineHeight, childSize.y);
+ }
+ else
+ {
+ maxDescent = wxMax(childDescent, maxDescent);
+ maxAscent = wxMax(childSize.y-childDescent, maxAscent);
+ }
+
+ lineHeight = wxMax(lineHeight, (maxDescent + maxAscent));
+
+ maxWidth = wxMax(maxWidth, currentWidth+startOffset);
+ lastEndPos = child->GetRange().GetEnd();
+
+ node = node->GetNext();
+ }
+ }
+
+ //wxASSERT(!(lastCompletedEndPos != -1 && lastCompletedEndPos < GetRange().GetEnd()-1));
+
+ // Remove remaining unused line objects, if any
+ ClearUnusedLines(lineCount);
+
+ // We need to add back the margins etc.
+ {
+ wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
+ contentRect = wxRect(wxPoint(0, 0), wxSize(maxWidth, currentPosition.y + spaceAfterPara));
+ GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
+ SetCachedSize(marginRect.GetSize());
+ }
+
+ // The maximum size is the length of the paragraph stretched out into a line.
+ // So if there were a single word, or an image, or a fixed-size text box, the object could be shrunk around
+ // this size. TODO: take into account line breaks.
+ {
+ wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
+ contentRect = wxRect(wxPoint(0, 0), wxSize(paraSize.x + wxMax(leftIndent, leftIndent + leftSubIndent) + rightIndent, currentPosition.y + spaceAfterPara));
+ GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
+ SetMaxSize(marginRect.GetSize());
+ }
+
+ // Find the greatest minimum size. Currently we only look at non-text objects,
+ // which isn't ideal but it would be slow to find the maximum word width to
+ // use as the minimum.
+ {
+ int minWidth = 0;
+ node = m_children.GetFirst();
+ while (node)
+ {
+ wxRichTextObject* child = node->GetData();
+
+ // If floating, ignore. We already laid out floats.
+ // Also ignore if empty object, except if we haven't got any
+ // size yet.
+ if (!child->IsFloating() && child->GetRange().GetLength() != 0 && !wxDynamicCast(child, wxRichTextPlainText))
+ {
+ if (child->GetCachedSize().x > minWidth)
+ minWidth = child->GetMinSize().x;
+ }
+ node = node->GetNext();
+ }
+
+ wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
+ contentRect = wxRect(wxPoint(0, 0), wxSize(minWidth, currentPosition.y + spaceAfterPara));
+ GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
+ SetMinSize(marginRect.GetSize());
+ }
+
+#if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
+#if wxRICHTEXT_USE_OPTIMIZED_LINE_DRAWING
+ // Use the text extents to calculate the size of each fragment in each line
+ wxRichTextLineList::compatibility_iterator lineNode = m_cachedLines.GetFirst();
+ while (lineNode)
+ {
+ wxRichTextLine* line = lineNode->GetData();
+ wxRichTextRange lineRange = line->GetAbsoluteRange();
+
+ // Loop through objects until we get to the one within range
+ wxRichTextObjectList::compatibility_iterator node2 = m_children.GetFirst();
+
+ while (node2)
+ {
+ wxRichTextObject* child = node2->GetData();
+
+ if (child->GetRange().GetLength() > 0 && !child->GetRange().IsOutside(lineRange))
+ {
+ wxRichTextRange rangeToUse = lineRange;
+ rangeToUse.LimitTo(child->GetRange());
+
+ // Find the size of the child from the text extents, and store in an array
+ // for drawing later
+ int left = 0;
+ if (rangeToUse.GetStart() > GetRange().GetStart())
+ left = partialExtents[(rangeToUse.GetStart()-1) - GetRange().GetStart()];
+ int right = partialExtents[rangeToUse.GetEnd() - GetRange().GetStart()];
+ int sz = right - left;
+ line->GetObjectSizes().Add(sz);
+ }
+ else if (child->GetRange().GetStart() > lineRange.GetEnd())
+ // Can break out of inner loop now since we've passed this line's range
+ break;
+
+ node2 = node2->GetNext();
+ }
+
+ lineNode = lineNode->GetNext();
+ }
+#endif
+#endif
+
+ return true;
+}
+
+/// Apply paragraph styles, such as centering, to wrapped lines
+/// TODO: take into account box attributes, possibly
+void wxRichTextParagraph::ApplyParagraphStyle(wxRichTextLine* line, const wxRichTextAttr& attr, const wxRect& rect, wxDC& dc)
+{
+ if (!attr.HasAlignment())
+ return;
+
+ wxPoint pos = line->GetPosition();
+ wxPoint originalPos = pos;
+ wxSize size = line->GetSize();
+
+ // centering, right-justification
+ if (attr.HasAlignment() && attr.GetAlignment() == wxTEXT_ALIGNMENT_CENTRE)
+ {
+ int rightIndent = ConvertTenthsMMToPixels(dc, attr.GetRightIndent());
+ pos.x = (rect.GetWidth() - rightIndent - size.x)/2 + pos.x;
+ line->SetPosition(pos);
+ }
+ else if (attr.HasAlignment() && attr.GetAlignment() == wxTEXT_ALIGNMENT_RIGHT)
+ {
+ int rightIndent = ConvertTenthsMMToPixels(dc, attr.GetRightIndent());
+ pos.x = pos.x + rect.GetWidth() - size.x - rightIndent;
+ line->SetPosition(pos);
+ }
+
+ if (pos != originalPos)
+ {
+ wxPoint inc = pos - originalPos;
+
+ wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
+
+ while (node)
+ {
+ wxRichTextObject* child = node->GetData();
+ if (child->IsTopLevel() && !child->GetRange().IsOutside(line->GetAbsoluteRange()))
+ child->Move(child->GetPosition() + inc);
+
+ node = node->GetNext();
+ }
+ }
+}
+
+/// Insert text at the given position
+bool wxRichTextParagraph::InsertText(long pos, const wxString& text)
+{
+ wxRichTextObject* childToUse = NULL;
+ wxRichTextObjectList::compatibility_iterator nodeToUse = wxRichTextObjectList::compatibility_iterator();
+
+ wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
+ while (node)
+ {
+ wxRichTextObject* child = node->GetData();
+ if (child->GetRange().Contains(pos) && child->GetRange().GetLength() > 0)
+ {
+ childToUse = child;
+ nodeToUse = node;
+ break;
+ }
+
+ node = node->GetNext();
+ }
+
+ if (childToUse)
+ {
+ wxRichTextPlainText* textObject = wxDynamicCast(childToUse, wxRichTextPlainText);
+ if (textObject)
+ {
+ int posInString = pos - textObject->GetRange().GetStart();
+
+ wxString newText = textObject->GetText().Mid(0, posInString) +
+ text + textObject->GetText().Mid(posInString);
+ textObject->SetText(newText);
+
+ int textLength = text.length();
+
+ textObject->SetRange(wxRichTextRange(textObject->GetRange().GetStart(),
+ textObject->GetRange().GetEnd() + textLength));
+
+ // Increment the end range of subsequent fragments in this paragraph.
+ // We'll set the paragraph range itself at a higher level.
+
+ wxRichTextObjectList::compatibility_iterator node = nodeToUse->GetNext();
+ while (node)
+ {
+ wxRichTextObject* child = node->GetData();
+ child->SetRange(wxRichTextRange(textObject->GetRange().GetStart() + textLength,
+ textObject->GetRange().GetEnd() + textLength));
+
+ node = node->GetNext();
+ }
+
+ return true;
+ }
+ else
+ {
+ // TODO: if not a text object, insert at closest position, e.g. in front of it
+ }
+ }
+ else
+ {
+ // Add at end.
+ // Don't pass parent initially to suppress auto-setting of parent range.
+ // We'll do that at a higher level.
+ wxRichTextPlainText* textObject = new wxRichTextPlainText(text, this);
+
+ AppendChild(textObject);
+ return true;
+ }
+
+ return false;
+}
+
+void wxRichTextParagraph::Copy(const wxRichTextParagraph& obj)
+{
+ wxRichTextCompositeObject::Copy(obj);
+}
+
+/// Clear the cached lines
+void wxRichTextParagraph::ClearLines()
+{
+ WX_CLEAR_LIST(wxRichTextLineList, m_cachedLines);
+}
+
+/// Get/set the object size for the given range. Returns false if the range
+/// is invalid for this object.
+bool wxRichTextParagraph::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, wxPoint position, wxArrayInt* partialExtents) const
+{
+ if (!range.IsWithin(GetRange()))
+ return false;
+
+ if (flags & wxRICHTEXT_UNFORMATTED)
+ {
+ // Just use unformatted data, assume no line breaks
+ wxSize sz;
+
+ wxArrayInt childExtents;
+ wxArrayInt* p;
+ if (partialExtents)
+ p = & childExtents;
+ else
+ p = NULL;
+
+ int maxDescent = 0;
+ int maxAscent = 0;
+ int maxLineHeight = 0;
+
+ wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
+ while (node)
+ {
+ wxRichTextObject* child = node->GetData();
+ if (!child->GetRange().IsOutside(range))
+ {
+ // Floating objects have a zero size within the paragraph.
+ if (child->IsFloating())
+ {
+ if (partialExtents)
+ {
+ int lastSize;
+ if (partialExtents->GetCount() > 0)
+ lastSize = (*partialExtents)[partialExtents->GetCount()-1];
+ else
+ lastSize = 0;
+
+ partialExtents->Add(0 /* zero size */ + lastSize);
+ }
+ }
+ else
+ {
+ wxSize childSize;
+
+ wxRichTextRange rangeToUse = range;
+ rangeToUse.LimitTo(child->GetRange());
+ int childDescent = 0;
+
+ // At present wxRICHTEXT_HEIGHT_ONLY is only fast if we've already cached the size,
+ // but it's only going to be used after caching has taken place.
+ if ((flags & wxRICHTEXT_HEIGHT_ONLY) && child->GetCachedSize().y != 0)
+ {
+ childDescent = child->GetDescent();
+ childSize = child->GetCachedSize();
+
+ if (childDescent == 0)
+ {
+ maxLineHeight = wxMax(maxLineHeight, childSize.y);
+ }
+ else
+ {
+ maxDescent = wxMax(maxDescent, childDescent);
+ maxAscent = wxMax(maxAscent, (childSize.y - childDescent));
+ }
+
+ maxLineHeight = wxMax(maxLineHeight, (maxAscent + maxDescent));
+
+ sz.y = wxMax(sz.y, maxLineHeight);
+ sz.x += childSize.x;
+ descent = maxDescent;
+ }
+ else if (child->IsTopLevel())
+ {
+ childDescent = child->GetDescent();
+ childSize = child->GetCachedSize();
+
+ if (childDescent == 0)
+ {
+ maxLineHeight = wxMax(maxLineHeight, childSize.y);
+ }
+ else
+ {
+ maxDescent = wxMax(maxDescent, childDescent);
+ maxAscent = wxMax(maxAscent, (childSize.y - childDescent));
+ }
+
+ maxLineHeight = wxMax(maxLineHeight, (maxAscent + maxDescent));
+
+ sz.y = wxMax(sz.y, maxLineHeight);
+ sz.x += childSize.x;
+ descent = maxDescent;
+
+ // FIXME: this won't change the original values.
+ // Should we be calling GetRangeSize above instead of using cached values?
+#if 0
+ if ((flags & wxRICHTEXT_CACHE_SIZE) && (rangeToUse == child->GetRange()))
+ {
+ child->SetCachedSize(childSize);
+ child->SetDescent(childDescent);
+ }
+#endif
+
+ if (partialExtents)
+ {
+ int lastSize;
+ if (partialExtents->GetCount() > 0)
+ lastSize = (*partialExtents)[partialExtents->GetCount()-1];
+ else
+ lastSize = 0;
+
+ partialExtents->Add(childSize.x + lastSize);
+ }
+ }
+ else if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, context, flags, wxPoint(position.x + sz.x, position.y), p))
+ {
+ if (childDescent == 0)
+ {
+ maxLineHeight = wxMax(maxLineHeight, childSize.y);
+ }
+ else
+ {
+ maxDescent = wxMax(maxDescent, childDescent);
+ maxAscent = wxMax(maxAscent, (childSize.y - childDescent));
+ }
+
+ maxLineHeight = wxMax(maxLineHeight, (maxAscent + maxDescent));
+
+ sz.y = wxMax(sz.y, maxLineHeight);
+ sz.x += childSize.x;
+ descent = maxDescent;
+
+ if ((flags & wxRICHTEXT_CACHE_SIZE) && (rangeToUse == child->GetRange()))
+ {
+ child->SetCachedSize(childSize);
+ child->SetDescent(childDescent);
+ }
+
+ if (partialExtents)
+ {
+ int lastSize;
+ if (partialExtents->GetCount() > 0)
+ lastSize = (*partialExtents)[partialExtents->GetCount()-1];
+ else
+ lastSize = 0;
+
+ size_t i;
+ for (i = 0; i < childExtents.GetCount(); i++)
+ {
+ partialExtents->Add(childExtents[i] + lastSize);
+ }
+ }
+ }
+ }
+
+ if (p)
+ p->Clear();
+ }
+
+ node = node->GetNext();
+ }
+ size = sz;
+ }
+ else
+ {
+ // Use formatted data, with line breaks
+ wxSize sz;
+
+ // We're going to loop through each line, and then for each line,
+ // call GetRangeSize for the fragment that comprises that line.
+ // Only we have to do that multiple times within the line, because
+ // the line may be broken into pieces. For now ignore line break commands
+ // (so we can assume that getting the unformatted size for a fragment
+ // within a line is the actual size)
+
+ wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
+ while (node)
+ {
+ wxRichTextLine* line = node->GetData();
+ wxRichTextRange lineRange = line->GetAbsoluteRange();
+ if (!lineRange.IsOutside(range))
+ {
+ int maxDescent = 0;
+ int maxAscent = 0;
+ int maxLineHeight = 0;
+ int maxLineWidth = 0;
+
+ wxRichTextObjectList::compatibility_iterator node2 = m_children.GetFirst();
+ while (node2)
+ {
+ wxRichTextObject* child = node2->GetData();
+
+ if (!child->IsFloating() && !child->GetRange().IsOutside(lineRange))
+ {
+ wxRichTextRange rangeToUse = lineRange;
+ rangeToUse.LimitTo(child->GetRange());
+ if (child->IsTopLevel())
+ rangeToUse = child->GetOwnRange();
+
+ wxSize childSize;
+ int childDescent = 0;
+ if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, context, flags, wxPoint(position.x + sz.x, position.y)))
+ {
+ if (childDescent == 0)
+ {
+ // Assume that if descent is zero, this child can occupy the full line height
+ // and does not need space for the line's maximum descent. So we influence
+ // the overall max line height only.
+ maxLineHeight = wxMax(maxLineHeight, childSize.y);
+ }
+ else
+ {
+ maxAscent = wxMax(maxAscent, (childSize.y - childDescent));
+ maxDescent = wxMax(maxAscent, childDescent);
+ }
+ maxLineHeight = wxMax(maxLineHeight, (maxAscent + maxDescent));
+ maxLineWidth += childSize.x;
+ }
+ }
+
+ node2 = node2->GetNext();
+ }
+
+ descent = wxMax(descent, maxDescent);
+
+ // Increase size by a line (TODO: paragraph spacing)
+ sz.y += maxLineHeight;
+ sz.x = wxMax(sz.x, maxLineWidth);
+ }
+ node = node->GetNext();
+ }
+ size = sz;
+ }
+ return true;
+}
+
+/// Finds the absolute position and row height for the given character position
+bool wxRichTextParagraph::FindPosition(wxDC& dc, wxRichTextDrawingContext& context, long index, wxPoint& pt, int* height, bool forceLineStart)
+{
+ if (index == -1)
+ {
+ wxRichTextLine* line = ((wxRichTextParagraphLayoutBox*)GetParent())->GetLineAtPosition(0);
+ if (line)
+ *height = line->GetSize().y;
+ else
+ *height = dc.GetCharHeight();
+
+ // -1 means 'the start of the buffer'.
+ pt = GetPosition();
+ if (line)
+ pt = pt + line->GetPosition();
+
+ return true;
+ }
+
+ // The final position in a paragraph is taken to mean the position
+ // at the start of the next paragraph.
+ if (index == GetRange().GetEnd())
+ {
+ wxRichTextParagraphLayoutBox* parent = wxDynamicCast(GetParent(), wxRichTextParagraphLayoutBox);
+ wxASSERT( parent != NULL );
+
+ // Find the height at the next paragraph, if any
+ wxRichTextLine* line = parent->GetLineAtPosition(index + 1);
+ if (line)
+ {
+ *height = line->GetSize().y;
+ pt = line->GetAbsolutePosition();
+ }
+ else
+ {
+ *height = dc.GetCharHeight();
+ int indent = ConvertTenthsMMToPixels(dc, m_attributes.GetLeftIndent());
+ pt = wxPoint(indent, GetCachedSize().y);
+ }
+
+ return true;
+ }
+
+ if (index < GetRange().GetStart() || index > GetRange().GetEnd())
+ return false;
+
+ wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
+ while (node)
+ {
+ wxRichTextLine* line = node->GetData();
+ wxRichTextRange lineRange = line->GetAbsoluteRange();
+ if (index >= lineRange.GetStart() && index <= lineRange.GetEnd())
+ {
+ // If this is the last point in the line, and we're forcing the
+ // returned value to be the start of the next line, do the required
+ // thing.
+ if (index == lineRange.GetEnd() && forceLineStart)
+ {
+ if (node->GetNext())
+ {
+ wxRichTextLine* nextLine = node->GetNext()->GetData();
+ *height = nextLine->GetSize().y;
+ pt = nextLine->GetAbsolutePosition();
+ return true;
+ }
+ }
+
+ pt.y = line->GetPosition().y + GetPosition().y;
+
+ wxRichTextRange r(lineRange.GetStart(), index);
+ wxSize rangeSize;
+ int descent = 0;
+
+ // We find the size of the line up to this point,
+ // then we can add this size to the line start position and
+ // paragraph start position to find the actual position.
+
+ if (GetRangeSize(r, rangeSize, descent, dc, context, wxRICHTEXT_UNFORMATTED, line->GetPosition()+ GetPosition()))
+ {
+ pt.x = line->GetPosition().x + GetPosition().x + rangeSize.x;
+ *height = line->GetSize().y;
+
+ return true;
+ }
+
+ }
+
+ node = node->GetNext();
+ }
+
+ return false;
+}
+
+/// Hit-testing: returns a flag indicating hit test details, plus
+/// information about position
+int wxRichTextParagraph::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int flags)
+{
+ if (!IsShown())
+ return wxRICHTEXT_HITTEST_NONE;
+
+ // If we're in the top-level container, then we can return
+ // a suitable hit test code even if the point is outside the container area,
+ // so that we can position the caret sensibly even if we don't
+ // click on valid content. If we're not at the top-level, and the point
+ // is not within this paragraph object, then we don't want to stop more
+ // precise hit-testing from working prematurely, so return immediately.
+ // NEW STRATEGY: use the parent boundary to test whether we're in the
+ // right region, not the paragraph, since the paragraph may be positioned
+ // some way in from where the user clicks.
+ {
+ long tmpPos;
+ wxRichTextObject* tempObj, *tempContextObj;
+ if (GetParent() && GetParent()->wxRichTextObject::HitTest(dc, context, pt, tmpPos, & tempObj, & tempContextObj, flags) == wxRICHTEXT_HITTEST_NONE)
+ return wxRICHTEXT_HITTEST_NONE;
+ }
+
+ wxRichTextObjectList::compatibility_iterator objNode = m_children.GetFirst();
+ while (objNode)
+ {
+ wxRichTextObject* child = objNode->GetData();
+ // Don't recurse if we have wxRICHTEXT_HITTEST_NO_NESTED_OBJECTS,
+ // and also, if this seems composite but actually is marked as atomic,
+ // don't recurse.
+ if (child->IsTopLevel() && ((flags & wxRICHTEXT_HITTEST_NO_NESTED_OBJECTS) == 0) &&
+ (! (((flags & wxRICHTEXT_HITTEST_HONOUR_ATOMIC) != 0) && child->IsAtomic())))
+ {
+ {
+ int hitTest = child->HitTest(dc, context, pt, textPosition, obj, contextObj);
+ if (hitTest != wxRICHTEXT_HITTEST_NONE)
+ return hitTest;
+ }
+ }
+
+ objNode = objNode->GetNext();
+ }
+
+ wxPoint paraPos = GetPosition();
+
+ wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
+ while (node)
+ {
+ wxRichTextLine* line = node->GetData();
+ wxPoint linePos = paraPos + line->GetPosition();
+ wxSize lineSize = line->GetSize();
+ wxRichTextRange lineRange = line->GetAbsoluteRange();
+
+ if (pt.y <= linePos.y + lineSize.y)
+ {
+ if (pt.x < linePos.x)
+ {
+ textPosition = lineRange.GetStart();
+ *obj = FindObjectAtPosition(textPosition);
+ *contextObj = GetContainer();
+ return wxRICHTEXT_HITTEST_BEFORE|wxRICHTEXT_HITTEST_OUTSIDE;
+ }
+ else if (pt.x >= (linePos.x + lineSize.x))
+ {
+ textPosition = lineRange.GetEnd();
+ *obj = FindObjectAtPosition(textPosition);
+ *contextObj = GetContainer();
+ return wxRICHTEXT_HITTEST_AFTER|wxRICHTEXT_HITTEST_OUTSIDE;
+ }
+ else
+ {
+#if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
+ wxArrayInt partialExtents;
+
+ wxSize paraSize;
+ int paraDescent;
+
+ // This calculates the partial text extents
+ GetRangeSize(lineRange, paraSize, paraDescent, dc, context, wxRICHTEXT_UNFORMATTED, linePos, & partialExtents);
+
+ int lastX = linePos.x;
+ size_t i;
+ for (i = 0; i < partialExtents.GetCount(); i++)
+ {
+ int nextX = partialExtents[i] + linePos.x;
+
+ if (pt.x >= lastX && pt.x <= nextX)
+ {
+ textPosition = i + lineRange.GetStart(); // minus 1?
+
+ *obj = FindObjectAtPosition(textPosition);
+ *contextObj = GetContainer();
+
+ // So now we know it's between i-1 and i.
+ // Let's see if we can be more precise about
+ // which side of the position it's on.
+
+ int midPoint = (nextX + lastX)/2;
+ if (pt.x >= midPoint)
+ return wxRICHTEXT_HITTEST_AFTER;
+ else
+ return wxRICHTEXT_HITTEST_BEFORE;
+ }
+
+ lastX = nextX;
+ }
+#else
+ long i;
+ int lastX = linePos.x;
+ for (i = lineRange.GetStart(); i <= lineRange.GetEnd(); i++)
+ {
+ wxSize childSize;
+ int descent = 0;
+
+ wxRichTextRange rangeToUse(lineRange.GetStart(), i);
+
+ GetRangeSize(rangeToUse, childSize, descent, dc, context, wxRICHTEXT_UNFORMATTED, linePos);
+
+ int nextX = childSize.x + linePos.x;
+
+ if (pt.x >= lastX && pt.x <= nextX)
+ {
+ textPosition = i;
+
+ *obj = FindObjectAtPosition(textPosition);
+ *contextObj = GetContainer();
+
+ // So now we know it's between i-1 and i.
+ // Let's see if we can be more precise about
+ // which side of the position it's on.
+
+ int midPoint = (nextX + lastX)/2;
+ if (pt.x >= midPoint)
+ return wxRICHTEXT_HITTEST_AFTER;
+ else
+ return wxRICHTEXT_HITTEST_BEFORE;
+ }
+ else
+ {
+ lastX = nextX;
+ }
+ }
+#endif
+ }
+ }
+
+ node = node->GetNext();
+ }
+
+ return wxRICHTEXT_HITTEST_NONE;
+}
+
+/// Split an object at this position if necessary, and return
+/// the previous object, or NULL if inserting at beginning.
+wxRichTextObject* wxRichTextParagraph::SplitAt(long pos, wxRichTextObject** previousObject)
+{
+ wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
+ while (node)
+ {
+ wxRichTextObject* child = node->GetData();
+
+ if (pos == child->GetRange().GetStart())
+ {
+ if (previousObject)
+ {
+ if (node->GetPrevious())
+ *previousObject = node->GetPrevious()->GetData();
+ else
+ *previousObject = NULL;
+ }
+
+ return child;
+ }
+
+ if (child->GetRange().Contains(pos))
+ {
+ // This should create a new object, transferring part of
+ // the content to the old object and the rest to the new object.
+ wxRichTextObject* newObject = child->DoSplit(pos);
+
+ // If we couldn't split this object, just insert in front of it.
+ if (!newObject)
+ {
+ // Maybe this is an empty string, try the next one
+ // return child;
+ }
+ else
+ {
+ // Insert the new object after 'child'
+ if (node->GetNext())
+ m_children.Insert(node->GetNext(), newObject);
+ else
+ m_children.Append(newObject);
+ newObject->SetParent(this);
+
+ if (previousObject)
+ *previousObject = child;
+
+ return newObject;
+ }
+ }
+
+ node = node->GetNext();
+ }
+ if (previousObject)
+ *previousObject = NULL;
+ return NULL;
+}
+
+/// Move content to a list from obj on
+void wxRichTextParagraph::MoveToList(wxRichTextObject* obj, wxList& list)
+{
+ wxRichTextObjectList::compatibility_iterator node = m_children.Find(obj);
+ while (node)
+ {
+ wxRichTextObject* child = node->GetData();
+ list.Append(child);
+
+ wxRichTextObjectList::compatibility_iterator oldNode = node;
+
+ node = node->GetNext();
+
+ m_children.DeleteNode(oldNode);
+ }
+}
+
+/// Add content back from list
+void wxRichTextParagraph::MoveFromList(wxList& list)
+{
+ for (wxList::compatibility_iterator node = list.GetFirst(); node; node = node->GetNext())
+ {
+ AppendChild((wxRichTextObject*) node->GetData());
+ }
+}
+
+/// Calculate range
+void wxRichTextParagraph::CalculateRange(long start, long& end)
+{
+ wxRichTextCompositeObject::CalculateRange(start, end);
+
+ // Add one for end of paragraph
+ end ++;
+
+ m_range.SetRange(start, end);
+}
+
+/// Find the object at the given position
+wxRichTextObject* wxRichTextParagraph::FindObjectAtPosition(long position)
+{
+ wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
+ while (node)
+ {
+ wxRichTextObject* obj = node->GetData();
+ if (obj->GetRange().Contains(position) ||
+ obj->GetRange().GetStart() == position ||
+ obj->GetRange().GetEnd() == position)
+ return obj;
+
+ node = node->GetNext();
+ }
+ return NULL;
+}
+
+/// Get the plain text searching from the start or end of the range.
+/// The resulting string may be shorter than the range given.
+bool wxRichTextParagraph::GetContiguousPlainText(wxString& text, const wxRichTextRange& range, bool fromStart)
+{
+ text = wxEmptyString;
+
+ if (fromStart)
+ {
+ wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
+ while (node)
+ {
+ wxRichTextObject* obj = node->GetData();
+ if (!obj->GetRange().IsOutside(range))
+ {
+ wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText);
+ if (textObj)
+ {
+ text += textObj->GetTextForRange(range);
+ }
+ else
+ {
+ text += wxT(" ");
+ }
+ }
+
+ node = node->GetNext();
+ }
+ }
+ else
+ {
+ wxRichTextObjectList::compatibility_iterator node = m_children.GetLast();
+ while (node)
+ {
+ wxRichTextObject* obj = node->GetData();
+ if (!obj->GetRange().IsOutside(range))
+ {
+ wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText);
+ if (textObj)
+ {
+ text = textObj->GetTextForRange(range) + text;
+ }
+ else
+ {
+ text = wxT(" ") + text;
+ }
+ }
+
+ node = node->GetPrevious();
+ }
+ }
+
+ return true;
+}
+
+/// Find a suitable wrap position.
+bool wxRichTextParagraph::FindWrapPosition(const wxRichTextRange& range, wxDC& dc, wxRichTextDrawingContext& context, int availableSpace, long& wrapPosition, wxArrayInt* partialExtents)
+{
+ if (range.GetLength() <= 0)
+ return false;
+
+ // Find the first position where the line exceeds the available space.
+ wxSize sz;
+ long breakPosition = range.GetEnd();
+
+#if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
+ if (partialExtents && partialExtents->GetCount() >= (size_t) (GetRange().GetLength()-1)) // the final position in a paragraph is the newline
+ {
+ int widthBefore;
+
+ if (range.GetStart() > GetRange().GetStart())
+ widthBefore = (*partialExtents)[range.GetStart() - GetRange().GetStart() - 1];
+ else
+ widthBefore = 0;
+
+ size_t i;
+ for (i = (size_t) range.GetStart(); i <= (size_t) range.GetEnd(); i++)
+ {
+ int widthFromStartOfThisRange = (*partialExtents)[i - GetRange().GetStart()] - widthBefore;
+
+ if (widthFromStartOfThisRange > availableSpace)
+ {
+ breakPosition = i-1;
+ break;
+ }
+ }
+ }
+ else
+#endif
+ {
+ // Binary chop for speed
+ long minPos = range.GetStart();
+ long maxPos = range.GetEnd();
+ while (true)
+ {
+ if (minPos == maxPos)
+ {
+ int descent = 0;
+ GetRangeSize(wxRichTextRange(range.GetStart(), minPos), sz, descent, dc, context, wxRICHTEXT_UNFORMATTED);
+
+ if (sz.x > availableSpace)
+ breakPosition = minPos - 1;
+ break;
+ }
+ else if ((maxPos - minPos) == 1)
+ {
+ int descent = 0;
+ GetRangeSize(wxRichTextRange(range.GetStart(), minPos), sz, descent, dc, context, wxRICHTEXT_UNFORMATTED);
+
+ if (sz.x > availableSpace)
+ breakPosition = minPos - 1;
+ else
+ {
+ GetRangeSize(wxRichTextRange(range.GetStart(), maxPos), sz, descent, dc, context, wxRICHTEXT_UNFORMATTED);
+ if (sz.x > availableSpace)
+ breakPosition = maxPos-1;
+ }
+ break;
+ }
+ else
+ {
+ long nextPos = minPos + ((maxPos - minPos) / 2);
+
+ int descent = 0;
+ GetRangeSize(wxRichTextRange(range.GetStart(), nextPos), sz, descent, dc, context, wxRICHTEXT_UNFORMATTED);
+
+ if (sz.x > availableSpace)
+ {
+ maxPos = nextPos;
+ }
+ else
+ {
+ minPos = nextPos;
+ }
+ }
+ }
+ }
+
+ // Now we know the last position on the line.
+ // Let's try to find a word break.
+
+ wxString plainText;
+ if (GetContiguousPlainText(plainText, wxRichTextRange(range.GetStart(), breakPosition), false))
+ {
+ int newLinePos = plainText.Find(wxRichTextLineBreakChar);
+ if (newLinePos != wxNOT_FOUND)
+ {
+ breakPosition = wxMax(0, range.GetStart() + newLinePos);
+ }
+ else
+ {
+ int spacePos = plainText.Find(wxT(' '), true);
+ int tabPos = plainText.Find(wxT('\t'), true);
+ int pos = wxMax(spacePos, tabPos);
+ if (pos != wxNOT_FOUND)
+ {
+ int positionsFromEndOfString = plainText.length() - pos - 1;
+ breakPosition = breakPosition - positionsFromEndOfString;
+ }
+ }
+ }
+
+ wrapPosition = breakPosition;
+
+ return true;
+}
+
+/// Get the bullet text for this paragraph.
+wxString wxRichTextParagraph::GetBulletText()
+{
+ if (GetAttributes().GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_NONE ||
+ (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_BITMAP))
+ return wxEmptyString;
+
+ int number = GetAttributes().GetBulletNumber();
+
+ wxString text;
+ if ((GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ARABIC) || (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE))
+ {
+ text.Printf(wxT("%d"), number);
+ }
+ else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_LETTERS_UPPER)
+ {
+ // TODO: Unicode, and also check if number > 26
+ text.Printf(wxT("%c"), (wxChar) (number+64));
+ }
+ else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_LETTERS_LOWER)
+ {
+ // TODO: Unicode, and also check if number > 26
+ text.Printf(wxT("%c"), (wxChar) (number+96));
+ }
+ else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ROMAN_UPPER)
+ {
+ text = wxRichTextDecimalToRoman(number);
+ }
+ else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ROMAN_LOWER)
+ {
+ text = wxRichTextDecimalToRoman(number);
+ text.MakeLower();
+ }
+ else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_SYMBOL)
+ {