]> git.saurik.com Git - wxWidgets.git/blame - src/richtext/richtextbuffer.cpp
fixing wxSystemColour conversion
[wxWidgets.git] / src / richtext / richtextbuffer.cpp
CommitLineData
5d7836c4 1/////////////////////////////////////////////////////////////////////////////
61399247 2// Name: src/richtext/richtextbuffer.cpp
5d7836c4
JS
3// Purpose: Buffer for wxRichTextCtrl
4// Author: Julian Smart
7fe8059f 5// Modified by:
5d7836c4 6// Created: 2005-09-30
7fe8059f 7// RCS-ID: $Id$
5d7836c4
JS
8// Copyright: (c) Julian Smart
9// Licence: wxWindows licence
10/////////////////////////////////////////////////////////////////////////////
11
12// For compilers that support precompilation, includes "wx.h".
13#include "wx/wxprec.h"
14
15#ifdef __BORLANDC__
61399247 16 #pragma hdrstop
5d7836c4
JS
17#endif
18
b01ca8b6
JS
19#if wxUSE_RICHTEXT
20
21#include "wx/richtext/richtextbuffer.h"
22
5d7836c4 23#ifndef WX_PRECOMP
61399247
WS
24 #include "wx/dc.h"
25 #include "wx/intl.h"
7947a48a 26 #include "wx/log.h"
28f92d74 27 #include "wx/dataobj.h"
02761f6c 28 #include "wx/module.h"
5d7836c4
JS
29#endif
30
0ec6da02 31#include "wx/settings.h"
5d7836c4
JS
32#include "wx/filename.h"
33#include "wx/clipbrd.h"
34#include "wx/wfstream.h"
5d7836c4
JS
35#include "wx/mstream.h"
36#include "wx/sstream.h"
0ca07313 37#include "wx/textfile.h"
44cc96a8 38#include "wx/hashmap.h"
5d7836c4 39
5d7836c4
JS
40#include "wx/richtext/richtextctrl.h"
41#include "wx/richtext/richtextstyles.h"
42
43#include "wx/listimpl.cpp"
44
412e0d47
DS
45WX_DEFINE_LIST(wxRichTextObjectList)
46WX_DEFINE_LIST(wxRichTextLineList)
5d7836c4 47
ea160b2e
JS
48// Switch off if the platform doesn't like it for some reason
49#define wxRICHTEXT_USE_OPTIMIZED_DRAWING 1
50
ff76711f
JS
51const wxChar wxRichTextLineBreakChar = (wxChar) 29;
52
ecb5fbf1
JS
53// Helpers for efficiency
54
55inline void wxCheckSetFont(wxDC& dc, const wxFont& font)
56{
57 const wxFont& font1 = dc.GetFont();
58 if (font1.IsOk() && font.IsOk())
59 {
60 if (font1.GetPointSize() == font.GetPointSize() &&
61 font1.GetFamily() == font.GetFamily() &&
62 font1.GetStyle() == font.GetStyle() &&
63 font1.GetWeight() == font.GetWeight() &&
3c8766f7 64 font1.GetUnderlined() == font.GetUnderlined() &&
ecb5fbf1
JS
65 font1.GetFaceName() == font.GetFaceName())
66 return;
67 }
68 dc.SetFont(font);
69}
70
71inline void wxCheckSetPen(wxDC& dc, const wxPen& pen)
72{
73 const wxPen& pen1 = dc.GetPen();
74 if (pen1.IsOk() && pen.IsOk())
75 {
76 if (pen1.GetWidth() == pen.GetWidth() &&
77 pen1.GetStyle() == pen.GetStyle() &&
78 pen1.GetColour() == pen.GetColour())
79 return;
80 }
81 dc.SetPen(pen);
82}
83
84inline void wxCheckSetBrush(wxDC& dc, const wxBrush& brush)
85{
86 const wxBrush& brush1 = dc.GetBrush();
87 if (brush1.IsOk() && brush.IsOk())
88 {
89 if (brush1.GetStyle() == brush.GetStyle() &&
90 brush1.GetColour() == brush.GetColour())
91 return;
92 }
93 dc.SetBrush(brush);
94}
95
5d7836c4
JS
96/*!
97 * wxRichTextObject
98 * This is the base for drawable objects.
99 */
100
101IMPLEMENT_CLASS(wxRichTextObject, wxObject)
102
103wxRichTextObject::wxRichTextObject(wxRichTextObject* parent)
104{
105 m_dirty = false;
106 m_refCount = 1;
107 m_parent = parent;
108 m_leftMargin = 0;
109 m_rightMargin = 0;
110 m_topMargin = 0;
111 m_bottomMargin = 0;
112 m_descent = 0;
113}
114
115wxRichTextObject::~wxRichTextObject()
116{
117}
118
119void wxRichTextObject::Dereference()
120{
121 m_refCount --;
122 if (m_refCount <= 0)
123 delete this;
124}
125
126/// Copy
127void wxRichTextObject::Copy(const wxRichTextObject& obj)
128{
129 m_size = obj.m_size;
130 m_pos = obj.m_pos;
131 m_dirty = obj.m_dirty;
132 m_range = obj.m_range;
133 m_attributes = obj.m_attributes;
134 m_descent = obj.m_descent;
5d7836c4
JS
135}
136
137void wxRichTextObject::SetMargins(int margin)
138{
139 m_leftMargin = m_rightMargin = m_topMargin = m_bottomMargin = margin;
140}
141
142void wxRichTextObject::SetMargins(int leftMargin, int rightMargin, int topMargin, int bottomMargin)
143{
144 m_leftMargin = leftMargin;
145 m_rightMargin = rightMargin;
146 m_topMargin = topMargin;
147 m_bottomMargin = bottomMargin;
148}
149
44219ff0 150// Convert units in tenths of a millimetre to device units
5d7836c4
JS
151int wxRichTextObject::ConvertTenthsMMToPixels(wxDC& dc, int units)
152{
44219ff0 153 int p = ConvertTenthsMMToPixels(dc.GetPPI().x, units);
5d7836c4 154
44219ff0
JS
155 // Unscale
156 wxRichTextBuffer* buffer = GetBuffer();
157 if (buffer)
158 p = (int) ((double)p / buffer->GetScale());
159 return p;
160}
161
162// Convert units in tenths of a millimetre to device units
163int wxRichTextObject::ConvertTenthsMMToPixels(int ppi, int units)
164{
5d7836c4
JS
165 // There are ppi pixels in 254.1 "1/10 mm"
166
167 double pixels = ((double) units * (double)ppi) / 254.1;
168
169 return (int) pixels;
170}
171
172/// Dump to output stream for debugging
173void wxRichTextObject::Dump(wxTextOutputStream& stream)
174{
175 stream << GetClassInfo()->GetClassName() << wxT("\n");
176 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");
177 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");
178}
179
44219ff0
JS
180/// Gets the containing buffer
181wxRichTextBuffer* wxRichTextObject::GetBuffer() const
182{
183 const wxRichTextObject* obj = this;
184 while (obj && !obj->IsKindOf(CLASSINFO(wxRichTextBuffer)))
185 obj = obj->GetParent();
186 return wxDynamicCast(obj, wxRichTextBuffer);
187}
5d7836c4
JS
188
189/*!
190 * wxRichTextCompositeObject
191 * This is the base for drawable objects.
192 */
193
194IMPLEMENT_CLASS(wxRichTextCompositeObject, wxRichTextObject)
195
196wxRichTextCompositeObject::wxRichTextCompositeObject(wxRichTextObject* parent):
197 wxRichTextObject(parent)
198{
199}
200
201wxRichTextCompositeObject::~wxRichTextCompositeObject()
202{
203 DeleteChildren();
204}
205
206/// Get the nth child
207wxRichTextObject* wxRichTextCompositeObject::GetChild(size_t n) const
208{
209 wxASSERT ( n < m_children.GetCount() );
210
211 return m_children.Item(n)->GetData();
212}
213
214/// Append a child, returning the position
215size_t wxRichTextCompositeObject::AppendChild(wxRichTextObject* child)
216{
217 m_children.Append(child);
218 child->SetParent(this);
219 return m_children.GetCount() - 1;
220}
221
222/// Insert the child in front of the given object, or at the beginning
223bool wxRichTextCompositeObject::InsertChild(wxRichTextObject* child, wxRichTextObject* inFrontOf)
224{
225 if (inFrontOf)
226 {
227 wxRichTextObjectList::compatibility_iterator node = m_children.Find(inFrontOf);
228 m_children.Insert(node, child);
229 }
230 else
231 m_children.Insert(child);
232 child->SetParent(this);
233
234 return true;
235}
236
237/// Delete the child
238bool wxRichTextCompositeObject::RemoveChild(wxRichTextObject* child, bool deleteChild)
239{
240 wxRichTextObjectList::compatibility_iterator node = m_children.Find(child);
241 if (node)
242 {
efbf6735
JS
243 wxRichTextObject* obj = node->GetData();
244 m_children.Erase(node);
5d7836c4 245 if (deleteChild)
efbf6735 246 delete obj;
5d7836c4
JS
247
248 return true;
249 }
250 return false;
251}
252
253/// Delete all children
254bool wxRichTextCompositeObject::DeleteChildren()
255{
256 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
257 while (node)
258 {
259 wxRichTextObjectList::compatibility_iterator oldNode = node;
260
261 wxRichTextObject* child = node->GetData();
262 child->Dereference(); // Only delete if reference count is zero
263
264 node = node->GetNext();
efbf6735 265 m_children.Erase(oldNode);
5d7836c4
JS
266 }
267
268 return true;
269}
270
271/// Get the child count
272size_t wxRichTextCompositeObject::GetChildCount() const
273{
274 return m_children.GetCount();
275}
276
277/// Copy
278void wxRichTextCompositeObject::Copy(const wxRichTextCompositeObject& obj)
279{
280 wxRichTextObject::Copy(obj);
281
282 DeleteChildren();
283
284 wxRichTextObjectList::compatibility_iterator node = obj.m_children.GetFirst();
285 while (node)
286 {
287 wxRichTextObject* child = node->GetData();
fe5aa22c
JS
288 wxRichTextObject* newChild = child->Clone();
289 newChild->SetParent(this);
290 m_children.Append(newChild);
5d7836c4
JS
291
292 node = node->GetNext();
293 }
294}
295
296/// Hit-testing: returns a flag indicating hit test details, plus
297/// information about position
298int wxRichTextCompositeObject::HitTest(wxDC& dc, const wxPoint& pt, long& textPosition)
299{
300 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
301 while (node)
302 {
303 wxRichTextObject* child = node->GetData();
304
305 int ret = child->HitTest(dc, pt, textPosition);
306 if (ret != wxRICHTEXT_HITTEST_NONE)
307 return ret;
308
309 node = node->GetNext();
310 }
311
312 return wxRICHTEXT_HITTEST_NONE;
313}
314
315/// Finds the absolute position and row height for the given character position
316bool wxRichTextCompositeObject::FindPosition(wxDC& dc, long index, wxPoint& pt, int* height, bool forceLineStart)
317{
318 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
319 while (node)
320 {
321 wxRichTextObject* child = node->GetData();
322
323 if (child->FindPosition(dc, index, pt, height, forceLineStart))
324 return true;
325
326 node = node->GetNext();
327 }
328
329 return false;
330}
331
332/// Calculate range
333void wxRichTextCompositeObject::CalculateRange(long start, long& end)
334{
335 long current = start;
336 long lastEnd = current;
337
338 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
339 while (node)
340 {
341 wxRichTextObject* child = node->GetData();
342 long childEnd = 0;
343
344 child->CalculateRange(current, childEnd);
345 lastEnd = childEnd;
346
347 current = childEnd + 1;
348
349 node = node->GetNext();
350 }
351
352 end = lastEnd;
353
354 // An object with no children has zero length
355 if (m_children.GetCount() == 0)
356 end --;
357
358 m_range.SetRange(start, end);
359}
360
361/// Delete range from layout.
362bool wxRichTextCompositeObject::DeleteRange(const wxRichTextRange& range)
363{
364 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
7fe8059f 365
5d7836c4
JS
366 while (node)
367 {
368 wxRichTextObject* obj = (wxRichTextObject*) node->GetData();
369 wxRichTextObjectList::compatibility_iterator next = node->GetNext();
7fe8059f 370
5d7836c4
JS
371 // Delete the range in each paragraph
372
373 // When a chunk has been deleted, internally the content does not
374 // now match the ranges.
375 // However, so long as deletion is not done on the same object twice this is OK.
376 // If you may delete content from the same object twice, recalculate
377 // the ranges inbetween DeleteRange calls by calling CalculateRanges, and
378 // adjust the range you're deleting accordingly.
7fe8059f 379
5d7836c4
JS
380 if (!obj->GetRange().IsOutside(range))
381 {
382 obj->DeleteRange(range);
383
384 // Delete an empty object, or paragraph within this range.
385 if (obj->IsEmpty() ||
386 (range.GetStart() <= obj->GetRange().GetStart() && range.GetEnd() >= obj->GetRange().GetEnd()))
387 {
388 // An empty paragraph has length 1, so won't be deleted unless the
389 // whole range is deleted.
7fe8059f 390 RemoveChild(obj, true);
5d7836c4
JS
391 }
392 }
7fe8059f 393
5d7836c4
JS
394 node = next;
395 }
7fe8059f 396
5d7836c4
JS
397 return true;
398}
399
400/// Get any text in this object for the given range
401wxString wxRichTextCompositeObject::GetTextForRange(const wxRichTextRange& range) const
402{
403 wxString text;
404 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
405 while (node)
406 {
407 wxRichTextObject* child = node->GetData();
408 wxRichTextRange childRange = range;
409 if (!child->GetRange().IsOutside(range))
410 {
411 childRange.LimitTo(child->GetRange());
7fe8059f 412
5d7836c4 413 wxString childText = child->GetTextForRange(childRange);
7fe8059f 414
5d7836c4
JS
415 text += childText;
416 }
417 node = node->GetNext();
418 }
419
420 return text;
421}
422
423/// Recursively merge all pieces that can be merged.
424bool wxRichTextCompositeObject::Defragment()
425{
426 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
427 while (node)
428 {
429 wxRichTextObject* child = node->GetData();
430 wxRichTextCompositeObject* composite = wxDynamicCast(child, wxRichTextCompositeObject);
7fe8059f 431 if (composite)
5d7836c4
JS
432 composite->Defragment();
433
434 if (node->GetNext())
435 {
436 wxRichTextObject* nextChild = node->GetNext()->GetData();
437 if (child->CanMerge(nextChild) && child->Merge(nextChild))
438 {
439 nextChild->Dereference();
9e31a660 440 m_children.Erase(node->GetNext());
5d7836c4
JS
441
442 // Don't set node -- we'll see if we can merge again with the next
443 // child.
444 }
445 else
446 node = node->GetNext();
447 }
448 else
449 node = node->GetNext();
450 }
451
452 return true;
453}
454
455/// Dump to output stream for debugging
456void wxRichTextCompositeObject::Dump(wxTextOutputStream& stream)
457{
458 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
459 while (node)
460 {
461 wxRichTextObject* child = node->GetData();
462 child->Dump(stream);
463 node = node->GetNext();
464 }
465}
466
467
468/*!
469 * wxRichTextBox
470 * This defines a 2D space to lay out objects
471 */
472
473IMPLEMENT_DYNAMIC_CLASS(wxRichTextBox, wxRichTextCompositeObject)
474
475wxRichTextBox::wxRichTextBox(wxRichTextObject* parent):
476 wxRichTextCompositeObject(parent)
477{
478}
479
480/// Draw the item
481bool wxRichTextBox::Draw(wxDC& dc, const wxRichTextRange& range, const wxRichTextRange& selectionRange, const wxRect& WXUNUSED(rect), int descent, int style)
482{
483 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
484 while (node)
485 {
486 wxRichTextObject* child = node->GetData();
487
488 wxRect childRect = wxRect(child->GetPosition(), child->GetCachedSize());
489 child->Draw(dc, range, selectionRange, childRect, descent, style);
490
491 node = node->GetNext();
492 }
493 return true;
494}
495
496/// Lay the item out
38113684 497bool wxRichTextBox::Layout(wxDC& dc, const wxRect& rect, int style)
5d7836c4
JS
498{
499 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
500 while (node)
501 {
502 wxRichTextObject* child = node->GetData();
38113684 503 child->Layout(dc, rect, style);
5d7836c4
JS
504
505 node = node->GetNext();
506 }
507 m_dirty = false;
508 return true;
509}
510
511/// Get/set the size for the given range. Assume only has one child.
7f0d9d71 512bool wxRichTextBox::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, int flags, wxPoint position) const
5d7836c4
JS
513{
514 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
515 if (node)
516 {
517 wxRichTextObject* child = node->GetData();
7f0d9d71 518 return child->GetRangeSize(range, size, descent, dc, flags, position);
5d7836c4
JS
519 }
520 else
521 return false;
522}
523
524/// Copy
525void wxRichTextBox::Copy(const wxRichTextBox& obj)
526{
527 wxRichTextCompositeObject::Copy(obj);
528}
529
530
531/*!
532 * wxRichTextParagraphLayoutBox
533 * This box knows how to lay out paragraphs.
534 */
535
536IMPLEMENT_DYNAMIC_CLASS(wxRichTextParagraphLayoutBox, wxRichTextBox)
537
538wxRichTextParagraphLayoutBox::wxRichTextParagraphLayoutBox(wxRichTextObject* parent):
539 wxRichTextBox(parent)
540{
541 Init();
542}
543
544/// Initialize the object.
545void wxRichTextParagraphLayoutBox::Init()
546{
547 m_ctrl = NULL;
548
549 // For now, assume is the only box and has no initial size.
550 m_range = wxRichTextRange(0, -1);
551
38113684 552 m_invalidRange.SetRange(-1, -1);
5d7836c4
JS
553 m_leftMargin = 4;
554 m_rightMargin = 4;
555 m_topMargin = 4;
556 m_bottomMargin = 4;
0ca07313 557 m_partialParagraph = false;
5d7836c4
JS
558}
559
560/// Draw the item
011b3dcb 561bool wxRichTextParagraphLayoutBox::Draw(wxDC& dc, const wxRichTextRange& range, const wxRichTextRange& selectionRange, const wxRect& rect, int descent, int style)
5d7836c4
JS
562{
563 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
564 while (node)
565 {
566 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
567 wxASSERT (child != NULL);
7fe8059f 568
5d7836c4
JS
569 if (child && !child->GetRange().IsOutside(range))
570 {
571 wxRect childRect(child->GetPosition(), child->GetCachedSize());
7fe8059f 572
ea160b2e
JS
573 if (((style & wxRICHTEXT_DRAW_IGNORE_CACHE) == 0) && childRect.GetTop() > rect.GetBottom())
574 {
575 // Stop drawing
576 break;
577 }
578 else if (((style & wxRICHTEXT_DRAW_IGNORE_CACHE) == 0) && childRect.GetBottom() < rect.GetTop())
011b3dcb
JS
579 {
580 // Skip
581 }
582 else
46e7a90e 583 child->Draw(dc, range, selectionRange, childRect, descent, style);
5d7836c4
JS
584 }
585
586 node = node->GetNext();
587 }
588 return true;
589}
590
591/// Lay the item out
38113684 592bool wxRichTextParagraphLayoutBox::Layout(wxDC& dc, const wxRect& rect, int style)
5d7836c4 593{
4d551ad5
JS
594 wxRect availableSpace;
595 bool formatRect = (style & wxRICHTEXT_LAYOUT_SPECIFIED_RECT) == wxRICHTEXT_LAYOUT_SPECIFIED_RECT;
596
597 // If only laying out a specific area, the passed rect has a different meaning:
44219ff0
JS
598 // the visible part of the buffer. This is used in wxRichTextCtrl::OnSize,
599 // so that during a size, only the visible part will be relaid out, or
600 // it would take too long causing flicker. As an approximation, we assume that
601 // everything up to the start of the visible area is laid out correctly.
4d551ad5
JS
602 if (formatRect)
603 {
604 availableSpace = wxRect(0 + m_leftMargin,
605 0 + m_topMargin,
606 rect.width - m_leftMargin - m_rightMargin,
607 rect.height);
608
609 // Invalidate the part of the buffer from the first visible line
610 // to the end. If other parts of the buffer are currently invalid,
611 // then they too will be taken into account if they are above
612 // the visible point.
613 long startPos = 0;
614 wxRichTextLine* line = GetLineAtYPosition(rect.y);
615 if (line)
616 startPos = line->GetAbsoluteRange().GetStart();
617
618 Invalidate(wxRichTextRange(startPos, GetRange().GetEnd()));
619 }
620 else
621 availableSpace = wxRect(rect.x + m_leftMargin,
5d7836c4
JS
622 rect.y + m_topMargin,
623 rect.width - m_leftMargin - m_rightMargin,
624 rect.height - m_topMargin - m_bottomMargin);
625
626 int maxWidth = 0;
7fe8059f 627
5d7836c4 628 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
39a1c2f2 629
38113684 630 bool layoutAll = true;
1e967276 631
38113684
JS
632 // Get invalid range, rounding to paragraph start/end.
633 wxRichTextRange invalidRange = GetInvalidRange(true);
634
4d551ad5 635 if (invalidRange == wxRICHTEXT_NONE && !formatRect)
1e967276
JS
636 return true;
637
638 if (invalidRange == wxRICHTEXT_ALL)
639 layoutAll = true;
38113684 640 else // If we know what range is affected, start laying out from that point on.
9b4af7b7 641 if (invalidRange.GetStart() >= GetRange().GetStart())
2c375f42 642 {
38113684 643 wxRichTextParagraph* firstParagraph = GetParagraphAtPosition(invalidRange.GetStart());
2c375f42
JS
644 if (firstParagraph)
645 {
646 wxRichTextObjectList::compatibility_iterator firstNode = m_children.Find(firstParagraph);
0cc70962
VZ
647 wxRichTextObjectList::compatibility_iterator previousNode;
648 if ( firstNode )
649 previousNode = firstNode->GetPrevious();
9b4af7b7 650 if (firstNode)
2c375f42 651 {
9b4af7b7
JS
652 if (previousNode)
653 {
654 wxRichTextParagraph* previousParagraph = wxDynamicCast(previousNode->GetData(), wxRichTextParagraph);
655 availableSpace.y = previousParagraph->GetPosition().y + previousParagraph->GetCachedSize().y;
656 }
7fe8059f 657
2c375f42
JS
658 // Now we're going to start iterating from the first affected paragraph.
659 node = firstNode;
1e967276
JS
660
661 layoutAll = false;
2c375f42
JS
662 }
663 }
664 }
665
4d551ad5
JS
666 // A way to force speedy rest-of-buffer layout (the 'else' below)
667 bool forceQuickLayout = false;
39a1c2f2 668
5d7836c4
JS
669 while (node)
670 {
671 // Assume this box only contains paragraphs
672
673 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
f120af9d 674 wxCHECK_MSG( child, false, _T("Unknown object in layout") );
7fe8059f 675
1e967276 676 // TODO: what if the child hasn't been laid out (e.g. involved in Undo) but still has 'old' lines
f120af9d
VZ
677 if ( !forceQuickLayout &&
678 (layoutAll ||
679 child->GetLines().IsEmpty() ||
680 !child->GetRange().IsOutside(invalidRange)) )
2c375f42 681 {
38113684 682 child->Layout(dc, availableSpace, style);
5d7836c4 683
2c375f42
JS
684 // Layout must set the cached size
685 availableSpace.y += child->GetCachedSize().y;
686 maxWidth = wxMax(maxWidth, child->GetCachedSize().x);
4d551ad5
JS
687
688 // If we're just formatting the visible part of the buffer,
689 // and we're now past the bottom of the window, start quick
690 // layout.
691 if (formatRect && child->GetPosition().y > rect.GetBottom())
692 forceQuickLayout = true;
2c375f42
JS
693 }
694 else
695 {
696 // We're outside the immediately affected range, so now let's just
697 // move everything up or down. This assumes that all the children have previously
698 // been laid out and have wrapped line lists associated with them.
699 // TODO: check all paragraphs before the affected range.
7fe8059f 700
2c375f42 701 int inc = availableSpace.y - child->GetPosition().y;
7fe8059f 702
2c375f42
JS
703 while (node)
704 {
705 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
706 if (child)
707 {
708 if (child->GetLines().GetCount() == 0)
38113684 709 child->Layout(dc, availableSpace, style);
2c375f42
JS
710 else
711 child->SetPosition(wxPoint(child->GetPosition().x, child->GetPosition().y + inc));
7fe8059f 712
2c375f42
JS
713 availableSpace.y += child->GetCachedSize().y;
714 maxWidth = wxMax(maxWidth, child->GetCachedSize().x);
715 }
7fe8059f
WS
716
717 node = node->GetNext();
2c375f42
JS
718 }
719 break;
720 }
5d7836c4
JS
721
722 node = node->GetNext();
723 }
724
725 SetCachedSize(wxSize(maxWidth, availableSpace.y));
726
727 m_dirty = false;
1e967276 728 m_invalidRange = wxRICHTEXT_NONE;
5d7836c4
JS
729
730 return true;
731}
732
733/// Copy
734void wxRichTextParagraphLayoutBox::Copy(const wxRichTextParagraphLayoutBox& obj)
735{
736 wxRichTextBox::Copy(obj);
0ca07313
JS
737
738 m_partialParagraph = obj.m_partialParagraph;
b78b6e88 739 m_defaultAttributes = obj.m_defaultAttributes;
5d7836c4
JS
740}
741
742/// Get/set the size for the given range.
7f0d9d71 743bool wxRichTextParagraphLayoutBox::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, int flags, wxPoint position) const
5d7836c4
JS
744{
745 wxSize sz;
746
09f14108
JS
747 wxRichTextObjectList::compatibility_iterator startPara = wxRichTextObjectList::compatibility_iterator();
748 wxRichTextObjectList::compatibility_iterator endPara = wxRichTextObjectList::compatibility_iterator();
5d7836c4
JS
749
750 // First find the first paragraph whose starting position is within the range.
751 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
752 while (node)
753 {
754 // child is a paragraph
755 wxRichTextObject* child = node->GetData();
756 const wxRichTextRange& r = child->GetRange();
757
758 if (r.GetStart() <= range.GetStart() && r.GetEnd() >= range.GetStart())
759 {
760 startPara = node;
761 break;
762 }
763
764 node = node->GetNext();
765 }
766
767 // Next find the last paragraph containing part of the range
768 node = m_children.GetFirst();
769 while (node)
770 {
771 // child is a paragraph
772 wxRichTextObject* child = node->GetData();
773 const wxRichTextRange& r = child->GetRange();
774
775 if (r.GetStart() <= range.GetEnd() && r.GetEnd() >= range.GetEnd())
776 {
777 endPara = node;
778 break;
779 }
780
781 node = node->GetNext();
782 }
783
784 if (!startPara || !endPara)
785 return false;
786
787 // Now we can add up the sizes
788 for (node = startPara; node ; node = node->GetNext())
789 {
790 // child is a paragraph
791 wxRichTextObject* child = node->GetData();
792 const wxRichTextRange& childRange = child->GetRange();
793 wxRichTextRange rangeToFind = range;
794 rangeToFind.LimitTo(childRange);
795
796 wxSize childSize;
797
798 int childDescent = 0;
7f0d9d71 799 child->GetRangeSize(rangeToFind, childSize, childDescent, dc, flags, position);
5d7836c4
JS
800
801 descent = wxMax(childDescent, descent);
802
803 sz.x = wxMax(sz.x, childSize.x);
804 sz.y += childSize.y;
805
806 if (node == endPara)
807 break;
808 }
809
810 size = sz;
811
812 return true;
813}
814
815/// Get the paragraph at the given position
816wxRichTextParagraph* wxRichTextParagraphLayoutBox::GetParagraphAtPosition(long pos, bool caretPosition) const
817{
818 if (caretPosition)
819 pos ++;
820
821 // First find the first paragraph whose starting position is within the range.
822 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
823 while (node)
824 {
825 // child is a paragraph
826 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
827 wxASSERT (child != NULL);
828
829 // Return first child in buffer if position is -1
830 // if (pos == -1)
831 // return child;
832
833 if (child->GetRange().Contains(pos))
834 return child;
835
836 node = node->GetNext();
837 }
838 return NULL;
839}
840
841/// Get the line at the given position
842wxRichTextLine* wxRichTextParagraphLayoutBox::GetLineAtPosition(long pos, bool caretPosition) const
843{
844 if (caretPosition)
845 pos ++;
846
847 // First find the first paragraph whose starting position is within the range.
848 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
849 while (node)
850 {
851 // child is a paragraph
852 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
853 wxASSERT (child != NULL);
854
855 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
856 while (node2)
857 {
858 wxRichTextLine* line = node2->GetData();
859
1e967276
JS
860 wxRichTextRange range = line->GetAbsoluteRange();
861
862 if (range.Contains(pos) ||
5d7836c4
JS
863
864 // If the position is end-of-paragraph, then return the last line of
865 // of the paragraph.
1e967276 866 (range.GetEnd() == child->GetRange().GetEnd()-1) && (pos == child->GetRange().GetEnd()))
5d7836c4
JS
867 return line;
868
869 node2 = node2->GetNext();
7fe8059f 870 }
5d7836c4
JS
871
872 node = node->GetNext();
873 }
874
875 int lineCount = GetLineCount();
876 if (lineCount > 0)
877 return GetLineForVisibleLineNumber(lineCount-1);
878 else
879 return NULL;
880}
881
882/// Get the line at the given y pixel position, or the last line.
883wxRichTextLine* wxRichTextParagraphLayoutBox::GetLineAtYPosition(int y) const
884{
885 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
886 while (node)
887 {
888 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
889 wxASSERT (child != NULL);
890
891 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
892 while (node2)
893 {
894 wxRichTextLine* line = node2->GetData();
895
896 wxRect rect(line->GetRect());
897
898 if (y <= rect.GetBottom())
899 return line;
900
901 node2 = node2->GetNext();
7fe8059f 902 }
5d7836c4
JS
903
904 node = node->GetNext();
905 }
906
907 // Return last line
908 int lineCount = GetLineCount();
909 if (lineCount > 0)
910 return GetLineForVisibleLineNumber(lineCount-1);
911 else
912 return NULL;
913}
914
915/// Get the number of visible lines
916int wxRichTextParagraphLayoutBox::GetLineCount() const
917{
918 int count = 0;
919
920 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
921 while (node)
922 {
923 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
924 wxASSERT (child != NULL);
925
926 count += child->GetLines().GetCount();
927 node = node->GetNext();
928 }
929 return count;
930}
931
932
933/// Get the paragraph for a given line
934wxRichTextParagraph* wxRichTextParagraphLayoutBox::GetParagraphForLine(wxRichTextLine* line) const
935{
1e967276 936 return GetParagraphAtPosition(line->GetAbsoluteRange().GetStart());
5d7836c4
JS
937}
938
939/// Get the line size at the given position
940wxSize wxRichTextParagraphLayoutBox::GetLineSizeAtPosition(long pos, bool caretPosition) const
941{
942 wxRichTextLine* line = GetLineAtPosition(pos, caretPosition);
943 if (line)
944 {
945 return line->GetSize();
946 }
947 else
948 return wxSize(0, 0);
949}
950
951
952/// Convenience function to add a paragraph of text
44cc96a8 953wxRichTextRange wxRichTextParagraphLayoutBox::AddParagraph(const wxString& text, wxTextAttr* paraStyle)
5d7836c4 954{
fe5aa22c 955 // Don't use the base style, just the default style, and the base style will
4f32b3cf
JS
956 // be combined at display time.
957 // Divide into paragraph and character styles.
3e541562 958
44cc96a8
JS
959 wxTextAttr defaultCharStyle;
960 wxTextAttr defaultParaStyle;
4f32b3cf 961
5912d19e 962 wxRichTextSplitParaCharStyles(GetDefaultStyle(), defaultParaStyle, defaultCharStyle);
44cc96a8
JS
963 wxTextAttr* pStyle = paraStyle ? paraStyle : (wxTextAttr*) & defaultParaStyle;
964 wxTextAttr* cStyle = & defaultCharStyle;
4f32b3cf
JS
965
966 wxRichTextParagraph* para = new wxRichTextParagraph(text, this, pStyle, cStyle);
5d7836c4
JS
967
968 AppendChild(para);
969
970 UpdateRanges();
971 SetDirty(true);
972
973 return para->GetRange();
974}
975
976/// Adds multiple paragraphs, based on newlines.
44cc96a8 977wxRichTextRange wxRichTextParagraphLayoutBox::AddParagraphs(const wxString& text, wxTextAttr* paraStyle)
5d7836c4 978{
fe5aa22c 979 // Don't use the base style, just the default style, and the base style will
4f32b3cf
JS
980 // be combined at display time.
981 // Divide into paragraph and character styles.
3e541562 982
44cc96a8
JS
983 wxTextAttr defaultCharStyle;
984 wxTextAttr defaultParaStyle;
4f32b3cf 985 wxRichTextSplitParaCharStyles(GetDefaultStyle(), defaultParaStyle, defaultCharStyle);
5d7836c4 986
44cc96a8
JS
987 wxTextAttr* pStyle = paraStyle ? paraStyle : (wxTextAttr*) & defaultParaStyle;
988 wxTextAttr* cStyle = & defaultCharStyle;
4f32b3cf 989
5d7836c4
JS
990 wxRichTextParagraph* firstPara = NULL;
991 wxRichTextParagraph* lastPara = NULL;
992
993 wxRichTextRange range(-1, -1);
0ca07313 994
5d7836c4 995 size_t i = 0;
28f92d74 996 size_t len = text.length();
5d7836c4 997 wxString line;
4f32b3cf 998 wxRichTextParagraph* para = new wxRichTextParagraph(wxEmptyString, this, pStyle, cStyle);
0ca07313
JS
999
1000 AppendChild(para);
1001
1002 firstPara = para;
1003 lastPara = para;
1004
5d7836c4
JS
1005 while (i < len)
1006 {
1007 wxChar ch = text[i];
1008 if (ch == wxT('\n') || ch == wxT('\r'))
1009 {
99404ab0
JS
1010 if (i != (len-1))
1011 {
1012 wxRichTextPlainText* plainText = (wxRichTextPlainText*) para->GetChildren().GetFirst()->GetData();
1013 plainText->SetText(line);
0ca07313 1014
99404ab0 1015 para = new wxRichTextParagraph(wxEmptyString, this, pStyle, cStyle);
5d7836c4 1016
99404ab0 1017 AppendChild(para);
0ca07313 1018
99404ab0
JS
1019 lastPara = para;
1020 line = wxEmptyString;
1021 }
5d7836c4
JS
1022 }
1023 else
1024 line += ch;
1025
1026 i ++;
1027 }
0ca07313 1028
7fe8059f 1029 if (!line.empty())
5d7836c4 1030 {
0ca07313
JS
1031 wxRichTextPlainText* plainText = (wxRichTextPlainText*) para->GetChildren().GetFirst()->GetData();
1032 plainText->SetText(line);
5d7836c4
JS
1033 }
1034
5d7836c4 1035 UpdateRanges();
0ca07313 1036
5d7836c4
JS
1037 SetDirty(false);
1038
0ca07313 1039 return wxRichTextRange(firstPara->GetRange().GetStart(), lastPara->GetRange().GetEnd());
5d7836c4
JS
1040}
1041
1042/// Convenience function to add an image
44cc96a8 1043wxRichTextRange wxRichTextParagraphLayoutBox::AddImage(const wxImage& image, wxTextAttr* paraStyle)
5d7836c4 1044{
fe5aa22c 1045 // Don't use the base style, just the default style, and the base style will
4f32b3cf
JS
1046 // be combined at display time.
1047 // Divide into paragraph and character styles.
3e541562 1048
44cc96a8
JS
1049 wxTextAttr defaultCharStyle;
1050 wxTextAttr defaultParaStyle;
4f32b3cf 1051 wxRichTextSplitParaCharStyles(GetDefaultStyle(), defaultParaStyle, defaultCharStyle);
5d7836c4 1052
44cc96a8
JS
1053 wxTextAttr* pStyle = paraStyle ? paraStyle : (wxTextAttr*) & defaultParaStyle;
1054 wxTextAttr* cStyle = & defaultCharStyle;
5d7836c4 1055
4f32b3cf
JS
1056 wxRichTextParagraph* para = new wxRichTextParagraph(this, pStyle);
1057 AppendChild(para);
1058 para->AppendChild(new wxRichTextImage(image, this, cStyle));
fe5aa22c 1059
5d7836c4
JS
1060 UpdateRanges();
1061 SetDirty(true);
1062
1063 return para->GetRange();
1064}
1065
1066
1067/// Insert fragment into this box at the given position. If partialParagraph is true,
1068/// it is assumed that the last (or only) paragraph is just a piece of data with no paragraph
1069/// marker.
5d7836c4 1070
0ca07313 1071bool wxRichTextParagraphLayoutBox::InsertFragment(long position, wxRichTextParagraphLayoutBox& fragment)
5d7836c4
JS
1072{
1073 SetDirty(true);
1074
1075 // First, find the first paragraph whose starting position is within the range.
1076 wxRichTextParagraph* para = GetParagraphAtPosition(position);
1077 if (para)
1078 {
99404ab0
JS
1079 wxTextAttrEx originalAttr = para->GetAttributes();
1080
5d7836c4
JS
1081 wxRichTextObjectList::compatibility_iterator node = m_children.Find(para);
1082
1083 // Now split at this position, returning the object to insert the new
1084 // ones in front of.
1085 wxRichTextObject* nextObject = para->SplitAt(position);
1086
1087 // Special case: partial paragraph, just one paragraph. Might be a small amount of
1088 // text, for example, so let's optimize.
1089
1090 if (fragment.GetPartialParagraph() && fragment.GetChildren().GetCount() == 1)
1091 {
1092 // Add the first para to this para...
1093 wxRichTextObjectList::compatibility_iterator firstParaNode = fragment.GetChildren().GetFirst();
1094 if (!firstParaNode)
1095 return false;
1096
1097 // Iterate through the fragment paragraph inserting the content into this paragraph.
1098 wxRichTextParagraph* firstPara = wxDynamicCast(firstParaNode->GetData(), wxRichTextParagraph);
1099 wxASSERT (firstPara != NULL);
1100
1101 wxRichTextObjectList::compatibility_iterator objectNode = firstPara->GetChildren().GetFirst();
1102 while (objectNode)
1103 {
1104 wxRichTextObject* newObj = objectNode->GetData()->Clone();
7fe8059f 1105
5d7836c4
JS
1106 if (!nextObject)
1107 {
1108 // Append
1109 para->AppendChild(newObj);
1110 }
1111 else
1112 {
1113 // Insert before nextObject
1114 para->InsertChild(newObj, nextObject);
1115 }
7fe8059f 1116
5d7836c4
JS
1117 objectNode = objectNode->GetNext();
1118 }
1119
1120 return true;
1121 }
1122 else
1123 {
1124 // Procedure for inserting a fragment consisting of a number of
1125 // paragraphs:
1126 //
1127 // 1. Remove and save the content that's after the insertion point, for adding
1128 // back once we've added the fragment.
1129 // 2. Add the content from the first fragment paragraph to the current
1130 // paragraph.
1131 // 3. Add remaining fragment paragraphs after the current paragraph.
1132 // 4. Add back the saved content from the first paragraph. If partialParagraph
1133 // is true, add it to the last paragraph added and not a new one.
1134
1135 // 1. Remove and save objects after split point.
1136 wxList savedObjects;
1137 if (nextObject)
1138 para->MoveToList(nextObject, savedObjects);
1139
1140 // 2. Add the content from the 1st fragment paragraph.
1141 wxRichTextObjectList::compatibility_iterator firstParaNode = fragment.GetChildren().GetFirst();
1142 if (!firstParaNode)
1143 return false;
1144
1145 wxRichTextParagraph* firstPara = wxDynamicCast(firstParaNode->GetData(), wxRichTextParagraph);
1146 wxASSERT(firstPara != NULL);
1147
99404ab0
JS
1148 para->SetAttributes(firstPara->GetAttributes());
1149
1150 // Save empty paragraph attributes for appending later
1151 // These are character attributes deliberately set for a new paragraph. Without this,
1152 // we couldn't pass default attributes when appending a new paragraph.
1153 wxTextAttrEx emptyParagraphAttributes;
1154
5d7836c4 1155 wxRichTextObjectList::compatibility_iterator objectNode = firstPara->GetChildren().GetFirst();
99404ab0
JS
1156
1157 if (objectNode && firstPara->GetChildren().GetCount() == 1 && objectNode->GetData()->IsEmpty())
1158 emptyParagraphAttributes = objectNode->GetData()->GetAttributes();
1159
5d7836c4
JS
1160 while (objectNode)
1161 {
99404ab0
JS
1162 if (!objectNode->GetData()->IsEmpty())
1163 {
1164 wxRichTextObject* newObj = objectNode->GetData()->Clone();
7fe8059f 1165
99404ab0
JS
1166 // Append
1167 para->AppendChild(newObj);
1168 }
7fe8059f 1169
5d7836c4
JS
1170 objectNode = objectNode->GetNext();
1171 }
1172
1173 // 3. Add remaining fragment paragraphs after the current paragraph.
1174 wxRichTextObjectList::compatibility_iterator nextParagraphNode = node->GetNext();
1175 wxRichTextObject* nextParagraph = NULL;
1176 if (nextParagraphNode)
1177 nextParagraph = nextParagraphNode->GetData();
1178
1179 wxRichTextObjectList::compatibility_iterator i = fragment.GetChildren().GetFirst()->GetNext();
1180 wxRichTextParagraph* finalPara = para;
1181
99404ab0
JS
1182 bool needExtraPara = (!i || !fragment.GetPartialParagraph());
1183
5d7836c4 1184 // If there was only one paragraph, we need to insert a new one.
99404ab0 1185 while (i)
5d7836c4 1186 {
99404ab0
JS
1187 wxRichTextParagraph* para = wxDynamicCast(i->GetData(), wxRichTextParagraph);
1188 wxASSERT( para != NULL );
5d7836c4 1189
99404ab0 1190 finalPara = (wxRichTextParagraph*) para->Clone();
5d7836c4
JS
1191
1192 if (nextParagraph)
1193 InsertChild(finalPara, nextParagraph);
1194 else
7fe8059f 1195 AppendChild(finalPara);
99404ab0
JS
1196
1197 i = i->GetNext();
5d7836c4 1198 }
5d7836c4 1199
99404ab0
JS
1200 // If there was only one paragraph, or we have full paragraphs in our fragment,
1201 // we need to insert a new one.
1202 if (needExtraPara)
1203 {
1204 finalPara = new wxRichTextParagraph;
5d7836c4
JS
1205
1206 if (nextParagraph)
1207 InsertChild(finalPara, nextParagraph);
1208 else
1209 AppendChild(finalPara);
5d7836c4
JS
1210 }
1211
1212 // 4. Add back the remaining content.
1213 if (finalPara)
1214 {
1215 finalPara->MoveFromList(savedObjects);
1216
1217 // Ensure there's at least one object
1218 if (finalPara->GetChildCount() == 0)
1219 {
7fe8059f 1220 wxRichTextPlainText* text = new wxRichTextPlainText(wxEmptyString);
99404ab0 1221 text->SetAttributes(emptyParagraphAttributes);
5d7836c4
JS
1222
1223 finalPara->AppendChild(text);
1224 }
1225 }
1226
99404ab0
JS
1227 if (finalPara && finalPara != para)
1228 finalPara->SetAttributes(originalAttr);
1229
5d7836c4
JS
1230 return true;
1231 }
1232 }
1233 else
1234 {
1235 // Append
1236 wxRichTextObjectList::compatibility_iterator i = fragment.GetChildren().GetFirst();
1237 while (i)
1238 {
1239 wxRichTextParagraph* para = wxDynamicCast(i->GetData(), wxRichTextParagraph);
1240 wxASSERT( para != NULL );
7fe8059f 1241
5d7836c4 1242 AppendChild(para->Clone());
7fe8059f 1243
5d7836c4
JS
1244 i = i->GetNext();
1245 }
1246
1247 return true;
1248 }
5d7836c4
JS
1249}
1250
1251/// Make a copy of the fragment corresponding to the given range, putting it in 'fragment'.
1252/// If there was an incomplete paragraph at the end, partialParagraph is set to true.
0ca07313 1253bool wxRichTextParagraphLayoutBox::CopyFragment(const wxRichTextRange& range, wxRichTextParagraphLayoutBox& fragment)
5d7836c4
JS
1254{
1255 wxRichTextObjectList::compatibility_iterator i = GetChildren().GetFirst();
1256 while (i)
1257 {
1258 wxRichTextParagraph* para = wxDynamicCast(i->GetData(), wxRichTextParagraph);
1259 wxASSERT( para != NULL );
1260
1261 if (!para->GetRange().IsOutside(range))
1262 {
1263 fragment.AppendChild(para->Clone());
7fe8059f 1264 }
5d7836c4
JS
1265 i = i->GetNext();
1266 }
1267
1268 // Now top and tail the first and last paragraphs in our new fragment (which might be the same).
1269 if (!fragment.IsEmpty())
1270 {
1271 wxRichTextRange topTailRange(range);
1272
1273 wxRichTextParagraph* firstPara = wxDynamicCast(fragment.GetChildren().GetFirst()->GetData(), wxRichTextParagraph);
1274 wxASSERT( firstPara != NULL );
1275
1276 // Chop off the start of the paragraph
1277 if (topTailRange.GetStart() > firstPara->GetRange().GetStart())
1278 {
1279 wxRichTextRange r(firstPara->GetRange().GetStart(), topTailRange.GetStart()-1);
1280 firstPara->DeleteRange(r);
1281
1282 // Make sure the numbering is correct
1283 long end;
1284 fragment.CalculateRange(firstPara->GetRange().GetStart(), end);
1285
1286 // Now, we've deleted some positions, so adjust the range
1287 // accordingly.
1288 topTailRange.SetEnd(topTailRange.GetEnd() - r.GetLength());
1289 }
1290
1291 wxRichTextParagraph* lastPara = wxDynamicCast(fragment.GetChildren().GetLast()->GetData(), wxRichTextParagraph);
1292 wxASSERT( lastPara != NULL );
1293
1294 if (topTailRange.GetEnd() < (lastPara->GetRange().GetEnd()-1))
1295 {
1296 wxRichTextRange r(topTailRange.GetEnd()+1, lastPara->GetRange().GetEnd()-1); /* -1 since actual text ends 1 position before end of para marker */
1297 lastPara->DeleteRange(r);
1298
1299 // Make sure the numbering is correct
1300 long end;
1301 fragment.CalculateRange(firstPara->GetRange().GetStart(), end);
1302
1303 // We only have part of a paragraph at the end
1304 fragment.SetPartialParagraph(true);
1305 }
1306 else
1307 {
1308 if (topTailRange.GetEnd() == (lastPara->GetRange().GetEnd() - 1))
1309 // We have a partial paragraph (don't save last new paragraph marker)
1310 fragment.SetPartialParagraph(true);
1311 else
1312 // We have a complete paragraph
1313 fragment.SetPartialParagraph(false);
1314 }
1315 }
1316
1317 return true;
1318}
1319
1320/// Given a position, get the number of the visible line (potentially many to a paragraph),
1321/// starting from zero at the start of the buffer.
1322long wxRichTextParagraphLayoutBox::GetVisibleLineNumber(long pos, bool caretPosition, bool startOfLine) const
1323{
1324 if (caretPosition)
1325 pos ++;
1326
1327 int lineCount = 0;
1328
1329 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1330 while (node)
1331 {
1332 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
1333 wxASSERT( child != NULL );
1334
1335 if (child->GetRange().Contains(pos))
1336 {
1337 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
1338 while (node2)
1339 {
1340 wxRichTextLine* line = node2->GetData();
1e967276 1341 wxRichTextRange lineRange = line->GetAbsoluteRange();
7fe8059f 1342
1e967276 1343 if (lineRange.Contains(pos))
5d7836c4
JS
1344 {
1345 // If the caret is displayed at the end of the previous wrapped line,
1346 // we want to return the line it's _displayed_ at (not the actual line
1347 // containing the position).
1e967276 1348 if (lineRange.GetStart() == pos && !startOfLine && child->GetRange().GetStart() != pos)
5d7836c4
JS
1349 return lineCount - 1;
1350 else
1351 return lineCount;
1352 }
1353
1354 lineCount ++;
7fe8059f 1355
5d7836c4
JS
1356 node2 = node2->GetNext();
1357 }
1358 // If we didn't find it in the lines, it must be
1359 // the last position of the paragraph. So return the last line.
1360 return lineCount-1;
1361 }
1362 else
1363 lineCount += child->GetLines().GetCount();
1364
1365 node = node->GetNext();
1366 }
1367
1368 // Not found
1369 return -1;
1370}
1371
1372/// Given a line number, get the corresponding wxRichTextLine object.
1373wxRichTextLine* wxRichTextParagraphLayoutBox::GetLineForVisibleLineNumber(long lineNumber) const
1374{
1375 int lineCount = 0;
1376
1377 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1378 while (node)
1379 {
1380 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
1381 wxASSERT(child != NULL);
1382
1383 if (lineNumber < (int) (child->GetLines().GetCount() + lineCount))
1384 {
1385 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
1386 while (node2)
1387 {
1388 wxRichTextLine* line = node2->GetData();
7fe8059f 1389
5d7836c4
JS
1390 if (lineCount == lineNumber)
1391 return line;
1392
1393 lineCount ++;
7fe8059f 1394
5d7836c4 1395 node2 = node2->GetNext();
7fe8059f 1396 }
5d7836c4
JS
1397 }
1398 else
1399 lineCount += child->GetLines().GetCount();
1400
1401 node = node->GetNext();
1402 }
1403
1404 // Didn't find it
1405 return NULL;
1406}
1407
1408/// Delete range from layout.
1409bool wxRichTextParagraphLayoutBox::DeleteRange(const wxRichTextRange& range)
1410{
1411 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
7fe8059f 1412
99404ab0 1413 wxRichTextParagraph* firstPara = NULL;
5d7836c4
JS
1414 while (node)
1415 {
1416 wxRichTextParagraph* obj = wxDynamicCast(node->GetData(), wxRichTextParagraph);
1417 wxASSERT (obj != NULL);
1418
1419 wxRichTextObjectList::compatibility_iterator next = node->GetNext();
7fe8059f 1420
5d7836c4
JS
1421 // Delete the range in each paragraph
1422
1423 if (!obj->GetRange().IsOutside(range))
1424 {
1425 // Deletes the content of this object within the given range
1426 obj->DeleteRange(range);
1427
99404ab0
JS
1428 wxRichTextRange thisRange = obj->GetRange();
1429
5d7836c4
JS
1430 // If the whole paragraph is within the range to delete,
1431 // delete the whole thing.
99404ab0 1432 if (range.GetStart() <= thisRange.GetStart() && range.GetEnd() >= thisRange.GetEnd())
5d7836c4
JS
1433 {
1434 // Delete the whole object
1435 RemoveChild(obj, true);
99404ab0 1436 obj = NULL;
5d7836c4 1437 }
99404ab0
JS
1438 else if (!firstPara)
1439 firstPara = obj;
1440
5d7836c4
JS
1441 // If the range includes the paragraph end, we need to join this
1442 // and the next paragraph.
99404ab0 1443 if (range.GetEnd() <= thisRange.GetEnd())
5d7836c4
JS
1444 {
1445 // We need to move the objects from the next paragraph
1446 // to this paragraph
1447
99404ab0
JS
1448 wxRichTextParagraph* nextParagraph = NULL;
1449 if ((range.GetEnd() < thisRange.GetEnd()) && obj)
1450 nextParagraph = obj;
1451 else
5d7836c4 1452 {
99404ab0
JS
1453 // We're ending at the end of the paragraph, so merge the _next_ paragraph.
1454 if (next)
1455 nextParagraph = wxDynamicCast(next->GetData(), wxRichTextParagraph);
1456 }
5d7836c4 1457
99404ab0 1458 bool applyFinalParagraphStyle = firstPara && nextParagraph && nextParagraph != firstPara;
5d7836c4 1459
99404ab0
JS
1460 wxTextAttrEx nextParaAttr;
1461 if (applyFinalParagraphStyle)
1462 nextParaAttr = nextParagraph->GetAttributes();
5d7836c4 1463
99404ab0
JS
1464 if (firstPara && nextParagraph && firstPara != nextParagraph)
1465 {
1466 // Move the objects to the previous para
1467 wxRichTextObjectList::compatibility_iterator node1 = nextParagraph->GetChildren().GetFirst();
5d7836c4 1468
99404ab0
JS
1469 while (node1)
1470 {
1471 wxRichTextObject* obj1 = node1->GetData();
5d7836c4 1472
99404ab0
JS
1473 // If the object is empty, optimise it out
1474 if (obj1->IsEmpty())
1475 {
1476 delete obj1;
1477 }
1478 else
1479 {
1480 firstPara->AppendChild(obj1);
5d7836c4
JS
1481 }
1482
99404ab0
JS
1483 wxRichTextObjectList::compatibility_iterator next1 = node1->GetNext();
1484 nextParagraph->GetChildren().Erase(node1);
5d7836c4 1485
99404ab0 1486 node1 = next1;
5d7836c4 1487 }
99404ab0
JS
1488
1489 // Delete the paragraph
1490 RemoveChild(nextParagraph, true);
7fe8059f 1491 }
5d7836c4 1492
99404ab0
JS
1493 if (applyFinalParagraphStyle)
1494 firstPara->SetAttributes(nextParaAttr);
1495
1496 return true;
5d7836c4
JS
1497 }
1498 }
7fe8059f 1499
5d7836c4
JS
1500 node = next;
1501 }
7fe8059f 1502
5d7836c4
JS
1503 return true;
1504}
1505
1506/// Get any text in this object for the given range
1507wxString wxRichTextParagraphLayoutBox::GetTextForRange(const wxRichTextRange& range) const
1508{
1509 int lineCount = 0;
1510 wxString text;
1511 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1512 while (node)
1513 {
1514 wxRichTextObject* child = node->GetData();
1515 if (!child->GetRange().IsOutside(range))
1516 {
5d7836c4
JS
1517 wxRichTextRange childRange = range;
1518 childRange.LimitTo(child->GetRange());
7fe8059f 1519
5d7836c4 1520 wxString childText = child->GetTextForRange(childRange);
7fe8059f 1521
5d7836c4
JS
1522 text += childText;
1523
1a75935d 1524 if ((childRange.GetEnd() == child->GetRange().GetEnd()) && node->GetNext())
fe5aa22c
JS
1525 text += wxT("\n");
1526
5d7836c4
JS
1527 lineCount ++;
1528 }
1529 node = node->GetNext();
1530 }
1531
1532 return text;
1533}
1534
1535/// Get all the text
1536wxString wxRichTextParagraphLayoutBox::GetText() const
1537{
1538 return GetTextForRange(GetRange());
1539}
1540
1541/// Get the paragraph by number
1542wxRichTextParagraph* wxRichTextParagraphLayoutBox::GetParagraphAtLine(long paragraphNumber) const
1543{
27e20452 1544 if ((size_t) paragraphNumber >= GetChildCount())
5d7836c4
JS
1545 return NULL;
1546
1547 return (wxRichTextParagraph*) GetChild((size_t) paragraphNumber);
1548}
1549
1550/// Get the length of the paragraph
1551int wxRichTextParagraphLayoutBox::GetParagraphLength(long paragraphNumber) const
1552{
1553 wxRichTextParagraph* para = GetParagraphAtLine(paragraphNumber);
1554 if (para)
1555 return para->GetRange().GetLength() - 1; // don't include newline
1556 else
1557 return 0;
1558}
1559
1560/// Get the text of the paragraph
1561wxString wxRichTextParagraphLayoutBox::GetParagraphText(long paragraphNumber) const
1562{
1563 wxRichTextParagraph* para = GetParagraphAtLine(paragraphNumber);
1564 if (para)
1565 return para->GetTextForRange(para->GetRange());
1566 else
1567 return wxEmptyString;
1568}
1569
1570/// Convert zero-based line column and paragraph number to a position.
1571long wxRichTextParagraphLayoutBox::XYToPosition(long x, long y) const
1572{
1573 wxRichTextParagraph* para = GetParagraphAtLine(y);
1574 if (para)
1575 {
1576 return para->GetRange().GetStart() + x;
1577 }
1578 else
1579 return -1;
1580}
1581
1582/// Convert zero-based position to line column and paragraph number
1583bool wxRichTextParagraphLayoutBox::PositionToXY(long pos, long* x, long* y) const
1584{
1585 wxRichTextParagraph* para = GetParagraphAtPosition(pos);
1586 if (para)
1587 {
1588 int count = 0;
1589 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1590 while (node)
1591 {
1592 wxRichTextObject* child = node->GetData();
1593 if (child == para)
1594 break;
1595 count ++;
1596 node = node->GetNext();
1597 }
1598
1599 *y = count;
1600 *x = pos - para->GetRange().GetStart();
1601
1602 return true;
1603 }
1604 else
1605 return false;
1606}
1607
1608/// Get the leaf object in a paragraph at this position.
1609/// Given a line number, get the corresponding wxRichTextLine object.
1610wxRichTextObject* wxRichTextParagraphLayoutBox::GetLeafObjectAtPosition(long position) const
1611{
1612 wxRichTextParagraph* para = GetParagraphAtPosition(position);
1613 if (para)
1614 {
1615 wxRichTextObjectList::compatibility_iterator node = para->GetChildren().GetFirst();
7fe8059f 1616
5d7836c4
JS
1617 while (node)
1618 {
1619 wxRichTextObject* child = node->GetData();
1620 if (child->GetRange().Contains(position))
1621 return child;
7fe8059f 1622
5d7836c4
JS
1623 node = node->GetNext();
1624 }
1625 if (position == para->GetRange().GetEnd() && para->GetChildCount() > 0)
1626 return para->GetChildren().GetLast()->GetData();
1627 }
1628 return NULL;
1629}
1630
1631/// Set character or paragraph text attributes: apply character styles only to immediate text nodes
44cc96a8 1632bool wxRichTextParagraphLayoutBox::SetStyle(const wxRichTextRange& range, const wxTextAttr& style, int flags)
5d7836c4
JS
1633{
1634 bool characterStyle = false;
1635 bool paragraphStyle = false;
1636
1637 if (style.IsCharacterStyle())
1638 characterStyle = true;
1639 if (style.IsParagraphStyle())
1640 paragraphStyle = true;
1641
59509217
JS
1642 bool withUndo = ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0);
1643 bool applyMinimal = ((flags & wxRICHTEXT_SETSTYLE_OPTIMIZE) != 0);
1644 bool parasOnly = ((flags & wxRICHTEXT_SETSTYLE_PARAGRAPHS_ONLY) != 0);
1645 bool charactersOnly = ((flags & wxRICHTEXT_SETSTYLE_CHARACTERS_ONLY) != 0);
523d2f14 1646 bool resetExistingStyle = ((flags & wxRICHTEXT_SETSTYLE_RESET) != 0);
aeb6ebe2 1647 bool removeStyle = ((flags & wxRICHTEXT_SETSTYLE_REMOVE) != 0);
523d2f14
JS
1648
1649 // Apply paragraph style first, if any
44cc96a8 1650 wxTextAttr wholeStyle(style);
523d2f14 1651
aeb6ebe2 1652 if (!removeStyle && wholeStyle.HasParagraphStyleName() && GetStyleSheet())
523d2f14
JS
1653 {
1654 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(wholeStyle.GetParagraphStyleName());
1655 if (def)
336d8ae9 1656 wxRichTextApplyStyle(wholeStyle, def->GetStyleMergedWithBase(GetStyleSheet()));
523d2f14 1657 }
59509217
JS
1658
1659 // Limit the attributes to be set to the content to only character attributes.
44cc96a8 1660 wxTextAttr characterAttributes(wholeStyle);
59509217
JS
1661 characterAttributes.SetFlags(characterAttributes.GetFlags() & (wxTEXT_ATTR_CHARACTER));
1662
aeb6ebe2 1663 if (!removeStyle && characterAttributes.HasCharacterStyleName() && GetStyleSheet())
523d2f14
JS
1664 {
1665 wxRichTextCharacterStyleDefinition* def = GetStyleSheet()->FindCharacterStyle(characterAttributes.GetCharacterStyleName());
1666 if (def)
336d8ae9 1667 wxRichTextApplyStyle(characterAttributes, def->GetStyleMergedWithBase(GetStyleSheet()));
523d2f14
JS
1668 }
1669
5d7836c4
JS
1670 // If we are associated with a control, make undoable; otherwise, apply immediately
1671 // to the data.
1672
1673 bool haveControl = (GetRichTextCtrl() != NULL);
1674
1675 wxRichTextAction* action = NULL;
7fe8059f 1676
5d7836c4
JS
1677 if (haveControl && withUndo)
1678 {
1679 action = new wxRichTextAction(NULL, _("Change Style"), wxRICHTEXT_CHANGE_STYLE, & GetRichTextCtrl()->GetBuffer(), GetRichTextCtrl());
1680 action->SetRange(range);
1681 action->SetPosition(GetRichTextCtrl()->GetCaretPosition());
1682 }
1683
1684 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1685 while (node)
1686 {
1687 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
1688 wxASSERT (para != NULL);
1689
1690 if (para && para->GetChildCount() > 0)
1691 {
1692 // Stop searching if we're beyond the range of interest
1693 if (para->GetRange().GetStart() > range.GetEnd())
1694 break;
1695
1696 if (!para->GetRange().IsOutside(range))
1697 {
1698 // We'll be using a copy of the paragraph to make style changes,
1699 // not updating the buffer directly.
4e09ebe8 1700 wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL);
7fe8059f 1701
5d7836c4
JS
1702 if (haveControl && withUndo)
1703 {
1704 newPara = new wxRichTextParagraph(*para);
1705 action->GetNewParagraphs().AppendChild(newPara);
1706
1707 // Also store the old ones for Undo
1708 action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para));
1709 }
1710 else
1711 newPara = para;
41a85215 1712
a7ed48a5
JS
1713 // If we're specifying paragraphs only, then we really mean character formatting
1714 // to be included in the paragraph style
1715 if ((paragraphStyle || parasOnly) && !charactersOnly)
59509217 1716 {
aeb6ebe2
JS
1717 if (removeStyle)
1718 {
1719 // Removes the given style from the paragraph
1720 wxRichTextRemoveStyle(newPara->GetAttributes(), style);
1721 }
1722 else if (resetExistingStyle)
523d2f14
JS
1723 newPara->GetAttributes() = wholeStyle;
1724 else
59509217 1725 {
523d2f14
JS
1726 if (applyMinimal)
1727 {
1728 // Only apply attributes that will make a difference to the combined
1729 // style as seen on the display
44cc96a8 1730 wxTextAttr combinedAttr(para->GetCombinedAttributes());
523d2f14
JS
1731 wxRichTextApplyStyle(newPara->GetAttributes(), wholeStyle, & combinedAttr);
1732 }
1733 else
1734 wxRichTextApplyStyle(newPara->GetAttributes(), wholeStyle);
59509217 1735 }
59509217 1736 }
5d7836c4 1737
5912d19e 1738 // When applying paragraph styles dynamically, don't change the text objects' attributes
fe5aa22c
JS
1739 // since they will computed as needed. Only apply the character styling if it's _only_
1740 // character styling. This policy is subject to change and might be put under user control.
1741
59509217
JS
1742 // Hm. we might well be applying a mix of paragraph and character styles, in which
1743 // case we _do_ want to apply character styles regardless of what para styles are set.
1744 // But if we're applying a paragraph style, which has some character attributes, but
1745 // we only want the paragraphs to hold this character style, then we _don't_ want to
1746 // apply the character style. So we need to be able to choose.
1747
1748 // if (!paragraphStyle && characterStyle && range.GetStart() != newPara->GetRange().GetEnd())
1749 if (!parasOnly && characterStyle && range.GetStart() != newPara->GetRange().GetEnd())
5d7836c4
JS
1750 {
1751 wxRichTextRange childRange(range);
1752 childRange.LimitTo(newPara->GetRange());
7fe8059f 1753
5d7836c4
JS
1754 // Find the starting position and if necessary split it so
1755 // we can start applying a different style.
1756 // TODO: check that the style actually changes or is different
1757 // from style outside of range
4e09ebe8
JS
1758 wxRichTextObject* firstObject wxDUMMY_INITIALIZE(NULL);
1759 wxRichTextObject* lastObject wxDUMMY_INITIALIZE(NULL);
7fe8059f 1760
5d7836c4
JS
1761 if (childRange.GetStart() == newPara->GetRange().GetStart())
1762 firstObject = newPara->GetChildren().GetFirst()->GetData();
1763 else
1764 firstObject = newPara->SplitAt(range.GetStart());
7fe8059f 1765
5d7836c4
JS
1766 // Increment by 1 because we're apply the style one _after_ the split point
1767 long splitPoint = childRange.GetEnd();
1768 if (splitPoint != newPara->GetRange().GetEnd())
1769 splitPoint ++;
7fe8059f 1770
5d7836c4
JS
1771 // Find last object
1772 if (splitPoint == newPara->GetRange().GetEnd() || splitPoint == (newPara->GetRange().GetEnd() - 1))
1773 lastObject = newPara->GetChildren().GetLast()->GetData();
1774 else
1775 // lastObject is set as a side-effect of splitting. It's
1776 // returned as the object before the new object.
1777 (void) newPara->SplitAt(splitPoint, & lastObject);
7fe8059f 1778
5d7836c4
JS
1779 wxASSERT(firstObject != NULL);
1780 wxASSERT(lastObject != NULL);
7fe8059f 1781
5d7836c4
JS
1782 if (!firstObject || !lastObject)
1783 continue;
7fe8059f 1784
5d7836c4
JS
1785 wxRichTextObjectList::compatibility_iterator firstNode = newPara->GetChildren().Find(firstObject);
1786 wxRichTextObjectList::compatibility_iterator lastNode = newPara->GetChildren().Find(lastObject);
7fe8059f 1787
4c9847e1
MW
1788 wxASSERT(firstNode);
1789 wxASSERT(lastNode);
7fe8059f 1790
5d7836c4 1791 wxRichTextObjectList::compatibility_iterator node2 = firstNode;
7fe8059f 1792
5d7836c4
JS
1793 while (node2)
1794 {
1795 wxRichTextObject* child = node2->GetData();
7fe8059f 1796
aeb6ebe2
JS
1797 if (removeStyle)
1798 {
1799 // Removes the given style from the paragraph
1800 wxRichTextRemoveStyle(child->GetAttributes(), style);
1801 }
1802 else if (resetExistingStyle)
523d2f14
JS
1803 child->GetAttributes() = characterAttributes;
1804 else
59509217 1805 {
523d2f14
JS
1806 if (applyMinimal)
1807 {
1808 // Only apply attributes that will make a difference to the combined
1809 // style as seen on the display
44cc96a8 1810 wxTextAttr combinedAttr(newPara->GetCombinedAttributes(child->GetAttributes()));
523d2f14
JS
1811 wxRichTextApplyStyle(child->GetAttributes(), characterAttributes, & combinedAttr);
1812 }
1813 else
1814 wxRichTextApplyStyle(child->GetAttributes(), characterAttributes);
59509217 1815 }
59509217 1816
5d7836c4
JS
1817 if (node2 == lastNode)
1818 break;
7fe8059f 1819
5d7836c4
JS
1820 node2 = node2->GetNext();
1821 }
1822 }
1823 }
1824 }
1825
1826 node = node->GetNext();
1827 }
1828
1829 // Do action, or delay it until end of batch.
1830 if (haveControl && withUndo)
1831 GetRichTextCtrl()->GetBuffer().SubmitAction(action);
1832
1833 return true;
1834}
1835
5d7836c4 1836/// Get the text attributes for this position.
44cc96a8 1837bool wxRichTextParagraphLayoutBox::GetStyle(long position, wxTextAttr& style)
5d7836c4 1838{
fe5aa22c
JS
1839 return DoGetStyle(position, style, true);
1840}
e191ee87 1841
44cc96a8 1842bool wxRichTextParagraphLayoutBox::GetUncombinedStyle(long position, wxTextAttr& style)
fe5aa22c
JS
1843{
1844 return DoGetStyle(position, style, false);
1845}
1846
fe5aa22c
JS
1847/// Implementation helper for GetStyle. If combineStyles is true, combine base, paragraph and
1848/// context attributes.
44cc96a8 1849bool wxRichTextParagraphLayoutBox::DoGetStyle(long position, wxTextAttr& style, bool combineStyles)
5d7836c4 1850{
4e09ebe8 1851 wxRichTextObject* obj wxDUMMY_INITIALIZE(NULL);
e191ee87 1852
5d7836c4 1853 if (style.IsParagraphStyle())
fe5aa22c 1854 {
5d7836c4 1855 obj = GetParagraphAtPosition(position);
fe5aa22c
JS
1856 if (obj)
1857 {
fe5aa22c
JS
1858 if (combineStyles)
1859 {
1860 // Start with the base style
1861 style = GetAttributes();
e191ee87 1862
fe5aa22c
JS
1863 // Apply the paragraph style
1864 wxRichTextApplyStyle(style, obj->GetAttributes());
1865 }
1866 else
1867 style = obj->GetAttributes();
5912d19e 1868
fe5aa22c
JS
1869 return true;
1870 }
5d7836c4
JS
1871 }
1872 else
fe5aa22c
JS
1873 {
1874 obj = GetLeafObjectAtPosition(position);
1875 if (obj)
1876 {
fe5aa22c
JS
1877 if (combineStyles)
1878 {
1879 wxRichTextParagraph* para = wxDynamicCast(obj->GetParent(), wxRichTextParagraph);
1880 style = para ? para->GetCombinedAttributes(obj->GetAttributes()) : obj->GetAttributes();
1881 }
1882 else
1883 style = obj->GetAttributes();
5912d19e 1884
fe5aa22c
JS
1885 return true;
1886 }
fe5aa22c
JS
1887 }
1888 return false;
5d7836c4
JS
1889}
1890
59509217
JS
1891static bool wxHasStyle(long flags, long style)
1892{
1893 return (flags & style) != 0;
1894}
1895
1896/// Combines 'style' with 'currentStyle' for the purpose of summarising the attributes of a range of
1897/// content.
44cc96a8 1898bool wxRichTextParagraphLayoutBox::CollectStyle(wxTextAttr& currentStyle, const wxTextAttr& style, long& multipleStyleAttributes, int& multipleTextEffectAttributes)
59509217
JS
1899{
1900 if (style.HasFont())
1901 {
336d8ae9 1902 if (style.HasFontSize() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_FONT_SIZE))
59509217 1903 {
44cc96a8 1904 if (currentStyle.HasFontSize())
59509217 1905 {
44cc96a8 1906 if (currentStyle.GetFontSize() != style.GetFontSize())
59509217
JS
1907 {
1908 // Clash of style - mark as such
1909 multipleStyleAttributes |= wxTEXT_ATTR_FONT_SIZE;
1910 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_FONT_SIZE);
1911 }
1912 }
1913 else
1914 {
44cc96a8 1915 currentStyle.SetFontSize(style.GetFontSize());
59509217
JS
1916 }
1917 }
1918
336d8ae9 1919 if (style.HasFontItalic() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_FONT_ITALIC))
59509217 1920 {
44cc96a8 1921 if (currentStyle.HasFontItalic())
59509217 1922 {
44cc96a8 1923 if (currentStyle.GetFontStyle() != style.GetFontStyle())
59509217
JS
1924 {
1925 // Clash of style - mark as such
1926 multipleStyleAttributes |= wxTEXT_ATTR_FONT_ITALIC;
1927 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_FONT_ITALIC);
1928 }
1929 }
1930 else
1931 {
44cc96a8 1932 currentStyle.SetFontStyle(style.GetFontStyle());
59509217
JS
1933 }
1934 }
1935
336d8ae9 1936 if (style.HasFontWeight() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_FONT_WEIGHT))
59509217 1937 {
44cc96a8 1938 if (currentStyle.HasFontWeight())
59509217 1939 {
44cc96a8 1940 if (currentStyle.GetFontWeight() != style.GetFontWeight())
59509217
JS
1941 {
1942 // Clash of style - mark as such
1943 multipleStyleAttributes |= wxTEXT_ATTR_FONT_WEIGHT;
1944 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_FONT_WEIGHT);
1945 }
1946 }
1947 else
1948 {
44cc96a8 1949 currentStyle.SetFontWeight(style.GetFontWeight());
59509217
JS
1950 }
1951 }
1952
336d8ae9 1953 if (style.HasFontFaceName() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_FONT_FACE))
59509217 1954 {
44cc96a8 1955 if (currentStyle.HasFontFaceName())
59509217 1956 {
44cc96a8
JS
1957 wxString faceName1(currentStyle.GetFontFaceName());
1958 wxString faceName2(style.GetFontFaceName());
59509217
JS
1959
1960 if (faceName1 != faceName2)
1961 {
1962 // Clash of style - mark as such
1963 multipleStyleAttributes |= wxTEXT_ATTR_FONT_FACE;
1964 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_FONT_FACE);
1965 }
1966 }
1967 else
1968 {
44cc96a8 1969 currentStyle.SetFontFaceName(style.GetFontFaceName());
59509217
JS
1970 }
1971 }
1972
336d8ae9 1973 if (style.HasFontUnderlined() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_FONT_UNDERLINE))
59509217 1974 {
44cc96a8 1975 if (currentStyle.HasFontUnderlined())
59509217 1976 {
44cc96a8 1977 if (currentStyle.GetFontUnderlined() != style.GetFontUnderlined())
59509217
JS
1978 {
1979 // Clash of style - mark as such
1980 multipleStyleAttributes |= wxTEXT_ATTR_FONT_UNDERLINE;
1981 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_FONT_UNDERLINE);
1982 }
1983 }
1984 else
1985 {
44cc96a8 1986 currentStyle.SetFontUnderlined(style.GetFontUnderlined());
59509217
JS
1987 }
1988 }
1989 }
1990
1991 if (style.HasTextColour() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_TEXT_COLOUR))
1992 {
1993 if (currentStyle.HasTextColour())
1994 {
1995 if (currentStyle.GetTextColour() != style.GetTextColour())
1996 {
1997 // Clash of style - mark as such
1998 multipleStyleAttributes |= wxTEXT_ATTR_TEXT_COLOUR;
1999 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_TEXT_COLOUR);
2000 }
2001 }
2002 else
2003 currentStyle.SetTextColour(style.GetTextColour());
2004 }
2005
2006 if (style.HasBackgroundColour() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_BACKGROUND_COLOUR))
2007 {
2008 if (currentStyle.HasBackgroundColour())
2009 {
2010 if (currentStyle.GetBackgroundColour() != style.GetBackgroundColour())
2011 {
2012 // Clash of style - mark as such
2013 multipleStyleAttributes |= wxTEXT_ATTR_BACKGROUND_COLOUR;
2014 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_BACKGROUND_COLOUR);
2015 }
2016 }
2017 else
2018 currentStyle.SetBackgroundColour(style.GetBackgroundColour());
2019 }
2020
2021 if (style.HasAlignment() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_ALIGNMENT))
2022 {
2023 if (currentStyle.HasAlignment())
2024 {
2025 if (currentStyle.GetAlignment() != style.GetAlignment())
2026 {
2027 // Clash of style - mark as such
2028 multipleStyleAttributes |= wxTEXT_ATTR_ALIGNMENT;
2029 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_ALIGNMENT);
2030 }
2031 }
2032 else
2033 currentStyle.SetAlignment(style.GetAlignment());
2034 }
2035
2036 if (style.HasTabs() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_TABS))
2037 {
2038 if (currentStyle.HasTabs())
2039 {
2040 if (!wxRichTextTabsEq(currentStyle.GetTabs(), style.GetTabs()))
2041 {
2042 // Clash of style - mark as such
2043 multipleStyleAttributes |= wxTEXT_ATTR_TABS;
2044 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_TABS);
2045 }
2046 }
2047 else
2048 currentStyle.SetTabs(style.GetTabs());
2049 }
2050
2051 if (style.HasLeftIndent() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_LEFT_INDENT))
2052 {
2053 if (currentStyle.HasLeftIndent())
2054 {
2055 if (currentStyle.GetLeftIndent() != style.GetLeftIndent() || currentStyle.GetLeftSubIndent() != style.GetLeftSubIndent())
2056 {
2057 // Clash of style - mark as such
2058 multipleStyleAttributes |= wxTEXT_ATTR_LEFT_INDENT;
2059 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_LEFT_INDENT);
2060 }
2061 }
2062 else
2063 currentStyle.SetLeftIndent(style.GetLeftIndent(), style.GetLeftSubIndent());
2064 }
2065
2066 if (style.HasRightIndent() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_RIGHT_INDENT))
2067 {
2068 if (currentStyle.HasRightIndent())
2069 {
2070 if (currentStyle.GetRightIndent() != style.GetRightIndent())
2071 {
2072 // Clash of style - mark as such
2073 multipleStyleAttributes |= wxTEXT_ATTR_RIGHT_INDENT;
2074 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_RIGHT_INDENT);
2075 }
2076 }
2077 else
2078 currentStyle.SetRightIndent(style.GetRightIndent());
2079 }
2080
2081 if (style.HasParagraphSpacingAfter() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_PARA_SPACING_AFTER))
2082 {
2083 if (currentStyle.HasParagraphSpacingAfter())
2084 {
42688aea 2085 if (currentStyle.GetParagraphSpacingAfter() != style.GetParagraphSpacingAfter())
59509217
JS
2086 {
2087 // Clash of style - mark as such
2088 multipleStyleAttributes |= wxTEXT_ATTR_PARA_SPACING_AFTER;
2089 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_PARA_SPACING_AFTER);
2090 }
2091 }
2092 else
2093 currentStyle.SetParagraphSpacingAfter(style.GetParagraphSpacingAfter());
2094 }
2095
2096 if (style.HasParagraphSpacingBefore() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_PARA_SPACING_BEFORE))
2097 {
2098 if (currentStyle.HasParagraphSpacingBefore())
2099 {
42688aea 2100 if (currentStyle.GetParagraphSpacingBefore() != style.GetParagraphSpacingBefore())
59509217
JS
2101 {
2102 // Clash of style - mark as such
2103 multipleStyleAttributes |= wxTEXT_ATTR_PARA_SPACING_BEFORE;
2104 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_PARA_SPACING_BEFORE);
2105 }
2106 }
2107 else
2108 currentStyle.SetParagraphSpacingBefore(style.GetParagraphSpacingBefore());
2109 }
2110
2111 if (style.HasLineSpacing() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_LINE_SPACING))
2112 {
2113 if (currentStyle.HasLineSpacing())
2114 {
42688aea 2115 if (currentStyle.GetLineSpacing() != style.GetLineSpacing())
59509217
JS
2116 {
2117 // Clash of style - mark as such
2118 multipleStyleAttributes |= wxTEXT_ATTR_LINE_SPACING;
2119 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_LINE_SPACING);
2120 }
2121 }
2122 else
2123 currentStyle.SetLineSpacing(style.GetLineSpacing());
2124 }
2125
2126 if (style.HasCharacterStyleName() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_CHARACTER_STYLE_NAME))
2127 {
2128 if (currentStyle.HasCharacterStyleName())
2129 {
42688aea 2130 if (currentStyle.GetCharacterStyleName() != style.GetCharacterStyleName())
59509217
JS
2131 {
2132 // Clash of style - mark as such
2133 multipleStyleAttributes |= wxTEXT_ATTR_CHARACTER_STYLE_NAME;
2134 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_CHARACTER_STYLE_NAME);
2135 }
2136 }
2137 else
2138 currentStyle.SetCharacterStyleName(style.GetCharacterStyleName());
2139 }
2140
2141 if (style.HasParagraphStyleName() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_PARAGRAPH_STYLE_NAME))
2142 {
2143 if (currentStyle.HasParagraphStyleName())
2144 {
42688aea 2145 if (currentStyle.GetParagraphStyleName() != style.GetParagraphStyleName())
59509217
JS
2146 {
2147 // Clash of style - mark as such
2148 multipleStyleAttributes |= wxTEXT_ATTR_PARAGRAPH_STYLE_NAME;
2149 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_PARAGRAPH_STYLE_NAME);
2150 }
2151 }
2152 else
2153 currentStyle.SetParagraphStyleName(style.GetParagraphStyleName());
2154 }
2155
38f833b1
JS
2156 if (style.HasListStyleName() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_LIST_STYLE_NAME))
2157 {
2158 if (currentStyle.HasListStyleName())
2159 {
42688aea 2160 if (currentStyle.GetListStyleName() != style.GetListStyleName())
38f833b1
JS
2161 {
2162 // Clash of style - mark as such
2163 multipleStyleAttributes |= wxTEXT_ATTR_LIST_STYLE_NAME;
2164 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_LIST_STYLE_NAME);
2165 }
2166 }
2167 else
2168 currentStyle.SetListStyleName(style.GetListStyleName());
2169 }
2170
59509217
JS
2171 if (style.HasBulletStyle() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_BULLET_STYLE))
2172 {
2173 if (currentStyle.HasBulletStyle())
2174 {
42688aea 2175 if (currentStyle.GetBulletStyle() != style.GetBulletStyle())
59509217
JS
2176 {
2177 // Clash of style - mark as such
2178 multipleStyleAttributes |= wxTEXT_ATTR_BULLET_STYLE;
2179 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_BULLET_STYLE);
2180 }
2181 }
2182 else
2183 currentStyle.SetBulletStyle(style.GetBulletStyle());
2184 }
2185
2186 if (style.HasBulletNumber() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_BULLET_NUMBER))
2187 {
2188 if (currentStyle.HasBulletNumber())
2189 {
42688aea 2190 if (currentStyle.GetBulletNumber() != style.GetBulletNumber())
59509217
JS
2191 {
2192 // Clash of style - mark as such
2193 multipleStyleAttributes |= wxTEXT_ATTR_BULLET_NUMBER;
2194 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_BULLET_NUMBER);
2195 }
2196 }
2197 else
2198 currentStyle.SetBulletNumber(style.GetBulletNumber());
2199 }
2200
d2d0adc7 2201 if (style.HasBulletText() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_BULLET_TEXT))
59509217 2202 {
d2d0adc7 2203 if (currentStyle.HasBulletText())
59509217 2204 {
42688aea 2205 if (currentStyle.GetBulletText() != style.GetBulletText())
59509217
JS
2206 {
2207 // Clash of style - mark as such
d2d0adc7
JS
2208 multipleStyleAttributes |= wxTEXT_ATTR_BULLET_TEXT;
2209 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_BULLET_TEXT);
59509217
JS
2210 }
2211 }
2212 else
2213 {
d2d0adc7 2214 currentStyle.SetBulletText(style.GetBulletText());
59509217
JS
2215 currentStyle.SetBulletFont(style.GetBulletFont());
2216 }
2217 }
2218
f089713f
JS
2219 if (style.HasBulletName() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_BULLET_NAME))
2220 {
2221 if (currentStyle.HasBulletName())
2222 {
42688aea 2223 if (currentStyle.GetBulletName() != style.GetBulletName())
f089713f
JS
2224 {
2225 // Clash of style - mark as such
2226 multipleStyleAttributes |= wxTEXT_ATTR_BULLET_NAME;
2227 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_BULLET_NAME);
2228 }
2229 }
2230 else
2231 {
2232 currentStyle.SetBulletName(style.GetBulletName());
2233 }
2234 }
2235
d2d0adc7
JS
2236 if (style.HasURL() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_URL))
2237 {
2238 if (currentStyle.HasURL())
2239 {
42688aea 2240 if (currentStyle.GetURL() != style.GetURL())
d2d0adc7
JS
2241 {
2242 // Clash of style - mark as such
2243 multipleStyleAttributes |= wxTEXT_ATTR_URL;
2244 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_URL);
2245 }
2246 }
2247 else
2248 {
2249 currentStyle.SetURL(style.GetURL());
2250 }
2251 }
2252
42688aea
JS
2253 if (style.HasTextEffects() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_EFFECTS))
2254 {
2255 if (currentStyle.HasTextEffects())
2256 {
2257 // We need to find the bits in the new style that are different:
2258 // just look at those bits that are specified by the new style.
3e541562 2259
42688aea
JS
2260 int currentRelevantTextEffects = currentStyle.GetTextEffects() & style.GetTextEffectFlags();
2261 int newRelevantTextEffects = style.GetTextEffects() & style.GetTextEffectFlags();
2262
2263 if (currentRelevantTextEffects != newRelevantTextEffects)
2264 {
2265 // Find the text effects that were different, using XOR
2266 int differentEffects = currentRelevantTextEffects ^ newRelevantTextEffects;
3e541562 2267
42688aea
JS
2268 // Clash of style - mark as such
2269 multipleTextEffectAttributes |= differentEffects;
2270 currentStyle.SetTextEffectFlags(currentStyle.GetTextEffectFlags() & ~differentEffects);
2271 }
2272 }
2273 else
2274 {
2275 currentStyle.SetTextEffects(style.GetTextEffects());
2276 currentStyle.SetTextEffectFlags(style.GetTextEffectFlags());
2277 }
2278 }
2279
4d6d8bf4
JS
2280 if (style.HasOutlineLevel() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_OUTLINE_LEVEL))
2281 {
2282 if (currentStyle.HasOutlineLevel())
2283 {
2284 if (currentStyle.GetOutlineLevel() != style.GetOutlineLevel())
2285 {
2286 // Clash of style - mark as such
2287 multipleStyleAttributes |= wxTEXT_ATTR_OUTLINE_LEVEL;
2288 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_OUTLINE_LEVEL);
2289 }
2290 }
2291 else
2292 currentStyle.SetOutlineLevel(style.GetOutlineLevel());
2293 }
2294
59509217
JS
2295 return true;
2296}
2297
2298/// Get the combined style for a range - if any attribute is different within the range,
2299/// that attribute is not present within the flags.
2300/// *** Note that this is not recursive, and so assumes that content inside a paragraph is not itself
2301/// nested.
44cc96a8 2302bool wxRichTextParagraphLayoutBox::GetStyleForRange(const wxRichTextRange& range, wxTextAttr& style)
59509217 2303{
44cc96a8 2304 style = wxTextAttr();
59509217
JS
2305
2306 // The attributes that aren't valid because of multiple styles within the range
2307 long multipleStyleAttributes = 0;
42688aea 2308 int multipleTextEffectAttributes = 0;
3e541562 2309
59509217
JS
2310 wxRichTextObjectList::compatibility_iterator node = GetChildren().GetFirst();
2311 while (node)
2312 {
2313 wxRichTextParagraph* para = (wxRichTextParagraph*) node->GetData();
2314 if (!(para->GetRange().GetStart() > range.GetEnd() || para->GetRange().GetEnd() < range.GetStart()))
2315 {
2316 if (para->GetChildren().GetCount() == 0)
2317 {
44cc96a8 2318 wxTextAttr paraStyle = para->GetCombinedAttributes();
59509217 2319
42688aea 2320 CollectStyle(style, paraStyle, multipleStyleAttributes, multipleTextEffectAttributes);
59509217
JS
2321 }
2322 else
2323 {
2324 wxRichTextRange paraRange(para->GetRange());
2325 paraRange.LimitTo(range);
2326
2327 // First collect paragraph attributes only
44cc96a8 2328 wxTextAttr paraStyle = para->GetCombinedAttributes();
59509217 2329 paraStyle.SetFlags(paraStyle.GetFlags() & wxTEXT_ATTR_PARAGRAPH);
42688aea 2330 CollectStyle(style, paraStyle, multipleStyleAttributes, multipleTextEffectAttributes);
59509217
JS
2331
2332 wxRichTextObjectList::compatibility_iterator childNode = para->GetChildren().GetFirst();
2333
2334 while (childNode)
2335 {
2336 wxRichTextObject* child = childNode->GetData();
2337 if (!(child->GetRange().GetStart() > range.GetEnd() || child->GetRange().GetEnd() < range.GetStart()))
2338 {
44cc96a8 2339 wxTextAttr childStyle = para->GetCombinedAttributes(child->GetAttributes());
59509217
JS
2340
2341 // Now collect character attributes only
2342 childStyle.SetFlags(childStyle.GetFlags() & wxTEXT_ATTR_CHARACTER);
2343
42688aea 2344 CollectStyle(style, childStyle, multipleStyleAttributes, multipleTextEffectAttributes);
59509217
JS
2345 }
2346
2347 childNode = childNode->GetNext();
2348 }
2349 }
2350 }
2351 node = node->GetNext();
2352 }
2353 return true;
2354}
2355
5d7836c4 2356/// Set default style
44cc96a8 2357bool wxRichTextParagraphLayoutBox::SetDefaultStyle(const wxTextAttr& style)
5d7836c4 2358{
fe5aa22c 2359 m_defaultAttributes = style;
5d7836c4
JS
2360 return true;
2361}
2362
2363/// Test if this whole range has character attributes of the specified kind. If any
2364/// of the attributes are different within the range, the test fails. You
2365/// can use this to implement, for example, bold button updating. style must have
2366/// flags indicating which attributes are of interest.
44cc96a8 2367bool wxRichTextParagraphLayoutBox::HasCharacterAttributes(const wxRichTextRange& range, const wxTextAttr& style) const
5d7836c4
JS
2368{
2369 int foundCount = 0;
2370 int matchingCount = 0;
2371
2372 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2373 while (node)
2374 {
2375 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2376 wxASSERT (para != NULL);
2377
2378 if (para)
2379 {
2380 // Stop searching if we're beyond the range of interest
2381 if (para->GetRange().GetStart() > range.GetEnd())
2382 return foundCount == matchingCount;
2383
2384 if (!para->GetRange().IsOutside(range))
2385 {
2386 wxRichTextObjectList::compatibility_iterator node2 = para->GetChildren().GetFirst();
2387
2388 while (node2)
2389 {
2390 wxRichTextObject* child = node2->GetData();
2391 if (!child->GetRange().IsOutside(range) && child->IsKindOf(CLASSINFO(wxRichTextPlainText)))
2392 {
2393 foundCount ++;
44cc96a8 2394 wxTextAttr textAttr = para->GetCombinedAttributes(child->GetAttributes());
5912d19e 2395
fe5aa22c 2396 if (wxTextAttrEqPartial(textAttr, style, style.GetFlags()))
5d7836c4
JS
2397 matchingCount ++;
2398 }
2399
2400 node2 = node2->GetNext();
2401 }
2402 }
2403 }
2404
2405 node = node->GetNext();
2406 }
2407
2408 return foundCount == matchingCount;
2409}
2410
5d7836c4
JS
2411/// Test if this whole range has paragraph attributes of the specified kind. If any
2412/// of the attributes are different within the range, the test fails. You
2413/// can use this to implement, for example, centering button updating. style must have
2414/// flags indicating which attributes are of interest.
44cc96a8 2415bool wxRichTextParagraphLayoutBox::HasParagraphAttributes(const wxRichTextRange& range, const wxTextAttr& style) const
5d7836c4
JS
2416{
2417 int foundCount = 0;
2418 int matchingCount = 0;
2419
2420 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2421 while (node)
2422 {
2423 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2424 wxASSERT (para != NULL);
2425
2426 if (para)
2427 {
2428 // Stop searching if we're beyond the range of interest
2429 if (para->GetRange().GetStart() > range.GetEnd())
2430 return foundCount == matchingCount;
2431
2432 if (!para->GetRange().IsOutside(range))
2433 {
44cc96a8 2434 wxTextAttr textAttr = GetAttributes();
fe5aa22c
JS
2435 // Apply the paragraph style
2436 wxRichTextApplyStyle(textAttr, para->GetAttributes());
2437
5d7836c4 2438 foundCount ++;
fe5aa22c 2439 if (wxTextAttrEqPartial(textAttr, style, style.GetFlags()))
5d7836c4
JS
2440 matchingCount ++;
2441 }
2442 }
2443
2444 node = node->GetNext();
2445 }
2446 return foundCount == matchingCount;
2447}
2448
5d7836c4
JS
2449void wxRichTextParagraphLayoutBox::Clear()
2450{
2451 DeleteChildren();
2452}
2453
2454void wxRichTextParagraphLayoutBox::Reset()
2455{
2456 Clear();
2457
cd8ba0d9
JS
2458 wxRichTextBuffer* buffer = wxDynamicCast(this, wxRichTextBuffer);
2459 if (buffer && GetRichTextCtrl())
2460 {
2461 wxRichTextEvent event(wxEVT_COMMAND_RICHTEXT_BUFFER_RESET, GetRichTextCtrl()->GetId());
2462 event.SetEventObject(GetRichTextCtrl());
2463
2464 buffer->SendEvent(event, true);
2465 }
2466
7fe8059f 2467 AddParagraph(wxEmptyString);
3e541562 2468
85d8909b 2469 Invalidate(wxRICHTEXT_ALL);
5d7836c4
JS
2470}
2471
38113684
JS
2472/// Invalidate the buffer. With no argument, invalidates whole buffer.
2473void wxRichTextParagraphLayoutBox::Invalidate(const wxRichTextRange& invalidRange)
2474{
2475 SetDirty(true);
39a1c2f2 2476
1e967276 2477 if (invalidRange == wxRICHTEXT_ALL)
38113684 2478 {
1e967276 2479 m_invalidRange = wxRICHTEXT_ALL;
38113684
JS
2480 return;
2481 }
1e967276
JS
2482
2483 // Already invalidating everything
2484 if (m_invalidRange == wxRICHTEXT_ALL)
2485 return;
39a1c2f2 2486
1e967276 2487 if ((invalidRange.GetStart() < m_invalidRange.GetStart()) || m_invalidRange.GetStart() == -1)
38113684
JS
2488 m_invalidRange.SetStart(invalidRange.GetStart());
2489 if (invalidRange.GetEnd() > m_invalidRange.GetEnd())
2490 m_invalidRange.SetEnd(invalidRange.GetEnd());
2491}
2492
2493/// Get invalid range, rounding to entire paragraphs if argument is true.
2494wxRichTextRange wxRichTextParagraphLayoutBox::GetInvalidRange(bool wholeParagraphs) const
2495{
1e967276 2496 if (m_invalidRange == wxRICHTEXT_ALL || m_invalidRange == wxRICHTEXT_NONE)
38113684 2497 return m_invalidRange;
39a1c2f2 2498
38113684 2499 wxRichTextRange range = m_invalidRange;
39a1c2f2 2500
38113684
JS
2501 if (wholeParagraphs)
2502 {
2503 wxRichTextParagraph* para1 = GetParagraphAtPosition(range.GetStart());
2504 wxRichTextParagraph* para2 = GetParagraphAtPosition(range.GetEnd());
2505 if (para1)
2506 range.SetStart(para1->GetRange().GetStart());
2507 if (para2)
2508 range.SetEnd(para2->GetRange().GetEnd());
2509 }
2510 return range;
2511}
2512
fe5aa22c
JS
2513/// Apply the style sheet to the buffer, for example if the styles have changed.
2514bool wxRichTextParagraphLayoutBox::ApplyStyleSheet(wxRichTextStyleSheet* styleSheet)
2515{
2516 wxASSERT(styleSheet != NULL);
2517 if (!styleSheet)
2518 return false;
2519
2520 int foundCount = 0;
2521
44580804
JS
2522 wxRichTextAttr attr(GetBasicStyle());
2523 if (GetBasicStyle().HasParagraphStyleName())
2524 {
2525 wxRichTextParagraphStyleDefinition* paraDef = styleSheet->FindParagraphStyle(GetBasicStyle().GetParagraphStyleName());
2526 if (paraDef)
2527 {
2528 attr.Apply(paraDef->GetStyleMergedWithBase(styleSheet));
2529 SetBasicStyle(attr);
2530 foundCount ++;
2531 }
2532 }
2533
2534 if (GetBasicStyle().HasCharacterStyleName())
2535 {
2536 wxRichTextCharacterStyleDefinition* charDef = styleSheet->FindCharacterStyle(GetBasicStyle().GetCharacterStyleName());
2537 if (charDef)
2538 {
2539 attr.Apply(charDef->GetStyleMergedWithBase(styleSheet));
2540 SetBasicStyle(attr);
2541 foundCount ++;
2542 }
2543 }
2544
fe5aa22c
JS
2545 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2546 while (node)
2547 {
2548 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2549 wxASSERT (para != NULL);
2550
2551 if (para)
2552 {
38f833b1
JS
2553 // Combine paragraph and list styles. If there is a list style in the original attributes,
2554 // the current indentation overrides anything else and is used to find the item indentation.
2555 // Also, for applying paragraph styles, consider having 2 modes: (1) we merge with what we have,
2556 // thereby taking into account all user changes, (2) reset the style completely (except for indentation/list
2557 // exception as above).
2558 // Problem: when changing from one list style to another, there's a danger that the level info will get lost.
2559 // So when changing a list style interactively, could retrieve level based on current style, then
2560 // set appropriate indent and apply new style.
41a85215 2561
38f833b1
JS
2562 if (!para->GetAttributes().GetParagraphStyleName().IsEmpty() && !para->GetAttributes().GetListStyleName().IsEmpty())
2563 {
2564 int currentIndent = para->GetAttributes().GetLeftIndent();
2565
2566 wxRichTextParagraphStyleDefinition* paraDef = styleSheet->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName());
2567 wxRichTextListStyleDefinition* listDef = styleSheet->FindListStyle(para->GetAttributes().GetListStyleName());
2568 if (paraDef && !listDef)
2569 {
336d8ae9 2570 para->GetAttributes() = paraDef->GetStyleMergedWithBase(styleSheet);
38f833b1
JS
2571 foundCount ++;
2572 }
2573 else if (listDef && !paraDef)
2574 {
2575 // Set overall style defined for the list style definition
336d8ae9 2576 para->GetAttributes() = listDef->GetStyleMergedWithBase(styleSheet);
38f833b1
JS
2577
2578 // Apply the style for this level
2579 wxRichTextApplyStyle(para->GetAttributes(), * listDef->GetLevelAttributes(listDef->FindLevelForIndent(currentIndent)));
2580 foundCount ++;
2581 }
2582 else if (listDef && paraDef)
2583 {
2584 // Combines overall list style, style for level, and paragraph style
336d8ae9 2585 para->GetAttributes() = listDef->CombineWithParagraphStyle(currentIndent, paraDef->GetStyleMergedWithBase(styleSheet));
38f833b1
JS
2586 foundCount ++;
2587 }
2588 }
2589 else if (para->GetAttributes().GetParagraphStyleName().IsEmpty() && !para->GetAttributes().GetListStyleName().IsEmpty())
2590 {
2591 int currentIndent = para->GetAttributes().GetLeftIndent();
2592
2593 wxRichTextListStyleDefinition* listDef = styleSheet->FindListStyle(para->GetAttributes().GetListStyleName());
2594
41a85215 2595 // Overall list definition style
336d8ae9 2596 para->GetAttributes() = listDef->GetStyleMergedWithBase(styleSheet);
41a85215 2597
38f833b1
JS
2598 // Style for this level
2599 wxRichTextApplyStyle(para->GetAttributes(), * listDef->GetLevelAttributes(listDef->FindLevelForIndent(currentIndent)));
2600
2601 foundCount ++;
2602 }
2603 else if (!para->GetAttributes().GetParagraphStyleName().IsEmpty() && para->GetAttributes().GetListStyleName().IsEmpty())
fe5aa22c
JS
2604 {
2605 wxRichTextParagraphStyleDefinition* def = styleSheet->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName());
2606 if (def)
2607 {
336d8ae9 2608 para->GetAttributes() = def->GetStyleMergedWithBase(styleSheet);
fe5aa22c
JS
2609 foundCount ++;
2610 }
2611 }
2612 }
2613
2614 node = node->GetNext();
2615 }
2616 return foundCount != 0;
2617}
2618
38f833b1
JS
2619/// Set list style
2620bool wxRichTextParagraphLayoutBox::SetListStyle(const wxRichTextRange& range, wxRichTextListStyleDefinition* def, int flags, int startFrom, int specifiedLevel)
2621{
336d8ae9 2622 wxRichTextStyleSheet* styleSheet = GetStyleSheet();
3e541562 2623
38f833b1
JS
2624 bool withUndo = ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0);
2625 // bool applyMinimal = ((flags & wxRICHTEXT_SETSTYLE_OPTIMIZE) != 0);
2626 bool specifyLevel = ((flags & wxRICHTEXT_SETSTYLE_SPECIFY_LEVEL) != 0);
2627 bool renumber = ((flags & wxRICHTEXT_SETSTYLE_RENUMBER) != 0);
41a85215 2628
38f833b1
JS
2629 // Current number, if numbering
2630 int n = startFrom;
41a85215 2631
38f833b1
JS
2632 wxASSERT (!specifyLevel || (specifyLevel && (specifiedLevel >= 0)));
2633
2634 // If we are associated with a control, make undoable; otherwise, apply immediately
2635 // to the data.
2636
2637 bool haveControl = (GetRichTextCtrl() != NULL);
2638
2639 wxRichTextAction* action = NULL;
2640
2641 if (haveControl && withUndo)
2642 {
2643 action = new wxRichTextAction(NULL, _("Change List Style"), wxRICHTEXT_CHANGE_STYLE, & GetRichTextCtrl()->GetBuffer(), GetRichTextCtrl());
2644 action->SetRange(range);
2645 action->SetPosition(GetRichTextCtrl()->GetCaretPosition());
2646 }
2647
2648 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2649 while (node)
2650 {
2651 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2652 wxASSERT (para != NULL);
2653
2654 if (para && para->GetChildCount() > 0)
2655 {
2656 // Stop searching if we're beyond the range of interest
2657 if (para->GetRange().GetStart() > range.GetEnd())
2658 break;
2659
2660 if (!para->GetRange().IsOutside(range))
2661 {
2662 // We'll be using a copy of the paragraph to make style changes,
2663 // not updating the buffer directly.
2664 wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL);
2665
2666 if (haveControl && withUndo)
2667 {
2668 newPara = new wxRichTextParagraph(*para);
2669 action->GetNewParagraphs().AppendChild(newPara);
2670
2671 // Also store the old ones for Undo
2672 action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para));
2673 }
2674 else
2675 newPara = para;
41a85215 2676
38f833b1
JS
2677 if (def)
2678 {
2679 int thisIndent = newPara->GetAttributes().GetLeftIndent();
2680 int thisLevel = specifyLevel ? specifiedLevel : def->FindLevelForIndent(thisIndent);
41a85215 2681
38f833b1
JS
2682 // How is numbering going to work?
2683 // If we are renumbering, or numbering for the first time, we need to keep
2684 // track of the number for each level. But we might be simply applying a different
2685 // list style.
2686 // In Word, applying a style to several paragraphs, even if at different levels,
2687 // reverts the level back to the same one. So we could do the same here.
2688 // Renumbering will need to be done when we promote/demote a paragraph.
2689
2690 // Apply the overall list style, and item style for this level
44cc96a8 2691 wxTextAttr listStyle(def->GetCombinedStyleForLevel(thisLevel, styleSheet));
38f833b1 2692 wxRichTextApplyStyle(newPara->GetAttributes(), listStyle);
41a85215 2693
d2d0adc7 2694 // Now we need to do numbering
38f833b1
JS
2695 if (renumber)
2696 {
2697 newPara->GetAttributes().SetBulletNumber(n);
2698 }
41a85215 2699
38f833b1
JS
2700 n ++;
2701 }
2702 else if (!newPara->GetAttributes().GetListStyleName().IsEmpty())
2703 {
2704 // if def is NULL, remove list style, applying any associated paragraph style
2705 // to restore the attributes
2706
2707 newPara->GetAttributes().SetListStyleName(wxEmptyString);
2708 newPara->GetAttributes().SetLeftIndent(0, 0);
d2d0adc7 2709 newPara->GetAttributes().SetBulletText(wxEmptyString);
41a85215 2710
38f833b1 2711 // Eliminate the main list-related attributes
d2d0adc7 2712 newPara->GetAttributes().SetFlags(newPara->GetAttributes().GetFlags() & ~wxTEXT_ATTR_LEFT_INDENT & ~wxTEXT_ATTR_BULLET_STYLE & ~wxTEXT_ATTR_BULLET_NUMBER & ~wxTEXT_ATTR_BULLET_TEXT & wxTEXT_ATTR_LIST_STYLE_NAME);
41a85215 2713
38f833b1
JS
2714 if (styleSheet && !newPara->GetAttributes().GetParagraphStyleName().IsEmpty())
2715 {
2716 wxRichTextParagraphStyleDefinition* def = styleSheet->FindParagraphStyle(newPara->GetAttributes().GetParagraphStyleName());
2717 if (def)
2718 {
336d8ae9 2719 newPara->GetAttributes() = def->GetStyleMergedWithBase(styleSheet);
38f833b1
JS
2720 }
2721 }
2722 }
2723 }
2724 }
2725
2726 node = node->GetNext();
2727 }
2728
2729 // Do action, or delay it until end of batch.
2730 if (haveControl && withUndo)
2731 GetRichTextCtrl()->GetBuffer().SubmitAction(action);
2732
2733 return true;
2734}
2735
2736bool wxRichTextParagraphLayoutBox::SetListStyle(const wxRichTextRange& range, const wxString& defName, int flags, int startFrom, int specifiedLevel)
2737{
2738 if (GetStyleSheet())
2739 {
2740 wxRichTextListStyleDefinition* def = GetStyleSheet()->FindListStyle(defName);
2741 if (def)
2742 return SetListStyle(range, def, flags, startFrom, specifiedLevel);
2743 }
2744 return false;
2745}
2746
2747/// Clear list for given range
2748bool wxRichTextParagraphLayoutBox::ClearListStyle(const wxRichTextRange& range, int flags)
2749{
2750 return SetListStyle(range, NULL, flags);
2751}
2752
2753/// Number/renumber any list elements in the given range
2754bool wxRichTextParagraphLayoutBox::NumberList(const wxRichTextRange& range, wxRichTextListStyleDefinition* def, int flags, int startFrom, int specifiedLevel)
2755{
2756 return DoNumberList(range, range, 0, def, flags, startFrom, specifiedLevel);
2757}
2758
2759/// Number/renumber any list elements in the given range. Also do promotion or demotion of items, if specified
2760bool wxRichTextParagraphLayoutBox::DoNumberList(const wxRichTextRange& range, const wxRichTextRange& promotionRange, int promoteBy,
2761 wxRichTextListStyleDefinition* def, int flags, int startFrom, int specifiedLevel)
2762{
336d8ae9
VZ
2763 wxRichTextStyleSheet* styleSheet = GetStyleSheet();
2764
38f833b1
JS
2765 bool withUndo = ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0);
2766 // bool applyMinimal = ((flags & wxRICHTEXT_SETSTYLE_OPTIMIZE) != 0);
3c738608 2767#ifdef __WXDEBUG__
38f833b1 2768 bool specifyLevel = ((flags & wxRICHTEXT_SETSTYLE_SPECIFY_LEVEL) != 0);
3c738608 2769#endif
38f833b1
JS
2770
2771 bool renumber = ((flags & wxRICHTEXT_SETSTYLE_RENUMBER) != 0);
41a85215 2772
38f833b1
JS
2773 // Max number of levels
2774 const int maxLevels = 10;
41a85215 2775
38f833b1
JS
2776 // The level we're looking at now
2777 int currentLevel = -1;
41a85215 2778
38f833b1
JS
2779 // The item number for each level
2780 int levels[maxLevels];
2781 int i;
41a85215 2782
38f833b1
JS
2783 // Reset all numbering
2784 for (i = 0; i < maxLevels; i++)
2785 {
2786 if (startFrom != -1)
d2d0adc7 2787 levels[i] = startFrom-1;
38f833b1 2788 else if (renumber) // start again
d2d0adc7 2789 levels[i] = 0;
38f833b1
JS
2790 else
2791 levels[i] = -1; // start from the number we found, if any
2792 }
41a85215 2793
38f833b1
JS
2794 wxASSERT(!specifyLevel || (specifyLevel && (specifiedLevel >= 0)));
2795
2796 // If we are associated with a control, make undoable; otherwise, apply immediately
2797 // to the data.
2798
2799 bool haveControl = (GetRichTextCtrl() != NULL);
2800
2801 wxRichTextAction* action = NULL;
2802
2803 if (haveControl && withUndo)
2804 {
2805 action = new wxRichTextAction(NULL, _("Renumber List"), wxRICHTEXT_CHANGE_STYLE, & GetRichTextCtrl()->GetBuffer(), GetRichTextCtrl());
2806 action->SetRange(range);
2807 action->SetPosition(GetRichTextCtrl()->GetCaretPosition());
2808 }
2809
2810 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2811 while (node)
2812 {
2813 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2814 wxASSERT (para != NULL);
2815
2816 if (para && para->GetChildCount() > 0)
2817 {
2818 // Stop searching if we're beyond the range of interest
2819 if (para->GetRange().GetStart() > range.GetEnd())
2820 break;
2821
2822 if (!para->GetRange().IsOutside(range))
2823 {
2824 // We'll be using a copy of the paragraph to make style changes,
2825 // not updating the buffer directly.
2826 wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL);
2827
2828 if (haveControl && withUndo)
2829 {
2830 newPara = new wxRichTextParagraph(*para);
2831 action->GetNewParagraphs().AppendChild(newPara);
2832
2833 // Also store the old ones for Undo
2834 action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para));
2835 }
2836 else
2837 newPara = para;
41a85215 2838
38f833b1
JS
2839 wxRichTextListStyleDefinition* defToUse = def;
2840 if (!defToUse)
2841 {
336d8ae9
VZ
2842 if (styleSheet && !newPara->GetAttributes().GetListStyleName().IsEmpty())
2843 defToUse = styleSheet->FindListStyle(newPara->GetAttributes().GetListStyleName());
38f833b1 2844 }
41a85215 2845
38f833b1
JS
2846 if (defToUse)
2847 {
2848 int thisIndent = newPara->GetAttributes().GetLeftIndent();
2849 int thisLevel = defToUse->FindLevelForIndent(thisIndent);
2850
d2d0adc7
JS
2851 // If we've specified a level to apply to all, change the level.
2852 if (specifiedLevel != -1)
38f833b1 2853 thisLevel = specifiedLevel;
41a85215 2854
38f833b1
JS
2855 // Do promotion if specified
2856 if ((promoteBy != 0) && !para->GetRange().IsOutside(promotionRange))
2857 {
2858 thisLevel = thisLevel - promoteBy;
2859 if (thisLevel < 0)
2860 thisLevel = 0;
2861 if (thisLevel > 9)
2862 thisLevel = 9;
2863 }
41a85215 2864
38f833b1 2865 // Apply the overall list style, and item style for this level
44cc96a8 2866 wxTextAttr listStyle(defToUse->GetCombinedStyleForLevel(thisLevel, styleSheet));
38f833b1 2867 wxRichTextApplyStyle(newPara->GetAttributes(), listStyle);
41a85215 2868
38f833b1 2869 // OK, we've (re)applied the style, now let's get the numbering right.
41a85215 2870
38f833b1
JS
2871 if (currentLevel == -1)
2872 currentLevel = thisLevel;
41a85215 2873
38f833b1
JS
2874 // Same level as before, do nothing except increment level's number afterwards
2875 if (currentLevel == thisLevel)
2876 {
2877 }
2878 // A deeper level: start renumbering all levels after current level
2879 else if (thisLevel > currentLevel)
2880 {
2881 for (i = currentLevel+1; i <= thisLevel; i++)
2882 {
d2d0adc7 2883 levels[i] = 0;
38f833b1
JS
2884 }
2885 currentLevel = thisLevel;
2886 }
2887 else if (thisLevel < currentLevel)
2888 {
2889 currentLevel = thisLevel;
41a85215 2890 }
38f833b1
JS
2891
2892 // Use the current numbering if -1 and we have a bullet number already
2893 if (levels[currentLevel] == -1)
2894 {
2895 if (newPara->GetAttributes().HasBulletNumber())
2896 levels[currentLevel] = newPara->GetAttributes().GetBulletNumber();
2897 else
2898 levels[currentLevel] = 1;
2899 }
d2d0adc7
JS
2900 else
2901 {
2902 levels[currentLevel] ++;
2903 }
41a85215 2904
38f833b1
JS
2905 newPara->GetAttributes().SetBulletNumber(levels[currentLevel]);
2906
d2d0adc7
JS
2907 // Create the bullet text if an outline list
2908 if (listStyle.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE)
2909 {
2910 wxString text;
2911 for (i = 0; i <= currentLevel; i++)
2912 {
2913 if (!text.IsEmpty())
2914 text += wxT(".");
2915 text += wxString::Format(wxT("%d"), levels[i]);
2916 }
2917 newPara->GetAttributes().SetBulletText(text);
2918 }
38f833b1
JS
2919 }
2920 }
2921 }
2922
2923 node = node->GetNext();
2924 }
2925
2926 // Do action, or delay it until end of batch.
2927 if (haveControl && withUndo)
2928 GetRichTextCtrl()->GetBuffer().SubmitAction(action);
2929
2930 return true;
2931}
2932
2933bool wxRichTextParagraphLayoutBox::NumberList(const wxRichTextRange& range, const wxString& defName, int flags, int startFrom, int specifiedLevel)
2934{
2935 if (GetStyleSheet())
2936 {
2937 wxRichTextListStyleDefinition* def = NULL;
2938 if (!defName.IsEmpty())
2939 def = GetStyleSheet()->FindListStyle(defName);
2940 return NumberList(range, def, flags, startFrom, specifiedLevel);
2941 }
2942 return false;
2943}
2944
2945/// Promote the list items within the given range. promoteBy can be a positive or negative number, e.g. 1 or -1
2946bool wxRichTextParagraphLayoutBox::PromoteList(int promoteBy, const wxRichTextRange& range, wxRichTextListStyleDefinition* def, int flags, int specifiedLevel)
2947{
2948 // TODO
2949 // One strategy is to first work out the range within which renumbering must occur. Then could pass these two ranges
2950 // to NumberList with a flag indicating promotion is required within one of the ranges.
2951 // Find first and last paragraphs in range. Then for first, calculate new indentation and look back until we find
2952 // a paragraph that either has no list style, or has one that is different or whose indentation is less.
2953 // We start renumbering from the para after that different para we found. We specify that the numbering of that
2954 // list position will start from 1.
2955 // Similarly, we look after the last para in the promote range for an indentation that is less (or no list style).
2956 // We can end the renumbering at this point.
41a85215 2957
38f833b1 2958 // For now, only renumber within the promotion range.
41a85215 2959
38f833b1
JS
2960 return DoNumberList(range, range, promoteBy, def, flags, 1, specifiedLevel);
2961}
2962
2963bool wxRichTextParagraphLayoutBox::PromoteList(int promoteBy, const wxRichTextRange& range, const wxString& defName, int flags, int specifiedLevel)
2964{
2965 if (GetStyleSheet())
2966 {
2967 wxRichTextListStyleDefinition* def = NULL;
2968 if (!defName.IsEmpty())
2969 def = GetStyleSheet()->FindListStyle(defName);
2970 return PromoteList(promoteBy, range, def, flags, specifiedLevel);
2971 }
2972 return false;
2973}
2974
d2d0adc7
JS
2975/// Fills in the attributes for numbering a paragraph after previousParagraph. It also finds the
2976/// position of the paragraph that it had to start looking from.
44cc96a8 2977bool wxRichTextParagraphLayoutBox::FindNextParagraphNumber(wxRichTextParagraph* previousParagraph, wxTextAttr& attr) const
d2d0adc7 2978{
d2d0adc7
JS
2979 if (!previousParagraph->GetAttributes().HasFlag(wxTEXT_ATTR_BULLET_STYLE) || previousParagraph->GetAttributes().GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_NONE)
2980 return false;
3e541562 2981
336d8ae9
VZ
2982 wxRichTextStyleSheet* styleSheet = GetStyleSheet();
2983 if (styleSheet && !previousParagraph->GetAttributes().GetListStyleName().IsEmpty())
d2d0adc7 2984 {
336d8ae9 2985 wxRichTextListStyleDefinition* def = styleSheet->FindListStyle(previousParagraph->GetAttributes().GetListStyleName());
d2d0adc7
JS
2986 if (def)
2987 {
2988 // int thisIndent = previousParagraph->GetAttributes().GetLeftIndent();
2989 // int thisLevel = def->FindLevelForIndent(thisIndent);
3e541562 2990
d2d0adc7
JS
2991 bool isOutline = (previousParagraph->GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE) != 0;
2992
2993 attr.SetFlags(previousParagraph->GetAttributes().GetFlags() & (wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_BULLET_NUMBER|wxTEXT_ATTR_BULLET_TEXT|wxTEXT_ATTR_BULLET_NAME));
2994 if (previousParagraph->GetAttributes().HasBulletName())
2995 attr.SetBulletName(previousParagraph->GetAttributes().GetBulletName());
2996 attr.SetBulletStyle(previousParagraph->GetAttributes().GetBulletStyle());
2997 attr.SetListStyleName(previousParagraph->GetAttributes().GetListStyleName());
3e541562 2998
d2d0adc7
JS
2999 int nextNumber = previousParagraph->GetAttributes().GetBulletNumber() + 1;
3000 attr.SetBulletNumber(nextNumber);
3e541562 3001
d2d0adc7
JS
3002 if (isOutline)
3003 {
3004 wxString text = previousParagraph->GetAttributes().GetBulletText();
3005 if (!text.IsEmpty())
3006 {
3007 int pos = text.Find(wxT('.'), true);
3008 if (pos != wxNOT_FOUND)
3009 {
3010 text = text.Mid(0, text.Length() - pos - 1);
3011 }
3012 else
3013 text = wxEmptyString;
3014 if (!text.IsEmpty())
3015 text += wxT(".");
3016 text += wxString::Format(wxT("%d"), nextNumber);
3017 attr.SetBulletText(text);
3018 }
3019 }
3e541562 3020
d2d0adc7
JS
3021 return true;
3022 }
3023 else
3024 return false;
3025 }
3026 else
3027 return false;
3028}
3029
5d7836c4
JS
3030/*!
3031 * wxRichTextParagraph
3032 * This object represents a single paragraph (or in a straight text editor, a line).
3033 */
3034
3035IMPLEMENT_DYNAMIC_CLASS(wxRichTextParagraph, wxRichTextBox)
3036
cfa3b256
JS
3037wxArrayInt wxRichTextParagraph::sm_defaultTabs;
3038
44cc96a8 3039wxRichTextParagraph::wxRichTextParagraph(wxRichTextObject* parent, wxTextAttr* style):
5d7836c4
JS
3040 wxRichTextBox(parent)
3041{
5d7836c4
JS
3042 if (style)
3043 SetAttributes(*style);
3044}
3045
44cc96a8 3046wxRichTextParagraph::wxRichTextParagraph(const wxString& text, wxRichTextObject* parent, wxTextAttr* paraStyle, wxTextAttr* charStyle):
5d7836c4
JS
3047 wxRichTextBox(parent)
3048{
4f32b3cf
JS
3049 if (paraStyle)
3050 SetAttributes(*paraStyle);
5d7836c4 3051
4f32b3cf 3052 AppendChild(new wxRichTextPlainText(text, this, charStyle));
5d7836c4
JS
3053}
3054
3055wxRichTextParagraph::~wxRichTextParagraph()
3056{
3057 ClearLines();
3058}
3059
3060/// Draw the item
46e7a90e 3061bool wxRichTextParagraph::Draw(wxDC& dc, const wxRichTextRange& range, const wxRichTextRange& selectionRange, const wxRect& WXUNUSED(rect), int WXUNUSED(descent), int style)
5d7836c4 3062{
44cc96a8 3063 wxTextAttr attr = GetCombinedAttributes();
fe5aa22c 3064
5d7836c4 3065 // Draw the bullet, if any
fe5aa22c 3066 if (attr.GetBulletStyle() != wxTEXT_ATTR_BULLET_STYLE_NONE)
5d7836c4 3067 {
fe5aa22c 3068 if (attr.GetLeftSubIndent() != 0)
5d7836c4 3069 {
fe5aa22c 3070 int spaceBeforePara = ConvertTenthsMMToPixels(dc, attr.GetParagraphSpacingBefore());
fe5aa22c 3071 int leftIndent = ConvertTenthsMMToPixels(dc, attr.GetLeftIndent());
5d7836c4 3072
44cc96a8 3073 wxTextAttr bulletAttr(GetCombinedAttributes());
d2d0adc7 3074
e3eac0ff
JS
3075 // Combine with the font of the first piece of content, if one is specified
3076 if (GetChildren().GetCount() > 0)
3077 {
3078 wxRichTextObject* firstObj = (wxRichTextObject*) GetChildren().GetFirst()->GetData();
3079 if (firstObj->GetAttributes().HasFont())
3080 {
3081 wxRichTextApplyStyle(bulletAttr, firstObj->GetAttributes());
3082 }
3083 }
3084
d2d0adc7
JS
3085 // Get line height from first line, if any
3086 wxRichTextLine* line = m_cachedLines.GetFirst() ? (wxRichTextLine* ) m_cachedLines.GetFirst()->GetData() : (wxRichTextLine*) NULL;
3087
3088 wxPoint linePos;
3089 int lineHeight wxDUMMY_INITIALIZE(0);
3090 if (line)
5d7836c4 3091 {
d2d0adc7
JS
3092 lineHeight = line->GetSize().y;
3093 linePos = line->GetPosition() + GetPosition();
5d7836c4 3094 }
d2d0adc7 3095 else
f089713f 3096 {
f089713f 3097 wxFont font;
44cc96a8
JS
3098 if (bulletAttr.HasFont() && GetBuffer())
3099 font = GetBuffer()->GetFontTable().FindFont(bulletAttr);
f089713f
JS
3100 else
3101 font = (*wxNORMAL_FONT);
3102
ecb5fbf1 3103 wxCheckSetFont(dc, font);
f089713f 3104
d2d0adc7
JS
3105 lineHeight = dc.GetCharHeight();
3106 linePos = GetPosition();
3107 linePos.y += spaceBeforePara;
3108 }
f089713f 3109
d2d0adc7 3110 wxRect bulletRect(GetPosition().x + leftIndent, linePos.y, linePos.x - (GetPosition().x + leftIndent), lineHeight);
f089713f 3111
d2d0adc7
JS
3112 if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_BITMAP)
3113 {
3114 if (wxRichTextBuffer::GetRenderer())
3115 wxRichTextBuffer::GetRenderer()->DrawBitmapBullet(this, dc, bulletAttr, bulletRect);
3116 }
3e541562
JS
3117 else if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_STANDARD)
3118 {
d2d0adc7
JS
3119 if (wxRichTextBuffer::GetRenderer())
3120 wxRichTextBuffer::GetRenderer()->DrawStandardBullet(this, dc, bulletAttr, bulletRect);
f089713f 3121 }
5d7836c4
JS
3122 else
3123 {
3124 wxString bulletText = GetBulletText();
3e541562 3125
d2d0adc7
JS
3126 if (!bulletText.empty() && wxRichTextBuffer::GetRenderer())
3127 wxRichTextBuffer::GetRenderer()->DrawTextBullet(this, dc, bulletAttr, bulletRect, bulletText);
5d7836c4
JS
3128 }
3129 }
3130 }
7fe8059f 3131
5d7836c4
JS
3132 // Draw the range for each line, one object at a time.
3133
3134 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
3135 while (node)
3136 {
3137 wxRichTextLine* line = node->GetData();
1e967276 3138 wxRichTextRange lineRange = line->GetAbsoluteRange();
5d7836c4
JS
3139
3140 int maxDescent = line->GetDescent();
3141
3142 // Lines are specified relative to the paragraph
3143
3144 wxPoint linePosition = line->GetPosition() + GetPosition();
3145 wxPoint objectPosition = linePosition;
3146
3147 // Loop through objects until we get to the one within range
3148 wxRichTextObjectList::compatibility_iterator node2 = m_children.GetFirst();
3149 while (node2)
3150 {
3151 wxRichTextObject* child = node2->GetData();
3e541562 3152
46e7a90e 3153 if (!child->GetRange().IsOutside(lineRange) && !lineRange.IsOutside(range))
5d7836c4
JS
3154 {
3155 // Draw this part of the line at the correct position
3156 wxRichTextRange objectRange(child->GetRange());
1e967276 3157 objectRange.LimitTo(lineRange);
5d7836c4
JS
3158
3159 wxSize objectSize;
3160 int descent = 0;
7f0d9d71 3161 child->GetRangeSize(objectRange, objectSize, descent, dc, wxRICHTEXT_UNFORMATTED, objectPosition);
5d7836c4
JS
3162
3163 // Use the child object's width, but the whole line's height
3164 wxRect childRect(objectPosition, wxSize(objectSize.x, line->GetSize().y));
3165 child->Draw(dc, objectRange, selectionRange, childRect, maxDescent, style);
3166
3167 objectPosition.x += objectSize.x;
3168 }
1e967276 3169 else if (child->GetRange().GetStart() > lineRange.GetEnd())
5d7836c4
JS
3170 // Can break out of inner loop now since we've passed this line's range
3171 break;
3172
3173 node2 = node2->GetNext();
3174 }
3175
3176 node = node->GetNext();
7fe8059f 3177 }
5d7836c4
JS
3178
3179 return true;
3180}
3181
3182/// Lay the item out
38113684 3183bool wxRichTextParagraph::Layout(wxDC& dc, const wxRect& rect, int style)
5d7836c4 3184{
44cc96a8 3185 wxTextAttr attr = GetCombinedAttributes();
fe5aa22c 3186
169adfa9
JS
3187 // ClearLines();
3188
5d7836c4 3189 // Increase the size of the paragraph due to spacing
fe5aa22c
JS
3190 int spaceBeforePara = ConvertTenthsMMToPixels(dc, attr.GetParagraphSpacingBefore());
3191 int spaceAfterPara = ConvertTenthsMMToPixels(dc, attr.GetParagraphSpacingAfter());
3192 int leftIndent = ConvertTenthsMMToPixels(dc, attr.GetLeftIndent());
3193 int leftSubIndent = ConvertTenthsMMToPixels(dc, attr.GetLeftSubIndent());
3194 int rightIndent = ConvertTenthsMMToPixels(dc, attr.GetRightIndent());
5d7836c4
JS
3195
3196 int lineSpacing = 0;
3197
3198 // Let's assume line spacing of 10 is normal, 15 is 1.5, 20 is 2, etc.
44cc96a8 3199 if (attr.GetLineSpacing() != 10 && GetBuffer())
5d7836c4 3200 {
44cc96a8 3201 wxFont font(GetBuffer()->GetFontTable().FindFont(attr));
ecb5fbf1 3202 wxCheckSetFont(dc, font);
fe5aa22c 3203 lineSpacing = (ConvertTenthsMMToPixels(dc, dc.GetCharHeight()) * attr.GetLineSpacing())/10;
5d7836c4
JS
3204 }
3205
3206 // Available space for text on each line differs.
3207 int availableTextSpaceFirstLine = rect.GetWidth() - leftIndent - rightIndent;
3208
3209 // Bullets start the text at the same position as subsequent lines
fe5aa22c 3210 if (attr.GetBulletStyle() != wxTEXT_ATTR_BULLET_STYLE_NONE)
5d7836c4
JS
3211 availableTextSpaceFirstLine -= leftSubIndent;
3212
3213 int availableTextSpaceSubsequentLines = rect.GetWidth() - leftIndent - rightIndent - leftSubIndent;
3214
3215 // Start position for each line relative to the paragraph
3216 int startPositionFirstLine = leftIndent;
3217 int startPositionSubsequentLines = leftIndent + leftSubIndent;
3218
3219 // If we have a bullet in this paragraph, the start position for the first line's text
3220 // is actually leftIndent + leftSubIndent.
fe5aa22c 3221 if (attr.GetBulletStyle() != wxTEXT_ATTR_BULLET_STYLE_NONE)
5d7836c4
JS
3222 startPositionFirstLine = startPositionSubsequentLines;
3223
5d7836c4
JS
3224 long lastEndPos = GetRange().GetStart()-1;
3225 long lastCompletedEndPos = lastEndPos;
3226
3227 int currentWidth = 0;
3228 SetPosition(rect.GetPosition());
3229
3230 wxPoint currentPosition(0, spaceBeforePara); // We will calculate lines relative to paragraph
3231 int lineHeight = 0;
3232 int maxWidth = 0;
3233 int maxDescent = 0;
3234
3235 int lineCount = 0;
3236
ecb5fbf1
JS
3237 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3238 while (node)
3239 {
3240 wxRichTextObject* child = node->GetData();
3241
3242 child->SetCachedSize(wxDefaultSize);
3243 child->Layout(dc, rect, style);
3244
3245 node = node->GetNext();
3246 }
3247
5d7836c4
JS
3248 // Split up lines
3249
3250 // We may need to go back to a previous child, in which case create the new line,
3251 // find the child corresponding to the start position of the string, and
3252 // continue.
3253
ecb5fbf1 3254 node = m_children.GetFirst();
5d7836c4
JS
3255 while (node)
3256 {
3257 wxRichTextObject* child = node->GetData();
3258
3259 // If this is e.g. a composite text box, it will need to be laid out itself.
3260 // But if just a text fragment or image, for example, this will
3261 // do nothing. NB: won't we need to set the position after layout?
3262 // since for example if position is dependent on vertical line size, we
3263 // can't tell the position until the size is determined. So possibly introduce
3264 // another layout phase.
3265
5d7836c4
JS
3266 // Available width depends on whether we're on the first or subsequent lines
3267 int availableSpaceForText = (lineCount == 0 ? availableTextSpaceFirstLine : availableTextSpaceSubsequentLines);
3268
3269 currentPosition.x = (lineCount == 0 ? startPositionFirstLine : startPositionSubsequentLines);
3270
3271 // We may only be looking at part of a child, if we searched back for wrapping
3272 // and found a suitable point some way into the child. So get the size for the fragment
3273 // if necessary.
3e541562 3274
ff76711f
JS
3275 long nextBreakPos = GetFirstLineBreakPosition(lastEndPos+1);
3276 long lastPosToUse = child->GetRange().GetEnd();
3277 bool lineBreakInThisObject = (nextBreakPos > -1 && nextBreakPos <= child->GetRange().GetEnd());
3e541562 3278
ff76711f
JS
3279 if (lineBreakInThisObject)
3280 lastPosToUse = nextBreakPos;
5d7836c4
JS
3281
3282 wxSize childSize;
3283 int childDescent = 0;
3e541562 3284
ff76711f 3285 if ((nextBreakPos == -1) && (lastEndPos == child->GetRange().GetStart() - 1)) // i.e. we want to get the whole thing
5d7836c4
JS
3286 {
3287 childSize = child->GetCachedSize();
3288 childDescent = child->GetDescent();
3289 }
3290 else
ff76711f
JS
3291 GetRangeSize(wxRichTextRange(lastEndPos+1, lastPosToUse), childSize, childDescent, dc, wxRICHTEXT_UNFORMATTED, rect.GetPosition());
3292
3293 // Cases:
3294 // 1) There was a line break BEFORE the natural break
3295 // 2) There was a line break AFTER the natural break
3296 // 3) The child still fits (carry on)
5d7836c4 3297
ff76711f
JS
3298 if ((lineBreakInThisObject && (childSize.x + currentWidth <= availableSpaceForText)) ||
3299 (childSize.x + currentWidth > availableSpaceForText))
5d7836c4
JS
3300 {
3301 long wrapPosition = 0;
3302
3303 // Find a place to wrap. This may walk back to previous children,
3304 // for example if a word spans several objects.
3305 if (!FindWrapPosition(wxRichTextRange(lastCompletedEndPos+1, child->GetRange().GetEnd()), dc, availableSpaceForText, wrapPosition))
3306 {
3307 // If the function failed, just cut it off at the end of this child.
3308 wrapPosition = child->GetRange().GetEnd();
3309 }
3310
3311 // FindWrapPosition can still return a value that will put us in an endless wrapping loop
3312 if (wrapPosition <= lastCompletedEndPos)
3313 wrapPosition = wxMax(lastCompletedEndPos+1,child->GetRange().GetEnd());
3314
3315 // wxLogDebug(wxT("Split at %ld"), wrapPosition);
7fe8059f 3316
5d7836c4
JS
3317 // Let's find the actual size of the current line now
3318 wxSize actualSize;
3319 wxRichTextRange actualRange(lastCompletedEndPos+1, wrapPosition);
3320 GetRangeSize(actualRange, actualSize, childDescent, dc, wxRICHTEXT_UNFORMATTED);
3321 currentWidth = actualSize.x;
3322 lineHeight = wxMax(lineHeight, actualSize.y);
3323 maxDescent = wxMax(childDescent, maxDescent);
7fe8059f 3324
5d7836c4 3325 // Add a new line
1e967276 3326 wxRichTextLine* line = AllocateLine(lineCount);
39a1c2f2 3327
1e967276
JS
3328 // Set relative range so we won't have to change line ranges when paragraphs are moved
3329 line->SetRange(wxRichTextRange(actualRange.GetStart() - GetRange().GetStart(), actualRange.GetEnd() - GetRange().GetStart()));
5d7836c4
JS
3330 line->SetPosition(currentPosition);
3331 line->SetSize(wxSize(currentWidth, lineHeight));
3332 line->SetDescent(maxDescent);
3333
5d7836c4
JS
3334 // Now move down a line. TODO: add margins, spacing
3335 currentPosition.y += lineHeight;
3336 currentPosition.y += lineSpacing;
3337 currentWidth = 0;
3338 maxDescent = 0;
7fe8059f
WS
3339 maxWidth = wxMax(maxWidth, currentWidth);
3340
5d7836c4
JS
3341 lineCount ++;
3342
3343 // TODO: account for zero-length objects, such as fields
3344 wxASSERT(wrapPosition > lastCompletedEndPos);
7fe8059f 3345
5d7836c4
JS
3346 lastEndPos = wrapPosition;
3347 lastCompletedEndPos = lastEndPos;
3348
3349 lineHeight = 0;
3350
3351 // May need to set the node back to a previous one, due to searching back in wrapping
3352 wxRichTextObject* childAfterWrapPosition = FindObjectAtPosition(wrapPosition+1);
3353 if (childAfterWrapPosition)
3354 node = m_children.Find(childAfterWrapPosition);
3355 else
3356 node = node->GetNext();
3357 }
3358 else
3359 {
3360 // We still fit, so don't add a line, and keep going
3361 currentWidth += childSize.x;
3362 lineHeight = wxMax(lineHeight, childSize.y);
3363 maxDescent = wxMax(childDescent, maxDescent);
3364
3365 maxWidth = wxMax(maxWidth, currentWidth);
3366 lastEndPos = child->GetRange().GetEnd();
3367
3368 node = node->GetNext();
3369 }
3370 }
3371
3372 // Add the last line - it's the current pos -> last para pos
3373 // Substract -1 because the last position is always the end-paragraph position.
3374 if (lastCompletedEndPos <= GetRange().GetEnd()-1)
3375 {
3376 currentPosition.x = (lineCount == 0 ? startPositionFirstLine : startPositionSubsequentLines);
3377
1e967276
JS
3378 wxRichTextLine* line = AllocateLine(lineCount);
3379
3380 wxRichTextRange actualRange(lastCompletedEndPos+1, GetRange().GetEnd()-1);
3381
3382 // Set relative range so we won't have to change line ranges when paragraphs are moved
3383 line->SetRange(wxRichTextRange(actualRange.GetStart() - GetRange().GetStart(), actualRange.GetEnd() - GetRange().GetStart()));
7fe8059f 3384
5d7836c4
JS
3385 line->SetPosition(currentPosition);
3386
44cc96a8 3387 if (lineHeight == 0 && GetBuffer())
5d7836c4 3388 {
44cc96a8 3389 wxFont font(GetBuffer()->GetFontTable().FindFont(attr));
ecb5fbf1 3390 wxCheckSetFont(dc, font);
5d7836c4
JS
3391 lineHeight = dc.GetCharHeight();
3392 }
3393 if (maxDescent == 0)
3394 {
3395 int w, h;
3396 dc.GetTextExtent(wxT("X"), & w, &h, & maxDescent);
3397 }
3398
3399 line->SetSize(wxSize(currentWidth, lineHeight));
3400 line->SetDescent(maxDescent);
3401 currentPosition.y += lineHeight;
3402 currentPosition.y += lineSpacing;
3403 lineCount ++;
5d7836c4
JS
3404 }
3405
1e967276
JS
3406 // Remove remaining unused line objects, if any
3407 ClearUnusedLines(lineCount);
3408
5d7836c4 3409 // Apply styles to wrapped lines
fe5aa22c 3410 ApplyParagraphStyle(attr, rect);
5d7836c4
JS
3411
3412 SetCachedSize(wxSize(maxWidth, currentPosition.y + spaceBeforePara + spaceAfterPara));
3413
3414 m_dirty = false;
3415
3416 return true;
3417}
3418
3419/// Apply paragraph styles, such as centering, to wrapped lines
44cc96a8 3420void wxRichTextParagraph::ApplyParagraphStyle(const wxTextAttr& attr, const wxRect& rect)
5d7836c4 3421{
fe5aa22c 3422 if (!attr.HasAlignment())
5d7836c4
JS
3423 return;
3424
3425 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
3426 while (node)
3427 {
3428 wxRichTextLine* line = node->GetData();
3429
3430 wxPoint pos = line->GetPosition();
3431 wxSize size = line->GetSize();
3432
3433 // centering, right-justification
fe5aa22c 3434 if (attr.HasAlignment() && GetAttributes().GetAlignment() == wxTEXT_ALIGNMENT_CENTRE)
5d7836c4
JS
3435 {
3436 pos.x = (rect.GetWidth() - size.x)/2 + pos.x;
3437 line->SetPosition(pos);
3438 }
fe5aa22c 3439 else if (attr.HasAlignment() && GetAttributes().GetAlignment() == wxTEXT_ALIGNMENT_RIGHT)
5d7836c4 3440 {
44219ff0 3441 pos.x = pos.x + rect.GetWidth() - size.x;
5d7836c4
JS
3442 line->SetPosition(pos);
3443 }
3444
3445 node = node->GetNext();
3446 }
3447}
3448
3449/// Insert text at the given position
3450bool wxRichTextParagraph::InsertText(long pos, const wxString& text)
3451{
3452 wxRichTextObject* childToUse = NULL;
09f14108 3453 wxRichTextObjectList::compatibility_iterator nodeToUse = wxRichTextObjectList::compatibility_iterator();
5d7836c4
JS
3454
3455 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3456 while (node)
3457 {
3458 wxRichTextObject* child = node->GetData();
3459 if (child->GetRange().Contains(pos) && child->GetRange().GetLength() > 0)
3460 {
3461 childToUse = child;
3462 nodeToUse = node;
3463 break;
3464 }
3465
3466 node = node->GetNext();
3467 }
3468
3469 if (childToUse)
3470 {
3471 wxRichTextPlainText* textObject = wxDynamicCast(childToUse, wxRichTextPlainText);
3472 if (textObject)
3473 {
3474 int posInString = pos - textObject->GetRange().GetStart();
3475
3476 wxString newText = textObject->GetText().Mid(0, posInString) +
3477 text + textObject->GetText().Mid(posInString);
3478 textObject->SetText(newText);
3479
28f92d74 3480 int textLength = text.length();
5d7836c4
JS
3481
3482 textObject->SetRange(wxRichTextRange(textObject->GetRange().GetStart(),
3483 textObject->GetRange().GetEnd() + textLength));
3484
3485 // Increment the end range of subsequent fragments in this paragraph.
3486 // We'll set the paragraph range itself at a higher level.
3487
3488 wxRichTextObjectList::compatibility_iterator node = nodeToUse->GetNext();
3489 while (node)
3490 {
3491 wxRichTextObject* child = node->GetData();
3492 child->SetRange(wxRichTextRange(textObject->GetRange().GetStart() + textLength,
3493 textObject->GetRange().GetEnd() + textLength));
7fe8059f 3494
5d7836c4
JS
3495 node = node->GetNext();
3496 }
3497
3498 return true;
3499 }
3500 else
3501 {
3502 // TODO: if not a text object, insert at closest position, e.g. in front of it
3503 }
3504 }
3505 else
3506 {
3507 // Add at end.
3508 // Don't pass parent initially to suppress auto-setting of parent range.
3509 // We'll do that at a higher level.
3510 wxRichTextPlainText* textObject = new wxRichTextPlainText(text, this);
3511
3512 AppendChild(textObject);
3513 return true;
3514 }
3515
3516 return false;
3517}
3518
3519void wxRichTextParagraph::Copy(const wxRichTextParagraph& obj)
3520{
3521 wxRichTextBox::Copy(obj);
3522}
3523
3524/// Clear the cached lines
3525void wxRichTextParagraph::ClearLines()
3526{
3527 WX_CLEAR_LIST(wxRichTextLineList, m_cachedLines);
3528}
3529
3530/// Get/set the object size for the given range. Returns false if the range
3531/// is invalid for this object.
7f0d9d71 3532bool wxRichTextParagraph::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, int flags, wxPoint position) const
5d7836c4
JS
3533{
3534 if (!range.IsWithin(GetRange()))
3535 return false;
3536
3537 if (flags & wxRICHTEXT_UNFORMATTED)
3538 {
3539 // Just use unformatted data, assume no line breaks
3540 // TODO: take into account line breaks
3541
3542 wxSize sz;
3543
3544 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3545 while (node)
3546 {
3547 wxRichTextObject* child = node->GetData();
3548 if (!child->GetRange().IsOutside(range))
3549 {
3550 wxSize childSize;
7fe8059f 3551
5d7836c4
JS
3552 wxRichTextRange rangeToUse = range;
3553 rangeToUse.LimitTo(child->GetRange());
3554 int childDescent = 0;
7fe8059f 3555
0f1fbeb8 3556 if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, flags, wxPoint(position.x + sz.x, position.y)))
5d7836c4
JS
3557 {
3558 sz.y = wxMax(sz.y, childSize.y);
3559 sz.x += childSize.x;
3560 descent = wxMax(descent, childDescent);
3561 }
3562 }
3563
3564 node = node->GetNext();
3565 }
3566 size = sz;
3567 }
3568 else
3569 {
3570 // Use formatted data, with line breaks
3571 wxSize sz;
3572
3573 // We're going to loop through each line, and then for each line,
3574 // call GetRangeSize for the fragment that comprises that line.
3575 // Only we have to do that multiple times within the line, because
3576 // the line may be broken into pieces. For now ignore line break commands
3577 // (so we can assume that getting the unformatted size for a fragment
3578 // within a line is the actual size)
3579
3580 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
3581 while (node)
3582 {
3583 wxRichTextLine* line = node->GetData();
1e967276
JS
3584 wxRichTextRange lineRange = line->GetAbsoluteRange();
3585 if (!lineRange.IsOutside(range))
5d7836c4
JS
3586 {
3587 wxSize lineSize;
7fe8059f 3588
5d7836c4
JS
3589 wxRichTextObjectList::compatibility_iterator node2 = m_children.GetFirst();
3590 while (node2)
3591 {
3592 wxRichTextObject* child = node2->GetData();
7fe8059f 3593
1e967276 3594 if (!child->GetRange().IsOutside(lineRange))
5d7836c4 3595 {
1e967276 3596 wxRichTextRange rangeToUse = lineRange;
5d7836c4 3597 rangeToUse.LimitTo(child->GetRange());
7fe8059f 3598
5d7836c4
JS
3599 wxSize childSize;
3600 int childDescent = 0;
0f1fbeb8 3601 if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, flags, wxPoint(position.x + sz.x, position.y)))
5d7836c4
JS
3602 {
3603 lineSize.y = wxMax(lineSize.y, childSize.y);
3604 lineSize.x += childSize.x;
3605 }
3606 descent = wxMax(descent, childDescent);
3607 }
7fe8059f 3608
5d7836c4
JS
3609 node2 = node2->GetNext();
3610 }
3611
3612 // Increase size by a line (TODO: paragraph spacing)
3613 sz.y += lineSize.y;
3614 sz.x = wxMax(sz.x, lineSize.x);
3615 }
3616 node = node->GetNext();
3617 }
3618 size = sz;
3619 }
3620 return true;
3621}
3622
3623/// Finds the absolute position and row height for the given character position
3624bool wxRichTextParagraph::FindPosition(wxDC& dc, long index, wxPoint& pt, int* height, bool forceLineStart)
3625{
3626 if (index == -1)
3627 {
3628 wxRichTextLine* line = ((wxRichTextParagraphLayoutBox*)GetParent())->GetLineAtPosition(0);
3629 if (line)
3630 *height = line->GetSize().y;
3631 else
3632 *height = dc.GetCharHeight();
3633
3634 // -1 means 'the start of the buffer'.
3635 pt = GetPosition();
3636 if (line)
3637 pt = pt + line->GetPosition();
3638
5d7836c4
JS
3639 return true;
3640 }
3641
3642 // The final position in a paragraph is taken to mean the position
3643 // at the start of the next paragraph.
3644 if (index == GetRange().GetEnd())
3645 {
3646 wxRichTextParagraphLayoutBox* parent = wxDynamicCast(GetParent(), wxRichTextParagraphLayoutBox);
3647 wxASSERT( parent != NULL );
3648
3649 // Find the height at the next paragraph, if any
3650 wxRichTextLine* line = parent->GetLineAtPosition(index + 1);
3651 if (line)
3652 {
3653 *height = line->GetSize().y;
3654 pt = line->GetAbsolutePosition();
3655 }
3656 else
3657 {
3658 *height = dc.GetCharHeight();
3659 int indent = ConvertTenthsMMToPixels(dc, m_attributes.GetLeftIndent());
3660 pt = wxPoint(indent, GetCachedSize().y);
3661 }
3662
3663 return true;
3664 }
3665
3666 if (index < GetRange().GetStart() || index > GetRange().GetEnd())
3667 return false;
3668
3669 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
3670 while (node)
3671 {
3672 wxRichTextLine* line = node->GetData();
1e967276
JS
3673 wxRichTextRange lineRange = line->GetAbsoluteRange();
3674 if (index >= lineRange.GetStart() && index <= lineRange.GetEnd())
5d7836c4
JS
3675 {
3676 // If this is the last point in the line, and we're forcing the
3677 // returned value to be the start of the next line, do the required
3678 // thing.
1e967276 3679 if (index == lineRange.GetEnd() && forceLineStart)
5d7836c4
JS
3680 {
3681 if (node->GetNext())
3682 {
3683 wxRichTextLine* nextLine = node->GetNext()->GetData();
3684 *height = nextLine->GetSize().y;
3685 pt = nextLine->GetAbsolutePosition();
3686 return true;
3687 }
3688 }
3689
3690 pt.y = line->GetPosition().y + GetPosition().y;
3691
1e967276 3692 wxRichTextRange r(lineRange.GetStart(), index);
5d7836c4
JS
3693 wxSize rangeSize;
3694 int descent = 0;
3695
3696 // We find the size of the line up to this point,
3697 // then we can add this size to the line start position and
3698 // paragraph start position to find the actual position.
3699
7f0d9d71 3700 if (GetRangeSize(r, rangeSize, descent, dc, wxRICHTEXT_UNFORMATTED, line->GetPosition()+ GetPosition()))
5d7836c4
JS
3701 {
3702 pt.x = line->GetPosition().x + GetPosition().x + rangeSize.x;
3703 *height = line->GetSize().y;
3704
3705 return true;
3706 }
3707
3708 }
3709
3710 node = node->GetNext();
3711 }
3712
3713 return false;
3714}
3715
3716/// Hit-testing: returns a flag indicating hit test details, plus
3717/// information about position
3718int wxRichTextParagraph::HitTest(wxDC& dc, const wxPoint& pt, long& textPosition)
3719{
3720 wxPoint paraPos = GetPosition();
3721
3722 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
3723 while (node)
3724 {
3725 wxRichTextLine* line = node->GetData();
3726 wxPoint linePos = paraPos + line->GetPosition();
3727 wxSize lineSize = line->GetSize();
1e967276 3728 wxRichTextRange lineRange = line->GetAbsoluteRange();
5d7836c4
JS
3729
3730 if (pt.y >= linePos.y && pt.y <= linePos.y + lineSize.y)
3731 {
3732 if (pt.x < linePos.x)
3733 {
1e967276 3734 textPosition = lineRange.GetStart();
f262b25c 3735 return wxRICHTEXT_HITTEST_BEFORE|wxRICHTEXT_HITTEST_OUTSIDE;
5d7836c4
JS
3736 }
3737 else if (pt.x >= (linePos.x + lineSize.x))
3738 {
1e967276 3739 textPosition = lineRange.GetEnd();
f262b25c 3740 return wxRICHTEXT_HITTEST_AFTER|wxRICHTEXT_HITTEST_OUTSIDE;
5d7836c4
JS
3741 }
3742 else
3743 {
3744 long i;
3745 int lastX = linePos.x;
1e967276 3746 for (i = lineRange.GetStart(); i <= lineRange.GetEnd(); i++)
5d7836c4
JS
3747 {
3748 wxSize childSize;
3749 int descent = 0;
7fe8059f 3750
1e967276 3751 wxRichTextRange rangeToUse(lineRange.GetStart(), i);
7fe8059f 3752
7f0d9d71 3753 GetRangeSize(rangeToUse, childSize, descent, dc, wxRICHTEXT_UNFORMATTED, linePos);
5d7836c4
JS
3754
3755 int nextX = childSize.x + linePos.x;
3756
3757 if (pt.x >= lastX && pt.x <= nextX)
3758 {
3759 textPosition = i;
3760
3761 // So now we know it's between i-1 and i.
3762 // Let's see if we can be more precise about
3763 // which side of the position it's on.
3764
3765 int midPoint = (nextX - lastX)/2 + lastX;
3766 if (pt.x >= midPoint)
3767 return wxRICHTEXT_HITTEST_AFTER;
3768 else
3769 return wxRICHTEXT_HITTEST_BEFORE;
3770 }
3771 else
3772 {
3773 lastX = nextX;
3774 }
3775 }
3776 }
3777 }
7fe8059f 3778
5d7836c4
JS
3779 node = node->GetNext();
3780 }
3781
3782 return wxRICHTEXT_HITTEST_NONE;
3783}
3784
3785/// Split an object at this position if necessary, and return
3786/// the previous object, or NULL if inserting at beginning.
3787wxRichTextObject* wxRichTextParagraph::SplitAt(long pos, wxRichTextObject** previousObject)
3788{
3789 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3790 while (node)
3791 {
3792 wxRichTextObject* child = node->GetData();
3793
3794 if (pos == child->GetRange().GetStart())
3795 {
3796 if (previousObject)
4d551ad5
JS
3797 {
3798 if (node->GetPrevious())
3799 *previousObject = node->GetPrevious()->GetData();
3800 else
3801 *previousObject = NULL;
3802 }
5d7836c4
JS
3803
3804 return child;
3805 }
3806
3807 if (child->GetRange().Contains(pos))
3808 {
3809 // This should create a new object, transferring part of
3810 // the content to the old object and the rest to the new object.
3811 wxRichTextObject* newObject = child->DoSplit(pos);
3812
3813 // If we couldn't split this object, just insert in front of it.
3814 if (!newObject)
3815 {
3816 // Maybe this is an empty string, try the next one
3817 // return child;
3818 }
3819 else
3820 {
3821 // Insert the new object after 'child'
3822 if (node->GetNext())
3823 m_children.Insert(node->GetNext(), newObject);
3824 else
3825 m_children.Append(newObject);
3826 newObject->SetParent(this);
3827
3828 if (previousObject)
3829 *previousObject = child;
3830
3831 return newObject;
3832 }
3833 }
3834
3835 node = node->GetNext();
3836 }
3837 if (previousObject)
3838 *previousObject = NULL;
3839 return NULL;
3840}
3841
3842/// Move content to a list from obj on
3843void wxRichTextParagraph::MoveToList(wxRichTextObject* obj, wxList& list)
3844{
3845 wxRichTextObjectList::compatibility_iterator node = m_children.Find(obj);
3846 while (node)
3847 {
3848 wxRichTextObject* child = node->GetData();
3849 list.Append(child);
3850
3851 wxRichTextObjectList::compatibility_iterator oldNode = node;
3852
3853 node = node->GetNext();
3854
3855 m_children.DeleteNode(oldNode);
3856 }
3857}
3858
3859/// Add content back from list
3860void wxRichTextParagraph::MoveFromList(wxList& list)
3861{
09f14108 3862 for (wxList::compatibility_iterator node = list.GetFirst(); node; node = node->GetNext())
5d7836c4
JS
3863 {
3864 AppendChild((wxRichTextObject*) node->GetData());
3865 }
3866}
3867
3868/// Calculate range
3869void wxRichTextParagraph::CalculateRange(long start, long& end)
3870{
3871 wxRichTextCompositeObject::CalculateRange(start, end);
3872
3873 // Add one for end of paragraph
3874 end ++;
3875
3876 m_range.SetRange(start, end);
3877}
3878
3879/// Find the object at the given position
3880wxRichTextObject* wxRichTextParagraph::FindObjectAtPosition(long position)
3881{
3882 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3883 while (node)
3884 {
3885 wxRichTextObject* obj = node->GetData();
3886 if (obj->GetRange().Contains(position))
3887 return obj;
7fe8059f 3888
5d7836c4
JS
3889 node = node->GetNext();
3890 }
3891 return NULL;
3892}
3893
3894/// Get the plain text searching from the start or end of the range.
3895/// The resulting string may be shorter than the range given.
3896bool wxRichTextParagraph::GetContiguousPlainText(wxString& text, const wxRichTextRange& range, bool fromStart)
3897{
3898 text = wxEmptyString;
3899
3900 if (fromStart)
3901 {
3902 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3903 while (node)
3904 {
3905 wxRichTextObject* obj = node->GetData();
3906 if (!obj->GetRange().IsOutside(range))
3907 {
3908 wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText);
3909 if (textObj)
3910 {
3911 text += textObj->GetTextForRange(range);
3912 }
3913 else
3914 return true;
3915 }
3916
3917 node = node->GetNext();
3918 }
3919 }
3920 else
3921 {
3922 wxRichTextObjectList::compatibility_iterator node = m_children.GetLast();
3923 while (node)
3924 {
3925 wxRichTextObject* obj = node->GetData();
3926 if (!obj->GetRange().IsOutside(range))
3927 {
3928 wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText);
3929 if (textObj)
3930 {
3931 text = textObj->GetTextForRange(range) + text;
3932 }
3933 else
3934 return true;
3935 }
3936
3937 node = node->GetPrevious();
3938 }
3939 }
3940
3941 return true;
3942}
3943
3944/// Find a suitable wrap position.
3945bool wxRichTextParagraph::FindWrapPosition(const wxRichTextRange& range, wxDC& dc, int availableSpace, long& wrapPosition)
3946{
3947 // Find the first position where the line exceeds the available space.
3948 wxSize sz;
5d7836c4 3949 long breakPosition = range.GetEnd();
ecb5fbf1
JS
3950
3951 // Binary chop for speed
3952 long minPos = range.GetStart();
3953 long maxPos = range.GetEnd();
3954 while (true)
5d7836c4 3955 {
ecb5fbf1
JS
3956 if (minPos == maxPos)
3957 {
3958 int descent = 0;
3959 GetRangeSize(wxRichTextRange(range.GetStart(), minPos), sz, descent, dc, wxRICHTEXT_UNFORMATTED);
5d7836c4 3960
ecb5fbf1
JS
3961 if (sz.x > availableSpace)
3962 breakPosition = minPos - 1;
3963 break;
3964 }
3965 else if ((maxPos - minPos) == 1)
5d7836c4 3966 {
ecb5fbf1
JS
3967 int descent = 0;
3968 GetRangeSize(wxRichTextRange(range.GetStart(), minPos), sz, descent, dc, wxRICHTEXT_UNFORMATTED);
3969
3970 if (sz.x > availableSpace)
3971 breakPosition = minPos - 1;
3972 else
3973 {
3974 GetRangeSize(wxRichTextRange(range.GetStart(), maxPos), sz, descent, dc, wxRICHTEXT_UNFORMATTED);
3975 if (sz.x > availableSpace)
3976 breakPosition = maxPos-1;
3977 }
5d7836c4
JS
3978 break;
3979 }
ecb5fbf1
JS
3980 else
3981 {
3982 long nextPos = minPos + ((maxPos - minPos) / 2);
3983
3984 int descent = 0;
3985 GetRangeSize(wxRichTextRange(range.GetStart(), nextPos), sz, descent, dc, wxRICHTEXT_UNFORMATTED);
3986
3987 if (sz.x > availableSpace)
3988 {
3989 maxPos = nextPos;
3990 }
3991 else
3992 {
3993 minPos = nextPos;
3994 }
3995 }
5d7836c4
JS
3996 }
3997
3998 // Now we know the last position on the line.
3999 // Let's try to find a word break.
4000
4001 wxString plainText;
4002 if (GetContiguousPlainText(plainText, wxRichTextRange(range.GetStart(), breakPosition), false))
4003 {
ff76711f
JS
4004 int newLinePos = plainText.Find(wxRichTextLineBreakChar);
4005 if (newLinePos != wxNOT_FOUND)
5d7836c4 4006 {
ff76711f
JS
4007 breakPosition = wxMax(0, range.GetStart() + newLinePos);
4008 }
4009 else
4010 {
4011 int spacePos = plainText.Find(wxT(' '), true);
31002e44
JS
4012 int tabPos = plainText.Find(wxT('\t'), true);
4013 int pos = wxMax(spacePos, tabPos);
4014 if (pos != wxNOT_FOUND)
ff76711f 4015 {
31002e44 4016 int positionsFromEndOfString = plainText.length() - pos - 1;
ff76711f
JS
4017 breakPosition = breakPosition - positionsFromEndOfString;
4018 }
5d7836c4
JS
4019 }
4020 }
4021
4022 wrapPosition = breakPosition;
4023
4024 return true;
4025}
4026
4027/// Get the bullet text for this paragraph.
4028wxString wxRichTextParagraph::GetBulletText()
4029{
4030 if (GetAttributes().GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_NONE ||
4031 (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_BITMAP))
4032 return wxEmptyString;
4033
4034 int number = GetAttributes().GetBulletNumber();
4035
4036 wxString text;
d2d0adc7 4037 if ((GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ARABIC) || (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE))
5d7836c4
JS
4038 {
4039 text.Printf(wxT("%d"), number);
4040 }
4041 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_LETTERS_UPPER)
4042 {
4043 // TODO: Unicode, and also check if number > 26
4044 text.Printf(wxT("%c"), (wxChar) (number+64));
4045 }
4046 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_LETTERS_LOWER)
4047 {
4048 // TODO: Unicode, and also check if number > 26
4049 text.Printf(wxT("%c"), (wxChar) (number+96));
4050 }
4051 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ROMAN_UPPER)
4052 {
59509217 4053 text = wxRichTextDecimalToRoman(number);
5d7836c4
JS
4054 }
4055 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ROMAN_LOWER)
4056 {
59509217
JS
4057 text = wxRichTextDecimalToRoman(number);
4058 text.MakeLower();
5d7836c4
JS
4059 }
4060 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_SYMBOL)
4061 {
d2d0adc7
JS
4062 text = GetAttributes().GetBulletText();
4063 }
3e541562 4064
d2d0adc7
JS
4065 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE)
4066 {
4067 // The outline style relies on the text being computed statically,
4068 // since it depends on other levels points (e.g. 1.2.1.1). So normally the bullet text
4069 // should be stored in the attributes; if not, just use the number for this
4070 // level, as previously computed.
4071 if (!GetAttributes().GetBulletText().IsEmpty())
4072 text = GetAttributes().GetBulletText();
5d7836c4
JS
4073 }
4074
4075 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_PARENTHESES)
4076 {
4077 text = wxT("(") + text + wxT(")");
4078 }
d2d0adc7
JS
4079 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_RIGHT_PARENTHESIS)
4080 {
4081 text = text + wxT(")");
4082 }
4083
5d7836c4
JS
4084 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_PERIOD)
4085 {
4086 text += wxT(".");
4087 }
4088
4089 return text;
4090}
4091
1e967276
JS
4092/// Allocate or reuse a line object
4093wxRichTextLine* wxRichTextParagraph::AllocateLine(int pos)
4094{
4095 if (pos < (int) m_cachedLines.GetCount())
4096 {
4097 wxRichTextLine* line = m_cachedLines.Item(pos)->GetData();
4098 line->Init(this);
4099 return line;
4100 }
4101 else
4102 {
4103 wxRichTextLine* line = new wxRichTextLine(this);
4104 m_cachedLines.Append(line);
4105 return line;
4106 }
4107}
4108
4109/// Clear remaining unused line objects, if any
4110bool wxRichTextParagraph::ClearUnusedLines(int lineCount)
4111{
4112 int cachedLineCount = m_cachedLines.GetCount();
4113 if ((int) cachedLineCount > lineCount)
4114 {
4115 for (int i = 0; i < (int) (cachedLineCount - lineCount); i ++)
4116 {
4117 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetLast();
4118 wxRichTextLine* line = node->GetData();
4119 m_cachedLines.Erase(node);
4120 delete line;
4121 }
4122 }
4123 return true;
4124}
4125
fe5aa22c
JS
4126/// Get combined attributes of the base style, paragraph style and character style. We use this to dynamically
4127/// retrieve the actual style.
44cc96a8 4128wxTextAttr wxRichTextParagraph::GetCombinedAttributes(const wxTextAttr& contentStyle) const
fe5aa22c 4129{
44cc96a8 4130 wxTextAttr attr;
fe5aa22c
JS
4131 wxRichTextBuffer* buf = wxDynamicCast(GetParent(), wxRichTextBuffer);
4132 if (buf)
4133 {
4134 attr = buf->GetBasicStyle();
4135 wxRichTextApplyStyle(attr, GetAttributes());
4136 }
4137 else
4138 attr = GetAttributes();
4139
4140 wxRichTextApplyStyle(attr, contentStyle);
4141 return attr;
4142}
4143
4144/// Get combined attributes of the base style and paragraph style.
44cc96a8 4145wxTextAttr wxRichTextParagraph::GetCombinedAttributes() const
fe5aa22c 4146{
44cc96a8 4147 wxTextAttr attr;
fe5aa22c
JS
4148 wxRichTextBuffer* buf = wxDynamicCast(GetParent(), wxRichTextBuffer);
4149 if (buf)
4150 {
4151 attr = buf->GetBasicStyle();
4152 wxRichTextApplyStyle(attr, GetAttributes());
4153 }
4154 else
4155 attr = GetAttributes();
4156
4157 return attr;
4158}
5d7836c4 4159
cfa3b256
JS
4160/// Create default tabstop array
4161void wxRichTextParagraph::InitDefaultTabs()
4162{
4163 // create a default tab list at 10 mm each.
4164 for (int i = 0; i < 20; ++i)
4165 {
4166 sm_defaultTabs.Add(i*100);
4167 }
4168}
4169
4170/// Clear default tabstop array
4171void wxRichTextParagraph::ClearDefaultTabs()
4172{
4173 sm_defaultTabs.Clear();
4174}
4175
ff76711f
JS
4176/// Get the first position from pos that has a line break character.
4177long wxRichTextParagraph::GetFirstLineBreakPosition(long pos)
4178{
4179 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
4180 while (node)
4181 {
4182 wxRichTextObject* obj = node->GetData();
4183 if (pos >= obj->GetRange().GetStart() && pos <= obj->GetRange().GetEnd())
4184 {
4185 wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText);
4186 if (textObj)
4187 {
4188 long breakPos = textObj->GetFirstLineBreakPosition(pos);
4189 if (breakPos > -1)
4190 return breakPos;
4191 }
4192 }
4193 node = node->GetNext();
4194 }
4195 return -1;
4196}
cfa3b256 4197
5d7836c4
JS
4198/*!
4199 * wxRichTextLine
4200 * This object represents a line in a paragraph, and stores
4201 * offsets from the start of the paragraph representing the
4202 * start and end positions of the line.
4203 */
4204
4205wxRichTextLine::wxRichTextLine(wxRichTextParagraph* parent)
4206{
1e967276 4207 Init(parent);
5d7836c4
JS
4208}
4209
4210/// Initialisation
1e967276 4211void wxRichTextLine::Init(wxRichTextParagraph* parent)
5d7836c4 4212{
1e967276
JS
4213 m_parent = parent;
4214 m_range.SetRange(-1, -1);
4215 m_pos = wxPoint(0, 0);
4216 m_size = wxSize(0, 0);
5d7836c4
JS
4217 m_descent = 0;
4218}
4219
4220/// Copy
4221void wxRichTextLine::Copy(const wxRichTextLine& obj)
4222{
4223 m_range = obj.m_range;
4224}
4225
4226/// Get the absolute object position
4227wxPoint wxRichTextLine::GetAbsolutePosition() const
4228{
4229 return m_parent->GetPosition() + m_pos;
4230}
4231
1e967276
JS
4232/// Get the absolute range
4233wxRichTextRange wxRichTextLine::GetAbsoluteRange() const
4234{
4235 wxRichTextRange range(m_range.GetStart() + m_parent->GetRange().GetStart(), 0);
4236 range.SetEnd(range.GetStart() + m_range.GetLength()-1);
4237 return range;
4238}
4239
5d7836c4
JS
4240/*!
4241 * wxRichTextPlainText
4242 * This object represents a single piece of text.
4243 */
4244
4245IMPLEMENT_DYNAMIC_CLASS(wxRichTextPlainText, wxRichTextObject)
4246
44cc96a8 4247wxRichTextPlainText::wxRichTextPlainText(const wxString& text, wxRichTextObject* parent, wxTextAttr* style):
5d7836c4
JS
4248 wxRichTextObject(parent)
4249{
5d7836c4
JS
4250 if (style)
4251 SetAttributes(*style);
4252
4253 m_text = text;
4254}
4255
cfa3b256
JS
4256#define USE_KERNING_FIX 1
4257
4794d69c
JS
4258// If insufficient tabs are defined, this is the tab width used
4259#define WIDTH_FOR_DEFAULT_TABS 50
4260
5d7836c4
JS
4261/// Draw the item
4262bool wxRichTextPlainText::Draw(wxDC& dc, const wxRichTextRange& range, const wxRichTextRange& selectionRange, const wxRect& rect, int descent, int WXUNUSED(style))
4263{
fe5aa22c
JS
4264 wxRichTextParagraph* para = wxDynamicCast(GetParent(), wxRichTextParagraph);
4265 wxASSERT (para != NULL);
4266
44cc96a8 4267 wxTextAttr textAttr(para ? para->GetCombinedAttributes(GetAttributes()) : GetAttributes());
fe5aa22c 4268
5d7836c4
JS
4269 int offset = GetRange().GetStart();
4270
ff76711f
JS
4271 // Replace line break characters with spaces
4272 wxString str = m_text;
4273 wxString toRemove = wxRichTextLineBreakChar;
4274 str.Replace(toRemove, wxT(" "));
3e541562 4275
5d7836c4 4276 long len = range.GetLength();
ff76711f 4277 wxString stringChunk = str.Mid(range.GetStart() - offset, (size_t) len);
42688aea
JS
4278 if (textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_CAPITALS))
4279 stringChunk.MakeUpper();
5d7836c4
JS
4280
4281 int charHeight = dc.GetCharHeight();
4282
4283 int x = rect.x;
4284 int y = rect.y + (rect.height - charHeight - (descent - m_descent));
4285
4286 // Test for the optimized situations where all is selected, or none
4287 // is selected.
4288
44cc96a8 4289 wxFont font(GetBuffer()->GetFontTable().FindFont(textAttr));
ecb5fbf1 4290 wxCheckSetFont(dc, font);
5d7836c4
JS
4291
4292 // (a) All selected.
4293 if (selectionRange.GetStart() <= range.GetStart() && selectionRange.GetEnd() >= range.GetEnd())
ab14c7aa 4294 {
fe5aa22c 4295 DrawTabbedString(dc, textAttr, rect, stringChunk, x, y, true);
5d7836c4
JS
4296 }
4297 // (b) None selected.
4298 else if (selectionRange.GetEnd() < range.GetStart() || selectionRange.GetStart() > range.GetEnd())
4299 {
4300 // Draw all unselected
fe5aa22c 4301 DrawTabbedString(dc, textAttr, rect, stringChunk, x, y, false);
5d7836c4
JS
4302 }
4303 else
4304 {
4305 // (c) Part selected, part not
4306 // Let's draw unselected chunk, selected chunk, then unselected chunk.
4307
4308 dc.SetBackgroundMode(wxTRANSPARENT);
7fe8059f 4309
5d7836c4
JS
4310 // 1. Initial unselected chunk, if any, up until start of selection.
4311 if (selectionRange.GetStart() > range.GetStart() && selectionRange.GetStart() <= range.GetEnd())
4312 {
4313 int r1 = range.GetStart();
4314 int s1 = selectionRange.GetStart()-1;
4315 int fragmentLen = s1 - r1 + 1;
4316 if (fragmentLen < 0)
4317 wxLogDebug(wxT("Mid(%d, %d"), (int)(r1 - offset), (int)fragmentLen);
ff76711f 4318 wxString stringFragment = str.Mid(r1 - offset, fragmentLen);
5d7836c4 4319
fe5aa22c 4320 DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, false);
cfa3b256
JS
4321
4322#if USE_KERNING_FIX
4323 if (stringChunk.Find(wxT("\t")) == wxNOT_FOUND)
4324 {
4325 // Compensate for kerning difference
ff76711f
JS
4326 wxString stringFragment2(str.Mid(r1 - offset, fragmentLen+1));
4327 wxString stringFragment3(str.Mid(r1 - offset + fragmentLen, 1));
41a85215 4328
cfa3b256
JS
4329 wxCoord w1, h1, w2, h2, w3, h3;
4330 dc.GetTextExtent(stringFragment, & w1, & h1);
4331 dc.GetTextExtent(stringFragment2, & w2, & h2);
4332 dc.GetTextExtent(stringFragment3, & w3, & h3);
41a85215 4333
cfa3b256
JS
4334 int kerningDiff = (w1 + w3) - w2;
4335 x = x - kerningDiff;
4336 }
4337#endif
5d7836c4
JS
4338 }
4339
4340 // 2. Selected chunk, if any.
4341 if (selectionRange.GetEnd() >= range.GetStart())
4342 {
4343 int s1 = wxMax(selectionRange.GetStart(), range.GetStart());
4344 int s2 = wxMin(selectionRange.GetEnd(), range.GetEnd());
4345
4346 int fragmentLen = s2 - s1 + 1;
4347 if (fragmentLen < 0)
4348 wxLogDebug(wxT("Mid(%d, %d"), (int)(s1 - offset), (int)fragmentLen);
ff76711f 4349 wxString stringFragment = str.Mid(s1 - offset, fragmentLen);
5d7836c4 4350
fe5aa22c 4351 DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, true);
cfa3b256
JS
4352
4353#if USE_KERNING_FIX
4354 if (stringChunk.Find(wxT("\t")) == wxNOT_FOUND)
4355 {
4356 // Compensate for kerning difference
ff76711f
JS
4357 wxString stringFragment2(str.Mid(s1 - offset, fragmentLen+1));
4358 wxString stringFragment3(str.Mid(s1 - offset + fragmentLen, 1));
41a85215 4359
cfa3b256
JS
4360 wxCoord w1, h1, w2, h2, w3, h3;
4361 dc.GetTextExtent(stringFragment, & w1, & h1);
4362 dc.GetTextExtent(stringFragment2, & w2, & h2);
4363 dc.GetTextExtent(stringFragment3, & w3, & h3);
41a85215 4364
cfa3b256
JS
4365 int kerningDiff = (w1 + w3) - w2;
4366 x = x - kerningDiff;
4367 }
4368#endif
5d7836c4
JS
4369 }
4370
4371 // 3. Remaining unselected chunk, if any
4372 if (selectionRange.GetEnd() < range.GetEnd())
4373 {
4374 int s2 = wxMin(selectionRange.GetEnd()+1, range.GetEnd());
4375 int r2 = range.GetEnd();
4376
4377 int fragmentLen = r2 - s2 + 1;
4378 if (fragmentLen < 0)
4379 wxLogDebug(wxT("Mid(%d, %d"), (int)(s2 - offset), (int)fragmentLen);
ff76711f 4380 wxString stringFragment = str.Mid(s2 - offset, fragmentLen);
ab14c7aa 4381
fe5aa22c 4382 DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, false);
7fe8059f 4383 }
5d7836c4
JS
4384 }
4385
4386 return true;
4387}
61399247 4388
44cc96a8 4389bool wxRichTextPlainText::DrawTabbedString(wxDC& dc, const wxTextAttr& attr, const wxRect& rect,wxString& str, wxCoord& x, wxCoord& y, bool selected)
7f0d9d71 4390{
cfa3b256
JS
4391 bool hasTabs = (str.Find(wxT('\t')) != wxNOT_FOUND);
4392
4393 wxArrayInt tabArray;
4394 int tabCount;
4395 if (hasTabs)
ab14c7aa 4396 {
cfa3b256
JS
4397 if (attr.GetTabs().IsEmpty())
4398 tabArray = wxRichTextParagraph::GetDefaultTabs();
4399 else
4400 tabArray = attr.GetTabs();
4401 tabCount = tabArray.GetCount();
4402
4403 for (int i = 0; i < tabCount; ++i)
ab14c7aa 4404 {
cfa3b256
JS
4405 int pos = tabArray[i];
4406 pos = ConvertTenthsMMToPixels(dc, pos);
4407 tabArray[i] = pos;
7f0d9d71
JS
4408 }
4409 }
cfa3b256
JS
4410 else
4411 tabCount = 0;
ab14c7aa 4412
cfa3b256
JS
4413 int nextTabPos = -1;
4414 int tabPos = -1;
7f0d9d71 4415 wxCoord w, h;
ab14c7aa 4416
cfa3b256 4417 if (selected)
ab14c7aa 4418 {
0ec6da02
JS
4419 wxColour highlightColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT));
4420 wxColour highlightTextColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT));
4421
ecb5fbf1
JS
4422 wxCheckSetBrush(dc, wxBrush(highlightColour));
4423 wxCheckSetPen(dc, wxPen(highlightColour));
0ec6da02 4424 dc.SetTextForeground(highlightTextColour);
f0e9eda2 4425 dc.SetBackgroundMode(wxTRANSPARENT);
7f0d9d71 4426 }
ab14c7aa
JS
4427 else
4428 {
fe5aa22c 4429 dc.SetTextForeground(attr.GetTextColour());
ab14c7aa 4430
f0e9eda2
JS
4431 if (attr.HasFlag(wxTEXT_ATTR_BACKGROUND_COLOUR) && attr.GetBackgroundColour().IsOk())
4432 {
4433 dc.SetBackgroundMode(wxSOLID);
4434 dc.SetTextBackground(attr.GetBackgroundColour());
4435 }
4436 else
4437 dc.SetBackgroundMode(wxTRANSPARENT);
3e541562 4438 }
3e541562 4439
cfa3b256 4440 while (hasTabs)
ab14c7aa
JS
4441 {
4442 // the string has a tab
7f0d9d71
JS
4443 // break up the string at the Tab
4444 wxString stringChunk = str.BeforeFirst(wxT('\t'));
4445 str = str.AfterFirst(wxT('\t'));
4446 dc.GetTextExtent(stringChunk, & w, & h);
cfa3b256 4447 tabPos = x + w;
7f0d9d71 4448 bool not_found = true;
cfa3b256 4449 for (int i = 0; i < tabCount && not_found; ++i)
ab14c7aa 4450 {
cfa3b256 4451 nextTabPos = tabArray.Item(i);
4794d69c
JS
4452
4453 // Find the next tab position.
4454 // Even if we're at the end of the tab array, we must still draw the chunk.
4455
4456 if (nextTabPos > tabPos || (i == (tabCount - 1)))
ab14c7aa 4457 {
4794d69c
JS
4458 if (nextTabPos <= tabPos)
4459 {
4460 int defaultTabWidth = ConvertTenthsMMToPixels(dc, WIDTH_FOR_DEFAULT_TABS);
4461 nextTabPos = tabPos + defaultTabWidth;
4462 }
4463
7f0d9d71 4464 not_found = false;
ab14c7aa
JS
4465 if (selected)
4466 {
cfa3b256 4467 w = nextTabPos - x;
7f0d9d71 4468 wxRect selRect(x, rect.y, w, rect.GetHeight());
61399247 4469 dc.DrawRectangle(selRect);
7f0d9d71
JS
4470 }
4471 dc.DrawText(stringChunk, x, y);
42688aea
JS
4472
4473 if (attr.HasTextEffects() && (attr.GetTextEffects() & wxTEXT_ATTR_EFFECT_STRIKETHROUGH))
4474 {
4475 wxPen oldPen = dc.GetPen();
ecb5fbf1 4476 wxCheckSetPen(dc, wxPen(attr.GetTextColour(), 1));
42688aea 4477 dc.DrawLine(x, (int) (y+(h/2)+0.5), x+w, (int) (y+(h/2)+0.5));
ecb5fbf1 4478 wxCheckSetPen(dc, oldPen);
42688aea
JS
4479 }
4480
cfa3b256 4481 x = nextTabPos;
7f0d9d71
JS
4482 }
4483 }
cfa3b256 4484 hasTabs = (str.Find(wxT('\t')) != wxNOT_FOUND);
7f0d9d71 4485 }
61399247 4486
cfa3b256 4487 if (!str.IsEmpty())
ab14c7aa 4488 {
cfa3b256
JS
4489 dc.GetTextExtent(str, & w, & h);
4490 if (selected)
4491 {
4492 wxRect selRect(x, rect.y, w, rect.GetHeight());
4493 dc.DrawRectangle(selRect);
4494 }
4495 dc.DrawText(str, x, y);
42688aea
JS
4496
4497 if (attr.HasTextEffects() && (attr.GetTextEffects() & wxTEXT_ATTR_EFFECT_STRIKETHROUGH))
4498 {
4499 wxPen oldPen = dc.GetPen();
ecb5fbf1 4500 wxCheckSetPen(dc, wxPen(attr.GetTextColour(), 1));
42688aea 4501 dc.DrawLine(x, (int) (y+(h/2)+0.5), x+w, (int) (y+(h/2)+0.5));
ecb5fbf1 4502 wxCheckSetPen(dc, oldPen);
42688aea
JS
4503 }
4504
cfa3b256 4505 x += w;
7f0d9d71 4506 }
7f0d9d71 4507 return true;
5d7836c4 4508
7f0d9d71 4509}
fe5aa22c 4510
5d7836c4 4511/// Lay the item out
38113684 4512bool wxRichTextPlainText::Layout(wxDC& dc, const wxRect& WXUNUSED(rect), int WXUNUSED(style))
5d7836c4 4513{
ecb5fbf1
JS
4514 // Only lay out if we haven't already cached the size
4515 if (m_size.x == -1)
4516 GetRangeSize(GetRange(), m_size, m_descent, dc, 0, wxPoint(0, 0));
5d7836c4
JS
4517
4518 return true;
4519}
4520
4521/// Copy
4522void wxRichTextPlainText::Copy(const wxRichTextPlainText& obj)
4523{
4524 wxRichTextObject::Copy(obj);
4525
4526 m_text = obj.m_text;
4527}
4528
4529/// Get/set the object size for the given range. Returns false if the range
4530/// is invalid for this object.
7f0d9d71 4531bool wxRichTextPlainText::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, int WXUNUSED(flags), wxPoint position) const
5d7836c4
JS
4532{
4533 if (!range.IsWithin(GetRange()))
4534 return false;
4535
fe5aa22c
JS
4536 wxRichTextParagraph* para = wxDynamicCast(GetParent(), wxRichTextParagraph);
4537 wxASSERT (para != NULL);
4538
44cc96a8 4539 wxTextAttr textAttr(para ? para->GetCombinedAttributes(GetAttributes()) : GetAttributes());
fe5aa22c 4540
5d7836c4
JS
4541 // Always assume unformatted text, since at this level we have no knowledge
4542 // of line breaks - and we don't need it, since we'll calculate size within
4543 // formatted text by doing it in chunks according to the line ranges
4544
44cc96a8 4545 wxFont font(GetBuffer()->GetFontTable().FindFont(textAttr));
ecb5fbf1 4546 wxCheckSetFont(dc, font);
5d7836c4
JS
4547
4548 int startPos = range.GetStart() - GetRange().GetStart();
4549 long len = range.GetLength();
3e541562 4550
ff76711f
JS
4551 wxString str(m_text);
4552 wxString toReplace = wxRichTextLineBreakChar;
4553 str.Replace(toReplace, wxT(" "));
4554
4555 wxString stringChunk = str.Mid(startPos, (size_t) len);
42688aea
JS
4556
4557 if (textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_CAPITALS))
4558 stringChunk.MakeUpper();
4559
5d7836c4 4560 wxCoord w, h;
7f0d9d71 4561 int width = 0;
cfa3b256 4562 if (stringChunk.Find(wxT('\t')) != wxNOT_FOUND)
ab14c7aa
JS
4563 {
4564 // the string has a tab
cfa3b256
JS
4565 wxArrayInt tabArray;
4566 if (textAttr.GetTabs().IsEmpty())
4567 tabArray = wxRichTextParagraph::GetDefaultTabs();
4568 else
4569 tabArray = textAttr.GetTabs();
ab14c7aa 4570
cfa3b256 4571 int tabCount = tabArray.GetCount();
41a85215 4572
cfa3b256 4573 for (int i = 0; i < tabCount; ++i)
61399247 4574 {
cfa3b256
JS
4575 int pos = tabArray[i];
4576 pos = ((wxRichTextPlainText*) this)->ConvertTenthsMMToPixels(dc, pos);
4577 tabArray[i] = pos;
7f0d9d71 4578 }
41a85215 4579
cfa3b256 4580 int nextTabPos = -1;
61399247 4581
ab14c7aa
JS
4582 while (stringChunk.Find(wxT('\t')) >= 0)
4583 {
4584 // the string has a tab
7f0d9d71
JS
4585 // break up the string at the Tab
4586 wxString stringFragment = stringChunk.BeforeFirst(wxT('\t'));
4587 stringChunk = stringChunk.AfterFirst(wxT('\t'));
4588 dc.GetTextExtent(stringFragment, & w, & h);
4589 width += w;
cfa3b256 4590 int absoluteWidth = width + position.x;
4794d69c 4591
cfa3b256
JS
4592 bool notFound = true;
4593 for (int i = 0; i < tabCount && notFound; ++i)
ab14c7aa 4594 {
cfa3b256 4595 nextTabPos = tabArray.Item(i);
4794d69c
JS
4596
4597 // Find the next tab position.
4598 // Even if we're at the end of the tab array, we must still process the chunk.
4599
4600 if (nextTabPos > absoluteWidth || (i == (tabCount - 1)))
ab14c7aa 4601 {
4794d69c
JS
4602 if (nextTabPos <= absoluteWidth)
4603 {
4604 int defaultTabWidth = ((wxRichTextPlainText*) this)->ConvertTenthsMMToPixels(dc, WIDTH_FOR_DEFAULT_TABS);
4605 nextTabPos = absoluteWidth + defaultTabWidth;
4606 }
4607
cfa3b256
JS
4608 notFound = false;
4609 width = nextTabPos - position.x;
7f0d9d71
JS
4610 }
4611 }
4612 }
4613 }
5d7836c4 4614 dc.GetTextExtent(stringChunk, & w, & h, & descent);
7f0d9d71
JS
4615 width += w;
4616 size = wxSize(width, dc.GetCharHeight());
61399247 4617
5d7836c4
JS
4618 return true;
4619}
4620
4621/// Do a split, returning an object containing the second part, and setting
4622/// the first part in 'this'.
4623wxRichTextObject* wxRichTextPlainText::DoSplit(long pos)
4624{
ff76711f 4625 long index = pos - GetRange().GetStart();
3e541562 4626
28f92d74 4627 if (index < 0 || index >= (int) m_text.length())
5d7836c4
JS
4628 return NULL;
4629
4630 wxString firstPart = m_text.Mid(0, index);
4631 wxString secondPart = m_text.Mid(index);
4632
4633 m_text = firstPart;
4634
4635 wxRichTextPlainText* newObject = new wxRichTextPlainText(secondPart);
4636 newObject->SetAttributes(GetAttributes());
4637
4638 newObject->SetRange(wxRichTextRange(pos, GetRange().GetEnd()));
4639 GetRange().SetEnd(pos-1);
3e541562 4640
5d7836c4
JS
4641 return newObject;
4642}
4643
4644/// Calculate range
4645void wxRichTextPlainText::CalculateRange(long start, long& end)
4646{
28f92d74 4647 end = start + m_text.length() - 1;
5d7836c4
JS
4648 m_range.SetRange(start, end);
4649}
4650
4651/// Delete range
4652bool wxRichTextPlainText::DeleteRange(const wxRichTextRange& range)
4653{
4654 wxRichTextRange r = range;
4655
4656 r.LimitTo(GetRange());
4657
4658 if (r.GetStart() == GetRange().GetStart() && r.GetEnd() == GetRange().GetEnd())
4659 {
4660 m_text.Empty();
4661 return true;
4662 }
4663
4664 long startIndex = r.GetStart() - GetRange().GetStart();
4665 long len = r.GetLength();
4666
4667 m_text = m_text.Mid(0, startIndex) + m_text.Mid(startIndex+len);
4668 return true;
4669}
4670
4671/// Get text for the given range.
4672wxString wxRichTextPlainText::GetTextForRange(const wxRichTextRange& range) const
4673{
4674 wxRichTextRange r = range;
4675
4676 r.LimitTo(GetRange());
4677
4678 long startIndex = r.GetStart() - GetRange().GetStart();
4679 long len = r.GetLength();
4680
4681 return m_text.Mid(startIndex, len);
4682}
4683
4684/// Returns true if this object can merge itself with the given one.
4685bool wxRichTextPlainText::CanMerge(wxRichTextObject* object) const
4686{
4687 return object->GetClassInfo() == CLASSINFO(wxRichTextPlainText) &&
7fe8059f 4688 (m_text.empty() || wxTextAttrEq(GetAttributes(), object->GetAttributes()));
5d7836c4
JS
4689}
4690
4691/// Returns true if this object merged itself with the given one.
4692/// The calling code will then delete the given object.
4693bool wxRichTextPlainText::Merge(wxRichTextObject* object)
4694{
4695 wxRichTextPlainText* textObject = wxDynamicCast(object, wxRichTextPlainText);
4696 wxASSERT( textObject != NULL );
4697
4698 if (textObject)
4699 {
4700 m_text += textObject->GetText();
99404ab0 4701 wxRichTextApplyStyle(m_attributes, textObject->GetAttributes());
5d7836c4
JS
4702 return true;
4703 }
4704 else
4705 return false;
4706}
4707
4708/// Dump to output stream for debugging
4709void wxRichTextPlainText::Dump(wxTextOutputStream& stream)
4710{
4711 wxRichTextObject::Dump(stream);
4712 stream << m_text << wxT("\n");
4713}
4714
ff76711f
JS
4715/// Get the first position from pos that has a line break character.
4716long wxRichTextPlainText::GetFirstLineBreakPosition(long pos)
4717{
4718 int i;
4719 int len = m_text.length();
4720 int startPos = pos - m_range.GetStart();
4721 for (i = startPos; i < len; i++)
4722 {
4723 wxChar ch = m_text[i];
4724 if (ch == wxRichTextLineBreakChar)
4725 {
4726 return i + m_range.GetStart();
4727 }
4728 }
4729 return -1;
4730}
4731
5d7836c4
JS
4732/*!
4733 * wxRichTextBuffer
4734 * This is a kind of box, used to represent the whole buffer
4735 */
4736
4737IMPLEMENT_DYNAMIC_CLASS(wxRichTextBuffer, wxRichTextParagraphLayoutBox)
4738
d2d0adc7
JS
4739wxList wxRichTextBuffer::sm_handlers;
4740wxRichTextRenderer* wxRichTextBuffer::sm_renderer = NULL;
4741int wxRichTextBuffer::sm_bulletRightMargin = 20;
4742float wxRichTextBuffer::sm_bulletProportion = (float) 0.3;
5d7836c4
JS
4743
4744/// Initialisation
4745void wxRichTextBuffer::Init()
4746{
4747 m_commandProcessor = new wxCommandProcessor;
4748 m_styleSheet = NULL;
4749 m_modified = false;
4750 m_batchedCommandDepth = 0;
4751 m_batchedCommand = NULL;
4752 m_suppressUndo = 0;
d2d0adc7 4753 m_handlerFlags = 0;
44219ff0 4754 m_scale = 1.0;
5d7836c4
JS
4755}
4756
4757/// Initialisation
4758wxRichTextBuffer::~wxRichTextBuffer()
4759{
4760 delete m_commandProcessor;
4761 delete m_batchedCommand;
4762
4763 ClearStyleStack();
d2d0adc7 4764 ClearEventHandlers();
5d7836c4
JS
4765}
4766
85d8909b 4767void wxRichTextBuffer::ResetAndClearCommands()
5d7836c4 4768{
85d8909b 4769 Reset();
3e541562 4770
5d7836c4 4771 GetCommandProcessor()->ClearCommands();
5d7836c4 4772
5d7836c4 4773 Modify(false);
1e967276 4774 Invalidate(wxRICHTEXT_ALL);
5d7836c4
JS
4775}
4776
0ca07313
JS
4777void wxRichTextBuffer::Copy(const wxRichTextBuffer& obj)
4778{
4779 wxRichTextParagraphLayoutBox::Copy(obj);
4780
4781 m_styleSheet = obj.m_styleSheet;
4782 m_modified = obj.m_modified;
4783 m_batchedCommandDepth = obj.m_batchedCommandDepth;
4784 m_batchedCommand = obj.m_batchedCommand;
4785 m_suppressUndo = obj.m_suppressUndo;
4786}
4787
38f833b1
JS
4788/// Push style sheet to top of stack
4789bool wxRichTextBuffer::PushStyleSheet(wxRichTextStyleSheet* styleSheet)
4790{
4791 if (m_styleSheet)
4792 styleSheet->InsertSheet(m_styleSheet);
4793
4794 SetStyleSheet(styleSheet);
41a85215 4795
38f833b1
JS
4796 return true;
4797}
4798
4799/// Pop style sheet from top of stack
4800wxRichTextStyleSheet* wxRichTextBuffer::PopStyleSheet()
4801{
4802 if (m_styleSheet)
4803 {
4804 wxRichTextStyleSheet* oldSheet = m_styleSheet;
4805 m_styleSheet = oldSheet->GetNextSheet();
4806 oldSheet->Unlink();
41a85215 4807
38f833b1
JS
4808 return oldSheet;
4809 }
4810 else
4811 return NULL;
4812}
4813
0ca07313
JS
4814/// Submit command to insert paragraphs
4815bool wxRichTextBuffer::InsertParagraphsWithUndo(long pos, const wxRichTextParagraphLayoutBox& paragraphs, wxRichTextCtrl* ctrl, int flags)
4816{
4817 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, this, ctrl, false);
4818
44cc96a8 4819 wxTextAttr attr(GetDefaultStyle());
4f32b3cf 4820
44cc96a8
JS
4821 wxTextAttr* p = NULL;
4822 wxTextAttr paraAttr;
0ca07313
JS
4823 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
4824 {
4825 paraAttr = GetStyleForNewParagraph(pos);
4826 if (!paraAttr.IsDefault())
4827 p = & paraAttr;
4828 }
4f32b3cf
JS
4829 else
4830 p = & attr;
0ca07313
JS
4831
4832 action->GetNewParagraphs() = paragraphs;
59509217 4833
0ca07313
JS
4834 action->SetPosition(pos);
4835
99404ab0
JS
4836 wxRichTextRange range = wxRichTextRange(pos, pos + paragraphs.GetRange().GetEnd() - 1);
4837 if (!paragraphs.GetPartialParagraph())
4838 range.SetEnd(range.GetEnd()+1);
4839
0ca07313 4840 // Set the range we'll need to delete in Undo
99404ab0 4841 action->SetRange(range);
0ca07313
JS
4842
4843 SubmitAction(action);
4844
4845 return true;
4846}
4847
5d7836c4 4848/// Submit command to insert the given text
fe5aa22c 4849bool wxRichTextBuffer::InsertTextWithUndo(long pos, const wxString& text, wxRichTextCtrl* ctrl, int flags)
5d7836c4
JS
4850{
4851 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, this, ctrl, false);
4852
44cc96a8
JS
4853 wxTextAttr* p = NULL;
4854 wxTextAttr paraAttr;
fe5aa22c
JS
4855 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
4856 {
7c081bd2
JS
4857 // Get appropriate paragraph style
4858 paraAttr = GetStyleForNewParagraph(pos, false, false);
fe5aa22c
JS
4859 if (!paraAttr.IsDefault())
4860 p = & paraAttr;
4861 }
4862
fe5aa22c 4863 action->GetNewParagraphs().AddParagraphs(text, p);
0ca07313
JS
4864
4865 int length = action->GetNewParagraphs().GetRange().GetLength();
4866
4867 if (text.length() > 0 && text.Last() != wxT('\n'))
4868 {
4869 // Don't count the newline when undoing
4870 length --;
5d7836c4 4871 action->GetNewParagraphs().SetPartialParagraph(true);
0ca07313 4872 }
46ee0e5b
JS
4873 else if (text.length() > 0 && text.Last() == wxT('\n'))
4874 length --;
5d7836c4
JS
4875
4876 action->SetPosition(pos);
4877
4878 // Set the range we'll need to delete in Undo
0ca07313 4879 action->SetRange(wxRichTextRange(pos, pos + length - 1));
7fe8059f 4880
5d7836c4 4881 SubmitAction(action);
7fe8059f 4882
5d7836c4
JS
4883 return true;
4884}
4885
4886/// Submit command to insert the given text
fe5aa22c 4887bool wxRichTextBuffer::InsertNewlineWithUndo(long pos, wxRichTextCtrl* ctrl, int flags)
5d7836c4
JS
4888{
4889 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, this, ctrl, false);
4890
44cc96a8
JS
4891 wxTextAttr* p = NULL;
4892 wxTextAttr paraAttr;
fe5aa22c
JS
4893 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
4894 {
7c081bd2 4895 paraAttr = GetStyleForNewParagraph(pos, false, true /* look for next paragraph style */);
fe5aa22c
JS
4896 if (!paraAttr.IsDefault())
4897 p = & paraAttr;
4898 }
4899
44cc96a8 4900 wxTextAttr attr(GetDefaultStyle());
7fe8059f
WS
4901
4902 wxRichTextParagraph* newPara = new wxRichTextParagraph(wxEmptyString, this, & attr);
5d7836c4
JS
4903 action->GetNewParagraphs().AppendChild(newPara);
4904 action->GetNewParagraphs().UpdateRanges();
4905 action->GetNewParagraphs().SetPartialParagraph(false);
4906 action->SetPosition(pos);
4907
fe5aa22c
JS
4908 if (p)
4909 newPara->SetAttributes(*p);
4910
99404ab0
JS
4911 // Use the default character style
4912 if (!GetDefaultStyle().IsDefault() && newPara->GetChildren().GetFirst())
4913 newPara->GetChildren().GetFirst()->GetData()->SetAttributes(GetDefaultStyle());
4914
5d7836c4
JS
4915 // Set the range we'll need to delete in Undo
4916 action->SetRange(wxRichTextRange(pos, pos));
7fe8059f 4917
5d7836c4 4918 SubmitAction(action);
7fe8059f 4919
5d7836c4
JS
4920 return true;
4921}
4922
4923/// Submit command to insert the given image
fe5aa22c 4924bool wxRichTextBuffer::InsertImageWithUndo(long pos, const wxRichTextImageBlock& imageBlock, wxRichTextCtrl* ctrl, int flags)
5d7836c4
JS
4925{
4926 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Image"), wxRICHTEXT_INSERT, this, ctrl, false);
4927
44cc96a8
JS
4928 wxTextAttr* p = NULL;
4929 wxTextAttr paraAttr;
fe5aa22c
JS
4930 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
4931 {
4932 paraAttr = GetStyleForNewParagraph(pos);
4933 if (!paraAttr.IsDefault())
4934 p = & paraAttr;
4935 }
4936
44cc96a8 4937 wxTextAttr attr(GetDefaultStyle());
7fe8059f 4938
5d7836c4 4939 wxRichTextParagraph* newPara = new wxRichTextParagraph(this, & attr);
fe5aa22c
JS
4940 if (p)
4941 newPara->SetAttributes(*p);
4942
5d7836c4
JS
4943 wxRichTextImage* imageObject = new wxRichTextImage(imageBlock, newPara);
4944 newPara->AppendChild(imageObject);
4945 action->GetNewParagraphs().AppendChild(newPara);
4946 action->GetNewParagraphs().UpdateRanges();
4947
4948 action->GetNewParagraphs().SetPartialParagraph(true);
4949
4950 action->SetPosition(pos);
4951
4952 // Set the range we'll need to delete in Undo
4953 action->SetRange(wxRichTextRange(pos, pos));
7fe8059f 4954
5d7836c4 4955 SubmitAction(action);
7fe8059f 4956
5d7836c4
JS
4957 return true;
4958}
4959
fe5aa22c
JS
4960/// Get the style that is appropriate for a new paragraph at this position.
4961/// If the previous paragraph has a paragraph style name, look up the next-paragraph
4962/// style.
44cc96a8 4963wxTextAttr wxRichTextBuffer::GetStyleForNewParagraph(long pos, bool caretPosition, bool lookUpNewParaStyle) const
fe5aa22c
JS
4964{
4965 wxRichTextParagraph* para = GetParagraphAtPosition(pos, caretPosition);
4966 if (para)
4967 {
44cc96a8 4968 wxTextAttr attr;
d2d0adc7 4969 bool foundAttributes = false;
3e541562 4970
d2d0adc7 4971 // Look for a matching paragraph style
7c081bd2 4972 if (lookUpNewParaStyle && !para->GetAttributes().GetParagraphStyleName().IsEmpty() && GetStyleSheet())
fe5aa22c
JS
4973 {
4974 wxRichTextParagraphStyleDefinition* paraDef = GetStyleSheet()->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName());
d2d0adc7 4975 if (paraDef)
fe5aa22c 4976 {
caad0109
JS
4977 // If we're not at the end of the paragraph, then we apply THIS style, and not the designated next style.
4978 if (para->GetRange().GetEnd() == pos && !paraDef->GetNextStyle().IsEmpty())
d2d0adc7
JS
4979 {
4980 wxRichTextParagraphStyleDefinition* nextParaDef = GetStyleSheet()->FindParagraphStyle(paraDef->GetNextStyle());
4981 if (nextParaDef)
4982 {
4983 foundAttributes = true;
336d8ae9 4984 attr = nextParaDef->GetStyleMergedWithBase(GetStyleSheet());
d2d0adc7
JS
4985 }
4986 }
3e541562 4987
d2d0adc7
JS
4988 // If we didn't find the 'next style', use this style instead.
4989 if (!foundAttributes)
4990 {
4991 foundAttributes = true;
336d8ae9 4992 attr = paraDef->GetStyleMergedWithBase(GetStyleSheet());
d2d0adc7 4993 }
fe5aa22c
JS
4994 }
4995 }
d2d0adc7
JS
4996 if (!foundAttributes)
4997 {
4998 attr = para->GetAttributes();
4999 int flags = attr.GetFlags();
fe5aa22c 5000
d2d0adc7
JS
5001 // Eliminate character styles
5002 flags &= ( (~ wxTEXT_ATTR_FONT) |
fe5aa22c
JS
5003 (~ wxTEXT_ATTR_TEXT_COLOUR) |
5004 (~ wxTEXT_ATTR_BACKGROUND_COLOUR) );
d2d0adc7
JS
5005 attr.SetFlags(flags);
5006 }
3e541562 5007
d2d0adc7
JS
5008 // Now see if we need to number the paragraph.
5009 if (attr.HasBulletStyle())
5010 {
44cc96a8 5011 wxTextAttr numberingAttr;
d2d0adc7 5012 if (FindNextParagraphNumber(para, numberingAttr))
44cc96a8 5013 wxRichTextApplyStyle(attr, (const wxTextAttr&) numberingAttr);
d2d0adc7 5014 }
fe5aa22c
JS
5015
5016 return attr;
5017 }
5018 else
44cc96a8 5019 return wxTextAttr();
fe5aa22c
JS
5020}
5021
5d7836c4 5022/// Submit command to delete this range
12cc29c5 5023bool wxRichTextBuffer::DeleteRangeWithUndo(const wxRichTextRange& range, wxRichTextCtrl* ctrl)
5d7836c4
JS
5024{
5025 wxRichTextAction* action = new wxRichTextAction(NULL, _("Delete"), wxRICHTEXT_DELETE, this, ctrl);
7fe8059f 5026
12cc29c5 5027 action->SetPosition(ctrl->GetCaretPosition());
5d7836c4
JS
5028
5029 // Set the range to delete
5030 action->SetRange(range);
7fe8059f 5031
5d7836c4
JS
5032 // Copy the fragment that we'll need to restore in Undo
5033 CopyFragment(range, action->GetOldParagraphs());
5034
5035 // Special case: if there is only one (non-partial) paragraph,
5036 // we must save the *next* paragraph's style, because that
5037 // is the style we must apply when inserting the content back
5038 // when undoing the delete. (This is because we're merging the
5039 // paragraph with the previous paragraph and throwing away
5040 // the style, and we need to restore it.)
5041 if (!action->GetOldParagraphs().GetPartialParagraph() && action->GetOldParagraphs().GetChildCount() == 1)
5042 {
5043 wxRichTextParagraph* lastPara = GetParagraphAtPosition(range.GetStart());
5044 if (lastPara)
5045 {
5046 wxRichTextParagraph* nextPara = GetParagraphAtPosition(range.GetEnd()+1);
5047 if (nextPara)
5048 {
5049 wxRichTextParagraph* para = (wxRichTextParagraph*) action->GetOldParagraphs().GetChild(0);
5050 para->SetAttributes(nextPara->GetAttributes());
5051 }
5052 }
5053 }
5054
5055 SubmitAction(action);
7fe8059f 5056
5d7836c4
JS
5057 return true;
5058}
5059
5060/// Collapse undo/redo commands
5061bool wxRichTextBuffer::BeginBatchUndo(const wxString& cmdName)
5062{
5063 if (m_batchedCommandDepth == 0)
5064 {
5065 wxASSERT(m_batchedCommand == NULL);
5066 if (m_batchedCommand)
5067 {
5068 GetCommandProcessor()->Submit(m_batchedCommand);
5069 }
5070 m_batchedCommand = new wxRichTextCommand(cmdName);
5071 }
5072
7fe8059f 5073 m_batchedCommandDepth ++;
5d7836c4
JS
5074
5075 return true;
5076}
5077
5078/// Collapse undo/redo commands
5079bool wxRichTextBuffer::EndBatchUndo()
5080{
5081 m_batchedCommandDepth --;
5082
5083 wxASSERT(m_batchedCommandDepth >= 0);
5084 wxASSERT(m_batchedCommand != NULL);
5085
5086 if (m_batchedCommandDepth == 0)
5087 {
5088 GetCommandProcessor()->Submit(m_batchedCommand);
5089 m_batchedCommand = NULL;
5090 }
5091
5092 return true;
5093}
5094
5095/// Submit immediately, or delay according to whether collapsing is on
5096bool wxRichTextBuffer::SubmitAction(wxRichTextAction* action)
5097{
5098 if (BatchingUndo() && m_batchedCommand && !SuppressingUndo())
5099 m_batchedCommand->AddAction(action);
5100 else
5101 {
5102 wxRichTextCommand* cmd = new wxRichTextCommand(action->GetName());
5103 cmd->AddAction(action);
5104
5105 // Only store it if we're not suppressing undo.
5106 return GetCommandProcessor()->Submit(cmd, !SuppressingUndo());
5107 }
5108
5109 return true;
5110}
5111
5112/// Begin suppressing undo/redo commands.
5113bool wxRichTextBuffer::BeginSuppressUndo()
5114{
7fe8059f 5115 m_suppressUndo ++;
5d7836c4
JS
5116
5117 return true;
5118}
5119
5120/// End suppressing undo/redo commands.
5121bool wxRichTextBuffer::EndSuppressUndo()
5122{
7fe8059f 5123 m_suppressUndo --;
5d7836c4
JS
5124
5125 return true;
5126}
5127
5128/// Begin using a style
44cc96a8 5129bool wxRichTextBuffer::BeginStyle(const wxTextAttr& style)
5d7836c4 5130{
44cc96a8 5131 wxTextAttr newStyle(GetDefaultStyle());
5d7836c4
JS
5132
5133 // Save the old default style
44cc96a8 5134 m_attributeStack.Append((wxObject*) new wxTextAttr(GetDefaultStyle()));
5d7836c4
JS
5135
5136 wxRichTextApplyStyle(newStyle, style);
5137 newStyle.SetFlags(style.GetFlags()|newStyle.GetFlags());
5138
5139 SetDefaultStyle(newStyle);
5140
5141 // wxLogDebug("Default style size = %d", GetDefaultStyle().GetFont().GetPointSize());
5142
5143 return true;
5144}
5145
5146/// End the style
5147bool wxRichTextBuffer::EndStyle()
5148{
63886f6d 5149 if (!m_attributeStack.GetFirst())
5d7836c4
JS
5150 {
5151 wxLogDebug(_("Too many EndStyle calls!"));
5152 return false;
5153 }
5154
09f14108 5155 wxList::compatibility_iterator node = m_attributeStack.GetLast();
44cc96a8 5156 wxTextAttr* attr = (wxTextAttr*)node->GetData();
9e31a660 5157 m_attributeStack.Erase(node);
5d7836c4
JS
5158
5159 SetDefaultStyle(*attr);
5160
5161 delete attr;
5162 return true;
5163}
5164
5165/// End all styles
5166bool wxRichTextBuffer::EndAllStyles()
5167{
5168 while (m_attributeStack.GetCount() != 0)
5169 EndStyle();
5170 return true;
5171}
5172
5173/// Clear the style stack
5174void wxRichTextBuffer::ClearStyleStack()
5175{
09f14108 5176 for (wxList::compatibility_iterator node = m_attributeStack.GetFirst(); node; node = node->GetNext())
44cc96a8 5177 delete (wxTextAttr*) node->GetData();
5d7836c4
JS
5178 m_attributeStack.Clear();
5179}
5180
5181/// Begin using bold
5182bool wxRichTextBuffer::BeginBold()
5183{
44cc96a8
JS
5184 wxTextAttr attr;
5185 attr.SetFontWeight(wxBOLD);
7fe8059f 5186
5d7836c4
JS
5187 return BeginStyle(attr);
5188}
5189
5190/// Begin using italic
5191bool wxRichTextBuffer::BeginItalic()
5192{
44cc96a8
JS
5193 wxTextAttr attr;
5194 attr.SetFontStyle(wxITALIC);
7fe8059f 5195
5d7836c4
JS
5196 return BeginStyle(attr);
5197}
5198
5199/// Begin using underline
5200bool wxRichTextBuffer::BeginUnderline()
5201{
44cc96a8
JS
5202 wxTextAttr attr;
5203 attr.SetFontUnderlined(true);
7fe8059f 5204
5d7836c4
JS
5205 return BeginStyle(attr);
5206}
5207
5208/// Begin using point size
5209bool wxRichTextBuffer::BeginFontSize(int pointSize)
5210{
44cc96a8
JS
5211 wxTextAttr attr;
5212 attr.SetFontSize(pointSize);
7fe8059f 5213
5d7836c4
JS
5214 return BeginStyle(attr);
5215}
5216
5217/// Begin using this font
5218bool wxRichTextBuffer::BeginFont(const wxFont& font)
5219{
44cc96a8 5220 wxTextAttr attr;
5d7836c4 5221 attr.SetFont(font);
7fe8059f 5222
5d7836c4
JS
5223 return BeginStyle(attr);
5224}
5225
5226/// Begin using this colour
5227bool wxRichTextBuffer::BeginTextColour(const wxColour& colour)
5228{
44cc96a8 5229 wxTextAttr attr;
5d7836c4
JS
5230 attr.SetFlags(wxTEXT_ATTR_TEXT_COLOUR);
5231 attr.SetTextColour(colour);
7fe8059f 5232
5d7836c4
JS
5233 return BeginStyle(attr);
5234}
5235
5236/// Begin using alignment
5237bool wxRichTextBuffer::BeginAlignment(wxTextAttrAlignment alignment)
5238{
44cc96a8 5239 wxTextAttr attr;
5d7836c4
JS
5240 attr.SetFlags(wxTEXT_ATTR_ALIGNMENT);
5241 attr.SetAlignment(alignment);
7fe8059f 5242
5d7836c4
JS
5243 return BeginStyle(attr);
5244}
5245
5246/// Begin left indent
5247bool wxRichTextBuffer::BeginLeftIndent(int leftIndent, int leftSubIndent)
5248{
44cc96a8 5249 wxTextAttr attr;
5d7836c4
JS
5250 attr.SetFlags(wxTEXT_ATTR_LEFT_INDENT);
5251 attr.SetLeftIndent(leftIndent, leftSubIndent);
7fe8059f 5252
5d7836c4
JS
5253 return BeginStyle(attr);
5254}
5255
5256/// Begin right indent
5257bool wxRichTextBuffer::BeginRightIndent(int rightIndent)
5258{
44cc96a8 5259 wxTextAttr attr;
5d7836c4
JS
5260 attr.SetFlags(wxTEXT_ATTR_RIGHT_INDENT);
5261 attr.SetRightIndent(rightIndent);
7fe8059f 5262
5d7836c4
JS
5263 return BeginStyle(attr);
5264}
5265
5266/// Begin paragraph spacing
5267bool wxRichTextBuffer::BeginParagraphSpacing(int before, int after)
5268{
5269 long flags = 0;
5270 if (before != 0)
5271 flags |= wxTEXT_ATTR_PARA_SPACING_BEFORE;
5272 if (after != 0)
5273 flags |= wxTEXT_ATTR_PARA_SPACING_AFTER;
5274
44cc96a8 5275 wxTextAttr attr;
5d7836c4
JS
5276 attr.SetFlags(flags);
5277 attr.SetParagraphSpacingBefore(before);
5278 attr.SetParagraphSpacingAfter(after);
7fe8059f 5279
5d7836c4
JS
5280 return BeginStyle(attr);
5281}
5282
5283/// Begin line spacing
5284bool wxRichTextBuffer::BeginLineSpacing(int lineSpacing)
5285{
44cc96a8 5286 wxTextAttr attr;
5d7836c4
JS
5287 attr.SetFlags(wxTEXT_ATTR_LINE_SPACING);
5288 attr.SetLineSpacing(lineSpacing);
7fe8059f 5289
5d7836c4
JS
5290 return BeginStyle(attr);
5291}
5292
5293/// Begin numbered bullet
5294bool wxRichTextBuffer::BeginNumberedBullet(int bulletNumber, int leftIndent, int leftSubIndent, int bulletStyle)
5295{
44cc96a8 5296 wxTextAttr attr;
f089713f 5297 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT);
5d7836c4
JS
5298 attr.SetBulletStyle(bulletStyle);
5299 attr.SetBulletNumber(bulletNumber);
5300 attr.SetLeftIndent(leftIndent, leftSubIndent);
7fe8059f 5301
5d7836c4
JS
5302 return BeginStyle(attr);
5303}
5304
5305/// Begin symbol bullet
d2d0adc7 5306bool wxRichTextBuffer::BeginSymbolBullet(const wxString& symbol, int leftIndent, int leftSubIndent, int bulletStyle)
5d7836c4 5307{
44cc96a8 5308 wxTextAttr attr;
f089713f 5309 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT);
5d7836c4
JS
5310 attr.SetBulletStyle(bulletStyle);
5311 attr.SetLeftIndent(leftIndent, leftSubIndent);
d2d0adc7 5312 attr.SetBulletText(symbol);
7fe8059f 5313
5d7836c4
JS
5314 return BeginStyle(attr);
5315}
5316
f089713f
JS
5317/// Begin standard bullet
5318bool wxRichTextBuffer::BeginStandardBullet(const wxString& bulletName, int leftIndent, int leftSubIndent, int bulletStyle)
5319{
44cc96a8 5320 wxTextAttr attr;
f089713f
JS
5321 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT);
5322 attr.SetBulletStyle(bulletStyle);
5323 attr.SetLeftIndent(leftIndent, leftSubIndent);
5324 attr.SetBulletName(bulletName);
5325
5326 return BeginStyle(attr);
5327}
5328
5d7836c4
JS
5329/// Begin named character style
5330bool wxRichTextBuffer::BeginCharacterStyle(const wxString& characterStyle)
5331{
5332 if (GetStyleSheet())
5333 {
5334 wxRichTextCharacterStyleDefinition* def = GetStyleSheet()->FindCharacterStyle(characterStyle);
5335 if (def)
5336 {
44cc96a8 5337 wxTextAttr attr = def->GetStyleMergedWithBase(GetStyleSheet());
5d7836c4
JS
5338 return BeginStyle(attr);
5339 }
5340 }
5341 return false;
5342}
5343
5344/// Begin named paragraph style
5345bool wxRichTextBuffer::BeginParagraphStyle(const wxString& paragraphStyle)
5346{
5347 if (GetStyleSheet())
5348 {
5349 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(paragraphStyle);
5350 if (def)
5351 {
44cc96a8 5352 wxTextAttr attr = def->GetStyleMergedWithBase(GetStyleSheet());
5d7836c4
JS
5353 return BeginStyle(attr);
5354 }
5355 }
5356 return false;
5357}
5358
f089713f
JS
5359/// Begin named list style
5360bool wxRichTextBuffer::BeginListStyle(const wxString& listStyle, int level, int number)
5361{
5362 if (GetStyleSheet())
5363 {
5364 wxRichTextListStyleDefinition* def = GetStyleSheet()->FindListStyle(listStyle);
5365 if (def)
5366 {
44cc96a8 5367 wxTextAttr attr(def->GetCombinedStyleForLevel(level));
f089713f
JS
5368
5369 attr.SetBulletNumber(number);
5370
5371 return BeginStyle(attr);
5372 }
5373 }
5374 return false;
5375}
5376
d2d0adc7
JS
5377/// Begin URL
5378bool wxRichTextBuffer::BeginURL(const wxString& url, const wxString& characterStyle)
5379{
44cc96a8 5380 wxTextAttr attr;
d2d0adc7
JS
5381
5382 if (!characterStyle.IsEmpty() && GetStyleSheet())
5383 {
5384 wxRichTextCharacterStyleDefinition* def = GetStyleSheet()->FindCharacterStyle(characterStyle);
5385 if (def)
5386 {
336d8ae9 5387 attr = def->GetStyleMergedWithBase(GetStyleSheet());
d2d0adc7
JS
5388 }
5389 }
5390 attr.SetURL(url);
5391
5392 return BeginStyle(attr);
5393}
5394
5d7836c4
JS
5395/// Adds a handler to the end
5396void wxRichTextBuffer::AddHandler(wxRichTextFileHandler *handler)
5397{
5398 sm_handlers.Append(handler);
5399}
5400
5401/// Inserts a handler at the front
5402void wxRichTextBuffer::InsertHandler(wxRichTextFileHandler *handler)
5403{
5404 sm_handlers.Insert( handler );
5405}
5406
5407/// Removes a handler
5408bool wxRichTextBuffer::RemoveHandler(const wxString& name)
5409{
5410 wxRichTextFileHandler *handler = FindHandler(name);
5411 if (handler)
5412 {
5413 sm_handlers.DeleteObject(handler);
5414 delete handler;
5415 return true;
5416 }
5417 else
5418 return false;
5419}
5420
5421/// Finds a handler by filename or, if supplied, type
5422wxRichTextFileHandler *wxRichTextBuffer::FindHandlerFilenameOrType(const wxString& filename, int imageType)
5423{
5424 if (imageType != wxRICHTEXT_TYPE_ANY)
5425 return FindHandler(imageType);
0ca07313 5426 else if (!filename.IsEmpty())
5d7836c4
JS
5427 {
5428 wxString path, file, ext;
5429 wxSplitPath(filename, & path, & file, & ext);
5430 return FindHandler(ext, imageType);
5431 }
0ca07313
JS
5432 else
5433 return NULL;
5d7836c4
JS
5434}
5435
5436
5437/// Finds a handler by name
5438wxRichTextFileHandler* wxRichTextBuffer::FindHandler(const wxString& name)
5439{
5440 wxList::compatibility_iterator node = sm_handlers.GetFirst();
5441 while (node)
5442 {
5443 wxRichTextFileHandler *handler = (wxRichTextFileHandler*)node->GetData();
5444 if (handler->GetName().Lower() == name.Lower()) return handler;
5445
5446 node = node->GetNext();
5447 }
5448 return NULL;
5449}
5450
5451/// Finds a handler by extension and type
5452wxRichTextFileHandler* wxRichTextBuffer::FindHandler(const wxString& extension, int type)
5453{
5454 wxList::compatibility_iterator node = sm_handlers.GetFirst();
5455 while (node)
5456 {
5457 wxRichTextFileHandler *handler = (wxRichTextFileHandler*)node->GetData();
5458 if ( handler->GetExtension().Lower() == extension.Lower() &&
5459 (type == wxRICHTEXT_TYPE_ANY || handler->GetType() == type) )
5460 return handler;
5461 node = node->GetNext();
5462 }
5463 return 0;
5464}
5465
5466/// Finds a handler by type
5467wxRichTextFileHandler* wxRichTextBuffer::FindHandler(int type)
5468{
5469 wxList::compatibility_iterator node = sm_handlers.GetFirst();
5470 while (node)
5471 {
5472 wxRichTextFileHandler *handler = (wxRichTextFileHandler *)node->GetData();
5473 if (handler->GetType() == type) return handler;
5474 node = node->GetNext();
5475 }
5476 return NULL;
5477}
5478
5479void wxRichTextBuffer::InitStandardHandlers()
5480{
5481 if (!FindHandler(wxRICHTEXT_TYPE_TEXT))
5482 AddHandler(new wxRichTextPlainTextHandler);
5483}
5484
5485void wxRichTextBuffer::CleanUpHandlers()
5486{
5487 wxList::compatibility_iterator node = sm_handlers.GetFirst();
5488 while (node)
5489 {
5490 wxRichTextFileHandler* handler = (wxRichTextFileHandler*)node->GetData();
5491 wxList::compatibility_iterator next = node->GetNext();
5492 delete handler;
5493 node = next;
5494 }
5495
5496 sm_handlers.Clear();
5497}
5498
1e967276 5499wxString wxRichTextBuffer::GetExtWildcard(bool combine, bool save, wxArrayInt* types)
5d7836c4 5500{
1e967276
JS
5501 if (types)
5502 types->Clear();
5503
5d7836c4
JS
5504 wxString wildcard;
5505
5506 wxList::compatibility_iterator node = GetHandlers().GetFirst();
5507 int count = 0;
5508 while (node)
5509 {
5510 wxRichTextFileHandler* handler = (wxRichTextFileHandler*) node->GetData();
5511 if (handler->IsVisible() && ((save && handler->CanSave()) || !save && handler->CanLoad()))
5512 {
5513 if (combine)
5514 {
5515 if (count > 0)
5516 wildcard += wxT(";");
5517 wildcard += wxT("*.") + handler->GetExtension();
5518 }
5519 else
5520 {
5521 if (count > 0)
5522 wildcard += wxT("|");
5523 wildcard += handler->GetName();
5524 wildcard += wxT(" ");
5525 wildcard += _("files");
5526 wildcard += wxT(" (*.");
5527 wildcard += handler->GetExtension();
5528 wildcard += wxT(")|*.");
5529 wildcard += handler->GetExtension();
1e967276
JS
5530 if (types)
5531 types->Add(handler->GetType());
5d7836c4
JS
5532 }
5533 count ++;
5534 }
5535
5536 node = node->GetNext();
5537 }
5538
5539 if (combine)
5540 wildcard = wxT("(") + wildcard + wxT(")|") + wildcard;
5541 return wildcard;
5542}
5543
5544/// Load a file
5545bool wxRichTextBuffer::LoadFile(const wxString& filename, int type)
5546{
5547 wxRichTextFileHandler* handler = FindHandlerFilenameOrType(filename, type);
5548 if (handler)
1e967276 5549 {
44cc96a8 5550 SetDefaultStyle(wxTextAttr());
d2d0adc7 5551 handler->SetFlags(GetHandlerFlags());
1e967276
JS
5552 bool success = handler->LoadFile(this, filename);
5553 Invalidate(wxRICHTEXT_ALL);
5554 return success;
5555 }
5d7836c4
JS
5556 else
5557 return false;
5558}
5559
5560/// Save a file
5561bool wxRichTextBuffer::SaveFile(const wxString& filename, int type)
5562{
5563 wxRichTextFileHandler* handler = FindHandlerFilenameOrType(filename, type);
5564 if (handler)
d2d0adc7
JS
5565 {
5566 handler->SetFlags(GetHandlerFlags());
5d7836c4 5567 return handler->SaveFile(this, filename);
d2d0adc7 5568 }
5d7836c4
JS
5569 else
5570 return false;
5571}
5572
5573/// Load from a stream
5574bool wxRichTextBuffer::LoadFile(wxInputStream& stream, int type)
5575{
5576 wxRichTextFileHandler* handler = FindHandler(type);
5577 if (handler)
1e967276 5578 {
44cc96a8 5579 SetDefaultStyle(wxTextAttr());
d2d0adc7 5580 handler->SetFlags(GetHandlerFlags());
1e967276
JS
5581 bool success = handler->LoadFile(this, stream);
5582 Invalidate(wxRICHTEXT_ALL);
5583 return success;
5584 }
5d7836c4
JS
5585 else
5586 return false;
5587}
5588
5589/// Save to a stream
5590bool wxRichTextBuffer::SaveFile(wxOutputStream& stream, int type)
5591{
5592 wxRichTextFileHandler* handler = FindHandler(type);
5593 if (handler)
d2d0adc7
JS
5594 {
5595 handler->SetFlags(GetHandlerFlags());
5d7836c4 5596 return handler->SaveFile(this, stream);
d2d0adc7 5597 }
5d7836c4
JS
5598 else
5599 return false;
5600}
5601
5602/// Copy the range to the clipboard
5603bool wxRichTextBuffer::CopyToClipboard(const wxRichTextRange& range)
5604{
5605 bool success = false;
11ef729d 5606#if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
0ca07313 5607
d2142335 5608 if (!wxTheClipboard->IsOpened() && wxTheClipboard->Open())
7fe8059f 5609 {
0ca07313
JS
5610 wxTheClipboard->Clear();
5611
5612 // Add composite object
5613
5614 wxDataObjectComposite* compositeObject = new wxDataObjectComposite();
5615
5616 {
5617 wxString text = GetTextForRange(range);
5618
5619#ifdef __WXMSW__
5620 text = wxTextFile::Translate(text, wxTextFileType_Dos);
5621#endif
5622
5623 compositeObject->Add(new wxTextDataObject(text), false /* not preferred */);
5624 }
5625
5626 // Add rich text buffer data object. This needs the XML handler to be present.
5627
5628 if (FindHandler(wxRICHTEXT_TYPE_XML))
5629 {
5630 wxRichTextBuffer* richTextBuf = new wxRichTextBuffer;
5631 CopyFragment(range, *richTextBuf);
5632
5633 compositeObject->Add(new wxRichTextBufferDataObject(richTextBuf), true /* preferred */);
5634 }
5635
5636 if (wxTheClipboard->SetData(compositeObject))
5637 success = true;
5638
5d7836c4
JS
5639 wxTheClipboard->Close();
5640 }
0ca07313 5641
39a1c2f2
WS
5642#else
5643 wxUnusedVar(range);
5644#endif
5d7836c4
JS
5645 return success;
5646}
5647
5648/// Paste the clipboard content to the buffer
5649bool wxRichTextBuffer::PasteFromClipboard(long position)
5650{
5651 bool success = false;
11ef729d 5652#if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
5d7836c4
JS
5653 if (CanPasteFromClipboard())
5654 {
5655 if (wxTheClipboard->Open())
5656 {
0ca07313
JS
5657 if (wxTheClipboard->IsSupported(wxDataFormat(wxRichTextBufferDataObject::GetRichTextBufferFormatId())))
5658 {
5659 wxRichTextBufferDataObject data;
5660 wxTheClipboard->GetData(data);
5661 wxRichTextBuffer* richTextBuffer = data.GetRichTextBuffer();
5662 if (richTextBuffer)
5663 {
5664 InsertParagraphsWithUndo(position+1, *richTextBuffer, GetRichTextCtrl(), wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE);
5665 delete richTextBuffer;
5666 }
5667 }
5668 else if (wxTheClipboard->IsSupported(wxDF_TEXT) || wxTheClipboard->IsSupported(wxDF_UNICODETEXT))
5d7836c4
JS
5669 {
5670 wxTextDataObject data;
5671 wxTheClipboard->GetData(data);
5672 wxString text(data.GetText());
c21f3211
JS
5673#ifdef __WXMSW__
5674 wxString text2;
5675 text2.Alloc(text.Length()+1);
5676 size_t i;
5677 for (i = 0; i < text.Length(); i++)
5678 {
5679 wxChar ch = text[i];
5680 if (ch != wxT('\r'))
5681 text2 += ch;
5682 }
5683#else
5684 wxString text2 = text;
5685#endif
5686 InsertTextWithUndo(position+1, text2, GetRichTextCtrl());
7fe8059f 5687
5d7836c4
JS
5688 success = true;
5689 }
5690 else if (wxTheClipboard->IsSupported(wxDF_BITMAP))
5691 {
5692 wxBitmapDataObject data;
5693 wxTheClipboard->GetData(data);
5694 wxBitmap bitmap(data.GetBitmap());
5695 wxImage image(bitmap.ConvertToImage());
5696
5697 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Image"), wxRICHTEXT_INSERT, this, GetRichTextCtrl(), false);
7fe8059f 5698
5d7836c4
JS
5699 action->GetNewParagraphs().AddImage(image);
5700
5701 if (action->GetNewParagraphs().GetChildCount() == 1)
5702 action->GetNewParagraphs().SetPartialParagraph(true);
7fe8059f 5703
5d7836c4 5704 action->SetPosition(position);
7fe8059f 5705
5d7836c4
JS
5706 // Set the range we'll need to delete in Undo
5707 action->SetRange(wxRichTextRange(position, position));
7fe8059f 5708
5d7836c4
JS
5709 SubmitAction(action);
5710
5711 success = true;
5712 }
5713 wxTheClipboard->Close();
5714 }
5715 }
39a1c2f2
WS
5716#else
5717 wxUnusedVar(position);
5718#endif
5d7836c4
JS
5719 return success;
5720}
5721
5722/// Can we paste from the clipboard?
5723bool wxRichTextBuffer::CanPasteFromClipboard() const
5724{
7fe8059f 5725 bool canPaste = false;
11ef729d 5726#if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
d2142335 5727 if (!wxTheClipboard->IsOpened() && wxTheClipboard->Open())
5d7836c4 5728 {
0ca07313
JS
5729 if (wxTheClipboard->IsSupported(wxDF_TEXT) || wxTheClipboard->IsSupported(wxDF_UNICODETEXT) ||
5730 wxTheClipboard->IsSupported(wxDataFormat(wxRichTextBufferDataObject::GetRichTextBufferFormatId())) ||
5731 wxTheClipboard->IsSupported(wxDF_BITMAP))
5d7836c4 5732 {
7fe8059f 5733 canPaste = true;
5d7836c4
JS
5734 }
5735 wxTheClipboard->Close();
5736 }
39a1c2f2 5737#endif
5d7836c4
JS
5738 return canPaste;
5739}
5740
5741/// Dumps contents of buffer for debugging purposes
5742void wxRichTextBuffer::Dump()
5743{
5744 wxString text;
5745 {
5746 wxStringOutputStream stream(& text);
5747 wxTextOutputStream textStream(stream);
5748 Dump(textStream);
5749 }
5750
5751 wxLogDebug(text);
5752}
5753
d2d0adc7
JS
5754/// Add an event handler
5755bool wxRichTextBuffer::AddEventHandler(wxEvtHandler* handler)
5756{
5757 m_eventHandlers.Append(handler);
5758 return true;
5759}
5760
5761/// Remove an event handler
5762bool wxRichTextBuffer::RemoveEventHandler(wxEvtHandler* handler, bool deleteHandler)
5763{
5764 wxList::compatibility_iterator node = m_eventHandlers.Find(handler);
5765 if (node)
5766 {
5767 m_eventHandlers.Erase(node);
5768 if (deleteHandler)
5769 delete handler;
3e541562 5770
d2d0adc7
JS
5771 return true;
5772 }
5773 else
5774 return false;
5775}
5776
5777/// Clear event handlers
5778void wxRichTextBuffer::ClearEventHandlers()
5779{
5780 m_eventHandlers.Clear();
5781}
5782
5783/// Send event to event handlers. If sendToAll is true, will send to all event handlers,
5784/// otherwise will stop at the first successful one.
5785bool wxRichTextBuffer::SendEvent(wxEvent& event, bool sendToAll)
5786{
5787 bool success = false;
5788 for (wxList::compatibility_iterator node = m_eventHandlers.GetFirst(); node; node = node->GetNext())
5789 {
5790 wxEvtHandler* handler = (wxEvtHandler*) node->GetData();
5791 if (handler->ProcessEvent(event))
5792 {
5793 success = true;
5794 if (!sendToAll)
5795 return true;
5796 }
5797 }
5798 return success;
5799}
5800
5801/// Set style sheet and notify of the change
5802bool wxRichTextBuffer::SetStyleSheetAndNotify(wxRichTextStyleSheet* sheet)
5803{
5804 wxRichTextStyleSheet* oldSheet = GetStyleSheet();
3e541562 5805
d2d0adc7
JS
5806 wxWindowID id = wxID_ANY;
5807 if (GetRichTextCtrl())
5808 id = GetRichTextCtrl()->GetId();
3e541562 5809
d2d0adc7
JS
5810 wxRichTextEvent event(wxEVT_COMMAND_RICHTEXT_STYLESHEET_REPLACING, id);
5811 event.SetEventObject(GetRichTextCtrl());
5812 event.SetOldStyleSheet(oldSheet);
5813 event.SetNewStyleSheet(sheet);
5814 event.Allow();
3e541562 5815
d2d0adc7
JS
5816 if (SendEvent(event) && !event.IsAllowed())
5817 {
5818 if (sheet != oldSheet)
5819 delete sheet;
5820
5821 return false;
5822 }
5823
5824 if (oldSheet && oldSheet != sheet)
5825 delete oldSheet;
5826
5827 SetStyleSheet(sheet);
5828
5829 event.SetEventType(wxEVT_COMMAND_RICHTEXT_STYLESHEET_REPLACED);
5830 event.SetOldStyleSheet(NULL);
5831 event.Allow();
5832
5833 return SendEvent(event);
5834}
5835
5836/// Set renderer, deleting old one
5837void wxRichTextBuffer::SetRenderer(wxRichTextRenderer* renderer)
5838{
5839 if (sm_renderer)
5840 delete sm_renderer;
5841 sm_renderer = renderer;
5842}
5843
44cc96a8 5844bool wxRichTextStdRenderer::DrawStandardBullet(wxRichTextParagraph* paragraph, wxDC& dc, const wxTextAttr& bulletAttr, const wxRect& rect)
d2d0adc7
JS
5845{
5846 if (bulletAttr.GetTextColour().Ok())
5847 {
ecb5fbf1
JS
5848 wxCheckSetPen(dc, wxPen(bulletAttr.GetTextColour()));
5849 wxCheckSetBrush(dc, wxBrush(bulletAttr.GetTextColour()));
d2d0adc7
JS
5850 }
5851 else
5852 {
ecb5fbf1
JS
5853 wxCheckSetPen(dc, *wxBLACK_PEN);
5854 wxCheckSetBrush(dc, *wxBLACK_BRUSH);
d2d0adc7
JS
5855 }
5856
5857 wxFont font;
44cc96a8
JS
5858 if (bulletAttr.HasFont())
5859 {
5860 font = paragraph->GetBuffer()->GetFontTable().FindFont(bulletAttr);
5861 }
d2d0adc7
JS
5862 else
5863 font = (*wxNORMAL_FONT);
5864
ecb5fbf1 5865 wxCheckSetFont(dc, font);
d2d0adc7
JS
5866
5867 int charHeight = dc.GetCharHeight();
3e541562 5868
d2d0adc7
JS
5869 int bulletWidth = (int) (((float) charHeight) * wxRichTextBuffer::GetBulletProportion());
5870 int bulletHeight = bulletWidth;
5871
5872 int x = rect.x;
3e541562 5873
d2d0adc7
JS
5874 // Calculate the top position of the character (as opposed to the whole line height)
5875 int y = rect.y + (rect.height - charHeight);
3e541562 5876
d2d0adc7
JS
5877 // Calculate where the bullet should be positioned
5878 y = y + (charHeight+1)/2 - (bulletHeight+1)/2;
3e541562 5879
d2d0adc7 5880 // The margin between a bullet and text.
44219ff0 5881 int margin = paragraph->ConvertTenthsMMToPixels(dc, wxRichTextBuffer::GetBulletRightMargin());
3e541562 5882
d2d0adc7
JS
5883 if (bulletAttr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_RIGHT)
5884 x = rect.x + rect.width - bulletWidth - margin;
5885 else if (bulletAttr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_CENTRE)
5886 x = x + (rect.width)/2 - bulletWidth/2;
3e541562 5887
d2d0adc7
JS
5888 if (bulletAttr.GetBulletName() == wxT("standard/square"))
5889 {
5890 dc.DrawRectangle(x, y, bulletWidth, bulletHeight);
5891 }
5892 else if (bulletAttr.GetBulletName() == wxT("standard/diamond"))
5893 {
5894 wxPoint pts[5];
5895 pts[0].x = x; pts[0].y = y + bulletHeight/2;
5896 pts[1].x = x + bulletWidth/2; pts[1].y = y;
5897 pts[2].x = x + bulletWidth; pts[2].y = y + bulletHeight/2;
5898 pts[3].x = x + bulletWidth/2; pts[3].y = y + bulletHeight;
3e541562 5899
d2d0adc7
JS
5900 dc.DrawPolygon(4, pts);
5901 }
5902 else if (bulletAttr.GetBulletName() == wxT("standard/triangle"))
5903 {
5904 wxPoint pts[3];
5905 pts[0].x = x; pts[0].y = y;
5906 pts[1].x = x + bulletWidth; pts[1].y = y + bulletHeight/2;
5907 pts[2].x = x; pts[2].y = y + bulletHeight;
3e541562 5908
d2d0adc7
JS
5909 dc.DrawPolygon(3, pts);
5910 }
5911 else // "standard/circle", and catch-all
5912 {
5913 dc.DrawEllipse(x, y, bulletWidth, bulletHeight);
3e541562
JS
5914 }
5915
d2d0adc7
JS
5916 return true;
5917}
5918
44cc96a8 5919bool wxRichTextStdRenderer::DrawTextBullet(wxRichTextParagraph* paragraph, wxDC& dc, const wxTextAttr& attr, const wxRect& rect, const wxString& text)
d2d0adc7
JS
5920{
5921 if (!text.empty())
5922 {
5923 wxFont font;
44cc96a8
JS
5924 if ((attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_SYMBOL) && !attr.GetBulletFont().IsEmpty() && attr.HasFont())
5925 {
5926 wxTextAttr fontAttr;
5927 fontAttr.SetFontSize(attr.GetFontSize());
5928 fontAttr.SetFontStyle(attr.GetFontStyle());
5929 fontAttr.SetFontWeight(attr.GetFontWeight());
5930 fontAttr.SetFontUnderlined(attr.GetFontUnderlined());
5931 fontAttr.SetFontFaceName(attr.GetBulletFont());
5932 font = paragraph->GetBuffer()->GetFontTable().FindFont(fontAttr);
5933 }
5934 else if (attr.HasFont())
5935 font = paragraph->GetBuffer()->GetFontTable().FindFont(attr);
d2d0adc7
JS
5936 else
5937 font = (*wxNORMAL_FONT);
5938
ecb5fbf1 5939 wxCheckSetFont(dc, font);
d2d0adc7
JS
5940
5941 if (attr.GetTextColour().Ok())
5942 dc.SetTextForeground(attr.GetTextColour());
5943
5944 dc.SetBackgroundMode(wxTRANSPARENT);
5945
5946 int charHeight = dc.GetCharHeight();
5947 wxCoord tw, th;
5948 dc.GetTextExtent(text, & tw, & th);
5949
5950 int x = rect.x;
5951
5952 // Calculate the top position of the character (as opposed to the whole line height)
3e541562 5953 int y = rect.y + (rect.height - charHeight);
d2d0adc7
JS
5954
5955 // The margin between a bullet and text.
44219ff0 5956 int margin = paragraph->ConvertTenthsMMToPixels(dc, wxRichTextBuffer::GetBulletRightMargin());
3e541562 5957
d2d0adc7
JS
5958 if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_RIGHT)
5959 x = (rect.x + rect.width) - tw - margin;
5960 else if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_CENTRE)
5961 x = x + (rect.width)/2 - tw/2;
5962
5963 dc.DrawText(text, x, y);
3e541562 5964
d2d0adc7
JS
5965 return true;
5966 }
5967 else
5968 return false;
5969}
5970
44cc96a8 5971bool wxRichTextStdRenderer::DrawBitmapBullet(wxRichTextParagraph* WXUNUSED(paragraph), wxDC& WXUNUSED(dc), const wxTextAttr& WXUNUSED(attr), const wxRect& WXUNUSED(rect))
d2d0adc7
JS
5972{
5973 // Currently unimplemented. The intention is to store bitmaps by name in a media store associated
5974 // with the buffer. The store will allow retrieval from memory, disk or other means.
5975 return false;
5976}
5977
5978/// Enumerate the standard bullet names currently supported
5979bool wxRichTextStdRenderer::EnumerateStandardBulletNames(wxArrayString& bulletNames)
5980{
5981 bulletNames.Add(wxT("standard/circle"));
5982 bulletNames.Add(wxT("standard/square"));
5983 bulletNames.Add(wxT("standard/diamond"));
5984 bulletNames.Add(wxT("standard/triangle"));
5985
5986 return true;
5987}
5d7836c4
JS
5988
5989/*
5990 * Module to initialise and clean up handlers
5991 */
5992
5993class wxRichTextModule: public wxModule
5994{
5995DECLARE_DYNAMIC_CLASS(wxRichTextModule)
5996public:
5997 wxRichTextModule() {}
cfa3b256
JS
5998 bool OnInit()
5999 {
d2d0adc7 6000 wxRichTextBuffer::SetRenderer(new wxRichTextStdRenderer);
cfa3b256
JS
6001 wxRichTextBuffer::InitStandardHandlers();
6002 wxRichTextParagraph::InitDefaultTabs();
6003 return true;
47b378bd 6004 }
cfa3b256
JS
6005 void OnExit()
6006 {
6007 wxRichTextBuffer::CleanUpHandlers();
6008 wxRichTextDecimalToRoman(-1);
6009 wxRichTextParagraph::ClearDefaultTabs();
dadd4f55 6010 wxRichTextCtrl::ClearAvailableFontNames();
d2d0adc7 6011 wxRichTextBuffer::SetRenderer(NULL);
47b378bd 6012 }
5d7836c4
JS
6013};
6014
6015IMPLEMENT_DYNAMIC_CLASS(wxRichTextModule, wxModule)
6016
6017
f1d6804f
RD
6018// If the richtext lib is dynamically loaded after the app has already started
6019// (such as from wxPython) then the built-in module system will not init this
6020// module. Provide this function to do it manually.
6021void wxRichTextModuleInit()
6022{
6023 wxModule* module = new wxRichTextModule;
6024 module->Init();
6025 wxModule::RegisterModule(module);
6026}
6027
6028
5d7836c4
JS
6029/*!
6030 * Commands for undo/redo
6031 *
6032 */
6033
6034wxRichTextCommand::wxRichTextCommand(const wxString& name, wxRichTextCommandId id, wxRichTextBuffer* buffer,
7fe8059f 6035 wxRichTextCtrl* ctrl, bool ignoreFirstTime): wxCommand(true, name)
5d7836c4
JS
6036{
6037 /* wxRichTextAction* action = */ new wxRichTextAction(this, name, id, buffer, ctrl, ignoreFirstTime);
6038}
6039
7fe8059f 6040wxRichTextCommand::wxRichTextCommand(const wxString& name): wxCommand(true, name)
5d7836c4
JS
6041{
6042}
6043
6044wxRichTextCommand::~wxRichTextCommand()
6045{
6046 ClearActions();
6047}
6048
6049void wxRichTextCommand::AddAction(wxRichTextAction* action)
6050{
6051 if (!m_actions.Member(action))
6052 m_actions.Append(action);
6053}
6054
6055bool wxRichTextCommand::Do()
6056{
09f14108 6057 for (wxList::compatibility_iterator node = m_actions.GetFirst(); node; node = node->GetNext())
5d7836c4
JS
6058 {
6059 wxRichTextAction* action = (wxRichTextAction*) node->GetData();
6060 action->Do();
6061 }
6062
6063 return true;
6064}
6065
6066bool wxRichTextCommand::Undo()
6067{
09f14108 6068 for (wxList::compatibility_iterator node = m_actions.GetLast(); node; node = node->GetPrevious())
5d7836c4
JS
6069 {
6070 wxRichTextAction* action = (wxRichTextAction*) node->GetData();
6071 action->Undo();
6072 }
6073
6074 return true;
6075}
6076
6077void wxRichTextCommand::ClearActions()
6078{
6079 WX_CLEAR_LIST(wxList, m_actions);
6080}
6081
6082/*!
6083 * Individual action
6084 *
6085 */
6086
6087wxRichTextAction::wxRichTextAction(wxRichTextCommand* cmd, const wxString& name, wxRichTextCommandId id, wxRichTextBuffer* buffer,
6088 wxRichTextCtrl* ctrl, bool ignoreFirstTime)
6089{
6090 m_buffer = buffer;
6091 m_ignoreThis = ignoreFirstTime;
6092 m_cmdId = id;
6093 m_position = -1;
6094 m_ctrl = ctrl;
6095 m_name = name;
6096 m_newParagraphs.SetDefaultStyle(buffer->GetDefaultStyle());
6097 m_newParagraphs.SetBasicStyle(buffer->GetBasicStyle());
6098 if (cmd)
6099 cmd->AddAction(this);
6100}
6101
6102wxRichTextAction::~wxRichTextAction()
6103{
6104}
6105
6106bool wxRichTextAction::Do()
6107{
6108 m_buffer->Modify(true);
6109
6110 switch (m_cmdId)
6111 {
6112 case wxRICHTEXT_INSERT:
6113 {
ea160b2e
JS
6114 // Store a list of line start character and y positions so we can figure out which area
6115 // we need to refresh
6116 wxArrayInt optimizationLineCharPositions;
6117 wxArrayInt optimizationLineYPositions;
6118
6119#if wxRICHTEXT_USE_OPTIMIZED_DRAWING
6120 // NOTE: we're assuming that the buffer is laid out correctly at this point.
6121 // If we had several actions, which only invalidate and leave layout until the
6122 // paint handler is called, then this might not be true. So we may need to switch
6123 // optimisation on only when we're simply adding text and not simultaneously
6124 // deleting a selection, for example. Or, we make sure the buffer is laid out correctly
6125 // first, but of course this means we'll be doing it twice.
6126 if (!m_buffer->GetDirty() && m_ctrl) // can only do optimisation if the buffer is already laid out correctly
6127 {
6128 wxSize clientSize = m_ctrl->GetClientSize();
6129 wxPoint firstVisiblePt = m_ctrl->GetFirstVisiblePoint();
6130 int lastY = firstVisiblePt.y + clientSize.y;
3e541562 6131
ea160b2e
JS
6132 wxRichTextParagraph* para = m_buffer->GetParagraphAtPosition(GetPosition());
6133 wxRichTextObjectList::compatibility_iterator node = m_buffer->GetChildren().Find(para);
6134 while (node)
6135 {
6136 wxRichTextParagraph* child = (wxRichTextParagraph*) node->GetData();
6137 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
6138 while (node2)
6139 {
6140 wxRichTextLine* line = node2->GetData();
6141 wxPoint pt = line->GetAbsolutePosition();
6142 wxRichTextRange range = line->GetAbsoluteRange();
3e541562 6143
ea160b2e
JS
6144 if (pt.y > lastY)
6145 {
27b5dab3
JS
6146 node2 = wxRichTextLineList::compatibility_iterator();
6147 node = wxRichTextObjectList::compatibility_iterator();
ea160b2e
JS
6148 }
6149 else if (range.GetStart() > GetPosition() && pt.y >= firstVisiblePt.y)
3e541562 6150 {
ea160b2e
JS
6151 optimizationLineCharPositions.Add(range.GetStart());
6152 optimizationLineYPositions.Add(pt.y);
6153 }
6154
6155 if (node2)
6156 node2 = node2->GetNext();
6157 }
3e541562 6158
ea160b2e
JS
6159 if (node)
6160 node = node->GetNext();
6161 }
3e541562 6162 }
ea160b2e
JS
6163#endif
6164
5d7836c4
JS
6165 m_buffer->InsertFragment(GetPosition(), m_newParagraphs);
6166 m_buffer->UpdateRanges();
1e967276 6167 m_buffer->Invalidate(GetRange());
5d7836c4 6168
0ca07313
JS
6169 long newCaretPosition = GetPosition() + m_newParagraphs.GetRange().GetLength();
6170
6171 // Character position to caret position
6172 newCaretPosition --;
6173
6174 // Don't take into account the last newline
5d7836c4
JS
6175 if (m_newParagraphs.GetPartialParagraph())
6176 newCaretPosition --;
46ee0e5b 6177 else
7c081bd2 6178 if (m_newParagraphs.GetChildren().GetCount() > 1)
46ee0e5b
JS
6179 {
6180 wxRichTextObject* p = (wxRichTextObject*) m_newParagraphs.GetChildren().GetLast()->GetData();
6181 if (p->GetRange().GetLength() == 1)
6182 newCaretPosition --;
6183 }
5d7836c4 6184
3e541562 6185 newCaretPosition = wxMin(newCaretPosition, (m_buffer->GetRange().GetEnd()-1));
0ca07313 6186
ea160b2e
JS
6187 if (optimizationLineCharPositions.GetCount() > 0)
6188 UpdateAppearance(newCaretPosition, true /* send update event */, & optimizationLineCharPositions, & optimizationLineYPositions);
6189 else
6190 UpdateAppearance(newCaretPosition, true /* send update event */);
3e541562 6191
5912d19e
JS
6192 wxRichTextEvent cmdEvent(
6193 wxEVT_COMMAND_RICHTEXT_CONTENT_INSERTED,
6194 m_ctrl ? m_ctrl->GetId() : -1);
6195 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
6196 cmdEvent.SetRange(GetRange());
6197 cmdEvent.SetPosition(GetRange().GetStart());
3e541562 6198
5912d19e 6199 m_buffer->SendEvent(cmdEvent);
5d7836c4
JS
6200
6201 break;
6202 }
6203 case wxRICHTEXT_DELETE:
6204 {
6205 m_buffer->DeleteRange(GetRange());
6206 m_buffer->UpdateRanges();
1e967276 6207 m_buffer->Invalidate(wxRichTextRange(GetRange().GetStart(), GetRange().GetStart()));
5d7836c4
JS
6208
6209 UpdateAppearance(GetRange().GetStart()-1, true /* send update event */);
6210
5912d19e
JS
6211 wxRichTextEvent cmdEvent(
6212 wxEVT_COMMAND_RICHTEXT_CONTENT_DELETED,
6213 m_ctrl ? m_ctrl->GetId() : -1);
6214 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
6215 cmdEvent.SetRange(GetRange());
6216 cmdEvent.SetPosition(GetRange().GetStart());
3e541562 6217
5912d19e
JS
6218 m_buffer->SendEvent(cmdEvent);
6219
5d7836c4
JS
6220 break;
6221 }
6222 case wxRICHTEXT_CHANGE_STYLE:
6223 {
6224 ApplyParagraphs(GetNewParagraphs());
1e967276 6225 m_buffer->Invalidate(GetRange());
5d7836c4
JS
6226
6227 UpdateAppearance(GetPosition());
6228
5912d19e
JS
6229 wxRichTextEvent cmdEvent(
6230 wxEVT_COMMAND_RICHTEXT_STYLE_CHANGED,
6231 m_ctrl ? m_ctrl->GetId() : -1);
6232 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
6233 cmdEvent.SetRange(GetRange());
6234 cmdEvent.SetPosition(GetRange().GetStart());
3e541562 6235
5912d19e
JS
6236 m_buffer->SendEvent(cmdEvent);
6237
5d7836c4
JS
6238 break;
6239 }
6240 default:
6241 break;
6242 }
6243
6244 return true;
6245}
6246
6247bool wxRichTextAction::Undo()
6248{
6249 m_buffer->Modify(true);
6250
6251 switch (m_cmdId)
6252 {
6253 case wxRICHTEXT_INSERT:
6254 {
6255 m_buffer->DeleteRange(GetRange());
6256 m_buffer->UpdateRanges();
1e967276 6257 m_buffer->Invalidate(wxRichTextRange(GetRange().GetStart(), GetRange().GetStart()));
5d7836c4
JS
6258
6259 long newCaretPosition = GetPosition() - 1;
3e541562 6260
5d7836c4
JS
6261 UpdateAppearance(newCaretPosition, true /* send update event */);
6262
5912d19e
JS
6263 wxRichTextEvent cmdEvent(
6264 wxEVT_COMMAND_RICHTEXT_CONTENT_DELETED,
6265 m_ctrl ? m_ctrl->GetId() : -1);
6266 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
6267 cmdEvent.SetRange(GetRange());
6268 cmdEvent.SetPosition(GetRange().GetStart());
3e541562 6269
5912d19e
JS
6270 m_buffer->SendEvent(cmdEvent);
6271
5d7836c4
JS
6272 break;
6273 }
6274 case wxRICHTEXT_DELETE:
6275 {
6276 m_buffer->InsertFragment(GetRange().GetStart(), m_oldParagraphs);
6277 m_buffer->UpdateRanges();
1e967276 6278 m_buffer->Invalidate(GetRange());
5d7836c4
JS
6279
6280 UpdateAppearance(GetPosition(), true /* send update event */);
6281
5912d19e
JS
6282 wxRichTextEvent cmdEvent(
6283 wxEVT_COMMAND_RICHTEXT_CONTENT_INSERTED,
6284 m_ctrl ? m_ctrl->GetId() : -1);
6285 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
6286 cmdEvent.SetRange(GetRange());
6287 cmdEvent.SetPosition(GetRange().GetStart());
3e541562 6288
5912d19e
JS
6289 m_buffer->SendEvent(cmdEvent);
6290
5d7836c4
JS
6291 break;
6292 }
6293 case wxRICHTEXT_CHANGE_STYLE:
6294 {
6295 ApplyParagraphs(GetOldParagraphs());
1e967276 6296 m_buffer->Invalidate(GetRange());
5d7836c4
JS
6297
6298 UpdateAppearance(GetPosition());
6299
5912d19e
JS
6300 wxRichTextEvent cmdEvent(
6301 wxEVT_COMMAND_RICHTEXT_STYLE_CHANGED,
6302 m_ctrl ? m_ctrl->GetId() : -1);
6303 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
6304 cmdEvent.SetRange(GetRange());
6305 cmdEvent.SetPosition(GetRange().GetStart());
3e541562 6306
5912d19e
JS
6307 m_buffer->SendEvent(cmdEvent);
6308
5d7836c4
JS
6309 break;
6310 }
6311 default:
6312 break;
6313 }
6314
6315 return true;
6316}
6317
6318/// Update the control appearance
ea160b2e 6319void wxRichTextAction::UpdateAppearance(long caretPosition, bool sendUpdateEvent, wxArrayInt* optimizationLineCharPositions, wxArrayInt* optimizationLineYPositions)
5d7836c4
JS
6320{
6321 if (m_ctrl)
6322 {
6323 m_ctrl->SetCaretPosition(caretPosition);
6324 if (!m_ctrl->IsFrozen())
6325 {
2f36e8dc 6326 m_ctrl->LayoutContent();
5d7836c4 6327 m_ctrl->PositionCaret();
3e541562 6328
ea160b2e
JS
6329#if wxRICHTEXT_USE_OPTIMIZED_DRAWING
6330 // Find refresh rectangle if we are in a position to optimise refresh
6331 if (m_cmdId == wxRICHTEXT_INSERT && optimizationLineCharPositions && optimizationLineCharPositions->GetCount() > 0)
6332 {
6333 size_t i;
3e541562 6334
ea160b2e
JS
6335 wxSize clientSize = m_ctrl->GetClientSize();
6336 wxPoint firstVisiblePt = m_ctrl->GetFirstVisiblePoint();
3e541562 6337
ea160b2e
JS
6338 // Start/end positions
6339 int firstY = 0;
6340 int lastY = firstVisiblePt.y + clientSize.y;
3e541562 6341
ea160b2e
JS
6342 bool foundStart = false;
6343 bool foundEnd = false;
3e541562 6344
ea160b2e
JS
6345 // position offset - how many characters were inserted
6346 int positionOffset = GetRange().GetLength();
6347
6348 // find the first line which is being drawn at the same position as it was
6349 // before. Since we're talking about a simple insertion, we can assume
6350 // that the rest of the window does not need to be redrawn.
3e541562 6351
ea160b2e
JS
6352 wxRichTextParagraph* para = m_buffer->GetParagraphAtPosition(GetPosition());
6353 wxRichTextObjectList::compatibility_iterator node = m_buffer->GetChildren().Find(para);
6354 while (node)
6355 {
6356 wxRichTextParagraph* child = (wxRichTextParagraph*) node->GetData();
6357 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
6358 while (node2)
6359 {
6360 wxRichTextLine* line = node2->GetData();
6361 wxPoint pt = line->GetAbsolutePosition();
6362 wxRichTextRange range = line->GetAbsoluteRange();
3e541562 6363
ea160b2e
JS
6364 // we want to find the first line that is in the same position
6365 // as before. This will mean we're at the end of the changed text.
3e541562 6366
ea160b2e
JS
6367 if (pt.y > lastY) // going past the end of the window, no more info
6368 {
27b5dab3
JS
6369 node2 = wxRichTextLineList::compatibility_iterator();
6370 node = wxRichTextObjectList::compatibility_iterator();
ea160b2e
JS
6371 }
6372 else
6373 {
6374 if (!foundStart)
6375 {
6376 firstY = pt.y - firstVisiblePt.y;
6377 foundStart = true;
6378 }
6379
3e541562 6380 // search for this line being at the same position as before
ea160b2e
JS
6381 for (i = 0; i < optimizationLineCharPositions->GetCount(); i++)
6382 {
6383 if (((*optimizationLineCharPositions)[i] + positionOffset == range.GetStart()) &&
6384 ((*optimizationLineYPositions)[i] == pt.y))
6385 {
6386 // Stop, we're now the same as we were
6387 foundEnd = true;
6388 lastY = pt.y - firstVisiblePt.y;
6389
27b5dab3
JS
6390 node2 = wxRichTextLineList::compatibility_iterator();
6391 node = wxRichTextObjectList::compatibility_iterator();
6392
ea160b2e 6393 break;
3e541562 6394 }
ea160b2e
JS
6395 }
6396 }
6397
6398 if (node2)
6399 node2 = node2->GetNext();
6400 }
3e541562 6401
ea160b2e
JS
6402 if (node)
6403 node = node->GetNext();
6404 }
3e541562 6405
ea160b2e
JS
6406 if (!foundStart)
6407 firstY = firstVisiblePt.y;
6408 if (!foundEnd)
6409 lastY = firstVisiblePt.y + clientSize.y;
6410
6411 wxRect rect(firstVisiblePt.x, firstY, firstVisiblePt.x + clientSize.x, lastY - firstY);
6412 m_ctrl->RefreshRect(rect);
3e541562 6413
ea160b2e
JS
6414 // TODO: we need to make sure that lines are only drawn if in the update region. The rect
6415 // passed to Draw is currently used in different ways (to pass the position the content should
6416 // be drawn at as well as the relevant region).
6417 }
3e541562 6418 else
ea160b2e
JS
6419#endif
6420 m_ctrl->Refresh(false);
5d7836c4
JS
6421
6422 if (sendUpdateEvent)
0ec1179b 6423 wxTextCtrl::SendTextUpdatedEvent(m_ctrl);
5d7836c4 6424 }
7fe8059f 6425 }
5d7836c4
JS
6426}
6427
6428/// Replace the buffer paragraphs with the new ones.
0ca07313 6429void wxRichTextAction::ApplyParagraphs(const wxRichTextParagraphLayoutBox& fragment)
5d7836c4
JS
6430{
6431 wxRichTextObjectList::compatibility_iterator node = fragment.GetChildren().GetFirst();
6432 while (node)
6433 {
6434 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
6435 wxASSERT (para != NULL);
6436
6437 // We'll replace the existing paragraph by finding the paragraph at this position,
6438 // delete its node data, and setting a copy as the new node data.
6439 // TODO: make more efficient by simply swapping old and new paragraph objects.
6440
6441 wxRichTextParagraph* existingPara = m_buffer->GetParagraphAtPosition(para->GetRange().GetStart());
6442 if (existingPara)
6443 {
6444 wxRichTextObjectList::compatibility_iterator bufferParaNode = m_buffer->GetChildren().Find(existingPara);
6445 if (bufferParaNode)
6446 {
6447 wxRichTextParagraph* newPara = new wxRichTextParagraph(*para);
6448 newPara->SetParent(m_buffer);
6449
6450 bufferParaNode->SetData(newPara);
6451
6452 delete existingPara;
6453 }
6454 }
6455
6456 node = node->GetNext();
6457 }
6458}
6459
6460
6461/*!
6462 * wxRichTextRange
6463 * This stores beginning and end positions for a range of data.
6464 */
6465
6466/// Limit this range to be within 'range'
6467bool wxRichTextRange::LimitTo(const wxRichTextRange& range)
6468{
6469 if (m_start < range.m_start)
6470 m_start = range.m_start;
6471
6472 if (m_end > range.m_end)
6473 m_end = range.m_end;
6474
6475 return true;
6476}
6477
6478/*!
6479 * wxRichTextImage implementation
6480 * This object represents an image.
6481 */
6482
6483IMPLEMENT_DYNAMIC_CLASS(wxRichTextImage, wxRichTextObject)
6484
44cc96a8 6485wxRichTextImage::wxRichTextImage(const wxImage& image, wxRichTextObject* parent, wxTextAttr* charStyle):
5d7836c4
JS
6486 wxRichTextObject(parent)
6487{
6488 m_image = image;
4f32b3cf
JS
6489 if (charStyle)
6490 SetAttributes(*charStyle);
5d7836c4
JS
6491}
6492
44cc96a8 6493wxRichTextImage::wxRichTextImage(const wxRichTextImageBlock& imageBlock, wxRichTextObject* parent, wxTextAttr* charStyle):
5d7836c4
JS
6494 wxRichTextObject(parent)
6495{
6496 m_imageBlock = imageBlock;
6497 m_imageBlock.Load(m_image);
4f32b3cf
JS
6498 if (charStyle)
6499 SetAttributes(*charStyle);
5d7836c4
JS
6500}
6501
6502/// Load wxImage from the block
6503bool wxRichTextImage::LoadFromBlock()
6504{
6505 m_imageBlock.Load(m_image);
6506 return m_imageBlock.Ok();
6507}
6508
6509/// Make block from the wxImage
6510bool wxRichTextImage::MakeBlock()
6511{
6512 if (m_imageBlock.GetImageType() == wxBITMAP_TYPE_ANY || m_imageBlock.GetImageType() == -1)
6513 m_imageBlock.SetImageType(wxBITMAP_TYPE_PNG);
6514
6515 m_imageBlock.MakeImageBlock(m_image, m_imageBlock.GetImageType());
6516 return m_imageBlock.Ok();
6517}
6518
6519
6520/// Draw the item
6521bool wxRichTextImage::Draw(wxDC& dc, const wxRichTextRange& range, const wxRichTextRange& selectionRange, const wxRect& rect, int WXUNUSED(descent), int WXUNUSED(style))
6522{
6523 if (!m_image.Ok() && m_imageBlock.Ok())
6524 LoadFromBlock();
6525
6526 if (!m_image.Ok())
6527 return false;
6528
6529 if (m_image.Ok() && !m_bitmap.Ok())
6530 m_bitmap = wxBitmap(m_image);
6531
6532 int y = rect.y + (rect.height - m_image.GetHeight());
6533
6534 if (m_bitmap.Ok())
6535 dc.DrawBitmap(m_bitmap, rect.x, y, true);
6536
6537 if (selectionRange.Contains(range.GetStart()))
6538 {
ecb5fbf1
JS
6539 wxCheckSetBrush(dc, *wxBLACK_BRUSH);
6540 wxCheckSetPen(dc, *wxBLACK_PEN);
5d7836c4
JS
6541 dc.SetLogicalFunction(wxINVERT);
6542 dc.DrawRectangle(rect);
6543 dc.SetLogicalFunction(wxCOPY);
6544 }
6545
6546 return true;
6547}
6548
6549/// Lay the item out
38113684 6550bool wxRichTextImage::Layout(wxDC& WXUNUSED(dc), const wxRect& rect, int WXUNUSED(style))
5d7836c4
JS
6551{
6552 if (!m_image.Ok())
6553 LoadFromBlock();
6554
6555 if (m_image.Ok())
6556 {
6557 SetCachedSize(wxSize(m_image.GetWidth(), m_image.GetHeight()));
6558 SetPosition(rect.GetPosition());
6559 }
6560
6561 return true;
6562}
6563
6564/// Get/set the object size for the given range. Returns false if the range
6565/// is invalid for this object.
7f0d9d71 6566bool wxRichTextImage::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& WXUNUSED(descent), wxDC& WXUNUSED(dc), int WXUNUSED(flags), wxPoint WXUNUSED(position)) const
5d7836c4
JS
6567{
6568 if (!range.IsWithin(GetRange()))
6569 return false;
6570
6571 if (!m_image.Ok())
6572 return false;
6573
6574 size.x = m_image.GetWidth();
6575 size.y = m_image.GetHeight();
6576
6577 return true;
6578}
6579
6580/// Copy
6581void wxRichTextImage::Copy(const wxRichTextImage& obj)
6582{
59509217
JS
6583 wxRichTextObject::Copy(obj);
6584
5d7836c4
JS
6585 m_image = obj.m_image;
6586 m_imageBlock = obj.m_imageBlock;
6587}
6588
6589/*!
6590 * Utilities
6591 *
6592 */
6593
6594/// Compare two attribute objects
44cc96a8 6595bool wxTextAttrEq(const wxTextAttr& attr1, const wxTextAttr& attr2)
5d7836c4 6596{
38f833b1 6597 return (attr1 == attr2);
5d7836c4
JS
6598}
6599
44cc96a8
JS
6600// Partial equality test taking flags into account
6601bool wxTextAttrEqPartial(const wxTextAttr& attr1, const wxTextAttr& attr2, int flags)
6602{
6603 return attr1.EqPartial(attr2, flags);
6604}
5d7836c4 6605
44cc96a8
JS
6606/// Compare tabs
6607bool wxRichTextTabsEq(const wxArrayInt& tabs1, const wxArrayInt& tabs2)
6608{
6609 if (tabs1.GetCount() != tabs2.GetCount())
5d7836c4
JS
6610 return false;
6611
44cc96a8
JS
6612 size_t i;
6613 for (i = 0; i < tabs1.GetCount(); i++)
6614 {
6615 if (tabs1[i] != tabs2[i])
6616 return false;
6617 }
6618 return true;
6619}
5d7836c4 6620
44cc96a8
JS
6621bool wxRichTextApplyStyle(wxTextAttr& destStyle, const wxTextAttr& style, wxTextAttr* compareWith)
6622{
6623 return destStyle.Apply(style, compareWith);
6624}
5d7836c4 6625
44cc96a8
JS
6626// Remove attributes
6627bool wxRichTextRemoveStyle(wxTextAttr& destStyle, const wxTextAttr& style)
6628{
6629 return wxTextAttr::RemoveStyle(destStyle, style);
6630}
5d7836c4 6631
44cc96a8
JS
6632/// Combine two bitlists, specifying the bits of interest with separate flags.
6633bool wxRichTextCombineBitlists(int& valueA, int valueB, int& flagsA, int flagsB)
6634{
6635 return wxTextAttr::CombineBitlists(valueA, valueB, flagsA, flagsB);
6636}
5d7836c4 6637
44cc96a8
JS
6638/// Compare two bitlists
6639bool wxRichTextBitlistsEqPartial(int valueA, int valueB, int flags)
6640{
6641 return wxTextAttr::BitlistsEqPartial(valueA, valueB, flags);
6642}
38f833b1 6643
44cc96a8
JS
6644/// Split into paragraph and character styles
6645bool wxRichTextSplitParaCharStyles(const wxTextAttr& style, wxTextAttr& parStyle, wxTextAttr& charStyle)
6646{
6647 return wxTextAttr::SplitParaCharStyles(style, parStyle, charStyle);
6648}
5d7836c4 6649
44cc96a8
JS
6650/// Convert a decimal to Roman numerals
6651wxString wxRichTextDecimalToRoman(long n)
6652{
6653 static wxArrayInt decimalNumbers;
6654 static wxArrayString romanNumbers;
5d7836c4 6655
44cc96a8
JS
6656 // Clean up arrays
6657 if (n == -1)
6658 {
6659 decimalNumbers.Clear();
6660 romanNumbers.Clear();
6661 return wxEmptyString;
6662 }
5d7836c4 6663
44cc96a8
JS
6664 if (decimalNumbers.GetCount() == 0)
6665 {
6666 #define wxRichTextAddDecRom(n, r) decimalNumbers.Add(n); romanNumbers.Add(r);
59509217 6667
44cc96a8
JS
6668 wxRichTextAddDecRom(1000, wxT("M"));
6669 wxRichTextAddDecRom(900, wxT("CM"));
6670 wxRichTextAddDecRom(500, wxT("D"));
6671 wxRichTextAddDecRom(400, wxT("CD"));
6672 wxRichTextAddDecRom(100, wxT("C"));
6673 wxRichTextAddDecRom(90, wxT("XC"));
6674 wxRichTextAddDecRom(50, wxT("L"));
6675 wxRichTextAddDecRom(40, wxT("XL"));
6676 wxRichTextAddDecRom(10, wxT("X"));
6677 wxRichTextAddDecRom(9, wxT("IX"));
6678 wxRichTextAddDecRom(5, wxT("V"));
6679 wxRichTextAddDecRom(4, wxT("IV"));
6680 wxRichTextAddDecRom(1, wxT("I"));
6681 }
5d7836c4 6682
44cc96a8
JS
6683 int i = 0;
6684 wxString roman;
ea160b2e 6685
44cc96a8 6686 while (n > 0 && i < 13)
42688aea 6687 {
44cc96a8
JS
6688 if (n >= decimalNumbers[i])
6689 {
6690 n -= decimalNumbers[i];
6691 roman += romanNumbers[i];
6692 }
6693 else
6694 {
6695 i ++;
6696 }
42688aea 6697 }
44cc96a8
JS
6698 if (roman.IsEmpty())
6699 roman = wxT("0");
6700 return roman;
6701}
42688aea 6702
44cc96a8
JS
6703/*!
6704 * wxRichTextFileHandler
6705 * Base class for file handlers
6706 */
4d6d8bf4 6707
44cc96a8 6708IMPLEMENT_CLASS(wxRichTextFileHandler, wxObject)
5d7836c4 6709
44cc96a8
JS
6710#if wxUSE_FFILE && wxUSE_STREAMS
6711bool wxRichTextFileHandler::LoadFile(wxRichTextBuffer *buffer, const wxString& filename)
5d7836c4 6712{
44cc96a8
JS
6713 wxFFileInputStream stream(filename);
6714 if (stream.Ok())
6715 return LoadFile(buffer, stream);
5d7836c4 6716
44cc96a8
JS
6717 return false;
6718}
5d7836c4 6719
44cc96a8
JS
6720bool wxRichTextFileHandler::SaveFile(wxRichTextBuffer *buffer, const wxString& filename)
6721{
6722 wxFFileOutputStream stream(filename);
6723 if (stream.Ok())
6724 return SaveFile(buffer, stream);
5d7836c4 6725
44cc96a8
JS
6726 return false;
6727}
6728#endif // wxUSE_FFILE && wxUSE_STREAMS
5d7836c4 6729
44cc96a8
JS
6730/// Can we handle this filename (if using files)? By default, checks the extension.
6731bool wxRichTextFileHandler::CanHandle(const wxString& filename) const
6732{
6733 wxString path, file, ext;
6734 wxSplitPath(filename, & path, & file, & ext);
5d7836c4 6735
44cc96a8
JS
6736 return (ext.Lower() == GetExtension());
6737}
5d7836c4 6738
44cc96a8
JS
6739/*!
6740 * wxRichTextTextHandler
6741 * Plain text handler
6742 */
5d7836c4 6743
44cc96a8 6744IMPLEMENT_CLASS(wxRichTextPlainTextHandler, wxRichTextFileHandler)
5d7836c4 6745
44cc96a8
JS
6746#if wxUSE_STREAMS
6747bool wxRichTextPlainTextHandler::DoLoadFile(wxRichTextBuffer *buffer, wxInputStream& stream)
6748{
6749 if (!stream.IsOk())
797e38dd
JS
6750 return false;
6751
44cc96a8
JS
6752 wxString str;
6753 int lastCh = 0;
5d7836c4 6754
44cc96a8
JS
6755 while (!stream.Eof())
6756 {
6757 int ch = stream.GetC();
5d7836c4 6758
44cc96a8
JS
6759 if (!stream.Eof())
6760 {
6761 if (ch == 10 && lastCh != 13)
6762 str += wxT('\n');
5d7836c4 6763
44cc96a8
JS
6764 if (ch > 0 && ch != 10)
6765 str += wxChar(ch);
5d7836c4 6766
44cc96a8
JS
6767 lastCh = ch;
6768 }
6769 }
5d7836c4 6770
44cc96a8
JS
6771 buffer->ResetAndClearCommands();
6772 buffer->Clear();
6773 buffer->AddParagraphs(str);
6774 buffer->UpdateRanges();
5d7836c4 6775
44cc96a8
JS
6776 return true;
6777}
5d7836c4 6778
44cc96a8
JS
6779bool wxRichTextPlainTextHandler::DoSaveFile(wxRichTextBuffer *buffer, wxOutputStream& stream)
6780{
6781 if (!stream.IsOk())
5d7836c4
JS
6782 return false;
6783
44cc96a8 6784 wxString text = buffer->GetText();
38f833b1 6785
44cc96a8
JS
6786 wxString newLine = wxRichTextLineBreakChar;
6787 text.Replace(newLine, wxT("\n"));
5d7836c4 6788
44cc96a8 6789 wxCharBuffer buf = text.ToAscii();
5d7836c4 6790
44cc96a8
JS
6791 stream.Write((const char*) buf, text.length());
6792 return true;
6793}
6794#endif // wxUSE_STREAMS
5d7836c4 6795
44cc96a8
JS
6796/*
6797 * Stores information about an image, in binary in-memory form
6798 */
59509217 6799
44cc96a8
JS
6800wxRichTextImageBlock::wxRichTextImageBlock()
6801{
6802 Init();
6803}
5d7836c4 6804
44cc96a8
JS
6805wxRichTextImageBlock::wxRichTextImageBlock(const wxRichTextImageBlock& block):wxObject()
6806{
6807 Init();
6808 Copy(block);
6809}
ea160b2e 6810
44cc96a8
JS
6811wxRichTextImageBlock::~wxRichTextImageBlock()
6812{
6813 if (m_data)
42688aea 6814 {
44cc96a8
JS
6815 delete[] m_data;
6816 m_data = NULL;
42688aea 6817 }
5d7836c4
JS
6818}
6819
44cc96a8 6820void wxRichTextImageBlock::Init()
5d7836c4
JS
6821{
6822 m_data = NULL;
6823 m_dataSize = 0;
6824 m_imageType = -1;
6825}
6826
6827void wxRichTextImageBlock::Clear()
6828{
b01ca8b6 6829 delete[] m_data;
5d7836c4
JS
6830 m_data = NULL;
6831 m_dataSize = 0;
6832 m_imageType = -1;
6833}
6834
6835
6836// Load the original image into a memory block.
6837// If the image is not a JPEG, we must convert it into a JPEG
6838// to conserve space.
6839// If it's not a JPEG we can make use of 'image', already scaled, so we don't have to
6840// load the image a 2nd time.
6841
6842bool wxRichTextImageBlock::MakeImageBlock(const wxString& filename, int imageType, wxImage& image, bool convertToJPEG)
6843{
6844 m_imageType = imageType;
6845
6846 wxString filenameToRead(filename);
7fe8059f 6847 bool removeFile = false;
5d7836c4
JS
6848
6849 if (imageType == -1)
7fe8059f 6850 return false; // Could not determine image type
5d7836c4
JS
6851
6852 if ((imageType != wxBITMAP_TYPE_JPEG) && convertToJPEG)
6853 {
6854 wxString tempFile;
6855 bool success = wxGetTempFileName(_("image"), tempFile) ;
6856
6857 wxASSERT(success);
6858
6859 wxUnusedVar(success);
6860
6861 image.SaveFile(tempFile, wxBITMAP_TYPE_JPEG);
6862 filenameToRead = tempFile;
7fe8059f 6863 removeFile = true;
5d7836c4
JS
6864
6865 m_imageType = wxBITMAP_TYPE_JPEG;
6866 }
6867 wxFile file;
6868 if (!file.Open(filenameToRead))
7fe8059f 6869 return false;
5d7836c4
JS
6870
6871 m_dataSize = (size_t) file.Length();
6872 file.Close();
6873
6874 if (m_data)
6875 delete[] m_data;
6876 m_data = ReadBlock(filenameToRead, m_dataSize);
6877
6878 if (removeFile)
6879 wxRemoveFile(filenameToRead);
6880
6881 return (m_data != NULL);
6882}
6883
6884// Make an image block from the wxImage in the given
6885// format.
6886bool wxRichTextImageBlock::MakeImageBlock(wxImage& image, int imageType, int quality)
6887{
6888 m_imageType = imageType;
6889 image.SetOption(wxT("quality"), quality);
6890
6891 if (imageType == -1)
7fe8059f 6892 return false; // Could not determine image type
5d7836c4
JS
6893
6894 wxString tempFile;
6895 bool success = wxGetTempFileName(_("image"), tempFile) ;
7fe8059f 6896
5d7836c4
JS
6897 wxASSERT(success);
6898 wxUnusedVar(success);
7fe8059f 6899
5d7836c4
JS
6900 if (!image.SaveFile(tempFile, m_imageType))
6901 {
6902 if (wxFileExists(tempFile))
6903 wxRemoveFile(tempFile);
7fe8059f 6904 return false;
5d7836c4
JS
6905 }
6906
6907 wxFile file;
6908 if (!file.Open(tempFile))
7fe8059f 6909 return false;
5d7836c4
JS
6910
6911 m_dataSize = (size_t) file.Length();
6912 file.Close();
6913
6914 if (m_data)
6915 delete[] m_data;
6916 m_data = ReadBlock(tempFile, m_dataSize);
6917
6918 wxRemoveFile(tempFile);
6919
6920 return (m_data != NULL);
6921}
6922
6923
6924// Write to a file
6925bool wxRichTextImageBlock::Write(const wxString& filename)
6926{
6927 return WriteBlock(filename, m_data, m_dataSize);
6928}
6929
6930void wxRichTextImageBlock::Copy(const wxRichTextImageBlock& block)
6931{
6932 m_imageType = block.m_imageType;
6933 if (m_data)
6934 {
6935 delete[] m_data;
6936 m_data = NULL;
6937 }
6938 m_dataSize = block.m_dataSize;
6939 if (m_dataSize == 0)
6940 return;
6941
6942 m_data = new unsigned char[m_dataSize];
6943 unsigned int i;
6944 for (i = 0; i < m_dataSize; i++)
6945 m_data[i] = block.m_data[i];
6946}
6947
6948//// Operators
6949void wxRichTextImageBlock::operator=(const wxRichTextImageBlock& block)
6950{
6951 Copy(block);
6952}
6953
6954// Load a wxImage from the block
6955bool wxRichTextImageBlock::Load(wxImage& image)
6956{
6957 if (!m_data)
7fe8059f 6958 return false;
5d7836c4
JS
6959
6960 // Read in the image.
0ca07313 6961#if wxUSE_STREAMS
5d7836c4
JS
6962 wxMemoryInputStream mstream(m_data, m_dataSize);
6963 bool success = image.LoadFile(mstream, GetImageType());
6964#else
6965 wxString tempFile;
6966 bool success = wxGetTempFileName(_("image"), tempFile) ;
6967 wxASSERT(success);
6968
6969 if (!WriteBlock(tempFile, m_data, m_dataSize))
6970 {
7fe8059f 6971 return false;
5d7836c4
JS
6972 }
6973 success = image.LoadFile(tempFile, GetImageType());
6974 wxRemoveFile(tempFile);
6975#endif
6976
6977 return success;
6978}
6979
6980// Write data in hex to a stream
6981bool wxRichTextImageBlock::WriteHex(wxOutputStream& stream)
6982{
351c0647
JS
6983 const int bufSize = 512;
6984 char buf[bufSize+1];
6985
6986 int left = m_dataSize;
6987 int n, i, j;
6988 j = 0;
6989 while (left > 0)
5d7836c4 6990 {
351c0647
JS
6991 if (left*2 > bufSize)
6992 {
6993 n = bufSize; left -= (bufSize/2);
6994 }
6995 else
6996 {
6997 n = left*2; left = 0;
6998 }
7fe8059f 6999
351c0647
JS
7000 char* b = buf;
7001 for (i = 0; i < (n/2); i++)
7002 {
f728025e 7003 wxDecToHex(m_data[j], b, b+1);
351c0647
JS
7004 b += 2; j ++;
7005 }
5d7836c4 7006
351c0647
JS
7007 buf[n] = 0;
7008 stream.Write((const char*) buf, n);
7009 }
5d7836c4
JS
7010 return true;
7011}
7012
7013// Read data in hex from a stream
7014bool wxRichTextImageBlock::ReadHex(wxInputStream& stream, int length, int imageType)
7015{
7016 int dataSize = length/2;
7017
7018 if (m_data)
7019 delete[] m_data;
7020
351c0647 7021 wxChar str[2];
5d7836c4
JS
7022 m_data = new unsigned char[dataSize];
7023 int i;
7024 for (i = 0; i < dataSize; i ++)
7025 {
c9f78968
VS
7026 str[0] = (char)stream.GetC();
7027 str[1] = (char)stream.GetC();
5d7836c4 7028
7fe8059f 7029 m_data[i] = (unsigned char)wxHexToDec(str);
5d7836c4
JS
7030 }
7031
7032 m_dataSize = dataSize;
7033 m_imageType = imageType;
7034
7035 return true;
7036}
7037
5d7836c4
JS
7038// Allocate and read from stream as a block of memory
7039unsigned char* wxRichTextImageBlock::ReadBlock(wxInputStream& stream, size_t size)
7040{
7041 unsigned char* block = new unsigned char[size];
7042 if (!block)
7043 return NULL;
7044
7045 stream.Read(block, size);
7046
7047 return block;
7048}
7049
7050unsigned char* wxRichTextImageBlock::ReadBlock(const wxString& filename, size_t size)
7051{
7052 wxFileInputStream stream(filename);
7053 if (!stream.Ok())
7054 return NULL;
7055
7056 return ReadBlock(stream, size);
7057}
7058
7059// Write memory block to stream
7060bool wxRichTextImageBlock::WriteBlock(wxOutputStream& stream, unsigned char* block, size_t size)
7061{
7062 stream.Write((void*) block, size);
7063 return stream.IsOk();
7064
7065}
7066
7067// Write memory block to file
7068bool wxRichTextImageBlock::WriteBlock(const wxString& filename, unsigned char* block, size_t size)
7069{
7070 wxFileOutputStream outStream(filename);
7071 if (!outStream.Ok())
7fe8059f 7072 return false;
5d7836c4
JS
7073
7074 return WriteBlock(outStream, block, size);
7075}
7076
d2d0adc7
JS
7077// Gets the extension for the block's type
7078wxString wxRichTextImageBlock::GetExtension() const
7079{
7080 wxImageHandler* handler = wxImage::FindHandler(GetImageType());
7081 if (handler)
7082 return handler->GetExtension();
7083 else
7084 return wxEmptyString;
7085}
7086
0ca07313
JS
7087#if wxUSE_DATAOBJ
7088
7089/*!
7090 * The data object for a wxRichTextBuffer
7091 */
7092
7093const wxChar *wxRichTextBufferDataObject::ms_richTextBufferFormatId = wxT("wxShape");
7094
7095wxRichTextBufferDataObject::wxRichTextBufferDataObject(wxRichTextBuffer* richTextBuffer)
7096{
7097 m_richTextBuffer = richTextBuffer;
7098
7099 // this string should uniquely identify our format, but is otherwise
7100 // arbitrary
7101 m_formatRichTextBuffer.SetId(GetRichTextBufferFormatId());
7102
7103 SetFormat(m_formatRichTextBuffer);
7104}
7105
7106wxRichTextBufferDataObject::~wxRichTextBufferDataObject()
7107{
7108 delete m_richTextBuffer;
7109}
7110
7111// after a call to this function, the richTextBuffer is owned by the caller and it
7112// is responsible for deleting it!
7113wxRichTextBuffer* wxRichTextBufferDataObject::GetRichTextBuffer()
7114{
7115 wxRichTextBuffer* richTextBuffer = m_richTextBuffer;
7116 m_richTextBuffer = NULL;
7117
7118 return richTextBuffer;
7119}
7120
7121wxDataFormat wxRichTextBufferDataObject::GetPreferredFormat(Direction WXUNUSED(dir)) const
7122{
7123 return m_formatRichTextBuffer;
7124}
7125
7126size_t wxRichTextBufferDataObject::GetDataSize() const
7127{
7128 if (!m_richTextBuffer)
7129 return 0;
7130
7131 wxString bufXML;
7132
7133 {
7134 wxStringOutputStream stream(& bufXML);
7135 if (!m_richTextBuffer->SaveFile(stream, wxRICHTEXT_TYPE_XML))
7136 {
7137 wxLogError(wxT("Could not write the buffer to an XML stream.\nYou may have forgotten to add the XML file handler."));
7138 return 0;
7139 }
7140 }
7141
7142#if wxUSE_UNICODE
7143 wxCharBuffer buffer = bufXML.mb_str(wxConvUTF8);
7144 return strlen(buffer) + 1;
7145#else
7146 return bufXML.Length()+1;
7147#endif
7148}
7149
7150bool wxRichTextBufferDataObject::GetDataHere(void *pBuf) const
7151{
7152 if (!pBuf || !m_richTextBuffer)
7153 return false;
7154
7155 wxString bufXML;
7156
7157 {
7158 wxStringOutputStream stream(& bufXML);
7159 if (!m_richTextBuffer->SaveFile(stream, wxRICHTEXT_TYPE_XML))
7160 {
7161 wxLogError(wxT("Could not write the buffer to an XML stream.\nYou may have forgotten to add the XML file handler."));
7162 return 0;
7163 }
7164 }
7165
7166#if wxUSE_UNICODE
7167 wxCharBuffer buffer = bufXML.mb_str(wxConvUTF8);
7168 size_t len = strlen(buffer);
7169 memcpy((char*) pBuf, (const char*) buffer, len);
7170 ((char*) pBuf)[len] = 0;
7171#else
7172 size_t len = bufXML.Length();
7173 memcpy((char*) pBuf, (const char*) bufXML.c_str(), len);
7174 ((char*) pBuf)[len] = 0;
7175#endif
7176
7177 return true;
7178}
7179
7180bool wxRichTextBufferDataObject::SetData(size_t WXUNUSED(len), const void *buf)
7181{
7182 delete m_richTextBuffer;
7183 m_richTextBuffer = NULL;
7184
7185 wxString bufXML((const char*) buf, wxConvUTF8);
7186
7187 m_richTextBuffer = new wxRichTextBuffer;
7188
7189 wxStringInputStream stream(bufXML);
7190 if (!m_richTextBuffer->LoadFile(stream, wxRICHTEXT_TYPE_XML))
7191 {
7192 wxLogError(wxT("Could not read the buffer from an XML stream.\nYou may have forgotten to add the XML file handler."));
7193
7194 delete m_richTextBuffer;
7195 m_richTextBuffer = NULL;
7196
7197 return false;
7198 }
7199 return true;
7200}
7201
7202#endif
7203 // wxUSE_DATAOBJ
7204
44cc96a8
JS
7205
7206/*
7207 * wxRichTextFontTable
7208 * Manages quick access to a pool of fonts for rendering rich text
7209 */
7210
d65381ac 7211WX_DECLARE_STRING_HASH_MAP_WITH_DECL(wxFont, wxRichTextFontTableHashMap, class WXDLLIMPEXP_RICHTEXT);
44cc96a8
JS
7212
7213class wxRichTextFontTableData: public wxObjectRefData
7214{
7215public:
7216 wxRichTextFontTableData() {}
7217
7218 wxFont FindFont(const wxTextAttr& fontSpec);
7219
7220 wxRichTextFontTableHashMap m_hashMap;
7221};
7222
7223wxFont wxRichTextFontTableData::FindFont(const wxTextAttr& fontSpec)
7224{
7225 wxString facename(fontSpec.GetFontFaceName());
7226 wxString spec(wxString::Format(wxT("%d-%d-%d-%d-%s-%d"), fontSpec.GetFontSize(), fontSpec.GetFontStyle(), fontSpec.GetFontWeight(), (int) fontSpec.GetFontUnderlined(), facename.c_str(), (int) fontSpec.GetFontEncoding()));
7227 wxRichTextFontTableHashMap::iterator entry = m_hashMap.find(spec);
7228
7229 if ( entry == m_hashMap.end() )
7230 {
7231 wxFont font(fontSpec.GetFontSize(), wxDEFAULT, fontSpec.GetFontStyle(), fontSpec.GetFontWeight(), fontSpec.GetFontUnderlined(), facename.c_str());
7232 m_hashMap[spec] = font;
7233 return font;
7234 }
7235 else
7236 {
7237 return entry->second;
7238 }
7239}
7240
7241IMPLEMENT_DYNAMIC_CLASS(wxRichTextFontTable, wxObject)
7242
7243wxRichTextFontTable::wxRichTextFontTable()
7244{
7245 m_refData = new wxRichTextFontTableData;
44cc96a8
JS
7246}
7247
7248wxRichTextFontTable::wxRichTextFontTable(const wxRichTextFontTable& table)
7249{
7250 (*this) = table;
7251}
7252
7253wxRichTextFontTable::~wxRichTextFontTable()
7254{
7255 UnRef();
7256}
7257
7258bool wxRichTextFontTable::operator == (const wxRichTextFontTable& table) const
7259{
7260 return (m_refData == table.m_refData);
7261}
7262
7263void wxRichTextFontTable::operator= (const wxRichTextFontTable& table)
7264{
7265 Ref(table);
7266}
7267
7268wxFont wxRichTextFontTable::FindFont(const wxTextAttr& fontSpec)
7269{
7270 wxRichTextFontTableData* data = (wxRichTextFontTableData*) m_refData;
7271 if (data)
7272 return data->FindFont(fontSpec);
7273 else
7274 return wxFont();
7275}
7276
7277void wxRichTextFontTable::Clear()
7278{
7279 wxRichTextFontTableData* data = (wxRichTextFontTableData*) m_refData;
7280 if (data)
7281 data->m_hashMap.clear();
7282}
7283
5d7836c4
JS
7284#endif
7285 // wxUSE_RICHTEXT