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