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