Added double-left-click word selection support. Now saves only the active attributes to XML. Eliminated wxRichTextFragment class to allow wxRichTextBuffer to be used where wxRichTextFragment was used. Fixed AddParagraph virtual function hiding warning. Miscellaneous small wxRichTextCtrl bug fixes and cleanup. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@41542 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
6134 lines
183 KiB
C++
6134 lines
183 KiB
C++
/////////////////////////////////////////////////////////////////////////////
|
|
// Name: src/richtext/richtextbuffer.cpp
|
|
// Purpose: Buffer for wxRichTextCtrl
|
|
// Author: Julian Smart
|
|
// Modified by:
|
|
// Created: 2005-09-30
|
|
// RCS-ID: $Id$
|
|
// 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/filename.h"
|
|
#include "wx/clipbrd.h"
|
|
#include "wx/wfstream.h"
|
|
#include "wx/mstream.h"
|
|
#include "wx/sstream.h"
|
|
#include "wx/textfile.h"
|
|
|
|
#include "wx/richtext/richtextctrl.h"
|
|
#include "wx/richtext/richtextstyles.h"
|
|
|
|
#include "wx/listimpl.cpp"
|
|
|
|
WX_DEFINE_LIST(wxRichTextObjectList)
|
|
WX_DEFINE_LIST(wxRichTextLineList)
|
|
|
|
/*!
|
|
* wxRichTextObject
|
|
* This is the base for drawable objects.
|
|
*/
|
|
|
|
IMPLEMENT_CLASS(wxRichTextObject, wxObject)
|
|
|
|
wxRichTextObject::wxRichTextObject(wxRichTextObject* parent)
|
|
{
|
|
m_dirty = false;
|
|
m_refCount = 1;
|
|
m_parent = parent;
|
|
m_leftMargin = 0;
|
|
m_rightMargin = 0;
|
|
m_topMargin = 0;
|
|
m_bottomMargin = 0;
|
|
m_descent = 0;
|
|
}
|
|
|
|
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_pos = obj.m_pos;
|
|
m_dirty = obj.m_dirty;
|
|
m_range = obj.m_range;
|
|
m_attributes = obj.m_attributes;
|
|
m_descent = obj.m_descent;
|
|
/*
|
|
if (!m_attributes.GetFont().Ok())
|
|
wxLogDebug(wxT("No font!"));
|
|
if (!obj.m_attributes.GetFont().Ok())
|
|
wxLogDebug(wxT("Parent has no font!"));
|
|
*/
|
|
}
|
|
|
|
void wxRichTextObject::SetMargins(int margin)
|
|
{
|
|
m_leftMargin = m_rightMargin = m_topMargin = m_bottomMargin = margin;
|
|
}
|
|
|
|
void wxRichTextObject::SetMargins(int leftMargin, int rightMargin, int topMargin, int bottomMargin)
|
|
{
|
|
m_leftMargin = leftMargin;
|
|
m_rightMargin = rightMargin;
|
|
m_topMargin = topMargin;
|
|
m_bottomMargin = bottomMargin;
|
|
}
|
|
|
|
// Convert units in tends of a millimetre to device units
|
|
int wxRichTextObject::ConvertTenthsMMToPixels(wxDC& dc, int units)
|
|
{
|
|
int ppi = dc.GetPPI().x;
|
|
|
|
// There are ppi pixels in 254.1 "1/10 mm"
|
|
|
|
double pixels = ((double) units * (double)ppi) / 254.1;
|
|
|
|
return (int) pixels;
|
|
}
|
|
|
|
/// 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");
|
|
}
|
|
|
|
|
|
/*!
|
|
* wxRichTextCompositeObject
|
|
* This is the base for drawable objects.
|
|
*/
|
|
|
|
IMPLEMENT_CLASS(wxRichTextCompositeObject, wxRichTextObject)
|
|
|
|
wxRichTextCompositeObject::wxRichTextCompositeObject(wxRichTextObject* parent):
|
|
wxRichTextObject(parent)
|
|
{
|
|
}
|
|
|
|
wxRichTextCompositeObject::~wxRichTextCompositeObject()
|
|
{
|
|
DeleteChildren();
|
|
}
|
|
|
|
/// Get the nth child
|
|
wxRichTextObject* wxRichTextCompositeObject::GetChild(size_t n) const
|
|
{
|
|
wxASSERT ( n < m_children.GetCount() );
|
|
|
|
return m_children.Item(n)->GetData();
|
|
}
|
|
|
|
/// Append a child, returning the position
|
|
size_t wxRichTextCompositeObject::AppendChild(wxRichTextObject* child)
|
|
{
|
|
m_children.Append(child);
|
|
child->SetParent(this);
|
|
return m_children.GetCount() - 1;
|
|
}
|
|
|
|
/// Insert the child in front of the given object, or at the beginning
|
|
bool wxRichTextCompositeObject::InsertChild(wxRichTextObject* child, wxRichTextObject* inFrontOf)
|
|
{
|
|
if (inFrontOf)
|
|
{
|
|
wxRichTextObjectList::compatibility_iterator node = m_children.Find(inFrontOf);
|
|
m_children.Insert(node, child);
|
|
}
|
|
else
|
|
m_children.Insert(child);
|
|
child->SetParent(this);
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Delete the child
|
|
bool wxRichTextCompositeObject::RemoveChild(wxRichTextObject* child, bool deleteChild)
|
|
{
|
|
wxRichTextObjectList::compatibility_iterator node = m_children.Find(child);
|
|
if (node)
|
|
{
|
|
wxRichTextObject* obj = node->GetData();
|
|
m_children.Erase(node);
|
|
if (deleteChild)
|
|
delete obj;
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// Delete all children
|
|
bool wxRichTextCompositeObject::DeleteChildren()
|
|
{
|
|
wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
|
|
while (node)
|
|
{
|
|
wxRichTextObjectList::compatibility_iterator oldNode = node;
|
|
|
|
wxRichTextObject* child = node->GetData();
|
|
child->Dereference(); // Only delete if reference count is zero
|
|
|
|
node = node->GetNext();
|
|
m_children.Erase(oldNode);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Get the child count
|
|
size_t wxRichTextCompositeObject::GetChildCount() const
|
|
{
|
|
return m_children.GetCount();
|
|
}
|
|
|
|
/// Copy
|
|
void wxRichTextCompositeObject::Copy(const wxRichTextCompositeObject& obj)
|
|
{
|
|
wxRichTextObject::Copy(obj);
|
|
|
|
DeleteChildren();
|
|
|
|
wxRichTextObjectList::compatibility_iterator node = obj.m_children.GetFirst();
|
|
while (node)
|
|
{
|
|
wxRichTextObject* child = node->GetData();
|
|
wxRichTextObject* newChild = child->Clone();
|
|
newChild->SetParent(this);
|
|
m_children.Append(newChild);
|
|
|
|
node = node->GetNext();
|
|
}
|
|
}
|
|
|
|
/// Hit-testing: returns a flag indicating hit test details, plus
|
|
/// information about position
|
|
int wxRichTextCompositeObject::HitTest(wxDC& dc, const wxPoint& pt, long& textPosition)
|
|
{
|
|
wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
|
|
while (node)
|
|
{
|
|
wxRichTextObject* child = node->GetData();
|
|
|
|
int ret = child->HitTest(dc, pt, textPosition);
|
|
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, long index, wxPoint& pt, int* height, bool forceLineStart)
|
|
{
|
|
wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
|
|
while (node)
|
|
{
|
|
wxRichTextObject* child = node->GetData();
|
|
|
|
if (child->FindPosition(dc, 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;
|
|
|
|
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();
|
|
}
|
|
|
|
end = lastEnd;
|
|
|
|
// An object with no children has zero length
|
|
if (m_children.GetCount() == 0)
|
|
end --;
|
|
|
|
m_range.SetRange(start, end);
|
|
}
|
|
|
|
/// Delete range from layout.
|
|
bool wxRichTextCompositeObject::DeleteRange(const wxRichTextRange& range)
|
|
{
|
|
wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
|
|
|
|
while (node)
|
|
{
|
|
wxRichTextObject* obj = (wxRichTextObject*) node->GetData();
|
|
wxRichTextObjectList::compatibility_iterator next = node->GetNext();
|
|
|
|
// Delete the range in each paragraph
|
|
|
|
// When a chunk has been deleted, internally the content does not
|
|
// now match the ranges.
|
|
// However, so long as deletion is not done on the same object twice this is OK.
|
|
// If you may delete content from the same object twice, recalculate
|
|
// the ranges inbetween DeleteRange calls by calling CalculateRanges, and
|
|
// adjust the range you're deleting accordingly.
|
|
|
|
if (!obj->GetRange().IsOutside(range))
|
|
{
|
|
obj->DeleteRange(range);
|
|
|
|
// Delete an empty object, or paragraph within this range.
|
|
if (obj->IsEmpty() ||
|
|
(range.GetStart() <= obj->GetRange().GetStart() && range.GetEnd() >= obj->GetRange().GetEnd()))
|
|
{
|
|
// An empty paragraph has length 1, so won't be deleted unless the
|
|
// whole range is deleted.
|
|
RemoveChild(obj, true);
|
|
}
|
|
}
|
|
|
|
node = next;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Get any text in this object for the given range
|
|
wxString wxRichTextCompositeObject::GetTextForRange(const wxRichTextRange& range) const
|
|
{
|
|
wxString text;
|
|
wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
|
|
while (node)
|
|
{
|
|
wxRichTextObject* child = node->GetData();
|
|
wxRichTextRange childRange = range;
|
|
if (!child->GetRange().IsOutside(range))
|
|
{
|
|
childRange.LimitTo(child->GetRange());
|
|
|
|
wxString childText = child->GetTextForRange(childRange);
|
|
|
|
text += childText;
|
|
}
|
|
node = node->GetNext();
|
|
}
|
|
|
|
return text;
|
|
}
|
|
|
|
/// Recursively merge all pieces that can be merged.
|
|
bool wxRichTextCompositeObject::Defragment()
|
|
{
|
|
wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
|
|
while (node)
|
|
{
|
|
wxRichTextObject* child = node->GetData();
|
|
wxRichTextCompositeObject* composite = wxDynamicCast(child, wxRichTextCompositeObject);
|
|
if (composite)
|
|
composite->Defragment();
|
|
|
|
if (node->GetNext())
|
|
{
|
|
wxRichTextObject* nextChild = node->GetNext()->GetData();
|
|
if (child->CanMerge(nextChild) && child->Merge(nextChild))
|
|
{
|
|
nextChild->Dereference();
|
|
m_children.Erase(node->GetNext());
|
|
|
|
// Don't set node -- we'll see if we can merge again with the next
|
|
// child.
|
|
}
|
|
else
|
|
node = node->GetNext();
|
|
}
|
|
else
|
|
node = node->GetNext();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Dump to output stream for debugging
|
|
void wxRichTextCompositeObject::Dump(wxTextOutputStream& stream)
|
|
{
|
|
wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
|
|
while (node)
|
|
{
|
|
wxRichTextObject* child = node->GetData();
|
|
child->Dump(stream);
|
|
node = node->GetNext();
|
|
}
|
|
}
|
|
|
|
|
|
/*!
|
|
* wxRichTextBox
|
|
* This defines a 2D space to lay out objects
|
|
*/
|
|
|
|
IMPLEMENT_DYNAMIC_CLASS(wxRichTextBox, wxRichTextCompositeObject)
|
|
|
|
wxRichTextBox::wxRichTextBox(wxRichTextObject* parent):
|
|
wxRichTextCompositeObject(parent)
|
|
{
|
|
}
|
|
|
|
/// Draw the item
|
|
bool wxRichTextBox::Draw(wxDC& dc, const wxRichTextRange& range, const wxRichTextRange& selectionRange, const wxRect& WXUNUSED(rect), int descent, int style)
|
|
{
|
|
wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
|
|
while (node)
|
|
{
|
|
wxRichTextObject* child = node->GetData();
|
|
|
|
wxRect childRect = wxRect(child->GetPosition(), child->GetCachedSize());
|
|
child->Draw(dc, range, selectionRange, childRect, descent, style);
|
|
|
|
node = node->GetNext();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// Lay the item out
|
|
bool wxRichTextBox::Layout(wxDC& dc, const wxRect& rect, int style)
|
|
{
|
|
wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
|
|
while (node)
|
|
{
|
|
wxRichTextObject* child = node->GetData();
|
|
child->Layout(dc, rect, style);
|
|
|
|
node = node->GetNext();
|
|
}
|
|
m_dirty = false;
|
|
return true;
|
|
}
|
|
|
|
/// Get/set the size for the given range. Assume only has one child.
|
|
bool wxRichTextBox::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, int flags, wxPoint position) const
|
|
{
|
|
wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
|
|
if (node)
|
|
{
|
|
wxRichTextObject* child = node->GetData();
|
|
return child->GetRangeSize(range, size, descent, dc, flags, position);
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
/// Copy
|
|
void wxRichTextBox::Copy(const wxRichTextBox& obj)
|
|
{
|
|
wxRichTextCompositeObject::Copy(obj);
|
|
}
|
|
|
|
|
|
/*!
|
|
* wxRichTextParagraphLayoutBox
|
|
* This box knows how to lay out paragraphs.
|
|
*/
|
|
|
|
IMPLEMENT_DYNAMIC_CLASS(wxRichTextParagraphLayoutBox, wxRichTextBox)
|
|
|
|
wxRichTextParagraphLayoutBox::wxRichTextParagraphLayoutBox(wxRichTextObject* parent):
|
|
wxRichTextBox(parent)
|
|
{
|
|
Init();
|
|
}
|
|
|
|
/// 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_invalidRange.SetRange(-1, -1);
|
|
m_leftMargin = 4;
|
|
m_rightMargin = 4;
|
|
m_topMargin = 4;
|
|
m_bottomMargin = 4;
|
|
m_partialParagraph = false;
|
|
}
|
|
|
|
/// Draw the item
|
|
bool wxRichTextParagraphLayoutBox::Draw(wxDC& dc, const wxRichTextRange& range, const wxRichTextRange& selectionRange, const wxRect& rect, int descent, int style)
|
|
{
|
|
wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
|
|
while (node)
|
|
{
|
|
wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
|
|
wxASSERT (child != NULL);
|
|
|
|
if (child && !child->GetRange().IsOutside(range))
|
|
{
|
|
wxRect childRect(child->GetPosition(), child->GetCachedSize());
|
|
|
|
if (childRect.GetTop() > rect.GetBottom() || childRect.GetBottom() < rect.GetTop())
|
|
{
|
|
// Skip
|
|
}
|
|
else
|
|
child->Draw(dc, child->GetRange(), selectionRange, childRect, descent, style);
|
|
}
|
|
|
|
node = node->GetNext();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// Lay the item out
|
|
bool wxRichTextParagraphLayoutBox::Layout(wxDC& dc, const wxRect& rect, int style)
|
|
{
|
|
wxRect availableSpace;
|
|
bool formatRect = (style & wxRICHTEXT_LAYOUT_SPECIFIED_RECT) == wxRICHTEXT_LAYOUT_SPECIFIED_RECT;
|
|
|
|
// If only laying out a specific area, the passed rect has a different meaning:
|
|
// the visible part of the buffer.
|
|
if (formatRect)
|
|
{
|
|
availableSpace = wxRect(0 + m_leftMargin,
|
|
0 + m_topMargin,
|
|
rect.width - m_leftMargin - m_rightMargin,
|
|
rect.height);
|
|
|
|
// 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, GetRange().GetEnd()));
|
|
}
|
|
else
|
|
availableSpace = wxRect(rect.x + m_leftMargin,
|
|
rect.y + m_topMargin,
|
|
rect.width - m_leftMargin - m_rightMargin,
|
|
rect.height - m_topMargin - m_bottomMargin);
|
|
|
|
int maxWidth = 0;
|
|
|
|
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)
|
|
layoutAll = true;
|
|
else // If we know what range is affected, start laying out from that point on.
|
|
if (invalidRange.GetStart() > GetRange().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 && 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// A way to force speedy rest-of-buffer layout (the 'else' below)
|
|
bool forceQuickLayout = false;
|
|
|
|
while (node)
|
|
{
|
|
// Assume this box only contains paragraphs
|
|
|
|
wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
|
|
wxCHECK_MSG( child, false, _T("Unknown object in layout") );
|
|
|
|
// 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)) )
|
|
{
|
|
child->Layout(dc, availableSpace, style);
|
|
|
|
// Layout must set the cached size
|
|
availableSpace.y += child->GetCachedSize().y;
|
|
maxWidth = wxMax(maxWidth, child->GetCachedSize().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 (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.
|
|
|
|
int inc = availableSpace.y - child->GetPosition().y;
|
|
|
|
while (node)
|
|
{
|
|
wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
|
|
if (child)
|
|
{
|
|
if (child->GetLines().GetCount() == 0)
|
|
child->Layout(dc, availableSpace, style);
|
|
else
|
|
child->SetPosition(wxPoint(child->GetPosition().x, child->GetPosition().y + inc));
|
|
|
|
availableSpace.y += child->GetCachedSize().y;
|
|
maxWidth = wxMax(maxWidth, child->GetCachedSize().x);
|
|
}
|
|
|
|
node = node->GetNext();
|
|
}
|
|
break;
|
|
}
|
|
|
|
node = node->GetNext();
|
|
}
|
|
|
|
SetCachedSize(wxSize(maxWidth, availableSpace.y));
|
|
|
|
m_dirty = false;
|
|
m_invalidRange = wxRICHTEXT_NONE;
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Copy
|
|
void wxRichTextParagraphLayoutBox::Copy(const wxRichTextParagraphLayoutBox& obj)
|
|
{
|
|
wxRichTextBox::Copy(obj);
|
|
|
|
m_partialParagraph = obj.m_partialParagraph;
|
|
}
|
|
|
|
/// Get/set the size for the given range.
|
|
bool wxRichTextParagraphLayoutBox::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, int flags, wxPoint position) 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);
|
|
|
|
wxSize childSize;
|
|
|
|
int childDescent = 0;
|
|
child->GetRangeSize(rangeToFind, childSize, childDescent, dc, flags, position);
|
|
|
|
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);
|
|
|
|
// 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)
|
|
{
|
|
// child is a paragraph
|
|
wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
|
|
wxASSERT (child != NULL);
|
|
|
|
wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
|
|
while (node2)
|
|
{
|
|
wxRichTextLine* line = node2->GetData();
|
|
|
|
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);
|
|
|
|
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);
|
|
|
|
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, wxTextAttrEx* paraStyle)
|
|
{
|
|
#if wxRICHTEXT_USE_DYNAMIC_STYLES
|
|
// Don't use the base style, just the default style, and the base style will
|
|
// be combined at display time
|
|
wxTextAttrEx style(GetDefaultStyle());
|
|
#else
|
|
wxTextAttrEx style(GetAttributes());
|
|
|
|
// Apply default style. If the style has no attributes set,
|
|
// then the attributes will remain the 'basic style' (i.e. the
|
|
// layout box's style).
|
|
wxRichTextApplyStyle(style, GetDefaultStyle());
|
|
#endif
|
|
wxRichTextParagraph* para = new wxRichTextParagraph(text, this, & style);
|
|
if (paraStyle)
|
|
para->SetAttributes(*paraStyle);
|
|
|
|
AppendChild(para);
|
|
|
|
UpdateRanges();
|
|
SetDirty(true);
|
|
|
|
return para->GetRange();
|
|
}
|
|
|
|
/// Adds multiple paragraphs, based on newlines.
|
|
wxRichTextRange wxRichTextParagraphLayoutBox::AddParagraphs(const wxString& text, wxTextAttrEx* paraStyle)
|
|
{
|
|
#if wxRICHTEXT_USE_DYNAMIC_STYLES
|
|
// Don't use the base style, just the default style, and the base style will
|
|
// be combined at display time
|
|
wxTextAttrEx style(GetDefaultStyle());
|
|
#else
|
|
wxTextAttrEx style(GetAttributes());
|
|
|
|
//wxLogDebug("Initial style = %s", style.GetFont().GetFaceName());
|
|
//wxLogDebug("Initial size = %d", style.GetFont().GetPointSize());
|
|
|
|
// Apply default style. If the style has no attributes set,
|
|
// then the attributes will remain the 'basic style' (i.e. the
|
|
// layout box's style).
|
|
wxRichTextApplyStyle(style, GetDefaultStyle());
|
|
|
|
//wxLogDebug("Style after applying default style = %s", style.GetFont().GetFaceName());
|
|
//wxLogDebug("Size after applying default style = %d", style.GetFont().GetPointSize());
|
|
#endif
|
|
|
|
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(wxT(""), this, & style);
|
|
if (paraStyle)
|
|
para->SetAttributes(*paraStyle);
|
|
|
|
AppendChild(para);
|
|
|
|
firstPara = para;
|
|
lastPara = para;
|
|
|
|
while (i < len)
|
|
{
|
|
wxChar ch = text[i];
|
|
if (ch == wxT('\n') || ch == wxT('\r'))
|
|
{
|
|
wxRichTextPlainText* plainText = (wxRichTextPlainText*) para->GetChildren().GetFirst()->GetData();
|
|
plainText->SetText(line);
|
|
|
|
para = new wxRichTextParagraph(wxT(""), this, & style);
|
|
if (paraStyle)
|
|
para->SetAttributes(*paraStyle);
|
|
|
|
AppendChild(para);
|
|
|
|
//if (!firstPara)
|
|
// firstPara = para;
|
|
|
|
lastPara = para;
|
|
line = wxEmptyString;
|
|
}
|
|
else
|
|
line += ch;
|
|
|
|
i ++;
|
|
}
|
|
|
|
if (!line.empty())
|
|
{
|
|
wxRichTextPlainText* plainText = (wxRichTextPlainText*) para->GetChildren().GetFirst()->GetData();
|
|
plainText->SetText(line);
|
|
}
|
|
|
|
/*
|
|
if (firstPara)
|
|
range.SetStart(firstPara->GetRange().GetStart());
|
|
else if (lastPara)
|
|
range.SetStart(lastPara->GetRange().GetStart());
|
|
|
|
if (lastPara)
|
|
range.SetEnd(lastPara->GetRange().GetEnd());
|
|
else if (firstPara)
|
|
range.SetEnd(firstPara->GetRange().GetEnd());
|
|
*/
|
|
|
|
UpdateRanges();
|
|
|
|
SetDirty(false);
|
|
|
|
return wxRichTextRange(firstPara->GetRange().GetStart(), lastPara->GetRange().GetEnd());
|
|
}
|
|
|
|
/// Convenience function to add an image
|
|
wxRichTextRange wxRichTextParagraphLayoutBox::AddImage(const wxImage& image, wxTextAttrEx* paraStyle)
|
|
{
|
|
#if wxRICHTEXT_USE_DYNAMIC_STYLES
|
|
// Don't use the base style, just the default style, and the base style will
|
|
// be combined at display time
|
|
wxTextAttrEx style(GetDefaultStyle());
|
|
#else
|
|
wxTextAttrEx style(GetAttributes());
|
|
|
|
// Apply default style. If the style has no attributes set,
|
|
// then the attributes will remain the 'basic style' (i.e. the
|
|
// layout box's style).
|
|
wxRichTextApplyStyle(style, GetDefaultStyle());
|
|
#endif
|
|
|
|
wxRichTextParagraph* para = new wxRichTextParagraph(this, & style);
|
|
AppendChild(para);
|
|
para->AppendChild(new wxRichTextImage(image, this));
|
|
|
|
if (paraStyle)
|
|
para->SetAttributes(*paraStyle);
|
|
|
|
UpdateRanges();
|
|
SetDirty(true);
|
|
|
|
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.
|
|
/// TODO: if fragment is inserted inside styled fragment, must apply that style to
|
|
/// to the data (if it has a default style, anyway).
|
|
|
|
bool wxRichTextParagraphLayoutBox::InsertFragment(long position, wxRichTextParagraphLayoutBox& fragment)
|
|
{
|
|
SetDirty(true);
|
|
|
|
// First, find the first paragraph whose starting position is within the range.
|
|
wxRichTextParagraph* para = GetParagraphAtPosition(position);
|
|
if (para)
|
|
{
|
|
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);
|
|
|
|
wxRichTextObjectList::compatibility_iterator objectNode = firstPara->GetChildren().GetFirst();
|
|
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;
|
|
|
|
// If there was only one paragraph, we need to insert a new one.
|
|
if (!i)
|
|
{
|
|
finalPara = new wxRichTextParagraph;
|
|
|
|
// TODO: These attributes should come from the subsequent paragraph
|
|
// when originally deleted, since the subsequent para takes on
|
|
// the previous para's attributes.
|
|
finalPara->SetAttributes(firstPara->GetAttributes());
|
|
|
|
if (nextParagraph)
|
|
InsertChild(finalPara, nextParagraph);
|
|
else
|
|
AppendChild(finalPara);
|
|
}
|
|
else while (i)
|
|
{
|
|
wxRichTextParagraph* para = wxDynamicCast(i->GetData(), wxRichTextParagraph);
|
|
wxASSERT( para != NULL );
|
|
|
|
finalPara = (wxRichTextParagraph*) para->Clone();
|
|
|
|
if (nextParagraph)
|
|
InsertChild(finalPara, nextParagraph);
|
|
else
|
|
AppendChild(finalPara);
|
|
|
|
i = i->GetNext();
|
|
}
|
|
|
|
// 4. Add back the remaining content.
|
|
if (finalPara)
|
|
{
|
|
finalPara->MoveFromList(savedObjects);
|
|
|
|
// Ensure there's at least one object
|
|
if (finalPara->GetChildCount() == 0)
|
|
{
|
|
wxRichTextPlainText* text = new wxRichTextPlainText(wxEmptyString);
|
|
#if !wxRICHTEXT_USE_DYNAMIC_STYLES
|
|
text->SetAttributes(finalPara->GetAttributes());
|
|
#endif
|
|
|
|
finalPara->AppendChild(text);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Append
|
|
wxRichTextObjectList::compatibility_iterator i = fragment.GetChildren().GetFirst();
|
|
while (i)
|
|
{
|
|
wxRichTextParagraph* para = wxDynamicCast(i->GetData(), wxRichTextParagraph);
|
|
wxASSERT( para != NULL );
|
|
|
|
AppendChild(para->Clone());
|
|
|
|
i = i->GetNext();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/// Make a copy of the fragment corresponding to the given range, putting it in 'fragment'.
|
|
/// If there was an incomplete paragraph at the end, partialParagraph is set to true.
|
|
bool wxRichTextParagraphLayoutBox::CopyFragment(const wxRichTextRange& range, wxRichTextParagraphLayoutBox& fragment)
|
|
{
|
|
wxRichTextObjectList::compatibility_iterator i = GetChildren().GetFirst();
|
|
while (i)
|
|
{
|
|
wxRichTextParagraph* para = wxDynamicCast(i->GetData(), wxRichTextParagraph);
|
|
wxASSERT( para != NULL );
|
|
|
|
if (!para->GetRange().IsOutside(range))
|
|
{
|
|
fragment.AppendChild(para->Clone());
|
|
}
|
|
i = i->GetNext();
|
|
}
|
|
|
|
// Now top and tail the first and last paragraphs in our new fragment (which might be the same).
|
|
if (!fragment.IsEmpty())
|
|
{
|
|
wxRichTextRange topTailRange(range);
|
|
|
|
wxRichTextParagraph* firstPara = wxDynamicCast(fragment.GetChildren().GetFirst()->GetData(), wxRichTextParagraph);
|
|
wxASSERT( firstPara != NULL );
|
|
|
|
// Chop off the start of the paragraph
|
|
if (topTailRange.GetStart() > firstPara->GetRange().GetStart())
|
|
{
|
|
wxRichTextRange r(firstPara->GetRange().GetStart(), topTailRange.GetStart()-1);
|
|
firstPara->DeleteRange(r);
|
|
|
|
// Make sure the numbering is correct
|
|
long end;
|
|
fragment.CalculateRange(firstPara->GetRange().GetStart(), end);
|
|
|
|
// Now, we've deleted some positions, so adjust the range
|
|
// accordingly.
|
|
topTailRange.SetEnd(topTailRange.GetEnd() - r.GetLength());
|
|
}
|
|
|
|
wxRichTextParagraph* lastPara = wxDynamicCast(fragment.GetChildren().GetLast()->GetData(), wxRichTextParagraph);
|
|
wxASSERT( lastPara != NULL );
|
|
|
|
if (topTailRange.GetEnd() < (lastPara->GetRange().GetEnd()-1))
|
|
{
|
|
wxRichTextRange r(topTailRange.GetEnd()+1, lastPara->GetRange().GetEnd()-1); /* -1 since actual text ends 1 position before end of para marker */
|
|
lastPara->DeleteRange(r);
|
|
|
|
// Make sure the numbering is correct
|
|
long end;
|
|
fragment.CalculateRange(firstPara->GetRange().GetStart(), end);
|
|
|
|
// We only have part of a paragraph at the end
|
|
fragment.SetPartialParagraph(true);
|
|
}
|
|
else
|
|
{
|
|
if (topTailRange.GetEnd() == (lastPara->GetRange().GetEnd() - 1))
|
|
// We have a partial paragraph (don't save last new paragraph marker)
|
|
fragment.SetPartialParagraph(true);
|
|
else
|
|
// We have a complete paragraph
|
|
fragment.SetPartialParagraph(false);
|
|
}
|
|
}
|
|
|
|
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->GetRange().Contains(pos))
|
|
{
|
|
wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
|
|
while (node2)
|
|
{
|
|
wxRichTextLine* line = node2->GetData();
|
|
wxRichTextRange lineRange = line->GetAbsoluteRange();
|
|
|
|
if (lineRange.Contains(pos))
|
|
{
|
|
// 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 (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();
|
|
|
|
while (node)
|
|
{
|
|
wxRichTextParagraph* obj = wxDynamicCast(node->GetData(), wxRichTextParagraph);
|
|
wxASSERT (obj != NULL);
|
|
|
|
wxRichTextObjectList::compatibility_iterator next = node->GetNext();
|
|
|
|
// Delete the range in each paragraph
|
|
|
|
if (!obj->GetRange().IsOutside(range))
|
|
{
|
|
// Deletes the content of this object within the given range
|
|
obj->DeleteRange(range);
|
|
|
|
// If the whole paragraph is within the range to delete,
|
|
// delete the whole thing.
|
|
if (range.GetStart() <= obj->GetRange().GetStart() && range.GetEnd() >= obj->GetRange().GetEnd())
|
|
{
|
|
// Delete the whole object
|
|
RemoveChild(obj, true);
|
|
}
|
|
// If the range includes the paragraph end, we need to join this
|
|
// and the next paragraph.
|
|
else if (range.Contains(obj->GetRange().GetEnd()))
|
|
{
|
|
// We need to move the objects from the next paragraph
|
|
// to this paragraph
|
|
|
|
if (next)
|
|
{
|
|
wxRichTextParagraph* nextParagraph = wxDynamicCast(next->GetData(), wxRichTextParagraph);
|
|
next = next->GetNext();
|
|
if (nextParagraph)
|
|
{
|
|
// Delete the stuff we need to delete
|
|
nextParagraph->DeleteRange(range);
|
|
|
|
// Move the objects to the previous para
|
|
wxRichTextObjectList::compatibility_iterator node1 = nextParagraph->GetChildren().GetFirst();
|
|
|
|
while (node1)
|
|
{
|
|
wxRichTextObject* obj1 = node1->GetData();
|
|
|
|
// If the object is empty, optimise it out
|
|
if (obj1->IsEmpty())
|
|
{
|
|
delete obj1;
|
|
}
|
|
else
|
|
{
|
|
obj->AppendChild(obj1);
|
|
}
|
|
|
|
wxRichTextObjectList::compatibility_iterator next1 = node1->GetNext();
|
|
nextParagraph->GetChildren().Erase(node1);
|
|
|
|
node1 = next1;
|
|
}
|
|
|
|
// Delete the paragraph
|
|
RemoveChild(nextParagraph, 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))
|
|
{
|
|
// if (lineCount > 0)
|
|
// text += wxT("\n");
|
|
wxRichTextRange childRange = range;
|
|
childRange.LimitTo(child->GetRange());
|
|
|
|
wxString childText = child->GetTextForRange(childRange);
|
|
|
|
text += childText;
|
|
|
|
if (childRange.GetEnd() == child->GetRange().GetEnd())
|
|
text += wxT("\n");
|
|
|
|
lineCount ++;
|
|
}
|
|
node = node->GetNext();
|
|
}
|
|
|
|
return text;
|
|
}
|
|
|
|
/// Get all the text
|
|
wxString wxRichTextParagraphLayoutBox::GetText() const
|
|
{
|
|
return GetTextForRange(GetRange());
|
|
}
|
|
|
|
/// 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.
|
|
/// Given a line number, get the corresponding wxRichTextLine object.
|
|
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, bool withUndo)
|
|
{
|
|
bool characterStyle = false;
|
|
bool paragraphStyle = false;
|
|
|
|
if (style.IsCharacterStyle())
|
|
characterStyle = true;
|
|
if (style.IsParagraphStyle())
|
|
paragraphStyle = true;
|
|
|
|
// If we are associated with a control, make undoable; otherwise, apply immediately
|
|
// to the data.
|
|
|
|
bool haveControl = (GetRichTextCtrl() != NULL);
|
|
|
|
wxRichTextAction* action = NULL;
|
|
|
|
if (haveControl && withUndo)
|
|
{
|
|
action = new wxRichTextAction(NULL, _("Change Style"), wxRICHTEXT_CHANGE_STYLE, & GetRichTextCtrl()->GetBuffer(), GetRichTextCtrl());
|
|
action->SetRange(range);
|
|
action->SetPosition(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 (paragraphStyle)
|
|
wxRichTextApplyStyle(newPara->GetAttributes(), style);
|
|
|
|
#if wxRICHTEXT_USE_DYNAMIC_STYLES
|
|
// If 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.
|
|
|
|
if (!paragraphStyle && characterStyle && range.GetStart() != newPara->GetRange().GetEnd())
|
|
#else
|
|
if (characterStyle && range.GetStart() != newPara->GetRange().GetEnd())
|
|
#endif
|
|
{
|
|
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() || splitPoint == (newPara->GetRange().GetEnd() - 1))
|
|
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();
|
|
|
|
wxRichTextApplyStyle(child->GetAttributes(), style);
|
|
if (node2 == lastNode)
|
|
break;
|
|
|
|
node2 = node2->GetNext();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
node = node->GetNext();
|
|
}
|
|
|
|
// Do action, or delay it until end of batch.
|
|
if (haveControl && withUndo)
|
|
GetRichTextCtrl()->GetBuffer().SubmitAction(action);
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Set text attributes
|
|
bool wxRichTextParagraphLayoutBox::SetStyle(const wxRichTextRange& range, const wxTextAttrEx& style, bool withUndo)
|
|
{
|
|
wxRichTextAttr richStyle = style;
|
|
return SetStyle(range, richStyle, withUndo);
|
|
}
|
|
|
|
/// Get the text attributes for this position.
|
|
bool wxRichTextParagraphLayoutBox::GetStyle(long position, wxTextAttrEx& style)
|
|
{
|
|
return DoGetStyle(position, style, true);
|
|
}
|
|
|
|
/// Get the text attributes for this position.
|
|
bool wxRichTextParagraphLayoutBox::GetStyle(long position, wxRichTextAttr& style)
|
|
{
|
|
wxTextAttrEx textAttrEx(style);
|
|
if (GetStyle(position, textAttrEx))
|
|
{
|
|
style = textAttrEx;
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
/// Get the content (uncombined) attributes for this position.
|
|
bool wxRichTextParagraphLayoutBox::GetUncombinedStyle(long position, wxTextAttrEx& style)
|
|
{
|
|
return DoGetStyle(position, style, false);
|
|
}
|
|
|
|
bool wxRichTextParagraphLayoutBox::GetUncombinedStyle(long position, wxRichTextAttr& style)
|
|
{
|
|
wxTextAttrEx textAttrEx(style);
|
|
if (GetUncombinedStyle(position, textAttrEx))
|
|
{
|
|
style = textAttrEx;
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
/// Implementation helper for GetStyle. If combineStyles is true, combine base, paragraph and
|
|
/// context attributes.
|
|
bool wxRichTextParagraphLayoutBox::DoGetStyle(long position, wxTextAttrEx& style, bool combineStyles)
|
|
{
|
|
wxRichTextObject* obj wxDUMMY_INITIALIZE(NULL);
|
|
|
|
if (style.IsParagraphStyle())
|
|
{
|
|
obj = GetParagraphAtPosition(position);
|
|
if (obj)
|
|
{
|
|
#if wxRICHTEXT_USE_DYNAMIC_STYLES
|
|
if (combineStyles)
|
|
{
|
|
// Start with the base style
|
|
style = GetAttributes();
|
|
|
|
// Apply the paragraph style
|
|
wxRichTextApplyStyle(style, obj->GetAttributes());
|
|
}
|
|
else
|
|
style = obj->GetAttributes();
|
|
#else
|
|
style = obj->GetAttributes();
|
|
#endif
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
obj = GetLeafObjectAtPosition(position);
|
|
if (obj)
|
|
{
|
|
#if wxRICHTEXT_USE_DYNAMIC_STYLES
|
|
if (combineStyles)
|
|
{
|
|
wxRichTextParagraph* para = wxDynamicCast(obj->GetParent(), wxRichTextParagraph);
|
|
style = para ? para->GetCombinedAttributes(obj->GetAttributes()) : obj->GetAttributes();
|
|
}
|
|
else
|
|
style = obj->GetAttributes();
|
|
#else
|
|
style = obj->GetAttributes();
|
|
#endif
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// Set default style
|
|
bool wxRichTextParagraphLayoutBox::SetDefaultStyle(const wxTextAttrEx& style)
|
|
{
|
|
// I don't think the default style should be combined with the previous
|
|
// default style.
|
|
m_defaultAttributes = style;
|
|
|
|
#if 0
|
|
// keep the old attributes if the new style doesn't specify them unless the
|
|
// new style is empty - then reset m_defaultStyle (as there is no other way
|
|
// to do it)
|
|
if ( style.IsDefault() )
|
|
m_defaultAttributes = style;
|
|
else
|
|
m_defaultAttributes = wxTextAttrEx::CombineEx(style, m_defaultAttributes, NULL);
|
|
#endif
|
|
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;
|
|
|
|
if (!para->GetRange().IsOutside(range))
|
|
{
|
|
wxRichTextObjectList::compatibility_iterator node2 = para->GetChildren().GetFirst();
|
|
|
|
while (node2)
|
|
{
|
|
wxRichTextObject* child = node2->GetData();
|
|
if (!child->GetRange().IsOutside(range) && child->IsKindOf(CLASSINFO(wxRichTextPlainText)))
|
|
{
|
|
foundCount ++;
|
|
#if wxRICHTEXT_USE_DYNAMIC_STYLES
|
|
wxTextAttrEx textAttr = para->GetCombinedAttributes(child->GetAttributes());
|
|
#else
|
|
const wxTextAttrEx& textAttr = child->GetAttributes();
|
|
#endif
|
|
if (wxTextAttrEqPartial(textAttr, style, style.GetFlags()))
|
|
matchingCount ++;
|
|
}
|
|
|
|
node2 = node2->GetNext();
|
|
}
|
|
}
|
|
}
|
|
|
|
node = node->GetNext();
|
|
}
|
|
|
|
return foundCount == matchingCount;
|
|
}
|
|
|
|
bool wxRichTextParagraphLayoutBox::HasCharacterAttributes(const wxRichTextRange& range, const wxTextAttrEx& style) const
|
|
{
|
|
wxRichTextAttr richStyle = style;
|
|
return HasCharacterAttributes(range, richStyle);
|
|
}
|
|
|
|
/// Test if this whole range has paragraph attributes of the specified kind. If any
|
|
/// of the attributes are different within the range, the test fails. You
|
|
/// can use this to implement, for example, centering button updating. style must have
|
|
/// flags indicating which attributes are of interest.
|
|
bool wxRichTextParagraphLayoutBox::HasParagraphAttributes(const wxRichTextRange& range, const wxRichTextAttr& style) const
|
|
{
|
|
int foundCount = 0;
|
|
int matchingCount = 0;
|
|
|
|
wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
|
|
while (node)
|
|
{
|
|
wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
|
|
wxASSERT (para != NULL);
|
|
|
|
if (para)
|
|
{
|
|
// Stop searching if we're beyond the range of interest
|
|
if (para->GetRange().GetStart() > range.GetEnd())
|
|
return foundCount == matchingCount;
|
|
|
|
if (!para->GetRange().IsOutside(range))
|
|
{
|
|
#if wxRICHTEXT_USE_DYNAMIC_STYLES
|
|
wxTextAttrEx textAttr = GetAttributes();
|
|
// Apply the paragraph style
|
|
wxRichTextApplyStyle(textAttr, para->GetAttributes());
|
|
|
|
#else
|
|
const wxTextAttrEx& textAttr = para->GetAttributes();
|
|
#endif
|
|
foundCount ++;
|
|
if (wxTextAttrEqPartial(textAttr, style, style.GetFlags()))
|
|
matchingCount ++;
|
|
}
|
|
}
|
|
|
|
node = node->GetNext();
|
|
}
|
|
return foundCount == matchingCount;
|
|
}
|
|
|
|
bool wxRichTextParagraphLayoutBox::HasParagraphAttributes(const wxRichTextRange& range, const wxTextAttrEx& style) const
|
|
{
|
|
wxRichTextAttr richStyle = style;
|
|
return HasParagraphAttributes(range, richStyle);
|
|
}
|
|
|
|
void wxRichTextParagraphLayoutBox::Clear()
|
|
{
|
|
DeleteChildren();
|
|
}
|
|
|
|
void wxRichTextParagraphLayoutBox::Reset()
|
|
{
|
|
Clear();
|
|
|
|
AddParagraph(wxEmptyString);
|
|
}
|
|
|
|
/// Invalidate the buffer. With no argument, invalidates whole buffer.
|
|
void wxRichTextParagraphLayoutBox::Invalidate(const wxRichTextRange& invalidRange)
|
|
{
|
|
SetDirty(true);
|
|
|
|
if (invalidRange == wxRICHTEXT_ALL)
|
|
{
|
|
m_invalidRange = wxRICHTEXT_ALL;
|
|
return;
|
|
}
|
|
|
|
// Already invalidating everything
|
|
if (m_invalidRange == wxRICHTEXT_ALL)
|
|
return;
|
|
|
|
if ((invalidRange.GetStart() < m_invalidRange.GetStart()) || m_invalidRange.GetStart() == -1)
|
|
m_invalidRange.SetStart(invalidRange.GetStart());
|
|
if (invalidRange.GetEnd() > m_invalidRange.GetEnd())
|
|
m_invalidRange.SetEnd(invalidRange.GetEnd());
|
|
}
|
|
|
|
/// Get invalid range, rounding to entire paragraphs if argument is true.
|
|
wxRichTextRange wxRichTextParagraphLayoutBox::GetInvalidRange(bool wholeParagraphs) const
|
|
{
|
|
if (m_invalidRange == wxRICHTEXT_ALL || m_invalidRange == wxRICHTEXT_NONE)
|
|
return m_invalidRange;
|
|
|
|
wxRichTextRange range = m_invalidRange;
|
|
|
|
if (wholeParagraphs)
|
|
{
|
|
wxRichTextParagraph* para1 = GetParagraphAtPosition(range.GetStart());
|
|
wxRichTextParagraph* para2 = GetParagraphAtPosition(range.GetEnd());
|
|
if (para1)
|
|
range.SetStart(para1->GetRange().GetStart());
|
|
if (para2)
|
|
range.SetEnd(para2->GetRange().GetEnd());
|
|
}
|
|
return range;
|
|
}
|
|
|
|
/// Apply the style sheet to the buffer, for example if the styles have changed.
|
|
bool wxRichTextParagraphLayoutBox::ApplyStyleSheet(wxRichTextStyleSheet* styleSheet)
|
|
{
|
|
wxASSERT(styleSheet != NULL);
|
|
if (!styleSheet)
|
|
return false;
|
|
|
|
int foundCount = 0;
|
|
|
|
wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
|
|
while (node)
|
|
{
|
|
wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
|
|
wxASSERT (para != NULL);
|
|
|
|
if (para)
|
|
{
|
|
if (!para->GetAttributes().GetParagraphStyleName().IsEmpty())
|
|
{
|
|
wxRichTextParagraphStyleDefinition* def = styleSheet->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName());
|
|
if (def)
|
|
{
|
|
para->GetAttributes() = def->GetStyle();
|
|
foundCount ++;
|
|
}
|
|
}
|
|
}
|
|
|
|
node = node->GetNext();
|
|
}
|
|
return foundCount != 0;
|
|
}
|
|
|
|
/*!
|
|
* wxRichTextParagraph
|
|
* This object represents a single paragraph (or in a straight text editor, a line).
|
|
*/
|
|
|
|
IMPLEMENT_DYNAMIC_CLASS(wxRichTextParagraph, wxRichTextBox)
|
|
|
|
wxRichTextParagraph::wxRichTextParagraph(wxRichTextObject* parent, wxTextAttrEx* style):
|
|
wxRichTextBox(parent)
|
|
{
|
|
if (parent && !style)
|
|
SetAttributes(parent->GetAttributes());
|
|
if (style)
|
|
SetAttributes(*style);
|
|
}
|
|
|
|
wxRichTextParagraph::wxRichTextParagraph(const wxString& text, wxRichTextObject* parent, wxTextAttrEx* style):
|
|
wxRichTextBox(parent)
|
|
{
|
|
if (parent && !style)
|
|
SetAttributes(parent->GetAttributes());
|
|
if (style)
|
|
SetAttributes(*style);
|
|
|
|
AppendChild(new wxRichTextPlainText(text, this));
|
|
}
|
|
|
|
wxRichTextParagraph::~wxRichTextParagraph()
|
|
{
|
|
ClearLines();
|
|
}
|
|
|
|
/// Draw the item
|
|
bool wxRichTextParagraph::Draw(wxDC& dc, const wxRichTextRange& WXUNUSED(range), const wxRichTextRange& selectionRange, const wxRect& WXUNUSED(rect), int WXUNUSED(descent), int style)
|
|
{
|
|
#if wxRICHTEXT_USE_DYNAMIC_STYLES
|
|
wxTextAttrEx attr = GetCombinedAttributes();
|
|
#else
|
|
const wxTextAttrEx& attr = GetAttributes();
|
|
#endif
|
|
|
|
// Draw the bullet, if any
|
|
if (attr.GetBulletStyle() != wxTEXT_ATTR_BULLET_STYLE_NONE)
|
|
{
|
|
if (attr.GetLeftSubIndent() != 0)
|
|
{
|
|
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());
|
|
|
|
if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_BITMAP)
|
|
{
|
|
// TODO
|
|
}
|
|
else
|
|
{
|
|
wxString bulletText = GetBulletText();
|
|
if (!bulletText.empty())
|
|
{
|
|
if (attr.GetFont().Ok())
|
|
dc.SetFont(attr.GetFont());
|
|
|
|
if (attr.GetTextColour().Ok())
|
|
dc.SetTextForeground(attr.GetTextColour());
|
|
|
|
dc.SetBackgroundMode(wxTRANSPARENT);
|
|
|
|
// Get line height from first line, if any
|
|
wxRichTextLine* line = m_cachedLines.GetFirst() ? (wxRichTextLine* ) m_cachedLines.GetFirst()->GetData() : (wxRichTextLine*) NULL;
|
|
|
|
wxPoint linePos;
|
|
int lineHeight wxDUMMY_INITIALIZE(0);
|
|
if (line)
|
|
{
|
|
lineHeight = line->GetSize().y;
|
|
linePos = line->GetPosition() + GetPosition();
|
|
}
|
|
else
|
|
{
|
|
lineHeight = dc.GetCharHeight();
|
|
linePos = GetPosition();
|
|
linePos.y += spaceBeforePara;
|
|
}
|
|
|
|
int charHeight = dc.GetCharHeight();
|
|
|
|
int x = GetPosition().x + leftIndent;
|
|
int y = linePos.y + (lineHeight - charHeight);
|
|
|
|
dc.DrawText(bulletText, x, y);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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();
|
|
|
|
int maxDescent = line->GetDescent();
|
|
|
|
// Lines are specified relative to the paragraph
|
|
|
|
wxPoint linePosition = line->GetPosition() + GetPosition();
|
|
wxPoint objectPosition = linePosition;
|
|
|
|
// 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().IsOutside(lineRange))
|
|
{
|
|
// Draw this part of the line at the correct position
|
|
wxRichTextRange objectRange(child->GetRange());
|
|
objectRange.LimitTo(lineRange);
|
|
|
|
wxSize objectSize;
|
|
int descent = 0;
|
|
child->GetRangeSize(objectRange, objectSize, descent, dc, 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, objectRange, selectionRange, childRect, maxDescent, style);
|
|
|
|
objectPosition.x += objectSize.x;
|
|
}
|
|
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;
|
|
}
|
|
|
|
/// Lay the item out
|
|
bool wxRichTextParagraph::Layout(wxDC& dc, const wxRect& rect, int style)
|
|
{
|
|
#if wxRICHTEXT_USE_DYNAMIC_STYLES
|
|
wxTextAttrEx attr = GetCombinedAttributes();
|
|
#else
|
|
const wxTextAttrEx& attr = GetAttributes();
|
|
#endif
|
|
|
|
// 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.GetLineSpacing() > 10 && attr.GetFont().Ok())
|
|
{
|
|
dc.SetFont(attr.GetFont());
|
|
lineSpacing = (ConvertTenthsMMToPixels(dc, dc.GetCharHeight()) * attr.GetLineSpacing())/10;
|
|
}
|
|
|
|
// Available space for text on each line differs.
|
|
int availableTextSpaceFirstLine = rect.GetWidth() - leftIndent - rightIndent;
|
|
|
|
// Bullets start the text at the same position as subsequent lines
|
|
if (attr.GetBulletStyle() != wxTEXT_ATTR_BULLET_STYLE_NONE)
|
|
availableTextSpaceFirstLine -= leftSubIndent;
|
|
|
|
int availableTextSpaceSubsequentLines = rect.GetWidth() - leftIndent - rightIndent - leftSubIndent;
|
|
|
|
// 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;
|
|
|
|
//bool restrictWidth = wxRichTextHasStyle(style, wxRICHTEXT_FIXED_WIDTH);
|
|
//bool restrictHeight = wxRichTextHasStyle(style, wxRICHTEXT_FIXED_HEIGHT);
|
|
|
|
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 maxDescent = 0;
|
|
|
|
int lineCount = 0;
|
|
|
|
// 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.
|
|
|
|
wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
|
|
while (node)
|
|
{
|
|
wxRichTextObject* child = node->GetData();
|
|
|
|
// 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.
|
|
|
|
child->Layout(dc, rect, style);
|
|
|
|
// Available width depends on whether we're on the first or subsequent lines
|
|
int availableSpaceForText = (lineCount == 0 ? availableTextSpaceFirstLine : availableTextSpaceSubsequentLines);
|
|
|
|
currentPosition.x = (lineCount == 0 ? startPositionFirstLine : startPositionSubsequentLines);
|
|
|
|
// 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.
|
|
|
|
wxSize childSize;
|
|
int childDescent = 0;
|
|
if (lastEndPos == child->GetRange().GetStart() - 1)
|
|
{
|
|
childSize = child->GetCachedSize();
|
|
childDescent = child->GetDescent();
|
|
}
|
|
else
|
|
GetRangeSize(wxRichTextRange(lastEndPos+1, child->GetRange().GetEnd()), childSize, childDescent, dc, wxRICHTEXT_UNFORMATTED,rect.GetPosition());
|
|
|
|
if (childSize.x + currentWidth > availableSpaceForText)
|
|
{
|
|
long wrapPosition = 0;
|
|
|
|
// Find a place to wrap. This may walk back to previous children,
|
|
// for example if a word spans several objects.
|
|
if (!FindWrapPosition(wxRichTextRange(lastCompletedEndPos+1, child->GetRange().GetEnd()), dc, availableSpaceForText, wrapPosition))
|
|
{
|
|
// 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());
|
|
|
|
// wxLogDebug(wxT("Split at %ld"), wrapPosition);
|
|
|
|
// Let's find the actual size of the current line now
|
|
wxSize actualSize;
|
|
wxRichTextRange actualRange(lastCompletedEndPos+1, wrapPosition);
|
|
GetRangeSize(actualRange, actualSize, childDescent, dc, wxRICHTEXT_UNFORMATTED);
|
|
currentWidth = actualSize.x;
|
|
lineHeight = wxMax(lineHeight, actualSize.y);
|
|
maxDescent = wxMax(childDescent, 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);
|
|
|
|
// Now move down a line. TODO: add margins, spacing
|
|
currentPosition.y += lineHeight;
|
|
currentPosition.y += lineSpacing;
|
|
currentWidth = 0;
|
|
maxDescent = 0;
|
|
maxWidth = wxMax(maxWidth, currentWidth);
|
|
|
|
lineCount ++;
|
|
|
|
// TODO: account for zero-length objects, such as fields
|
|
wxASSERT(wrapPosition > lastCompletedEndPos);
|
|
|
|
lastEndPos = wrapPosition;
|
|
lastCompletedEndPos = lastEndPos;
|
|
|
|
lineHeight = 0;
|
|
|
|
// 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
|
|
{
|
|
// We still fit, so don't add a line, and keep going
|
|
currentWidth += childSize.x;
|
|
lineHeight = wxMax(lineHeight, childSize.y);
|
|
maxDescent = wxMax(childDescent, maxDescent);
|
|
|
|
maxWidth = wxMax(maxWidth, currentWidth);
|
|
lastEndPos = child->GetRange().GetEnd();
|
|
|
|
node = node->GetNext();
|
|
}
|
|
}
|
|
|
|
// Add the last line - it's the current pos -> last para pos
|
|
// Substract -1 because the last position is always the end-paragraph position.
|
|
if (lastCompletedEndPos <= GetRange().GetEnd()-1)
|
|
{
|
|
currentPosition.x = (lineCount == 0 ? startPositionFirstLine : startPositionSubsequentLines);
|
|
|
|
wxRichTextLine* line = AllocateLine(lineCount);
|
|
|
|
wxRichTextRange actualRange(lastCompletedEndPos+1, GetRange().GetEnd()-1);
|
|
|
|
// Set relative range so we won't have to change line ranges when paragraphs are moved
|
|
line->SetRange(wxRichTextRange(actualRange.GetStart() - GetRange().GetStart(), actualRange.GetEnd() - GetRange().GetStart()));
|
|
|
|
line->SetPosition(currentPosition);
|
|
|
|
if (lineHeight == 0)
|
|
{
|
|
if (attr.GetFont().Ok())
|
|
dc.SetFont(attr.GetFont());
|
|
lineHeight = dc.GetCharHeight();
|
|
}
|
|
if (maxDescent == 0)
|
|
{
|
|
int w, h;
|
|
dc.GetTextExtent(wxT("X"), & w, &h, & maxDescent);
|
|
}
|
|
|
|
line->SetSize(wxSize(currentWidth, lineHeight));
|
|
line->SetDescent(maxDescent);
|
|
currentPosition.y += lineHeight;
|
|
currentPosition.y += lineSpacing;
|
|
lineCount ++;
|
|
}
|
|
|
|
// Remove remaining unused line objects, if any
|
|
ClearUnusedLines(lineCount);
|
|
|
|
// Apply styles to wrapped lines
|
|
ApplyParagraphStyle(attr, rect);
|
|
|
|
SetCachedSize(wxSize(maxWidth, currentPosition.y + spaceBeforePara + spaceAfterPara));
|
|
|
|
m_dirty = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Apply paragraph styles, such as centering, to wrapped lines
|
|
void wxRichTextParagraph::ApplyParagraphStyle(const wxTextAttrEx& attr, const wxRect& rect)
|
|
{
|
|
if (!attr.HasAlignment())
|
|
return;
|
|
|
|
wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
|
|
while (node)
|
|
{
|
|
wxRichTextLine* line = node->GetData();
|
|
|
|
wxPoint pos = line->GetPosition();
|
|
wxSize size = line->GetSize();
|
|
|
|
// centering, right-justification
|
|
if (attr.HasAlignment() && GetAttributes().GetAlignment() == wxTEXT_ALIGNMENT_CENTRE)
|
|
{
|
|
pos.x = (rect.GetWidth() - size.x)/2 + pos.x;
|
|
line->SetPosition(pos);
|
|
}
|
|
else if (attr.HasAlignment() && GetAttributes().GetAlignment() == wxTEXT_ALIGNMENT_RIGHT)
|
|
{
|
|
pos.x = rect.GetRight() - size.x;
|
|
line->SetPosition(pos);
|
|
}
|
|
|
|
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)
|
|
{
|
|
wxRichTextBox::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, int flags, wxPoint position) const
|
|
{
|
|
if (!range.IsWithin(GetRange()))
|
|
return false;
|
|
|
|
if (flags & wxRICHTEXT_UNFORMATTED)
|
|
{
|
|
// Just use unformatted data, assume no line breaks
|
|
// TODO: take into account line breaks
|
|
|
|
wxSize sz;
|
|
|
|
wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
|
|
while (node)
|
|
{
|
|
wxRichTextObject* child = node->GetData();
|
|
if (!child->GetRange().IsOutside(range))
|
|
{
|
|
wxSize childSize;
|
|
|
|
wxRichTextRange rangeToUse = range;
|
|
rangeToUse.LimitTo(child->GetRange());
|
|
int childDescent = 0;
|
|
|
|
if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, flags, position))
|
|
{
|
|
sz.y = wxMax(sz.y, childSize.y);
|
|
sz.x += childSize.x;
|
|
descent = wxMax(descent, childDescent);
|
|
}
|
|
}
|
|
|
|
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))
|
|
{
|
|
wxSize lineSize;
|
|
|
|
wxRichTextObjectList::compatibility_iterator node2 = m_children.GetFirst();
|
|
while (node2)
|
|
{
|
|
wxRichTextObject* child = node2->GetData();
|
|
|
|
if (!child->GetRange().IsOutside(lineRange))
|
|
{
|
|
wxRichTextRange rangeToUse = lineRange;
|
|
rangeToUse.LimitTo(child->GetRange());
|
|
|
|
wxSize childSize;
|
|
int childDescent = 0;
|
|
if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, flags, position))
|
|
{
|
|
lineSize.y = wxMax(lineSize.y, childSize.y);
|
|
lineSize.x += childSize.x;
|
|
}
|
|
descent = wxMax(descent, childDescent);
|
|
}
|
|
|
|
node2 = node2->GetNext();
|
|
}
|
|
|
|
// Increase size by a line (TODO: paragraph spacing)
|
|
sz.y += lineSize.y;
|
|
sz.x = wxMax(sz.x, lineSize.x);
|
|
}
|
|
node = node->GetNext();
|
|
}
|
|
size = sz;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// Finds the absolute position and row height for the given character position
|
|
bool wxRichTextParagraph::FindPosition(wxDC& dc, 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, 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, const wxPoint& pt, long& textPosition)
|
|
{
|
|
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 && pt.y <= linePos.y + lineSize.y)
|
|
{
|
|
if (pt.x < linePos.x)
|
|
{
|
|
textPosition = lineRange.GetStart();
|
|
return wxRICHTEXT_HITTEST_BEFORE;
|
|
}
|
|
else if (pt.x >= (linePos.x + lineSize.x))
|
|
{
|
|
textPosition = lineRange.GetEnd();
|
|
return wxRICHTEXT_HITTEST_AFTER;
|
|
}
|
|
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, wxRICHTEXT_UNFORMATTED, linePos);
|
|
|
|
int nextX = childSize.x + linePos.x;
|
|
|
|
if (pt.x >= lastX && pt.x <= nextX)
|
|
{
|
|
textPosition = i;
|
|
|
|
// 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 + lastX;
|
|
if (pt.x >= midPoint)
|
|
return wxRICHTEXT_HITTEST_AFTER;
|
|
else
|
|
return wxRICHTEXT_HITTEST_BEFORE;
|
|
}
|
|
else
|
|
{
|
|
lastX = nextX;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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))
|
|
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
|
|
return true;
|
|
}
|
|
|
|
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
|
|
return true;
|
|
}
|
|
|
|
node = node->GetPrevious();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Find a suitable wrap position.
|
|
bool wxRichTextParagraph::FindWrapPosition(const wxRichTextRange& range, wxDC& dc, int availableSpace, long& wrapPosition)
|
|
{
|
|
// Find the first position where the line exceeds the available space.
|
|
wxSize sz;
|
|
long i;
|
|
long breakPosition = range.GetEnd();
|
|
for (i = range.GetStart(); i <= range.GetEnd(); i++)
|
|
{
|
|
int descent = 0;
|
|
GetRangeSize(wxRichTextRange(range.GetStart(), i), sz, descent, dc, wxRICHTEXT_UNFORMATTED);
|
|
|
|
if (sz.x > availableSpace)
|
|
{
|
|
breakPosition = i-1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 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 spacePos = plainText.Find(wxT(' '), true);
|
|
if (spacePos != wxNOT_FOUND)
|
|
{
|
|
int positionsFromEndOfString = plainText.length() - spacePos - 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)
|
|
{
|
|
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)
|
|
{
|
|
// TODO: convert from number to roman numeral
|
|
if (number == 1)
|
|
text = wxT("I");
|
|
else if (number == 2)
|
|
text = wxT("II");
|
|
else if (number == 3)
|
|
text = wxT("III");
|
|
else if (number == 4)
|
|
text = wxT("IV");
|
|
else
|
|
text = wxT("TODO");
|
|
}
|
|
else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ROMAN_LOWER)
|
|
{
|
|
// TODO: convert from number to roman numeral
|
|
if (number == 1)
|
|
text = wxT("i");
|
|
else if (number == 2)
|
|
text = wxT("ii");
|
|
else if (number == 3)
|
|
text = wxT("iii");
|
|
else if (number == 4)
|
|
text = wxT("iv");
|
|
else
|
|
text = wxT("TODO");
|
|
}
|
|
else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_SYMBOL)
|
|
{
|
|
text = GetAttributes().GetBulletSymbol();
|
|
}
|
|
|
|
if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_PARENTHESES)
|
|
{
|
|
text = wxT("(") + text + wxT(")");
|
|
}
|
|
if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_PERIOD)
|
|
{
|
|
text += wxT(".");
|
|
}
|
|
|
|
return text;
|
|
}
|
|
|
|
/// Allocate or reuse a line object
|
|
wxRichTextLine* wxRichTextParagraph::AllocateLine(int pos)
|
|
{
|
|
if (pos < (int) m_cachedLines.GetCount())
|
|
{
|
|
wxRichTextLine* line = m_cachedLines.Item(pos)->GetData();
|
|
line->Init(this);
|
|
return line;
|
|
}
|
|
else
|
|
{
|
|
wxRichTextLine* line = new wxRichTextLine(this);
|
|
m_cachedLines.Append(line);
|
|
return line;
|
|
}
|
|
}
|
|
|
|
/// Clear remaining unused line objects, if any
|
|
bool wxRichTextParagraph::ClearUnusedLines(int lineCount)
|
|
{
|
|
int cachedLineCount = m_cachedLines.GetCount();
|
|
if ((int) cachedLineCount > lineCount)
|
|
{
|
|
for (int i = 0; i < (int) (cachedLineCount - lineCount); i ++)
|
|
{
|
|
wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetLast();
|
|
wxRichTextLine* line = node->GetData();
|
|
m_cachedLines.Erase(node);
|
|
delete line;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// Get combined attributes of the base style, paragraph style and character style. We use this to dynamically
|
|
/// retrieve the actual style.
|
|
wxTextAttrEx wxRichTextParagraph::GetCombinedAttributes(const wxTextAttr& contentStyle) const
|
|
{
|
|
wxTextAttrEx attr;
|
|
wxRichTextBuffer* buf = wxDynamicCast(GetParent(), wxRichTextBuffer);
|
|
if (buf)
|
|
{
|
|
attr = buf->GetBasicStyle();
|
|
wxRichTextApplyStyle(attr, GetAttributes());
|
|
}
|
|
else
|
|
attr = GetAttributes();
|
|
|
|
wxRichTextApplyStyle(attr, contentStyle);
|
|
return attr;
|
|
}
|
|
|
|
/// Get combined attributes of the base style and paragraph style.
|
|
wxTextAttrEx wxRichTextParagraph::GetCombinedAttributes() const
|
|
{
|
|
wxTextAttrEx attr;
|
|
wxRichTextBuffer* buf = wxDynamicCast(GetParent(), wxRichTextBuffer);
|
|
if (buf)
|
|
{
|
|
attr = buf->GetBasicStyle();
|
|
wxRichTextApplyStyle(attr, GetAttributes());
|
|
}
|
|
else
|
|
attr = GetAttributes();
|
|
|
|
return attr;
|
|
}
|
|
|
|
/*!
|
|
* wxRichTextLine
|
|
* This object represents a line in a paragraph, and stores
|
|
* offsets from the start of the paragraph representing the
|
|
* start and end positions of the line.
|
|
*/
|
|
|
|
wxRichTextLine::wxRichTextLine(wxRichTextParagraph* parent)
|
|
{
|
|
Init(parent);
|
|
}
|
|
|
|
/// Initialisation
|
|
void wxRichTextLine::Init(wxRichTextParagraph* parent)
|
|
{
|
|
m_parent = parent;
|
|
m_range.SetRange(-1, -1);
|
|
m_pos = wxPoint(0, 0);
|
|
m_size = wxSize(0, 0);
|
|
m_descent = 0;
|
|
}
|
|
|
|
/// Copy
|
|
void wxRichTextLine::Copy(const wxRichTextLine& obj)
|
|
{
|
|
m_range = obj.m_range;
|
|
}
|
|
|
|
/// Get the absolute object position
|
|
wxPoint wxRichTextLine::GetAbsolutePosition() const
|
|
{
|
|
return m_parent->GetPosition() + m_pos;
|
|
}
|
|
|
|
/// Get the absolute range
|
|
wxRichTextRange wxRichTextLine::GetAbsoluteRange() const
|
|
{
|
|
wxRichTextRange range(m_range.GetStart() + m_parent->GetRange().GetStart(), 0);
|
|
range.SetEnd(range.GetStart() + m_range.GetLength()-1);
|
|
return range;
|
|
}
|
|
|
|
/*!
|
|
* wxRichTextPlainText
|
|
* This object represents a single piece of text.
|
|
*/
|
|
|
|
IMPLEMENT_DYNAMIC_CLASS(wxRichTextPlainText, wxRichTextObject)
|
|
|
|
wxRichTextPlainText::wxRichTextPlainText(const wxString& text, wxRichTextObject* parent, wxTextAttrEx* style):
|
|
wxRichTextObject(parent)
|
|
{
|
|
if (parent && !style)
|
|
SetAttributes(parent->GetAttributes());
|
|
if (style)
|
|
SetAttributes(*style);
|
|
|
|
m_text = text;
|
|
}
|
|
|
|
/// Draw the item
|
|
bool wxRichTextPlainText::Draw(wxDC& dc, const wxRichTextRange& range, const wxRichTextRange& selectionRange, const wxRect& rect, int descent, int WXUNUSED(style))
|
|
{
|
|
#if wxRICHTEXT_USE_DYNAMIC_STYLES
|
|
wxRichTextParagraph* para = wxDynamicCast(GetParent(), wxRichTextParagraph);
|
|
wxASSERT (para != NULL);
|
|
|
|
wxTextAttrEx textAttr(para ? para->GetCombinedAttributes(GetAttributes()) : GetAttributes());
|
|
#else
|
|
wxTextAttrEx textAttr(GetAttributes());
|
|
#endif
|
|
|
|
int offset = GetRange().GetStart();
|
|
|
|
long len = range.GetLength();
|
|
wxString stringChunk = m_text.Mid(range.GetStart() - offset, (size_t) len);
|
|
|
|
int charHeight = dc.GetCharHeight();
|
|
|
|
int x = rect.x;
|
|
int y = rect.y + (rect.height - charHeight - (descent - m_descent));
|
|
|
|
// Test for the optimized situations where all is selected, or none
|
|
// is selected.
|
|
|
|
if (textAttr.GetFont().Ok())
|
|
dc.SetFont(textAttr.GetFont());
|
|
|
|
// (a) All selected.
|
|
if (selectionRange.GetStart() <= range.GetStart() && selectionRange.GetEnd() >= range.GetEnd())
|
|
{
|
|
DrawTabbedString(dc, textAttr, rect, stringChunk, x, y, true);
|
|
}
|
|
// (b) None selected.
|
|
else if (selectionRange.GetEnd() < range.GetStart() || selectionRange.GetStart() > range.GetEnd())
|
|
{
|
|
// Draw all unselected
|
|
DrawTabbedString(dc, textAttr, rect, stringChunk, x, y, false);
|
|
}
|
|
else
|
|
{
|
|
// (c) Part selected, part not
|
|
// Let's draw unselected chunk, selected chunk, then unselected chunk.
|
|
|
|
dc.SetBackgroundMode(wxTRANSPARENT);
|
|
|
|
// 1. Initial unselected chunk, if any, up until start of selection.
|
|
if (selectionRange.GetStart() > range.GetStart() && selectionRange.GetStart() <= range.GetEnd())
|
|
{
|
|
int r1 = range.GetStart();
|
|
int s1 = selectionRange.GetStart()-1;
|
|
int fragmentLen = s1 - r1 + 1;
|
|
if (fragmentLen < 0)
|
|
wxLogDebug(wxT("Mid(%d, %d"), (int)(r1 - offset), (int)fragmentLen);
|
|
wxString stringFragment = m_text.Mid(r1 - offset, fragmentLen);
|
|
|
|
DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, false);
|
|
}
|
|
|
|
// 2. Selected chunk, if any.
|
|
if (selectionRange.GetEnd() >= range.GetStart())
|
|
{
|
|
int s1 = wxMax(selectionRange.GetStart(), range.GetStart());
|
|
int s2 = wxMin(selectionRange.GetEnd(), range.GetEnd());
|
|
|
|
int fragmentLen = s2 - s1 + 1;
|
|
if (fragmentLen < 0)
|
|
wxLogDebug(wxT("Mid(%d, %d"), (int)(s1 - offset), (int)fragmentLen);
|
|
wxString stringFragment = m_text.Mid(s1 - offset, fragmentLen);
|
|
|
|
DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, true);
|
|
}
|
|
|
|
// 3. Remaining unselected chunk, if any
|
|
if (selectionRange.GetEnd() < range.GetEnd())
|
|
{
|
|
int s2 = wxMin(selectionRange.GetEnd()+1, range.GetEnd());
|
|
int r2 = range.GetEnd();
|
|
|
|
int fragmentLen = r2 - s2 + 1;
|
|
if (fragmentLen < 0)
|
|
wxLogDebug(wxT("Mid(%d, %d"), (int)(s2 - offset), (int)fragmentLen);
|
|
wxString stringFragment = m_text.Mid(s2 - offset, fragmentLen);
|
|
|
|
DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, false);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool wxRichTextPlainText::DrawTabbedString(wxDC& dc, const wxTextAttrEx& attr, const wxRect& rect,wxString& str, wxCoord& x, wxCoord& y, bool selected)
|
|
{
|
|
wxArrayInt tab_array = attr.GetTabs();
|
|
if (tab_array.IsEmpty())
|
|
{
|
|
// create a default tab list at 10 mm each.
|
|
for (int i = 0; i < 20; ++i)
|
|
{
|
|
tab_array.Add(i*100);
|
|
}
|
|
}
|
|
int map_mode = dc.GetMapMode();
|
|
dc.SetMapMode(wxMM_LOMETRIC );
|
|
int num_tabs = tab_array.GetCount();
|
|
for (int i = 0; i < num_tabs; ++i)
|
|
{
|
|
tab_array[i] = dc.LogicalToDeviceXRel(tab_array[i]);
|
|
}
|
|
|
|
dc.SetMapMode(map_mode );
|
|
int next_tab_pos = -1;
|
|
int tab_pos = -1;
|
|
wxCoord w, h;
|
|
|
|
if(selected)
|
|
{
|
|
dc.SetBrush(*wxBLACK_BRUSH);
|
|
dc.SetPen(*wxBLACK_PEN);
|
|
dc.SetTextForeground(*wxWHITE);
|
|
dc.SetBackgroundMode(wxTRANSPARENT);
|
|
}
|
|
else
|
|
{
|
|
dc.SetTextForeground(attr.GetTextColour());
|
|
dc.SetBackgroundMode(wxTRANSPARENT);
|
|
}
|
|
|
|
while (str.Find(wxT('\t')) >= 0)
|
|
{
|
|
// the string has a tab
|
|
// break up the string at the Tab
|
|
wxString stringChunk = str.BeforeFirst(wxT('\t'));
|
|
str = str.AfterFirst(wxT('\t'));
|
|
dc.GetTextExtent(stringChunk, & w, & h);
|
|
tab_pos = x + w;
|
|
bool not_found = true;
|
|
for (int i = 0; i < num_tabs && not_found; ++i)
|
|
{
|
|
next_tab_pos = tab_array.Item(i);
|
|
if (next_tab_pos > tab_pos)
|
|
{
|
|
not_found = false;
|
|
if (selected)
|
|
{
|
|
w = next_tab_pos - x;
|
|
wxRect selRect(x, rect.y, w, rect.GetHeight());
|
|
dc.DrawRectangle(selRect);
|
|
}
|
|
dc.DrawText(stringChunk, x, y);
|
|
x = next_tab_pos;
|
|
}
|
|
}
|
|
}
|
|
|
|
dc.GetTextExtent(str, & w, & h);
|
|
if (selected)
|
|
{
|
|
wxRect selRect(x, rect.y, w, rect.GetHeight());
|
|
dc.DrawRectangle(selRect);
|
|
}
|
|
dc.DrawText(str, x, y);
|
|
x += w;
|
|
return true;
|
|
|
|
}
|
|
|
|
/// Lay the item out
|
|
bool wxRichTextPlainText::Layout(wxDC& dc, const wxRect& WXUNUSED(rect), int WXUNUSED(style))
|
|
{
|
|
#if wxRICHTEXT_USE_DYNAMIC_STYLES
|
|
wxRichTextParagraph* para = wxDynamicCast(GetParent(), wxRichTextParagraph);
|
|
wxASSERT (para != NULL);
|
|
|
|
wxTextAttrEx textAttr(para ? para->GetCombinedAttributes(GetAttributes()) : GetAttributes());
|
|
#else
|
|
wxTextAttrEx textAttr(GetAttributes());
|
|
#endif
|
|
|
|
if (textAttr.GetFont().Ok())
|
|
dc.SetFont(textAttr.GetFont());
|
|
|
|
wxCoord w, h;
|
|
dc.GetTextExtent(m_text, & w, & h, & m_descent);
|
|
m_size = wxSize(w, dc.GetCharHeight());
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Copy
|
|
void wxRichTextPlainText::Copy(const wxRichTextPlainText& obj)
|
|
{
|
|
wxRichTextObject::Copy(obj);
|
|
|
|
m_text = obj.m_text;
|
|
}
|
|
|
|
/// Get/set the object size for the given range. Returns false if the range
|
|
/// is invalid for this object.
|
|
bool wxRichTextPlainText::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, int WXUNUSED(flags), wxPoint position) const
|
|
{
|
|
if (!range.IsWithin(GetRange()))
|
|
return false;
|
|
|
|
#if wxRICHTEXT_USE_DYNAMIC_STYLES
|
|
wxRichTextParagraph* para = wxDynamicCast(GetParent(), wxRichTextParagraph);
|
|
wxASSERT (para != NULL);
|
|
|
|
wxTextAttrEx textAttr(para ? para->GetCombinedAttributes(GetAttributes()) : GetAttributes());
|
|
#else
|
|
wxTextAttrEx textAttr(GetAttributes());
|
|
#endif
|
|
|
|
// Always assume unformatted text, since at this level we have no knowledge
|
|
// of line breaks - and we don't need it, since we'll calculate size within
|
|
// formatted text by doing it in chunks according to the line ranges
|
|
|
|
if (textAttr.GetFont().Ok())
|
|
dc.SetFont(textAttr.GetFont());
|
|
|
|
int startPos = range.GetStart() - GetRange().GetStart();
|
|
long len = range.GetLength();
|
|
wxString stringChunk = m_text.Mid(startPos, (size_t) len);
|
|
wxCoord w, h;
|
|
int width = 0;
|
|
if (stringChunk.Find(wxT('\t')) >= 0)
|
|
{
|
|
// the string has a tab
|
|
wxArrayInt tab_array = textAttr.GetTabs();
|
|
if (tab_array.IsEmpty())
|
|
{
|
|
// create a default tab list at 10 mm each.
|
|
for (int i = 0; i < 20; ++i)
|
|
{
|
|
tab_array.Add(i*100);
|
|
}
|
|
}
|
|
|
|
int map_mode = dc.GetMapMode();
|
|
dc.SetMapMode(wxMM_LOMETRIC );
|
|
int num_tabs = tab_array.GetCount();
|
|
|
|
for (int i = 0; i < num_tabs; ++i)
|
|
{
|
|
tab_array[i] = dc.LogicalToDeviceXRel(tab_array[i]);
|
|
}
|
|
dc.SetMapMode(map_mode );
|
|
int next_tab_pos = -1;
|
|
|
|
while (stringChunk.Find(wxT('\t')) >= 0)
|
|
{
|
|
// the string has a tab
|
|
// break up the string at the Tab
|
|
wxString stringFragment = stringChunk.BeforeFirst(wxT('\t'));
|
|
stringChunk = stringChunk.AfterFirst(wxT('\t'));
|
|
dc.GetTextExtent(stringFragment, & w, & h);
|
|
width += w;
|
|
int absolute_width = width + position.x;
|
|
bool not_found = true;
|
|
for (int i = 0; i < num_tabs && not_found; ++i)
|
|
{
|
|
next_tab_pos = tab_array.Item(i);
|
|
if (next_tab_pos > absolute_width)
|
|
{
|
|
not_found = false;
|
|
width = next_tab_pos - position.x;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
dc.GetTextExtent(stringChunk, & w, & h, & descent);
|
|
width += w;
|
|
size = wxSize(width, dc.GetCharHeight());
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Do a split, returning an object containing the second part, and setting
|
|
/// the first part in 'this'.
|
|
wxRichTextObject* wxRichTextPlainText::DoSplit(long pos)
|
|
{
|
|
int index = pos - GetRange().GetStart();
|
|
if (index < 0 || index >= (int) m_text.length())
|
|
return NULL;
|
|
|
|
wxString firstPart = m_text.Mid(0, index);
|
|
wxString secondPart = m_text.Mid(index);
|
|
|
|
m_text = firstPart;
|
|
|
|
wxRichTextPlainText* newObject = new wxRichTextPlainText(secondPart);
|
|
newObject->SetAttributes(GetAttributes());
|
|
|
|
newObject->SetRange(wxRichTextRange(pos, GetRange().GetEnd()));
|
|
GetRange().SetEnd(pos-1);
|
|
|
|
return newObject;
|
|
}
|
|
|
|
/// Calculate range
|
|
void wxRichTextPlainText::CalculateRange(long start, long& end)
|
|
{
|
|
end = start + m_text.length() - 1;
|
|
m_range.SetRange(start, end);
|
|
}
|
|
|
|
/// Delete range
|
|
bool wxRichTextPlainText::DeleteRange(const wxRichTextRange& range)
|
|
{
|
|
wxRichTextRange r = range;
|
|
|
|
r.LimitTo(GetRange());
|
|
|
|
if (r.GetStart() == GetRange().GetStart() && r.GetEnd() == GetRange().GetEnd())
|
|
{
|
|
m_text.Empty();
|
|
return true;
|
|
}
|
|
|
|
long startIndex = r.GetStart() - GetRange().GetStart();
|
|
long len = r.GetLength();
|
|
|
|
m_text = m_text.Mid(0, startIndex) + m_text.Mid(startIndex+len);
|
|
return true;
|
|
}
|
|
|
|
/// Get text for the given range.
|
|
wxString wxRichTextPlainText::GetTextForRange(const wxRichTextRange& range) const
|
|
{
|
|
wxRichTextRange r = range;
|
|
|
|
r.LimitTo(GetRange());
|
|
|
|
long startIndex = r.GetStart() - GetRange().GetStart();
|
|
long len = r.GetLength();
|
|
|
|
return m_text.Mid(startIndex, len);
|
|
}
|
|
|
|
/// Returns true if this object can merge itself with the given one.
|
|
bool wxRichTextPlainText::CanMerge(wxRichTextObject* object) const
|
|
{
|
|
return object->GetClassInfo() == CLASSINFO(wxRichTextPlainText) &&
|
|
(m_text.empty() || wxTextAttrEq(GetAttributes(), object->GetAttributes()));
|
|
}
|
|
|
|
/// Returns true if this object merged itself with the given one.
|
|
/// The calling code will then delete the given object.
|
|
bool wxRichTextPlainText::Merge(wxRichTextObject* object)
|
|
{
|
|
wxRichTextPlainText* textObject = wxDynamicCast(object, wxRichTextPlainText);
|
|
wxASSERT( textObject != NULL );
|
|
|
|
if (textObject)
|
|
{
|
|
m_text += textObject->GetText();
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
/// Dump to output stream for debugging
|
|
void wxRichTextPlainText::Dump(wxTextOutputStream& stream)
|
|
{
|
|
wxRichTextObject::Dump(stream);
|
|
stream << m_text << wxT("\n");
|
|
}
|
|
|
|
/*!
|
|
* wxRichTextBuffer
|
|
* This is a kind of box, used to represent the whole buffer
|
|
*/
|
|
|
|
IMPLEMENT_DYNAMIC_CLASS(wxRichTextBuffer, wxRichTextParagraphLayoutBox)
|
|
|
|
wxList wxRichTextBuffer::sm_handlers;
|
|
|
|
/// Initialisation
|
|
void wxRichTextBuffer::Init()
|
|
{
|
|
m_commandProcessor = new wxCommandProcessor;
|
|
m_styleSheet = NULL;
|
|
m_modified = false;
|
|
m_batchedCommandDepth = 0;
|
|
m_batchedCommand = NULL;
|
|
m_suppressUndo = 0;
|
|
}
|
|
|
|
/// Initialisation
|
|
wxRichTextBuffer::~wxRichTextBuffer()
|
|
{
|
|
delete m_commandProcessor;
|
|
delete m_batchedCommand;
|
|
|
|
ClearStyleStack();
|
|
}
|
|
|
|
void wxRichTextBuffer::Clear()
|
|
{
|
|
DeleteChildren();
|
|
GetCommandProcessor()->ClearCommands();
|
|
Modify(false);
|
|
Invalidate(wxRICHTEXT_ALL);
|
|
}
|
|
|
|
void wxRichTextBuffer::Reset()
|
|
{
|
|
DeleteChildren();
|
|
AddParagraph(wxEmptyString);
|
|
GetCommandProcessor()->ClearCommands();
|
|
Modify(false);
|
|
Invalidate(wxRICHTEXT_ALL);
|
|
}
|
|
|
|
void wxRichTextBuffer::Copy(const wxRichTextBuffer& obj)
|
|
{
|
|
wxRichTextParagraphLayoutBox::Copy(obj);
|
|
|
|
m_styleSheet = obj.m_styleSheet;
|
|
m_modified = obj.m_modified;
|
|
m_batchedCommandDepth = obj.m_batchedCommandDepth;
|
|
m_batchedCommand = obj.m_batchedCommand;
|
|
m_suppressUndo = obj.m_suppressUndo;
|
|
}
|
|
|
|
/// Submit command to insert paragraphs
|
|
bool wxRichTextBuffer::InsertParagraphsWithUndo(long pos, const wxRichTextParagraphLayoutBox& paragraphs, wxRichTextCtrl* ctrl, int flags)
|
|
{
|
|
wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, this, ctrl, false);
|
|
|
|
wxTextAttrEx* p = NULL;
|
|
wxTextAttrEx paraAttr;
|
|
if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
|
|
{
|
|
paraAttr = GetStyleForNewParagraph(pos);
|
|
if (!paraAttr.IsDefault())
|
|
p = & paraAttr;
|
|
}
|
|
|
|
#if wxRICHTEXT_USE_DYNAMIC_STYLES
|
|
wxTextAttrEx attr(GetDefaultStyle());
|
|
#else
|
|
wxTextAttrEx attr(GetBasicStyle());
|
|
wxRichTextApplyStyle(attr, GetDefaultStyle());
|
|
#endif
|
|
|
|
action->GetNewParagraphs() = paragraphs;
|
|
action->SetPosition(pos);
|
|
|
|
// Set the range we'll need to delete in Undo
|
|
action->SetRange(wxRichTextRange(pos, pos + paragraphs.GetRange().GetEnd() - 1));
|
|
|
|
SubmitAction(action);
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Submit command to insert the given text
|
|
bool wxRichTextBuffer::InsertTextWithUndo(long pos, const wxString& text, wxRichTextCtrl* ctrl, int flags)
|
|
{
|
|
wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, this, ctrl, false);
|
|
|
|
wxTextAttrEx* p = NULL;
|
|
wxTextAttrEx paraAttr;
|
|
if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
|
|
{
|
|
paraAttr = GetStyleForNewParagraph(pos);
|
|
if (!paraAttr.IsDefault())
|
|
p = & paraAttr;
|
|
}
|
|
|
|
#if wxRICHTEXT_USE_DYNAMIC_STYLES
|
|
wxTextAttrEx attr(GetDefaultStyle());
|
|
#else
|
|
wxTextAttrEx attr(GetBasicStyle());
|
|
wxRichTextApplyStyle(attr, GetDefaultStyle());
|
|
#endif
|
|
|
|
action->GetNewParagraphs().AddParagraphs(text, p);
|
|
|
|
int length = action->GetNewParagraphs().GetRange().GetLength();
|
|
|
|
if (text.length() > 0 && text.Last() != wxT('\n'))
|
|
{
|
|
// Don't count the newline when undoing
|
|
length --;
|
|
action->GetNewParagraphs().SetPartialParagraph(true);
|
|
}
|
|
|
|
action->SetPosition(pos);
|
|
|
|
// Set the range we'll need to delete in Undo
|
|
action->SetRange(wxRichTextRange(pos, pos + length - 1));
|
|
|
|
SubmitAction(action);
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Submit command to insert the given text
|
|
bool wxRichTextBuffer::InsertNewlineWithUndo(long pos, wxRichTextCtrl* ctrl, int flags)
|
|
{
|
|
wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, this, ctrl, false);
|
|
|
|
wxTextAttrEx* p = NULL;
|
|
wxTextAttrEx paraAttr;
|
|
if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
|
|
{
|
|
paraAttr = GetStyleForNewParagraph(pos);
|
|
if (!paraAttr.IsDefault())
|
|
p = & paraAttr;
|
|
}
|
|
|
|
#if wxRICHTEXT_USE_DYNAMIC_STYLES
|
|
wxTextAttrEx attr(GetDefaultStyle());
|
|
#else
|
|
wxTextAttrEx attr(GetBasicStyle());
|
|
wxRichTextApplyStyle(attr, GetDefaultStyle());
|
|
#endif
|
|
|
|
wxRichTextParagraph* newPara = new wxRichTextParagraph(wxEmptyString, this, & attr);
|
|
action->GetNewParagraphs().AppendChild(newPara);
|
|
action->GetNewParagraphs().UpdateRanges();
|
|
action->GetNewParagraphs().SetPartialParagraph(false);
|
|
action->SetPosition(pos);
|
|
|
|
if (p)
|
|
newPara->SetAttributes(*p);
|
|
|
|
// Set the range we'll need to delete in Undo
|
|
action->SetRange(wxRichTextRange(pos, pos));
|
|
|
|
SubmitAction(action);
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Submit command to insert the given image
|
|
bool wxRichTextBuffer::InsertImageWithUndo(long pos, const wxRichTextImageBlock& imageBlock, wxRichTextCtrl* ctrl, int flags)
|
|
{
|
|
wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Image"), wxRICHTEXT_INSERT, this, ctrl, false);
|
|
|
|
wxTextAttrEx* p = NULL;
|
|
wxTextAttrEx paraAttr;
|
|
if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
|
|
{
|
|
paraAttr = GetStyleForNewParagraph(pos);
|
|
if (!paraAttr.IsDefault())
|
|
p = & paraAttr;
|
|
}
|
|
|
|
#if wxRICHTEXT_USE_DYNAMIC_STYLES
|
|
wxTextAttrEx attr(GetDefaultStyle());
|
|
#else
|
|
wxTextAttrEx attr(GetBasicStyle());
|
|
wxRichTextApplyStyle(attr, GetDefaultStyle());
|
|
#endif
|
|
|
|
wxRichTextParagraph* newPara = new wxRichTextParagraph(this, & attr);
|
|
if (p)
|
|
newPara->SetAttributes(*p);
|
|
|
|
wxRichTextImage* imageObject = new wxRichTextImage(imageBlock, newPara);
|
|
newPara->AppendChild(imageObject);
|
|
action->GetNewParagraphs().AppendChild(newPara);
|
|
action->GetNewParagraphs().UpdateRanges();
|
|
|
|
action->GetNewParagraphs().SetPartialParagraph(true);
|
|
|
|
action->SetPosition(pos);
|
|
|
|
// Set the range we'll need to delete in Undo
|
|
action->SetRange(wxRichTextRange(pos, pos));
|
|
|
|
SubmitAction(action);
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Get the style that is appropriate for a new paragraph at this position.
|
|
/// If the previous paragraph has a paragraph style name, look up the next-paragraph
|
|
/// style.
|
|
wxRichTextAttr wxRichTextBuffer::GetStyleForNewParagraph(long pos, bool caretPosition) const
|
|
{
|
|
wxRichTextParagraph* para = GetParagraphAtPosition(pos, caretPosition);
|
|
if (para)
|
|
{
|
|
if (!para->GetAttributes().GetParagraphStyleName().IsEmpty() && GetStyleSheet())
|
|
{
|
|
wxRichTextParagraphStyleDefinition* paraDef = GetStyleSheet()->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName());
|
|
if (paraDef && !paraDef->GetNextStyle().IsEmpty())
|
|
{
|
|
wxRichTextParagraphStyleDefinition* nextParaDef = GetStyleSheet()->FindParagraphStyle(paraDef->GetNextStyle());
|
|
if (nextParaDef)
|
|
return nextParaDef->GetStyle();
|
|
}
|
|
}
|
|
wxRichTextAttr attr(para->GetAttributes());
|
|
int flags = attr.GetFlags();
|
|
|
|
// Eliminate character styles
|
|
flags &= ( (~ wxTEXT_ATTR_FONT) |
|
|
(~ wxTEXT_ATTR_TEXT_COLOUR) |
|
|
(~ wxTEXT_ATTR_BACKGROUND_COLOUR) );
|
|
attr.SetFlags(flags);
|
|
|
|
return attr;
|
|
}
|
|
else
|
|
return wxRichTextAttr();
|
|
}
|
|
|
|
/// Submit command to delete this range
|
|
bool wxRichTextBuffer::DeleteRangeWithUndo(const wxRichTextRange& range, long initialCaretPosition, long WXUNUSED(newCaretPositon), wxRichTextCtrl* ctrl)
|
|
{
|
|
wxRichTextAction* action = new wxRichTextAction(NULL, _("Delete"), wxRICHTEXT_DELETE, this, ctrl);
|
|
|
|
action->SetPosition(initialCaretPosition);
|
|
|
|
// Set the range to delete
|
|
action->SetRange(range);
|
|
|
|
// Copy the fragment that we'll need to restore in Undo
|
|
CopyFragment(range, action->GetOldParagraphs());
|
|
|
|
// Special case: if there is only one (non-partial) paragraph,
|
|
// we must save the *next* paragraph's style, because that
|
|
// is the style we must apply when inserting the content back
|
|
// when undoing the delete. (This is because we're merging the
|
|
// paragraph with the previous paragraph and throwing away
|
|
// the style, and we need to restore it.)
|
|
if (!action->GetOldParagraphs().GetPartialParagraph() && action->GetOldParagraphs().GetChildCount() == 1)
|
|
{
|
|
wxRichTextParagraph* lastPara = GetParagraphAtPosition(range.GetStart());
|
|
if (lastPara)
|
|
{
|
|
wxRichTextParagraph* nextPara = GetParagraphAtPosition(range.GetEnd()+1);
|
|
if (nextPara)
|
|
{
|
|
wxRichTextParagraph* para = (wxRichTextParagraph*) action->GetOldParagraphs().GetChild(0);
|
|
para->SetAttributes(nextPara->GetAttributes());
|
|
}
|
|
}
|
|
}
|
|
|
|
SubmitAction(action);
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Collapse undo/redo commands
|
|
bool wxRichTextBuffer::BeginBatchUndo(const wxString& cmdName)
|
|
{
|
|
if (m_batchedCommandDepth == 0)
|
|
{
|
|
wxASSERT(m_batchedCommand == NULL);
|
|
if (m_batchedCommand)
|
|
{
|
|
GetCommandProcessor()->Submit(m_batchedCommand);
|
|
}
|
|
m_batchedCommand = new wxRichTextCommand(cmdName);
|
|
}
|
|
|
|
m_batchedCommandDepth ++;
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Collapse undo/redo commands
|
|
bool wxRichTextBuffer::EndBatchUndo()
|
|
{
|
|
m_batchedCommandDepth --;
|
|
|
|
wxASSERT(m_batchedCommandDepth >= 0);
|
|
wxASSERT(m_batchedCommand != NULL);
|
|
|
|
if (m_batchedCommandDepth == 0)
|
|
{
|
|
GetCommandProcessor()->Submit(m_batchedCommand);
|
|
m_batchedCommand = NULL;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Submit immediately, or delay according to whether collapsing is on
|
|
bool wxRichTextBuffer::SubmitAction(wxRichTextAction* action)
|
|
{
|
|
if (BatchingUndo() && m_batchedCommand && !SuppressingUndo())
|
|
m_batchedCommand->AddAction(action);
|
|
else
|
|
{
|
|
wxRichTextCommand* cmd = new wxRichTextCommand(action->GetName());
|
|
cmd->AddAction(action);
|
|
|
|
// Only store it if we're not suppressing undo.
|
|
return GetCommandProcessor()->Submit(cmd, !SuppressingUndo());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Begin suppressing undo/redo commands.
|
|
bool wxRichTextBuffer::BeginSuppressUndo()
|
|
{
|
|
m_suppressUndo ++;
|
|
|
|
return true;
|
|
}
|
|
|
|
/// End suppressing undo/redo commands.
|
|
bool wxRichTextBuffer::EndSuppressUndo()
|
|
{
|
|
m_suppressUndo --;
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Begin using a style
|
|
bool wxRichTextBuffer::BeginStyle(const wxTextAttrEx& style)
|
|
{
|
|
wxTextAttrEx newStyle(GetDefaultStyle());
|
|
|
|
// Save the old default style
|
|
m_attributeStack.Append((wxObject*) new wxTextAttrEx(GetDefaultStyle()));
|
|
|
|
wxRichTextApplyStyle(newStyle, style);
|
|
newStyle.SetFlags(style.GetFlags()|newStyle.GetFlags());
|
|
|
|
SetDefaultStyle(newStyle);
|
|
|
|
// wxLogDebug("Default style size = %d", GetDefaultStyle().GetFont().GetPointSize());
|
|
|
|
return true;
|
|
}
|
|
|
|
/// End the style
|
|
bool wxRichTextBuffer::EndStyle()
|
|
{
|
|
if (!m_attributeStack.GetFirst())
|
|
{
|
|
wxLogDebug(_("Too many EndStyle calls!"));
|
|
return false;
|
|
}
|
|
|
|
wxList::compatibility_iterator node = m_attributeStack.GetLast();
|
|
wxTextAttrEx* attr = (wxTextAttrEx*)node->GetData();
|
|
m_attributeStack.Erase(node);
|
|
|
|
SetDefaultStyle(*attr);
|
|
|
|
delete attr;
|
|
return true;
|
|
}
|
|
|
|
/// End all styles
|
|
bool wxRichTextBuffer::EndAllStyles()
|
|
{
|
|
while (m_attributeStack.GetCount() != 0)
|
|
EndStyle();
|
|
return true;
|
|
}
|
|
|
|
/// Clear the style stack
|
|
void wxRichTextBuffer::ClearStyleStack()
|
|
{
|
|
for (wxList::compatibility_iterator node = m_attributeStack.GetFirst(); node; node = node->GetNext())
|
|
delete (wxTextAttrEx*) node->GetData();
|
|
m_attributeStack.Clear();
|
|
}
|
|
|
|
/// Begin using bold
|
|
bool wxRichTextBuffer::BeginBold()
|
|
{
|
|
wxFont font(GetBasicStyle().GetFont());
|
|
font.SetWeight(wxBOLD);
|
|
|
|
wxTextAttrEx attr;
|
|
attr.SetFont(font,wxTEXT_ATTR_FONT_WEIGHT);
|
|
|
|
return BeginStyle(attr);
|
|
}
|
|
|
|
/// Begin using italic
|
|
bool wxRichTextBuffer::BeginItalic()
|
|
{
|
|
wxFont font(GetBasicStyle().GetFont());
|
|
font.SetStyle(wxITALIC);
|
|
|
|
wxTextAttrEx attr;
|
|
attr.SetFont(font, wxTEXT_ATTR_FONT_ITALIC);
|
|
|
|
return BeginStyle(attr);
|
|
}
|
|
|
|
/// Begin using underline
|
|
bool wxRichTextBuffer::BeginUnderline()
|
|
{
|
|
wxFont font(GetBasicStyle().GetFont());
|
|
font.SetUnderlined(true);
|
|
|
|
wxTextAttrEx attr;
|
|
attr.SetFont(font, wxTEXT_ATTR_FONT_UNDERLINE);
|
|
|
|
return BeginStyle(attr);
|
|
}
|
|
|
|
/// Begin using point size
|
|
bool wxRichTextBuffer::BeginFontSize(int pointSize)
|
|
{
|
|
wxFont font(GetBasicStyle().GetFont());
|
|
font.SetPointSize(pointSize);
|
|
|
|
wxTextAttrEx attr;
|
|
attr.SetFont(font, wxTEXT_ATTR_FONT_SIZE);
|
|
|
|
return BeginStyle(attr);
|
|
}
|
|
|
|
/// Begin using this font
|
|
bool wxRichTextBuffer::BeginFont(const wxFont& font)
|
|
{
|
|
wxTextAttrEx attr;
|
|
attr.SetFlags(wxTEXT_ATTR_FONT);
|
|
attr.SetFont(font);
|
|
|
|
return BeginStyle(attr);
|
|
}
|
|
|
|
/// Begin using this colour
|
|
bool wxRichTextBuffer::BeginTextColour(const wxColour& colour)
|
|
{
|
|
wxTextAttrEx attr;
|
|
attr.SetFlags(wxTEXT_ATTR_TEXT_COLOUR);
|
|
attr.SetTextColour(colour);
|
|
|
|
return BeginStyle(attr);
|
|
}
|
|
|
|
/// Begin using alignment
|
|
bool wxRichTextBuffer::BeginAlignment(wxTextAttrAlignment alignment)
|
|
{
|
|
wxTextAttrEx attr;
|
|
attr.SetFlags(wxTEXT_ATTR_ALIGNMENT);
|
|
attr.SetAlignment(alignment);
|
|
|
|
return BeginStyle(attr);
|
|
}
|
|
|
|
/// Begin left indent
|
|
bool wxRichTextBuffer::BeginLeftIndent(int leftIndent, int leftSubIndent)
|
|
{
|
|
wxTextAttrEx attr;
|
|
attr.SetFlags(wxTEXT_ATTR_LEFT_INDENT);
|
|
attr.SetLeftIndent(leftIndent, leftSubIndent);
|
|
|
|
return BeginStyle(attr);
|
|
}
|
|
|
|
/// Begin right indent
|
|
bool wxRichTextBuffer::BeginRightIndent(int rightIndent)
|
|
{
|
|
wxTextAttrEx attr;
|
|
attr.SetFlags(wxTEXT_ATTR_RIGHT_INDENT);
|
|
attr.SetRightIndent(rightIndent);
|
|
|
|
return BeginStyle(attr);
|
|
}
|
|
|
|
/// Begin paragraph spacing
|
|
bool wxRichTextBuffer::BeginParagraphSpacing(int before, int after)
|
|
{
|
|
long flags = 0;
|
|
if (before != 0)
|
|
flags |= wxTEXT_ATTR_PARA_SPACING_BEFORE;
|
|
if (after != 0)
|
|
flags |= wxTEXT_ATTR_PARA_SPACING_AFTER;
|
|
|
|
wxTextAttrEx attr;
|
|
attr.SetFlags(flags);
|
|
attr.SetParagraphSpacingBefore(before);
|
|
attr.SetParagraphSpacingAfter(after);
|
|
|
|
return BeginStyle(attr);
|
|
}
|
|
|
|
/// Begin line spacing
|
|
bool wxRichTextBuffer::BeginLineSpacing(int lineSpacing)
|
|
{
|
|
wxTextAttrEx attr;
|
|
attr.SetFlags(wxTEXT_ATTR_LINE_SPACING);
|
|
attr.SetLineSpacing(lineSpacing);
|
|
|
|
return BeginStyle(attr);
|
|
}
|
|
|
|
/// Begin numbered bullet
|
|
bool wxRichTextBuffer::BeginNumberedBullet(int bulletNumber, int leftIndent, int leftSubIndent, int bulletStyle)
|
|
{
|
|
wxTextAttrEx attr;
|
|
attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_BULLET_NUMBER|wxTEXT_ATTR_LEFT_INDENT);
|
|
attr.SetBulletStyle(bulletStyle);
|
|
attr.SetBulletNumber(bulletNumber);
|
|
attr.SetLeftIndent(leftIndent, leftSubIndent);
|
|
|
|
return BeginStyle(attr);
|
|
}
|
|
|
|
/// Begin symbol bullet
|
|
bool wxRichTextBuffer::BeginSymbolBullet(wxChar symbol, int leftIndent, int leftSubIndent, int bulletStyle)
|
|
{
|
|
wxTextAttrEx attr;
|
|
attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_BULLET_SYMBOL|wxTEXT_ATTR_LEFT_INDENT);
|
|
attr.SetBulletStyle(bulletStyle);
|
|
attr.SetLeftIndent(leftIndent, leftSubIndent);
|
|
attr.SetBulletSymbol(symbol);
|
|
|
|
return BeginStyle(attr);
|
|
}
|
|
|
|
/// Begin named character style
|
|
bool wxRichTextBuffer::BeginCharacterStyle(const wxString& characterStyle)
|
|
{
|
|
if (GetStyleSheet())
|
|
{
|
|
wxRichTextCharacterStyleDefinition* def = GetStyleSheet()->FindCharacterStyle(characterStyle);
|
|
if (def)
|
|
{
|
|
wxTextAttrEx attr;
|
|
def->GetStyle().CopyTo(attr);
|
|
return BeginStyle(attr);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// Begin named paragraph style
|
|
bool wxRichTextBuffer::BeginParagraphStyle(const wxString& paragraphStyle)
|
|
{
|
|
if (GetStyleSheet())
|
|
{
|
|
wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(paragraphStyle);
|
|
if (def)
|
|
{
|
|
wxTextAttrEx attr;
|
|
def->GetStyle().CopyTo(attr);
|
|
return BeginStyle(attr);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// Adds a handler to the end
|
|
void wxRichTextBuffer::AddHandler(wxRichTextFileHandler *handler)
|
|
{
|
|
sm_handlers.Append(handler);
|
|
}
|
|
|
|
/// Inserts a handler at the front
|
|
void wxRichTextBuffer::InsertHandler(wxRichTextFileHandler *handler)
|
|
{
|
|
sm_handlers.Insert( handler );
|
|
}
|
|
|
|
/// Removes a handler
|
|
bool wxRichTextBuffer::RemoveHandler(const wxString& name)
|
|
{
|
|
wxRichTextFileHandler *handler = FindHandler(name);
|
|
if (handler)
|
|
{
|
|
sm_handlers.DeleteObject(handler);
|
|
delete handler;
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
/// Finds a handler by filename or, if supplied, type
|
|
wxRichTextFileHandler *wxRichTextBuffer::FindHandlerFilenameOrType(const wxString& filename, int imageType)
|
|
{
|
|
if (imageType != wxRICHTEXT_TYPE_ANY)
|
|
return FindHandler(imageType);
|
|
else if (!filename.IsEmpty())
|
|
{
|
|
wxString path, file, ext;
|
|
wxSplitPath(filename, & path, & file, & ext);
|
|
return FindHandler(ext, imageType);
|
|
}
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/// Finds a handler by name
|
|
wxRichTextFileHandler* wxRichTextBuffer::FindHandler(const wxString& name)
|
|
{
|
|
wxList::compatibility_iterator node = sm_handlers.GetFirst();
|
|
while (node)
|
|
{
|
|
wxRichTextFileHandler *handler = (wxRichTextFileHandler*)node->GetData();
|
|
if (handler->GetName().Lower() == name.Lower()) return handler;
|
|
|
|
node = node->GetNext();
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/// Finds a handler by extension and type
|
|
wxRichTextFileHandler* wxRichTextBuffer::FindHandler(const wxString& extension, int type)
|
|
{
|
|
wxList::compatibility_iterator node = sm_handlers.GetFirst();
|
|
while (node)
|
|
{
|
|
wxRichTextFileHandler *handler = (wxRichTextFileHandler*)node->GetData();
|
|
if ( handler->GetExtension().Lower() == extension.Lower() &&
|
|
(type == wxRICHTEXT_TYPE_ANY || handler->GetType() == type) )
|
|
return handler;
|
|
node = node->GetNext();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/// Finds a handler by type
|
|
wxRichTextFileHandler* wxRichTextBuffer::FindHandler(int type)
|
|
{
|
|
wxList::compatibility_iterator node = sm_handlers.GetFirst();
|
|
while (node)
|
|
{
|
|
wxRichTextFileHandler *handler = (wxRichTextFileHandler *)node->GetData();
|
|
if (handler->GetType() == type) return handler;
|
|
node = node->GetNext();
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void wxRichTextBuffer::InitStandardHandlers()
|
|
{
|
|
if (!FindHandler(wxRICHTEXT_TYPE_TEXT))
|
|
AddHandler(new wxRichTextPlainTextHandler);
|
|
}
|
|
|
|
void wxRichTextBuffer::CleanUpHandlers()
|
|
{
|
|
wxList::compatibility_iterator node = sm_handlers.GetFirst();
|
|
while (node)
|
|
{
|
|
wxRichTextFileHandler* handler = (wxRichTextFileHandler*)node->GetData();
|
|
wxList::compatibility_iterator next = node->GetNext();
|
|
delete handler;
|
|
node = next;
|
|
}
|
|
|
|
sm_handlers.Clear();
|
|
}
|
|
|
|
wxString wxRichTextBuffer::GetExtWildcard(bool combine, bool save, wxArrayInt* types)
|
|
{
|
|
if (types)
|
|
types->Clear();
|
|
|
|
wxString wildcard;
|
|
|
|
wxList::compatibility_iterator node = GetHandlers().GetFirst();
|
|
int count = 0;
|
|
while (node)
|
|
{
|
|
wxRichTextFileHandler* handler = (wxRichTextFileHandler*) node->GetData();
|
|
if (handler->IsVisible() && ((save && handler->CanSave()) || !save && handler->CanLoad()))
|
|
{
|
|
if (combine)
|
|
{
|
|
if (count > 0)
|
|
wildcard += wxT(";");
|
|
wildcard += wxT("*.") + handler->GetExtension();
|
|
}
|
|
else
|
|
{
|
|
if (count > 0)
|
|
wildcard += wxT("|");
|
|
wildcard += handler->GetName();
|
|
wildcard += wxT(" ");
|
|
wildcard += _("files");
|
|
wildcard += wxT(" (*.");
|
|
wildcard += handler->GetExtension();
|
|
wildcard += wxT(")|*.");
|
|
wildcard += handler->GetExtension();
|
|
if (types)
|
|
types->Add(handler->GetType());
|
|
}
|
|
count ++;
|
|
}
|
|
|
|
node = node->GetNext();
|
|
}
|
|
|
|
if (combine)
|
|
wildcard = wxT("(") + wildcard + wxT(")|") + wildcard;
|
|
return wildcard;
|
|
}
|
|
|
|
/// Load a file
|
|
bool wxRichTextBuffer::LoadFile(const wxString& filename, int type)
|
|
{
|
|
wxRichTextFileHandler* handler = FindHandlerFilenameOrType(filename, type);
|
|
if (handler)
|
|
{
|
|
SetDefaultStyle(wxTextAttrEx());
|
|
|
|
bool success = handler->LoadFile(this, filename);
|
|
Invalidate(wxRICHTEXT_ALL);
|
|
return success;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
/// Save a file
|
|
bool wxRichTextBuffer::SaveFile(const wxString& filename, int type)
|
|
{
|
|
wxRichTextFileHandler* handler = FindHandlerFilenameOrType(filename, type);
|
|
if (handler)
|
|
return handler->SaveFile(this, filename);
|
|
else
|
|
return false;
|
|
}
|
|
|
|
/// Load from a stream
|
|
bool wxRichTextBuffer::LoadFile(wxInputStream& stream, int type)
|
|
{
|
|
wxRichTextFileHandler* handler = FindHandler(type);
|
|
if (handler)
|
|
{
|
|
SetDefaultStyle(wxTextAttrEx());
|
|
bool success = handler->LoadFile(this, stream);
|
|
Invalidate(wxRICHTEXT_ALL);
|
|
return success;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
/// Save to a stream
|
|
bool wxRichTextBuffer::SaveFile(wxOutputStream& stream, int type)
|
|
{
|
|
wxRichTextFileHandler* handler = FindHandler(type);
|
|
if (handler)
|
|
return handler->SaveFile(this, stream);
|
|
else
|
|
return false;
|
|
}
|
|
|
|
/// Copy the range to the clipboard
|
|
bool wxRichTextBuffer::CopyToClipboard(const wxRichTextRange& range)
|
|
{
|
|
bool success = false;
|
|
#if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
|
|
|
|
if (!wxTheClipboard->IsOpened() && wxTheClipboard->Open())
|
|
{
|
|
wxTheClipboard->Clear();
|
|
|
|
// Add composite object
|
|
|
|
wxDataObjectComposite* compositeObject = new wxDataObjectComposite();
|
|
|
|
{
|
|
wxString text = GetTextForRange(range);
|
|
|
|
#ifdef __WXMSW__
|
|
text = wxTextFile::Translate(text, wxTextFileType_Dos);
|
|
#endif
|
|
|
|
compositeObject->Add(new wxTextDataObject(text), false /* not preferred */);
|
|
}
|
|
|
|
// Add rich text buffer data object. This needs the XML handler to be present.
|
|
|
|
if (FindHandler(wxRICHTEXT_TYPE_XML))
|
|
{
|
|
wxRichTextBuffer* richTextBuf = new wxRichTextBuffer;
|
|
CopyFragment(range, *richTextBuf);
|
|
|
|
compositeObject->Add(new wxRichTextBufferDataObject(richTextBuf), true /* preferred */);
|
|
}
|
|
|
|
if (wxTheClipboard->SetData(compositeObject))
|
|
success = true;
|
|
|
|
wxTheClipboard->Close();
|
|
}
|
|
|
|
#else
|
|
wxUnusedVar(range);
|
|
#endif
|
|
return success;
|
|
}
|
|
|
|
/// Paste the clipboard content to the buffer
|
|
bool wxRichTextBuffer::PasteFromClipboard(long position)
|
|
{
|
|
bool success = false;
|
|
#if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
|
|
if (CanPasteFromClipboard())
|
|
{
|
|
if (wxTheClipboard->Open())
|
|
{
|
|
if (wxTheClipboard->IsSupported(wxDataFormat(wxRichTextBufferDataObject::GetRichTextBufferFormatId())))
|
|
{
|
|
wxRichTextBufferDataObject data;
|
|
wxTheClipboard->GetData(data);
|
|
wxRichTextBuffer* richTextBuffer = data.GetRichTextBuffer();
|
|
if (richTextBuffer)
|
|
{
|
|
InsertParagraphsWithUndo(position+1, *richTextBuffer, GetRichTextCtrl(), wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE);
|
|
delete richTextBuffer;
|
|
}
|
|
}
|
|
else if (wxTheClipboard->IsSupported(wxDF_TEXT) || wxTheClipboard->IsSupported(wxDF_UNICODETEXT))
|
|
{
|
|
wxTextDataObject data;
|
|
wxTheClipboard->GetData(data);
|
|
wxString text(data.GetText());
|
|
text.Replace(_T("\r\n"), _T("\n"));
|
|
|
|
InsertTextWithUndo(position+1, text, GetRichTextCtrl());
|
|
|
|
success = true;
|
|
}
|
|
else if (wxTheClipboard->IsSupported(wxDF_BITMAP))
|
|
{
|
|
wxBitmapDataObject data;
|
|
wxTheClipboard->GetData(data);
|
|
wxBitmap bitmap(data.GetBitmap());
|
|
wxImage image(bitmap.ConvertToImage());
|
|
|
|
wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Image"), wxRICHTEXT_INSERT, this, GetRichTextCtrl(), false);
|
|
|
|
action->GetNewParagraphs().AddImage(image);
|
|
|
|
if (action->GetNewParagraphs().GetChildCount() == 1)
|
|
action->GetNewParagraphs().SetPartialParagraph(true);
|
|
|
|
action->SetPosition(position);
|
|
|
|
// Set the range we'll need to delete in Undo
|
|
action->SetRange(wxRichTextRange(position, position));
|
|
|
|
SubmitAction(action);
|
|
|
|
success = true;
|
|
}
|
|
wxTheClipboard->Close();
|
|
}
|
|
}
|
|
#else
|
|
wxUnusedVar(position);
|
|
#endif
|
|
return success;
|
|
}
|
|
|
|
/// Can we paste from the clipboard?
|
|
bool wxRichTextBuffer::CanPasteFromClipboard() const
|
|
{
|
|
bool canPaste = false;
|
|
#if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
|
|
if (!wxTheClipboard->IsOpened() && wxTheClipboard->Open())
|
|
{
|
|
if (wxTheClipboard->IsSupported(wxDF_TEXT) || wxTheClipboard->IsSupported(wxDF_UNICODETEXT) ||
|
|
wxTheClipboard->IsSupported(wxDataFormat(wxRichTextBufferDataObject::GetRichTextBufferFormatId())) ||
|
|
wxTheClipboard->IsSupported(wxDF_BITMAP))
|
|
{
|
|
canPaste = true;
|
|
}
|
|
wxTheClipboard->Close();
|
|
}
|
|
#endif
|
|
return canPaste;
|
|
}
|
|
|
|
/// Dumps contents of buffer for debugging purposes
|
|
void wxRichTextBuffer::Dump()
|
|
{
|
|
wxString text;
|
|
{
|
|
wxStringOutputStream stream(& text);
|
|
wxTextOutputStream textStream(stream);
|
|
Dump(textStream);
|
|
}
|
|
|
|
wxLogDebug(text);
|
|
}
|
|
|
|
|
|
/*
|
|
* Module to initialise and clean up handlers
|
|
*/
|
|
|
|
class wxRichTextModule: public wxModule
|
|
{
|
|
DECLARE_DYNAMIC_CLASS(wxRichTextModule)
|
|
public:
|
|
wxRichTextModule() {}
|
|
bool OnInit() { wxRichTextBuffer::InitStandardHandlers(); return true; };
|
|
void OnExit() { wxRichTextBuffer::CleanUpHandlers(); };
|
|
};
|
|
|
|
IMPLEMENT_DYNAMIC_CLASS(wxRichTextModule, wxModule)
|
|
|
|
|
|
/*!
|
|
* Commands for undo/redo
|
|
*
|
|
*/
|
|
|
|
wxRichTextCommand::wxRichTextCommand(const wxString& name, wxRichTextCommandId id, wxRichTextBuffer* buffer,
|
|
wxRichTextCtrl* ctrl, bool ignoreFirstTime): wxCommand(true, name)
|
|
{
|
|
/* wxRichTextAction* action = */ new wxRichTextAction(this, name, id, buffer, ctrl, ignoreFirstTime);
|
|
}
|
|
|
|
wxRichTextCommand::wxRichTextCommand(const wxString& name): wxCommand(true, name)
|
|
{
|
|
}
|
|
|
|
wxRichTextCommand::~wxRichTextCommand()
|
|
{
|
|
ClearActions();
|
|
}
|
|
|
|
void wxRichTextCommand::AddAction(wxRichTextAction* action)
|
|
{
|
|
if (!m_actions.Member(action))
|
|
m_actions.Append(action);
|
|
}
|
|
|
|
bool wxRichTextCommand::Do()
|
|
{
|
|
for (wxList::compatibility_iterator node = m_actions.GetFirst(); node; node = node->GetNext())
|
|
{
|
|
wxRichTextAction* action = (wxRichTextAction*) node->GetData();
|
|
action->Do();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool wxRichTextCommand::Undo()
|
|
{
|
|
for (wxList::compatibility_iterator node = m_actions.GetLast(); node; node = node->GetPrevious())
|
|
{
|
|
wxRichTextAction* action = (wxRichTextAction*) node->GetData();
|
|
action->Undo();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void wxRichTextCommand::ClearActions()
|
|
{
|
|
WX_CLEAR_LIST(wxList, m_actions);
|
|
}
|
|
|
|
/*!
|
|
* Individual action
|
|
*
|
|
*/
|
|
|
|
wxRichTextAction::wxRichTextAction(wxRichTextCommand* cmd, const wxString& name, wxRichTextCommandId id, wxRichTextBuffer* buffer,
|
|
wxRichTextCtrl* ctrl, bool ignoreFirstTime)
|
|
{
|
|
m_buffer = buffer;
|
|
m_ignoreThis = ignoreFirstTime;
|
|
m_cmdId = id;
|
|
m_position = -1;
|
|
m_ctrl = ctrl;
|
|
m_name = name;
|
|
m_newParagraphs.SetDefaultStyle(buffer->GetDefaultStyle());
|
|
m_newParagraphs.SetBasicStyle(buffer->GetBasicStyle());
|
|
if (cmd)
|
|
cmd->AddAction(this);
|
|
}
|
|
|
|
wxRichTextAction::~wxRichTextAction()
|
|
{
|
|
}
|
|
|
|
bool wxRichTextAction::Do()
|
|
{
|
|
m_buffer->Modify(true);
|
|
|
|
switch (m_cmdId)
|
|
{
|
|
case wxRICHTEXT_INSERT:
|
|
{
|
|
m_buffer->InsertFragment(GetPosition(), m_newParagraphs);
|
|
m_buffer->UpdateRanges();
|
|
m_buffer->Invalidate(GetRange());
|
|
|
|
long newCaretPosition = GetPosition() + m_newParagraphs.GetRange().GetLength();
|
|
|
|
// Character position to caret position
|
|
newCaretPosition --;
|
|
|
|
// Don't take into account the last newline
|
|
if (m_newParagraphs.GetPartialParagraph())
|
|
newCaretPosition --;
|
|
|
|
newCaretPosition = wxMin(newCaretPosition, (m_buffer->GetRange().GetEnd()-1));
|
|
|
|
UpdateAppearance(newCaretPosition, true /* send update event */);
|
|
|
|
break;
|
|
}
|
|
case wxRICHTEXT_DELETE:
|
|
{
|
|
m_buffer->DeleteRange(GetRange());
|
|
m_buffer->UpdateRanges();
|
|
m_buffer->Invalidate(wxRichTextRange(GetRange().GetStart(), GetRange().GetStart()));
|
|
|
|
UpdateAppearance(GetRange().GetStart()-1, true /* send update event */);
|
|
|
|
break;
|
|
}
|
|
case wxRICHTEXT_CHANGE_STYLE:
|
|
{
|
|
ApplyParagraphs(GetNewParagraphs());
|
|
m_buffer->Invalidate(GetRange());
|
|
|
|
UpdateAppearance(GetPosition());
|
|
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool wxRichTextAction::Undo()
|
|
{
|
|
m_buffer->Modify(true);
|
|
|
|
switch (m_cmdId)
|
|
{
|
|
case wxRICHTEXT_INSERT:
|
|
{
|
|
m_buffer->DeleteRange(GetRange());
|
|
m_buffer->UpdateRanges();
|
|
m_buffer->Invalidate(wxRichTextRange(GetRange().GetStart(), GetRange().GetStart()));
|
|
|
|
long newCaretPosition = GetPosition() - 1;
|
|
// if (m_newParagraphs.GetPartialParagraph())
|
|
// newCaretPosition --;
|
|
|
|
UpdateAppearance(newCaretPosition, true /* send update event */);
|
|
|
|
break;
|
|
}
|
|
case wxRICHTEXT_DELETE:
|
|
{
|
|
m_buffer->InsertFragment(GetRange().GetStart(), m_oldParagraphs);
|
|
m_buffer->UpdateRanges();
|
|
m_buffer->Invalidate(GetRange());
|
|
|
|
UpdateAppearance(GetPosition(), true /* send update event */);
|
|
|
|
break;
|
|
}
|
|
case wxRICHTEXT_CHANGE_STYLE:
|
|
{
|
|
ApplyParagraphs(GetOldParagraphs());
|
|
m_buffer->Invalidate(GetRange());
|
|
|
|
UpdateAppearance(GetPosition());
|
|
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Update the control appearance
|
|
void wxRichTextAction::UpdateAppearance(long caretPosition, bool sendUpdateEvent)
|
|
{
|
|
if (m_ctrl)
|
|
{
|
|
m_ctrl->SetCaretPosition(caretPosition);
|
|
if (!m_ctrl->IsFrozen())
|
|
{
|
|
m_ctrl->LayoutContent();
|
|
m_ctrl->PositionCaret();
|
|
m_ctrl->Refresh(false);
|
|
|
|
if (sendUpdateEvent)
|
|
m_ctrl->SendUpdateEvent();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Replace the buffer paragraphs with the new ones.
|
|
void wxRichTextAction::ApplyParagraphs(const wxRichTextParagraphLayoutBox& fragment)
|
|
{
|
|
wxRichTextObjectList::compatibility_iterator node = fragment.GetChildren().GetFirst();
|
|
while (node)
|
|
{
|
|
wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
|
|
wxASSERT (para != NULL);
|
|
|
|
// We'll replace the existing paragraph by finding the paragraph at this position,
|
|
// delete its node data, and setting a copy as the new node data.
|
|
// TODO: make more efficient by simply swapping old and new paragraph objects.
|
|
|
|
wxRichTextParagraph* existingPara = m_buffer->GetParagraphAtPosition(para->GetRange().GetStart());
|
|
if (existingPara)
|
|
{
|
|
wxRichTextObjectList::compatibility_iterator bufferParaNode = m_buffer->GetChildren().Find(existingPara);
|
|
if (bufferParaNode)
|
|
{
|
|
wxRichTextParagraph* newPara = new wxRichTextParagraph(*para);
|
|
newPara->SetParent(m_buffer);
|
|
|
|
bufferParaNode->SetData(newPara);
|
|
|
|
delete existingPara;
|
|
}
|
|
}
|
|
|
|
node = node->GetNext();
|
|
}
|
|
}
|
|
|
|
|
|
/*!
|
|
* wxRichTextRange
|
|
* This stores beginning and end positions for a range of data.
|
|
*/
|
|
|
|
/// Limit this range to be within 'range'
|
|
bool wxRichTextRange::LimitTo(const wxRichTextRange& range)
|
|
{
|
|
if (m_start < range.m_start)
|
|
m_start = range.m_start;
|
|
|
|
if (m_end > range.m_end)
|
|
m_end = range.m_end;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*!
|
|
* wxRichTextImage implementation
|
|
* This object represents an image.
|
|
*/
|
|
|
|
IMPLEMENT_DYNAMIC_CLASS(wxRichTextImage, wxRichTextObject)
|
|
|
|
wxRichTextImage::wxRichTextImage(const wxImage& image, wxRichTextObject* parent):
|
|
wxRichTextObject(parent)
|
|
{
|
|
m_image = image;
|
|
}
|
|
|
|
wxRichTextImage::wxRichTextImage(const wxRichTextImageBlock& imageBlock, wxRichTextObject* parent):
|
|
wxRichTextObject(parent)
|
|
{
|
|
m_imageBlock = imageBlock;
|
|
m_imageBlock.Load(m_image);
|
|
}
|
|
|
|
/// Load wxImage from the block
|
|
bool wxRichTextImage::LoadFromBlock()
|
|
{
|
|
m_imageBlock.Load(m_image);
|
|
return m_imageBlock.Ok();
|
|
}
|
|
|
|
/// Make block from the wxImage
|
|
bool wxRichTextImage::MakeBlock()
|
|
{
|
|
if (m_imageBlock.GetImageType() == wxBITMAP_TYPE_ANY || m_imageBlock.GetImageType() == -1)
|
|
m_imageBlock.SetImageType(wxBITMAP_TYPE_PNG);
|
|
|
|
m_imageBlock.MakeImageBlock(m_image, m_imageBlock.GetImageType());
|
|
return m_imageBlock.Ok();
|
|
}
|
|
|
|
|
|
/// Draw the item
|
|
bool wxRichTextImage::Draw(wxDC& dc, const wxRichTextRange& range, const wxRichTextRange& selectionRange, const wxRect& rect, int WXUNUSED(descent), int WXUNUSED(style))
|
|
{
|
|
if (!m_image.Ok() && m_imageBlock.Ok())
|
|
LoadFromBlock();
|
|
|
|
if (!m_image.Ok())
|
|
return false;
|
|
|
|
if (m_image.Ok() && !m_bitmap.Ok())
|
|
m_bitmap = wxBitmap(m_image);
|
|
|
|
int y = rect.y + (rect.height - m_image.GetHeight());
|
|
|
|
if (m_bitmap.Ok())
|
|
dc.DrawBitmap(m_bitmap, rect.x, y, true);
|
|
|
|
if (selectionRange.Contains(range.GetStart()))
|
|
{
|
|
dc.SetBrush(*wxBLACK_BRUSH);
|
|
dc.SetPen(*wxBLACK_PEN);
|
|
dc.SetLogicalFunction(wxINVERT);
|
|
dc.DrawRectangle(rect);
|
|
dc.SetLogicalFunction(wxCOPY);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Lay the item out
|
|
bool wxRichTextImage::Layout(wxDC& WXUNUSED(dc), const wxRect& rect, int WXUNUSED(style))
|
|
{
|
|
if (!m_image.Ok())
|
|
LoadFromBlock();
|
|
|
|
if (m_image.Ok())
|
|
{
|
|
SetCachedSize(wxSize(m_image.GetWidth(), m_image.GetHeight()));
|
|
SetPosition(rect.GetPosition());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Get/set the object size for the given range. Returns false if the range
|
|
/// is invalid for this object.
|
|
bool wxRichTextImage::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& WXUNUSED(descent), wxDC& WXUNUSED(dc), int WXUNUSED(flags), wxPoint WXUNUSED(position)) const
|
|
{
|
|
if (!range.IsWithin(GetRange()))
|
|
return false;
|
|
|
|
if (!m_image.Ok())
|
|
return false;
|
|
|
|
size.x = m_image.GetWidth();
|
|
size.y = m_image.GetHeight();
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Copy
|
|
void wxRichTextImage::Copy(const wxRichTextImage& obj)
|
|
{
|
|
m_image = obj.m_image;
|
|
m_imageBlock = obj.m_imageBlock;
|
|
}
|
|
|
|
/*!
|
|
* Utilities
|
|
*
|
|
*/
|
|
|
|
/// Compare two attribute objects
|
|
bool wxTextAttrEq(const wxTextAttrEx& attr1, const wxTextAttrEx& attr2)
|
|
{
|
|
return (
|
|
attr1.GetTextColour() == attr2.GetTextColour() &&
|
|
attr1.GetBackgroundColour() == attr2.GetBackgroundColour() &&
|
|
attr1.GetFont() == attr2.GetFont() &&
|
|
attr1.GetAlignment() == attr2.GetAlignment() &&
|
|
attr1.GetLeftIndent() == attr2.GetLeftIndent() &&
|
|
attr1.GetRightIndent() == attr2.GetRightIndent() &&
|
|
attr1.GetLeftSubIndent() == attr2.GetLeftSubIndent() &&
|
|
attr1.GetTabs().GetCount() == attr2.GetTabs().GetCount() && // heuristic
|
|
attr1.GetLineSpacing() == attr2.GetLineSpacing() &&
|
|
attr1.GetParagraphSpacingAfter() == attr2.GetParagraphSpacingAfter() &&
|
|
attr1.GetParagraphSpacingBefore() == attr2.GetParagraphSpacingBefore() &&
|
|
attr1.GetBulletStyle() == attr2.GetBulletStyle() &&
|
|
attr1.GetBulletNumber() == attr2.GetBulletNumber() &&
|
|
attr1.GetBulletSymbol() == attr2.GetBulletSymbol() &&
|
|
attr1.GetCharacterStyleName() == attr2.GetCharacterStyleName() &&
|
|
attr1.GetParagraphStyleName() == attr2.GetParagraphStyleName());
|
|
}
|
|
|
|
bool wxTextAttrEq(const wxTextAttrEx& attr1, const wxRichTextAttr& attr2)
|
|
{
|
|
return (
|
|
attr1.GetTextColour() == attr2.GetTextColour() &&
|
|
attr1.GetBackgroundColour() == attr2.GetBackgroundColour() &&
|
|
attr1.GetFont().GetPointSize() == attr2.GetFontSize() &&
|
|
attr1.GetFont().GetStyle() == attr2.GetFontStyle() &&
|
|
attr1.GetFont().GetWeight() == attr2.GetFontWeight() &&
|
|
attr1.GetFont().GetFaceName() == attr2.GetFontFaceName() &&
|
|
attr1.GetFont().GetUnderlined() == attr2.GetFontUnderlined() &&
|
|
attr1.GetAlignment() == attr2.GetAlignment() &&
|
|
attr1.GetLeftIndent() == attr2.GetLeftIndent() &&
|
|
attr1.GetRightIndent() == attr2.GetRightIndent() &&
|
|
attr1.GetLeftSubIndent() == attr2.GetLeftSubIndent() &&
|
|
attr1.GetTabs().GetCount() == attr2.GetTabs().GetCount() && // heuristic
|
|
attr1.GetLineSpacing() == attr2.GetLineSpacing() &&
|
|
attr1.GetParagraphSpacingAfter() == attr2.GetParagraphSpacingAfter() &&
|
|
attr1.GetParagraphSpacingBefore() == attr2.GetParagraphSpacingBefore() &&
|
|
attr1.GetBulletStyle() == attr2.GetBulletStyle() &&
|
|
attr1.GetBulletNumber() == attr2.GetBulletNumber() &&
|
|
attr1.GetBulletSymbol() == attr2.GetBulletSymbol() &&
|
|
attr1.GetCharacterStyleName() == attr2.GetCharacterStyleName() &&
|
|
attr1.GetParagraphStyleName() == attr2.GetParagraphStyleName());
|
|
}
|
|
|
|
/// Compare two attribute objects, but take into account the flags
|
|
/// specifying attributes of interest.
|
|
bool wxTextAttrEqPartial(const wxTextAttrEx& attr1, const wxTextAttrEx& attr2, int flags)
|
|
{
|
|
if ((flags & wxTEXT_ATTR_TEXT_COLOUR) && attr1.GetTextColour() != attr2.GetTextColour())
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_BACKGROUND_COLOUR) && attr1.GetBackgroundColour() != attr2.GetBackgroundColour())
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_FONT_FACE) && attr1.GetFont().Ok() && attr2.GetFont().Ok() &&
|
|
attr1.GetFont().GetFaceName() != attr2.GetFont().GetFaceName())
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_FONT_SIZE) && attr1.GetFont().Ok() && attr2.GetFont().Ok() &&
|
|
attr1.GetFont().GetPointSize() != attr2.GetFont().GetPointSize())
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_FONT_WEIGHT) && attr1.GetFont().Ok() && attr2.GetFont().Ok() &&
|
|
attr1.GetFont().GetWeight() != attr2.GetFont().GetWeight())
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_FONT_ITALIC) && attr1.GetFont().Ok() && attr2.GetFont().Ok() &&
|
|
attr1.GetFont().GetStyle() != attr2.GetFont().GetStyle())
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_FONT_UNDERLINE) && attr1.GetFont().Ok() && attr2.GetFont().Ok() &&
|
|
attr1.GetFont().GetUnderlined() != attr2.GetFont().GetUnderlined())
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_ALIGNMENT) && attr1.GetAlignment() != attr2.GetAlignment())
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_LEFT_INDENT) &&
|
|
((attr1.GetLeftIndent() != attr2.GetLeftIndent()) || (attr1.GetLeftSubIndent() != attr2.GetLeftSubIndent())))
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_RIGHT_INDENT) &&
|
|
(attr1.GetRightIndent() != attr2.GetRightIndent()))
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_PARA_SPACING_AFTER) &&
|
|
(attr1.GetParagraphSpacingAfter() != attr2.GetParagraphSpacingAfter()))
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_PARA_SPACING_BEFORE) &&
|
|
(attr1.GetParagraphSpacingBefore() != attr2.GetParagraphSpacingBefore()))
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_LINE_SPACING) &&
|
|
(attr1.GetLineSpacing() != attr2.GetLineSpacing()))
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_CHARACTER_STYLE_NAME) &&
|
|
(attr1.GetCharacterStyleName() != attr2.GetCharacterStyleName()))
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_PARAGRAPH_STYLE_NAME) &&
|
|
(attr1.GetParagraphStyleName() != attr2.GetParagraphStyleName()))
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_BULLET_STYLE) &&
|
|
(attr1.GetBulletStyle() != attr2.GetBulletStyle()))
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_BULLET_NUMBER) &&
|
|
(attr1.GetBulletNumber() != attr2.GetBulletNumber()))
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_BULLET_SYMBOL) &&
|
|
(attr1.GetBulletSymbol() != attr2.GetBulletSymbol()))
|
|
return false;
|
|
|
|
/* TODO
|
|
if ((flags & wxTEXT_ATTR_TABS) &&
|
|
return false;
|
|
*/
|
|
|
|
return true;
|
|
}
|
|
|
|
bool wxTextAttrEqPartial(const wxTextAttrEx& attr1, const wxRichTextAttr& attr2, int flags)
|
|
{
|
|
if ((flags & wxTEXT_ATTR_TEXT_COLOUR) && attr1.GetTextColour() != attr2.GetTextColour())
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_BACKGROUND_COLOUR) && attr1.GetBackgroundColour() != attr2.GetBackgroundColour())
|
|
return false;
|
|
|
|
if ((flags & (wxTEXT_ATTR_FONT)) && !attr1.GetFont().Ok())
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_FONT_FACE) && attr1.GetFont().Ok() &&
|
|
attr1.GetFont().GetFaceName() != attr2.GetFontFaceName())
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_FONT_SIZE) && attr1.GetFont().Ok() &&
|
|
attr1.GetFont().GetPointSize() != attr2.GetFontSize())
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_FONT_WEIGHT) && attr1.GetFont().Ok() &&
|
|
attr1.GetFont().GetWeight() != attr2.GetFontWeight())
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_FONT_ITALIC) && attr1.GetFont().Ok() &&
|
|
attr1.GetFont().GetStyle() != attr2.GetFontStyle())
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_FONT_UNDERLINE) && attr1.GetFont().Ok() &&
|
|
attr1.GetFont().GetUnderlined() != attr2.GetFontUnderlined())
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_ALIGNMENT) && attr1.GetAlignment() != attr2.GetAlignment())
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_LEFT_INDENT) &&
|
|
((attr1.GetLeftIndent() != attr2.GetLeftIndent()) || (attr1.GetLeftSubIndent() != attr2.GetLeftSubIndent())))
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_RIGHT_INDENT) &&
|
|
(attr1.GetRightIndent() != attr2.GetRightIndent()))
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_PARA_SPACING_AFTER) &&
|
|
(attr1.GetParagraphSpacingAfter() != attr2.GetParagraphSpacingAfter()))
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_PARA_SPACING_BEFORE) &&
|
|
(attr1.GetParagraphSpacingBefore() != attr2.GetParagraphSpacingBefore()))
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_LINE_SPACING) &&
|
|
(attr1.GetLineSpacing() != attr2.GetLineSpacing()))
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_CHARACTER_STYLE_NAME) &&
|
|
(attr1.GetCharacterStyleName() != attr2.GetCharacterStyleName()))
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_PARAGRAPH_STYLE_NAME) &&
|
|
(attr1.GetParagraphStyleName() != attr2.GetParagraphStyleName()))
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_BULLET_STYLE) &&
|
|
(attr1.GetBulletStyle() != attr2.GetBulletStyle()))
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_BULLET_NUMBER) &&
|
|
(attr1.GetBulletNumber() != attr2.GetBulletNumber()))
|
|
return false;
|
|
|
|
if ((flags & wxTEXT_ATTR_BULLET_SYMBOL) &&
|
|
(attr1.GetBulletSymbol() != attr2.GetBulletSymbol()))
|
|
return false;
|
|
|
|
/* TODO
|
|
if ((flags & wxTEXT_ATTR_TABS) &&
|
|
return false;
|
|
*/
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/// Apply one style to another
|
|
bool wxRichTextApplyStyle(wxTextAttrEx& destStyle, const wxTextAttrEx& style)
|
|
{
|
|
// Whole font
|
|
if (style.GetFont().Ok() && ((style.GetFlags() & (wxTEXT_ATTR_FONT)) == (wxTEXT_ATTR_FONT)))
|
|
destStyle.SetFont(style.GetFont());
|
|
else if (style.GetFont().Ok())
|
|
{
|
|
wxFont font = destStyle.GetFont();
|
|
|
|
if (style.GetFlags() & wxTEXT_ATTR_FONT_FACE)
|
|
{
|
|
destStyle.SetFlags(destStyle.GetFlags() | wxTEXT_ATTR_FONT_FACE);
|
|
font.SetFaceName(style.GetFont().GetFaceName());
|
|
}
|
|
|
|
if (style.GetFlags() & wxTEXT_ATTR_FONT_SIZE)
|
|
{
|
|
destStyle.SetFlags(destStyle.GetFlags() | wxTEXT_ATTR_FONT_SIZE);
|
|
font.SetPointSize(style.GetFont().GetPointSize());
|
|
}
|
|
|
|
if (style.GetFlags() & wxTEXT_ATTR_FONT_ITALIC)
|
|
{
|
|
destStyle.SetFlags(destStyle.GetFlags() | wxTEXT_ATTR_FONT_ITALIC);
|
|
font.SetStyle(style.GetFont().GetStyle());
|
|
}
|
|
|
|
if (style.GetFlags() & wxTEXT_ATTR_FONT_WEIGHT)
|
|
{
|
|
destStyle.SetFlags(destStyle.GetFlags() | wxTEXT_ATTR_FONT_WEIGHT);
|
|
font.SetWeight(style.GetFont().GetWeight());
|
|
}
|
|
|
|
if (style.GetFlags() & wxTEXT_ATTR_FONT_UNDERLINE)
|
|
{
|
|
destStyle.SetFlags(destStyle.GetFlags() | wxTEXT_ATTR_FONT_UNDERLINE);
|
|
font.SetUnderlined(style.GetFont().GetUnderlined());
|
|
}
|
|
|
|
if (font != destStyle.GetFont())
|
|
{
|
|
int oldFlags = destStyle.GetFlags();
|
|
|
|
destStyle.SetFont(font);
|
|
|
|
destStyle.SetFlags(oldFlags);
|
|
}
|
|
}
|
|
|
|
if ( style.GetTextColour().Ok() && style.HasTextColour())
|
|
destStyle.SetTextColour(style.GetTextColour());
|
|
|
|
if ( style.GetBackgroundColour().Ok() && style.HasBackgroundColour())
|
|
destStyle.SetBackgroundColour(style.GetBackgroundColour());
|
|
|
|
if (style.HasAlignment())
|
|
destStyle.SetAlignment(style.GetAlignment());
|
|
|
|
if (style.HasTabs())
|
|
destStyle.SetTabs(style.GetTabs());
|
|
|
|
if (style.HasLeftIndent())
|
|
destStyle.SetLeftIndent(style.GetLeftIndent(), style.GetLeftSubIndent());
|
|
|
|
if (style.HasRightIndent())
|
|
destStyle.SetRightIndent(style.GetRightIndent());
|
|
|
|
if (style.HasParagraphSpacingAfter())
|
|
destStyle.SetParagraphSpacingAfter(style.GetParagraphSpacingAfter());
|
|
|
|
if (style.HasParagraphSpacingBefore())
|
|
destStyle.SetParagraphSpacingBefore(style.GetParagraphSpacingBefore());
|
|
|
|
if (style.HasLineSpacing())
|
|
destStyle.SetLineSpacing(style.GetLineSpacing());
|
|
|
|
if (style.HasCharacterStyleName())
|
|
destStyle.SetCharacterStyleName(style.GetCharacterStyleName());
|
|
|
|
if (style.HasParagraphStyleName())
|
|
destStyle.SetParagraphStyleName(style.GetParagraphStyleName());
|
|
|
|
if (style.HasBulletStyle())
|
|
{
|
|
destStyle.SetBulletStyle(style.GetBulletStyle());
|
|
destStyle.SetBulletSymbol(style.GetBulletSymbol());
|
|
}
|
|
|
|
if (style.HasBulletNumber())
|
|
destStyle.SetBulletNumber(style.GetBulletNumber());
|
|
|
|
return true;
|
|
}
|
|
|
|
bool wxRichTextApplyStyle(wxRichTextAttr& destStyle, const wxTextAttrEx& style)
|
|
{
|
|
wxTextAttrEx destStyle2;
|
|
destStyle.CopyTo(destStyle2);
|
|
wxRichTextApplyStyle(destStyle2, style);
|
|
destStyle = destStyle2;
|
|
return true;
|
|
}
|
|
|
|
bool wxRichTextApplyStyle(wxTextAttrEx& destStyle, const wxRichTextAttr& style)
|
|
{
|
|
// Whole font. Avoiding setting individual attributes if possible, since
|
|
// it recreates the font each time.
|
|
if ((style.GetFlags() & (wxTEXT_ATTR_FONT)) == (wxTEXT_ATTR_FONT))
|
|
{
|
|
destStyle.SetFont(wxFont(style.GetFontSize(), destStyle.GetFont().Ok() ? destStyle.GetFont().GetFamily() : wxDEFAULT,
|
|
style.GetFontStyle(), style.GetFontWeight(), style.GetFontUnderlined(), style.GetFontFaceName()));
|
|
}
|
|
else if (style.GetFlags() & (wxTEXT_ATTR_FONT))
|
|
{
|
|
wxFont font = destStyle.GetFont();
|
|
|
|
if (style.GetFlags() & wxTEXT_ATTR_FONT_FACE)
|
|
{
|
|
destStyle.SetFlags(destStyle.GetFlags() | wxTEXT_ATTR_FONT_FACE);
|
|
font.SetFaceName(style.GetFontFaceName());
|
|
}
|
|
|
|
if (style.GetFlags() & wxTEXT_ATTR_FONT_SIZE)
|
|
{
|
|
destStyle.SetFlags(destStyle.GetFlags() | wxTEXT_ATTR_FONT_SIZE);
|
|
font.SetPointSize(style.GetFontSize());
|
|
}
|
|
|
|
if (style.GetFlags() & wxTEXT_ATTR_FONT_ITALIC)
|
|
{
|
|
destStyle.SetFlags(destStyle.GetFlags() | wxTEXT_ATTR_FONT_ITALIC);
|
|
font.SetStyle(style.GetFontStyle());
|
|
}
|
|
|
|
if (style.GetFlags() & wxTEXT_ATTR_FONT_WEIGHT)
|
|
{
|
|
destStyle.SetFlags(destStyle.GetFlags() | wxTEXT_ATTR_FONT_WEIGHT);
|
|
font.SetWeight(style.GetFontWeight());
|
|
}
|
|
|
|
if (style.GetFlags() & wxTEXT_ATTR_FONT_UNDERLINE)
|
|
{
|
|
destStyle.SetFlags(destStyle.GetFlags() | wxTEXT_ATTR_FONT_UNDERLINE);
|
|
font.SetUnderlined(style.GetFontUnderlined());
|
|
}
|
|
|
|
if (font != destStyle.GetFont())
|
|
{
|
|
int oldFlags = destStyle.GetFlags();
|
|
|
|
destStyle.SetFont(font);
|
|
|
|
destStyle.SetFlags(oldFlags);
|
|
}
|
|
}
|
|
|
|
if ( style.GetTextColour().Ok() && style.HasTextColour())
|
|
destStyle.SetTextColour(style.GetTextColour());
|
|
|
|
if ( style.GetBackgroundColour().Ok() && style.HasBackgroundColour())
|
|
destStyle.SetBackgroundColour(style.GetBackgroundColour());
|
|
|
|
if (style.HasAlignment())
|
|
destStyle.SetAlignment(style.GetAlignment());
|
|
|
|
if (style.HasTabs())
|
|
destStyle.SetTabs(style.GetTabs());
|
|
|
|
if (style.HasLeftIndent())
|
|
destStyle.SetLeftIndent(style.GetLeftIndent(), style.GetLeftSubIndent());
|
|
|
|
if (style.HasRightIndent())
|
|
destStyle.SetRightIndent(style.GetRightIndent());
|
|
|
|
if (style.HasParagraphSpacingAfter())
|
|
destStyle.SetParagraphSpacingAfter(style.GetParagraphSpacingAfter());
|
|
|
|
if (style.HasParagraphSpacingBefore())
|
|
destStyle.SetParagraphSpacingBefore(style.GetParagraphSpacingBefore());
|
|
|
|
if (style.HasLineSpacing())
|
|
destStyle.SetLineSpacing(style.GetLineSpacing());
|
|
|
|
if (style.HasCharacterStyleName())
|
|
destStyle.SetCharacterStyleName(style.GetCharacterStyleName());
|
|
|
|
if (style.HasParagraphStyleName())
|
|
destStyle.SetParagraphStyleName(style.GetParagraphStyleName());
|
|
|
|
if (style.HasBulletStyle())
|
|
{
|
|
destStyle.SetBulletStyle(style.GetBulletStyle());
|
|
destStyle.SetBulletSymbol(style.GetBulletSymbol());
|
|
}
|
|
|
|
if (style.HasBulletNumber())
|
|
destStyle.SetBulletNumber(style.GetBulletNumber());
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/*!
|
|
* wxRichTextAttr stores attributes without a wxFont object, so is a much more
|
|
* efficient way to query styles.
|
|
*/
|
|
|
|
// ctors
|
|
wxRichTextAttr::wxRichTextAttr(const wxColour& colText,
|
|
const wxColour& colBack,
|
|
wxTextAttrAlignment alignment): m_textAlignment(alignment), m_colText(colText), m_colBack(colBack)
|
|
{
|
|
Init();
|
|
|
|
if (m_colText.Ok()) m_flags |= wxTEXT_ATTR_TEXT_COLOUR;
|
|
if (m_colBack.Ok()) m_flags |= wxTEXT_ATTR_BACKGROUND_COLOUR;
|
|
if (alignment != wxTEXT_ALIGNMENT_DEFAULT)
|
|
m_flags |= wxTEXT_ATTR_ALIGNMENT;
|
|
}
|
|
|
|
wxRichTextAttr::wxRichTextAttr(const wxTextAttrEx& attr)
|
|
{
|
|
Init();
|
|
|
|
(*this) = attr;
|
|
}
|
|
|
|
// operations
|
|
void wxRichTextAttr::Init()
|
|
{
|
|
m_textAlignment = wxTEXT_ALIGNMENT_DEFAULT;
|
|
m_flags = 0;
|
|
m_leftIndent = 0;
|
|
m_leftSubIndent = 0;
|
|
m_rightIndent = 0;
|
|
|
|
m_fontSize = 12;
|
|
m_fontStyle = wxNORMAL;
|
|
m_fontWeight = wxNORMAL;
|
|
m_fontUnderlined = false;
|
|
|
|
m_paragraphSpacingAfter = 0;
|
|
m_paragraphSpacingBefore = 0;
|
|
m_lineSpacing = 0;
|
|
m_bulletStyle = wxTEXT_ATTR_BULLET_STYLE_NONE;
|
|
m_bulletNumber = 0;
|
|
m_bulletSymbol = wxT('*');
|
|
}
|
|
|
|
// operators
|
|
void wxRichTextAttr::operator= (const wxRichTextAttr& attr)
|
|
{
|
|
m_colText = attr.m_colText;
|
|
m_colBack = attr.m_colBack;
|
|
m_textAlignment = attr.m_textAlignment;
|
|
m_leftIndent = attr.m_leftIndent;
|
|
m_leftSubIndent = attr.m_leftSubIndent;
|
|
m_rightIndent = attr.m_rightIndent;
|
|
m_tabs = attr.m_tabs;
|
|
m_flags = attr.m_flags;
|
|
|
|
m_fontSize = attr.m_fontSize;
|
|
m_fontStyle = attr.m_fontStyle;
|
|
m_fontWeight = attr.m_fontWeight;
|
|
m_fontUnderlined = attr.m_fontUnderlined;
|
|
m_fontFaceName = attr.m_fontFaceName;
|
|
|
|
m_paragraphSpacingAfter = attr.m_paragraphSpacingAfter;
|
|
m_paragraphSpacingBefore = attr.m_paragraphSpacingBefore;
|
|
m_lineSpacing = attr.m_lineSpacing;
|
|
m_characterStyleName = attr.m_characterStyleName;
|
|
m_paragraphStyleName = attr.m_paragraphStyleName;
|
|
m_bulletStyle = attr.m_bulletStyle;
|
|
m_bulletNumber = attr.m_bulletNumber;
|
|
m_bulletSymbol = attr.m_bulletSymbol;
|
|
}
|
|
|
|
// operators
|
|
void wxRichTextAttr::operator= (const wxTextAttrEx& attr)
|
|
{
|
|
m_colText = attr.GetTextColour();
|
|
m_colBack = attr.GetBackgroundColour();
|
|
m_textAlignment = attr.GetAlignment();
|
|
m_leftIndent = attr.GetLeftIndent();
|
|
m_leftSubIndent = attr.GetLeftSubIndent();
|
|
m_rightIndent = attr.GetRightIndent();
|
|
m_tabs = attr.GetTabs();
|
|
m_flags = attr.GetFlags();
|
|
|
|
m_paragraphSpacingAfter = attr.GetParagraphSpacingAfter();
|
|
m_paragraphSpacingBefore = attr.GetParagraphSpacingBefore();
|
|
m_lineSpacing = attr.GetLineSpacing();
|
|
m_characterStyleName = attr.GetCharacterStyleName();
|
|
m_paragraphStyleName = attr.GetParagraphStyleName();
|
|
|
|
if (attr.GetFont().Ok())
|
|
GetFontAttributes(attr.GetFont());
|
|
}
|
|
|
|
// Making a wxTextAttrEx object.
|
|
wxRichTextAttr::operator wxTextAttrEx () const
|
|
{
|
|
wxTextAttrEx attr;
|
|
CopyTo(attr);
|
|
return attr;
|
|
}
|
|
|
|
// Equality test
|
|
bool wxRichTextAttr::operator== (const wxRichTextAttr& attr) const
|
|
{
|
|
return GetFlags() == attr.GetFlags() &&
|
|
|
|
GetTextColour() == attr.GetTextColour() &&
|
|
GetBackgroundColour() == attr.GetBackgroundColour() &&
|
|
|
|
GetAlignment() == attr.GetAlignment() &&
|
|
GetLeftIndent() == attr.GetLeftIndent() &&
|
|
GetLeftSubIndent() == attr.GetLeftSubIndent() &&
|
|
GetRightIndent() == attr.GetRightIndent() &&
|
|
//GetTabs() == attr.GetTabs() &&
|
|
|
|
GetParagraphSpacingAfter() == attr.GetParagraphSpacingAfter() &&
|
|
GetParagraphSpacingBefore() == attr.GetParagraphSpacingBefore() &&
|
|
GetLineSpacing() == attr.GetLineSpacing() &&
|
|
GetCharacterStyleName() == attr.GetCharacterStyleName() &&
|
|
GetParagraphStyleName() == attr.GetParagraphStyleName() &&
|
|
|
|
m_fontSize == attr.m_fontSize &&
|
|
m_fontStyle == attr.m_fontStyle &&
|
|
m_fontWeight == attr.m_fontWeight &&
|
|
m_fontUnderlined == attr.m_fontUnderlined &&
|
|
m_fontFaceName == attr.m_fontFaceName;
|
|
}
|
|
|
|
// Copy to a wxTextAttr
|
|
void wxRichTextAttr::CopyTo(wxTextAttrEx& attr) const
|
|
{
|
|
attr.SetTextColour(GetTextColour());
|
|
attr.SetBackgroundColour(GetBackgroundColour());
|
|
attr.SetAlignment(GetAlignment());
|
|
attr.SetTabs(GetTabs());
|
|
attr.SetLeftIndent(GetLeftIndent(), GetLeftSubIndent());
|
|
attr.SetRightIndent(GetRightIndent());
|
|
attr.SetFont(CreateFont());
|
|
|
|
attr.SetParagraphSpacingAfter(m_paragraphSpacingAfter);
|
|
attr.SetParagraphSpacingBefore(m_paragraphSpacingBefore);
|
|
attr.SetLineSpacing(m_lineSpacing);
|
|
attr.SetBulletStyle(m_bulletStyle);
|
|
attr.SetBulletNumber(m_bulletNumber);
|
|
attr.SetBulletSymbol(m_bulletSymbol);
|
|
attr.SetCharacterStyleName(m_characterStyleName);
|
|
attr.SetParagraphStyleName(m_paragraphStyleName);
|
|
|
|
attr.SetFlags(GetFlags()); // Important: set after SetFont and others, since they set flags
|
|
}
|
|
|
|
// Create font from font attributes.
|
|
wxFont wxRichTextAttr::CreateFont() const
|
|
{
|
|
wxFont font(m_fontSize, wxDEFAULT, m_fontStyle, m_fontWeight, m_fontUnderlined, m_fontFaceName);
|
|
#ifdef __WXMAC__
|
|
font.SetNoAntiAliasing(true);
|
|
#endif
|
|
return font;
|
|
}
|
|
|
|
// Get attributes from font.
|
|
bool wxRichTextAttr::GetFontAttributes(const wxFont& font)
|
|
{
|
|
if (!font.Ok())
|
|
return false;
|
|
|
|
m_fontSize = font.GetPointSize();
|
|
m_fontStyle = font.GetStyle();
|
|
m_fontWeight = font.GetWeight();
|
|
m_fontUnderlined = font.GetUnderlined();
|
|
m_fontFaceName = font.GetFaceName();
|
|
|
|
return true;
|
|
}
|
|
|
|
wxRichTextAttr wxRichTextAttr::Combine(const wxRichTextAttr& attr,
|
|
const wxRichTextAttr& attrDef,
|
|
const wxTextCtrlBase *text)
|
|
{
|
|
wxColour colFg = attr.GetTextColour();
|
|
if ( !colFg.Ok() )
|
|
{
|
|
colFg = attrDef.GetTextColour();
|
|
|
|
if ( text && !colFg.Ok() )
|
|
colFg = text->GetForegroundColour();
|
|
}
|
|
|
|
wxColour colBg = attr.GetBackgroundColour();
|
|
if ( !colBg.Ok() )
|
|
{
|
|
colBg = attrDef.GetBackgroundColour();
|
|
|
|
if ( text && !colBg.Ok() )
|
|
colBg = text->GetBackgroundColour();
|
|
}
|
|
|
|
wxRichTextAttr newAttr(colFg, colBg);
|
|
|
|
if (attr.HasWeight())
|
|
newAttr.SetFontWeight(attr.GetFontWeight());
|
|
|
|
if (attr.HasSize())
|
|
newAttr.SetFontSize(attr.GetFontSize());
|
|
|
|
if (attr.HasItalic())
|
|
newAttr.SetFontStyle(attr.GetFontStyle());
|
|
|
|
if (attr.HasUnderlined())
|
|
newAttr.SetFontUnderlined(attr.GetFontUnderlined());
|
|
|
|
if (attr.HasFaceName())
|
|
newAttr.SetFontFaceName(attr.GetFontFaceName());
|
|
|
|
if (attr.HasAlignment())
|
|
newAttr.SetAlignment(attr.GetAlignment());
|
|
else if (attrDef.HasAlignment())
|
|
newAttr.SetAlignment(attrDef.GetAlignment());
|
|
|
|
if (attr.HasTabs())
|
|
newAttr.SetTabs(attr.GetTabs());
|
|
else if (attrDef.HasTabs())
|
|
newAttr.SetTabs(attrDef.GetTabs());
|
|
|
|
if (attr.HasLeftIndent())
|
|
newAttr.SetLeftIndent(attr.GetLeftIndent(), attr.GetLeftSubIndent());
|
|
else if (attrDef.HasLeftIndent())
|
|
newAttr.SetLeftIndent(attrDef.GetLeftIndent(), attr.GetLeftSubIndent());
|
|
|
|
if (attr.HasRightIndent())
|
|
newAttr.SetRightIndent(attr.GetRightIndent());
|
|
else if (attrDef.HasRightIndent())
|
|
newAttr.SetRightIndent(attrDef.GetRightIndent());
|
|
|
|
// NEW ATTRIBUTES
|
|
|
|
if (attr.HasParagraphSpacingAfter())
|
|
newAttr.SetParagraphSpacingAfter(attr.GetParagraphSpacingAfter());
|
|
|
|
if (attr.HasParagraphSpacingBefore())
|
|
newAttr.SetParagraphSpacingBefore(attr.GetParagraphSpacingBefore());
|
|
|
|
if (attr.HasLineSpacing())
|
|
newAttr.SetLineSpacing(attr.GetLineSpacing());
|
|
|
|
if (attr.HasCharacterStyleName())
|
|
newAttr.SetCharacterStyleName(attr.GetCharacterStyleName());
|
|
|
|
if (attr.HasParagraphStyleName())
|
|
newAttr.SetParagraphStyleName(attr.GetParagraphStyleName());
|
|
|
|
if (attr.HasBulletStyle())
|
|
newAttr.SetBulletStyle(attr.GetBulletStyle());
|
|
|
|
if (attr.HasBulletNumber())
|
|
newAttr.SetBulletNumber(attr.GetBulletNumber());
|
|
|
|
if (attr.HasBulletSymbol())
|
|
newAttr.SetBulletSymbol(attr.GetBulletSymbol());
|
|
|
|
return newAttr;
|
|
}
|
|
|
|
/*!
|
|
* wxTextAttrEx is an extended version of wxTextAttr with more paragraph attributes.
|
|
*/
|
|
|
|
wxTextAttrEx::wxTextAttrEx(const wxTextAttrEx& attr): wxTextAttr(attr)
|
|
{
|
|
m_paragraphSpacingAfter = attr.m_paragraphSpacingAfter;
|
|
m_paragraphSpacingBefore = attr.m_paragraphSpacingBefore;
|
|
m_lineSpacing = attr.m_lineSpacing;
|
|
m_paragraphStyleName = attr.m_paragraphStyleName;
|
|
m_characterStyleName = attr.m_characterStyleName;
|
|
m_bulletStyle = attr.m_bulletStyle;
|
|
m_bulletNumber = attr.m_bulletNumber;
|
|
m_bulletSymbol = attr.m_bulletSymbol;
|
|
}
|
|
|
|
// Initialise this object.
|
|
void wxTextAttrEx::Init()
|
|
{
|
|
m_paragraphSpacingAfter = 0;
|
|
m_paragraphSpacingBefore = 0;
|
|
m_lineSpacing = 0;
|
|
m_bulletStyle = wxTEXT_ATTR_BULLET_STYLE_NONE;
|
|
m_bulletNumber = 0;
|
|
m_bulletSymbol = 0;
|
|
m_bulletSymbol = wxT('*');
|
|
}
|
|
|
|
// Assignment from a wxTextAttrEx object
|
|
void wxTextAttrEx::operator= (const wxTextAttrEx& attr)
|
|
{
|
|
wxTextAttr::operator= (attr);
|
|
|
|
m_paragraphSpacingAfter = attr.m_paragraphSpacingAfter;
|
|
m_paragraphSpacingBefore = attr.m_paragraphSpacingBefore;
|
|
m_lineSpacing = attr.m_lineSpacing;
|
|
m_characterStyleName = attr.m_characterStyleName;
|
|
m_paragraphStyleName = attr.m_paragraphStyleName;
|
|
m_bulletStyle = attr.m_bulletStyle;
|
|
m_bulletNumber = attr.m_bulletNumber;
|
|
m_bulletSymbol = attr.m_bulletSymbol;
|
|
}
|
|
|
|
// Assignment from a wxTextAttr object.
|
|
void wxTextAttrEx::operator= (const wxTextAttr& attr)
|
|
{
|
|
wxTextAttr::operator= (attr);
|
|
}
|
|
|
|
wxTextAttrEx wxTextAttrEx::CombineEx(const wxTextAttrEx& attr,
|
|
const wxTextAttrEx& attrDef,
|
|
const wxTextCtrlBase *text)
|
|
{
|
|
wxTextAttrEx newAttr;
|
|
|
|
// If attr specifies the complete font, just use that font, overriding all
|
|
// default font attributes.
|
|
if ((attr.GetFlags() & wxTEXT_ATTR_FONT) == wxTEXT_ATTR_FONT)
|
|
newAttr.SetFont(attr.GetFont());
|
|
else
|
|
{
|
|
// First find the basic, default font
|
|
long flags = 0;
|
|
|
|
wxFont font;
|
|
if (attrDef.HasFont())
|
|
{
|
|
flags = (attrDef.GetFlags() & wxTEXT_ATTR_FONT);
|
|
font = attrDef.GetFont();
|
|
}
|
|
else
|
|
{
|
|
if (text)
|
|
font = text->GetFont();
|
|
|
|
// We leave flags at 0 because no font attributes have been specified yet
|
|
}
|
|
if (!font.Ok())
|
|
font = *wxNORMAL_FONT;
|
|
|
|
// Otherwise, if there are font attributes in attr, apply them
|
|
if (attr.HasFont())
|
|
{
|
|
if (attr.HasSize())
|
|
{
|
|
flags |= wxTEXT_ATTR_FONT_SIZE;
|
|
font.SetPointSize(attr.GetFont().GetPointSize());
|
|
}
|
|
if (attr.HasItalic())
|
|
{
|
|
flags |= wxTEXT_ATTR_FONT_ITALIC;;
|
|
font.SetStyle(attr.GetFont().GetStyle());
|
|
}
|
|
if (attr.HasWeight())
|
|
{
|
|
flags |= wxTEXT_ATTR_FONT_WEIGHT;
|
|
font.SetWeight(attr.GetFont().GetWeight());
|
|
}
|
|
if (attr.HasFaceName())
|
|
{
|
|
flags |= wxTEXT_ATTR_FONT_FACE;
|
|
font.SetFaceName(attr.GetFont().GetFaceName());
|
|
}
|
|
if (attr.HasUnderlined())
|
|
{
|
|
flags |= wxTEXT_ATTR_FONT_UNDERLINE;
|
|
font.SetUnderlined(attr.GetFont().GetUnderlined());
|
|
}
|
|
newAttr.SetFont(font);
|
|
newAttr.SetFlags(newAttr.GetFlags()|flags);
|
|
}
|
|
}
|
|
|
|
// TODO: should really check we are specifying these in the flags,
|
|
// before setting them, as per above; or we will set them willy-nilly.
|
|
// However, we should also check whether this is the intention
|
|
// as per wxTextAttr::Combine, i.e. always to have valid colours
|
|
// in the style.
|
|
wxColour colFg = attr.GetTextColour();
|
|
if ( !colFg.Ok() )
|
|
{
|
|
colFg = attrDef.GetTextColour();
|
|
|
|
if ( text && !colFg.Ok() )
|
|
colFg = text->GetForegroundColour();
|
|
}
|
|
|
|
wxColour colBg = attr.GetBackgroundColour();
|
|
if ( !colBg.Ok() )
|
|
{
|
|
colBg = attrDef.GetBackgroundColour();
|
|
|
|
if ( text && !colBg.Ok() )
|
|
colBg = text->GetBackgroundColour();
|
|
}
|
|
|
|
newAttr.SetTextColour(colFg);
|
|
newAttr.SetBackgroundColour(colBg);
|
|
|
|
if (attr.HasAlignment())
|
|
newAttr.SetAlignment(attr.GetAlignment());
|
|
else if (attrDef.HasAlignment())
|
|
newAttr.SetAlignment(attrDef.GetAlignment());
|
|
|
|
if (attr.HasTabs())
|
|
newAttr.SetTabs(attr.GetTabs());
|
|
else if (attrDef.HasTabs())
|
|
newAttr.SetTabs(attrDef.GetTabs());
|
|
|
|
if (attr.HasLeftIndent())
|
|
newAttr.SetLeftIndent(attr.GetLeftIndent(), attr.GetLeftSubIndent());
|
|
else if (attrDef.HasLeftIndent())
|
|
newAttr.SetLeftIndent(attrDef.GetLeftIndent(), attr.GetLeftSubIndent());
|
|
|
|
if (attr.HasRightIndent())
|
|
newAttr.SetRightIndent(attr.GetRightIndent());
|
|
else if (attrDef.HasRightIndent())
|
|
newAttr.SetRightIndent(attrDef.GetRightIndent());
|
|
|
|
// NEW ATTRIBUTES
|
|
|
|
if (attr.HasParagraphSpacingAfter())
|
|
newAttr.SetParagraphSpacingAfter(attr.GetParagraphSpacingAfter());
|
|
|
|
if (attr.HasParagraphSpacingBefore())
|
|
newAttr.SetParagraphSpacingBefore(attr.GetParagraphSpacingBefore());
|
|
|
|
if (attr.HasLineSpacing())
|
|
newAttr.SetLineSpacing(attr.GetLineSpacing());
|
|
|
|
if (attr.HasCharacterStyleName())
|
|
newAttr.SetCharacterStyleName(attr.GetCharacterStyleName());
|
|
|
|
if (attr.HasParagraphStyleName())
|
|
newAttr.SetParagraphStyleName(attr.GetParagraphStyleName());
|
|
|
|
if (attr.HasBulletStyle())
|
|
newAttr.SetBulletStyle(attr.GetBulletStyle());
|
|
|
|
if (attr.HasBulletNumber())
|
|
newAttr.SetBulletNumber(attr.GetBulletNumber());
|
|
|
|
if (attr.HasBulletSymbol())
|
|
newAttr.SetBulletSymbol(attr.GetBulletSymbol());
|
|
|
|
return newAttr;
|
|
}
|
|
|
|
|
|
/*!
|
|
* wxRichTextFileHandler
|
|
* Base class for file handlers
|
|
*/
|
|
|
|
IMPLEMENT_CLASS(wxRichTextFileHandler, wxObject)
|
|
|
|
#if wxUSE_STREAMS
|
|
bool wxRichTextFileHandler::LoadFile(wxRichTextBuffer *buffer, const wxString& filename)
|
|
{
|
|
wxFFileInputStream stream(filename);
|
|
if (stream.Ok())
|
|
return LoadFile(buffer, stream);
|
|
|
|
return false;
|
|
}
|
|
|
|
bool wxRichTextFileHandler::SaveFile(wxRichTextBuffer *buffer, const wxString& filename)
|
|
{
|
|
wxFFileOutputStream stream(filename);
|
|
if (stream.Ok())
|
|
return SaveFile(buffer, stream);
|
|
|
|
return false;
|
|
}
|
|
#endif // wxUSE_STREAMS
|
|
|
|
/// Can we handle this filename (if using files)? By default, checks the extension.
|
|
bool wxRichTextFileHandler::CanHandle(const wxString& filename) const
|
|
{
|
|
wxString path, file, ext;
|
|
wxSplitPath(filename, & path, & file, & ext);
|
|
|
|
return (ext.Lower() == GetExtension());
|
|
}
|
|
|
|
/*!
|
|
* wxRichTextTextHandler
|
|
* Plain text handler
|
|
*/
|
|
|
|
IMPLEMENT_CLASS(wxRichTextPlainTextHandler, wxRichTextFileHandler)
|
|
|
|
#if wxUSE_STREAMS
|
|
bool wxRichTextPlainTextHandler::DoLoadFile(wxRichTextBuffer *buffer, wxInputStream& stream)
|
|
{
|
|
if (!stream.IsOk())
|
|
return false;
|
|
|
|
wxString str;
|
|
int lastCh = 0;
|
|
|
|
while (!stream.Eof())
|
|
{
|
|
int ch = stream.GetC();
|
|
|
|
if (!stream.Eof())
|
|
{
|
|
if (ch == 10 && lastCh != 13)
|
|
str += wxT('\n');
|
|
|
|
if (ch > 0 && ch != 10)
|
|
str += wxChar(ch);
|
|
|
|
lastCh = ch;
|
|
}
|
|
}
|
|
|
|
buffer->Clear();
|
|
buffer->AddParagraphs(str);
|
|
buffer->UpdateRanges();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
bool wxRichTextPlainTextHandler::DoSaveFile(wxRichTextBuffer *buffer, wxOutputStream& stream)
|
|
{
|
|
if (!stream.IsOk())
|
|
return false;
|
|
|
|
wxString text = buffer->GetText();
|
|
wxCharBuffer buf = text.ToAscii();
|
|
|
|
stream.Write((const char*) buf, text.length());
|
|
return true;
|
|
}
|
|
#endif // wxUSE_STREAMS
|
|
|
|
/*
|
|
* Stores information about an image, in binary in-memory form
|
|
*/
|
|
|
|
wxRichTextImageBlock::wxRichTextImageBlock()
|
|
{
|
|
Init();
|
|
}
|
|
|
|
wxRichTextImageBlock::wxRichTextImageBlock(const wxRichTextImageBlock& block):wxObject()
|
|
{
|
|
Init();
|
|
Copy(block);
|
|
}
|
|
|
|
wxRichTextImageBlock::~wxRichTextImageBlock()
|
|
{
|
|
if (m_data)
|
|
{
|
|
delete[] m_data;
|
|
m_data = NULL;
|
|
}
|
|
}
|
|
|
|
void wxRichTextImageBlock::Init()
|
|
{
|
|
m_data = NULL;
|
|
m_dataSize = 0;
|
|
m_imageType = -1;
|
|
}
|
|
|
|
void wxRichTextImageBlock::Clear()
|
|
{
|
|
delete[] m_data;
|
|
m_data = NULL;
|
|
m_dataSize = 0;
|
|
m_imageType = -1;
|
|
}
|
|
|
|
|
|
// Load the original image into a memory block.
|
|
// If the image is not a JPEG, we must convert it into a JPEG
|
|
// to conserve space.
|
|
// If it's not a JPEG we can make use of 'image', already scaled, so we don't have to
|
|
// load the image a 2nd time.
|
|
|
|
bool wxRichTextImageBlock::MakeImageBlock(const wxString& filename, int imageType, wxImage& image, bool convertToJPEG)
|
|
{
|
|
m_imageType = imageType;
|
|
|
|
wxString filenameToRead(filename);
|
|
bool removeFile = false;
|
|
|
|
if (imageType == -1)
|
|
return false; // Could not determine image type
|
|
|
|
if ((imageType != wxBITMAP_TYPE_JPEG) && convertToJPEG)
|
|
{
|
|
wxString tempFile;
|
|
bool success = wxGetTempFileName(_("image"), tempFile) ;
|
|
|
|
wxASSERT(success);
|
|
|
|
wxUnusedVar(success);
|
|
|
|
image.SaveFile(tempFile, wxBITMAP_TYPE_JPEG);
|
|
filenameToRead = tempFile;
|
|
removeFile = true;
|
|
|
|
m_imageType = wxBITMAP_TYPE_JPEG;
|
|
}
|
|
wxFile file;
|
|
if (!file.Open(filenameToRead))
|
|
return false;
|
|
|
|
m_dataSize = (size_t) file.Length();
|
|
file.Close();
|
|
|
|
if (m_data)
|
|
delete[] m_data;
|
|
m_data = ReadBlock(filenameToRead, m_dataSize);
|
|
|
|
if (removeFile)
|
|
wxRemoveFile(filenameToRead);
|
|
|
|
return (m_data != NULL);
|
|
}
|
|
|
|
// Make an image block from the wxImage in the given
|
|
// format.
|
|
bool wxRichTextImageBlock::MakeImageBlock(wxImage& image, int imageType, int quality)
|
|
{
|
|
m_imageType = imageType;
|
|
image.SetOption(wxT("quality"), quality);
|
|
|
|
if (imageType == -1)
|
|
return false; // Could not determine image type
|
|
|
|
wxString tempFile;
|
|
bool success = wxGetTempFileName(_("image"), tempFile) ;
|
|
|
|
wxASSERT(success);
|
|
wxUnusedVar(success);
|
|
|
|
if (!image.SaveFile(tempFile, m_imageType))
|
|
{
|
|
if (wxFileExists(tempFile))
|
|
wxRemoveFile(tempFile);
|
|
return false;
|
|
}
|
|
|
|
wxFile file;
|
|
if (!file.Open(tempFile))
|
|
return false;
|
|
|
|
m_dataSize = (size_t) file.Length();
|
|
file.Close();
|
|
|
|
if (m_data)
|
|
delete[] m_data;
|
|
m_data = ReadBlock(tempFile, m_dataSize);
|
|
|
|
wxRemoveFile(tempFile);
|
|
|
|
return (m_data != NULL);
|
|
}
|
|
|
|
|
|
// Write to a file
|
|
bool wxRichTextImageBlock::Write(const wxString& filename)
|
|
{
|
|
return WriteBlock(filename, m_data, m_dataSize);
|
|
}
|
|
|
|
void wxRichTextImageBlock::Copy(const wxRichTextImageBlock& block)
|
|
{
|
|
m_imageType = block.m_imageType;
|
|
if (m_data)
|
|
{
|
|
delete[] m_data;
|
|
m_data = NULL;
|
|
}
|
|
m_dataSize = block.m_dataSize;
|
|
if (m_dataSize == 0)
|
|
return;
|
|
|
|
m_data = new unsigned char[m_dataSize];
|
|
unsigned int i;
|
|
for (i = 0; i < m_dataSize; i++)
|
|
m_data[i] = block.m_data[i];
|
|
}
|
|
|
|
//// Operators
|
|
void wxRichTextImageBlock::operator=(const wxRichTextImageBlock& block)
|
|
{
|
|
Copy(block);
|
|
}
|
|
|
|
// Load a wxImage from the block
|
|
bool wxRichTextImageBlock::Load(wxImage& image)
|
|
{
|
|
if (!m_data)
|
|
return false;
|
|
|
|
// Read in the image.
|
|
#if wxUSE_STREAMS
|
|
wxMemoryInputStream mstream(m_data, m_dataSize);
|
|
bool success = image.LoadFile(mstream, GetImageType());
|
|
#else
|
|
wxString tempFile;
|
|
bool success = wxGetTempFileName(_("image"), tempFile) ;
|
|
wxASSERT(success);
|
|
|
|
if (!WriteBlock(tempFile, m_data, m_dataSize))
|
|
{
|
|
return false;
|
|
}
|
|
success = image.LoadFile(tempFile, GetImageType());
|
|
wxRemoveFile(tempFile);
|
|
#endif
|
|
|
|
return success;
|
|
}
|
|
|
|
// Write data in hex to a stream
|
|
bool wxRichTextImageBlock::WriteHex(wxOutputStream& stream)
|
|
{
|
|
wxString hex;
|
|
int i;
|
|
for (i = 0; i < (int) m_dataSize; i++)
|
|
{
|
|
hex = wxDecToHex(m_data[i]);
|
|
wxCharBuffer buf = hex.ToAscii();
|
|
|
|
stream.Write((const char*) buf, hex.length());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Read data in hex from a stream
|
|
bool wxRichTextImageBlock::ReadHex(wxInputStream& stream, int length, int imageType)
|
|
{
|
|
int dataSize = length/2;
|
|
|
|
if (m_data)
|
|
delete[] m_data;
|
|
|
|
wxString str(wxT(" "));
|
|
m_data = new unsigned char[dataSize];
|
|
int i;
|
|
for (i = 0; i < dataSize; i ++)
|
|
{
|
|
str[0] = stream.GetC();
|
|
str[1] = stream.GetC();
|
|
|
|
m_data[i] = (unsigned char)wxHexToDec(str);
|
|
}
|
|
|
|
m_dataSize = dataSize;
|
|
m_imageType = imageType;
|
|
|
|
return true;
|
|
}
|
|
|
|
// Allocate and read from stream as a block of memory
|
|
unsigned char* wxRichTextImageBlock::ReadBlock(wxInputStream& stream, size_t size)
|
|
{
|
|
unsigned char* block = new unsigned char[size];
|
|
if (!block)
|
|
return NULL;
|
|
|
|
stream.Read(block, size);
|
|
|
|
return block;
|
|
}
|
|
|
|
unsigned char* wxRichTextImageBlock::ReadBlock(const wxString& filename, size_t size)
|
|
{
|
|
wxFileInputStream stream(filename);
|
|
if (!stream.Ok())
|
|
return NULL;
|
|
|
|
return ReadBlock(stream, size);
|
|
}
|
|
|
|
// Write memory block to stream
|
|
bool wxRichTextImageBlock::WriteBlock(wxOutputStream& stream, unsigned char* block, size_t size)
|
|
{
|
|
stream.Write((void*) block, size);
|
|
return stream.IsOk();
|
|
|
|
}
|
|
|
|
// Write memory block to file
|
|
bool wxRichTextImageBlock::WriteBlock(const wxString& filename, unsigned char* block, size_t size)
|
|
{
|
|
wxFileOutputStream outStream(filename);
|
|
if (!outStream.Ok())
|
|
return false;
|
|
|
|
return WriteBlock(outStream, block, size);
|
|
}
|
|
|
|
#if wxUSE_DATAOBJ
|
|
|
|
/*!
|
|
* The data object for a wxRichTextBuffer
|
|
*/
|
|
|
|
const wxChar *wxRichTextBufferDataObject::ms_richTextBufferFormatId = wxT("wxShape");
|
|
|
|
wxRichTextBufferDataObject::wxRichTextBufferDataObject(wxRichTextBuffer* richTextBuffer)
|
|
{
|
|
m_richTextBuffer = richTextBuffer;
|
|
|
|
// this string should uniquely identify our format, but is otherwise
|
|
// arbitrary
|
|
m_formatRichTextBuffer.SetId(GetRichTextBufferFormatId());
|
|
|
|
SetFormat(m_formatRichTextBuffer);
|
|
}
|
|
|
|
wxRichTextBufferDataObject::~wxRichTextBufferDataObject()
|
|
{
|
|
delete m_richTextBuffer;
|
|
}
|
|
|
|
// after a call to this function, the richTextBuffer is owned by the caller and it
|
|
// is responsible for deleting it!
|
|
wxRichTextBuffer* wxRichTextBufferDataObject::GetRichTextBuffer()
|
|
{
|
|
wxRichTextBuffer* richTextBuffer = m_richTextBuffer;
|
|
m_richTextBuffer = NULL;
|
|
|
|
return richTextBuffer;
|
|
}
|
|
|
|
wxDataFormat wxRichTextBufferDataObject::GetPreferredFormat(Direction WXUNUSED(dir)) const
|
|
{
|
|
return m_formatRichTextBuffer;
|
|
}
|
|
|
|
size_t wxRichTextBufferDataObject::GetDataSize() const
|
|
{
|
|
if (!m_richTextBuffer)
|
|
return 0;
|
|
|
|
wxString bufXML;
|
|
|
|
{
|
|
wxStringOutputStream stream(& bufXML);
|
|
if (!m_richTextBuffer->SaveFile(stream, wxRICHTEXT_TYPE_XML))
|
|
{
|
|
wxLogError(wxT("Could not write the buffer to an XML stream.\nYou may have forgotten to add the XML file handler."));
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
#if wxUSE_UNICODE
|
|
wxCharBuffer buffer = bufXML.mb_str(wxConvUTF8);
|
|
return strlen(buffer) + 1;
|
|
#else
|
|
return bufXML.Length()+1;
|
|
#endif
|
|
}
|
|
|
|
bool wxRichTextBufferDataObject::GetDataHere(void *pBuf) const
|
|
{
|
|
if (!pBuf || !m_richTextBuffer)
|
|
return false;
|
|
|
|
wxString bufXML;
|
|
|
|
{
|
|
wxStringOutputStream stream(& bufXML);
|
|
if (!m_richTextBuffer->SaveFile(stream, wxRICHTEXT_TYPE_XML))
|
|
{
|
|
wxLogError(wxT("Could not write the buffer to an XML stream.\nYou may have forgotten to add the XML file handler."));
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
#if wxUSE_UNICODE
|
|
wxCharBuffer buffer = bufXML.mb_str(wxConvUTF8);
|
|
size_t len = strlen(buffer);
|
|
memcpy((char*) pBuf, (const char*) buffer, len);
|
|
((char*) pBuf)[len] = 0;
|
|
#else
|
|
size_t len = bufXML.Length();
|
|
memcpy((char*) pBuf, (const char*) bufXML.c_str(), len);
|
|
((char*) pBuf)[len] = 0;
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
bool wxRichTextBufferDataObject::SetData(size_t WXUNUSED(len), const void *buf)
|
|
{
|
|
delete m_richTextBuffer;
|
|
m_richTextBuffer = NULL;
|
|
|
|
wxString bufXML((const char*) buf, wxConvUTF8);
|
|
|
|
m_richTextBuffer = new wxRichTextBuffer;
|
|
|
|
wxStringInputStream stream(bufXML);
|
|
if (!m_richTextBuffer->LoadFile(stream, wxRICHTEXT_TYPE_XML))
|
|
{
|
|
wxLogError(wxT("Could not read the buffer from an XML stream.\nYou may have forgotten to add the XML file handler."));
|
|
|
|
delete m_richTextBuffer;
|
|
m_richTextBuffer = NULL;
|
|
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#endif
|
|
// wxUSE_DATAOBJ
|
|
|
|
#endif
|
|
// wxUSE_RICHTEXT
|