]> git.saurik.com Git - wxWidgets.git/blame - src/richtext/richtextbuffer.cpp
[ 1936700 ] wxCAL_SHOW_WEEK_NUMBERS, slightly modified
[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 4319
5d7836c4
JS
4320 // Test for the optimized situations where all is selected, or none
4321 // is selected.
4322
30bf7630
JS
4323 wxFont textFont(GetBuffer()->GetFontTable().FindFont(textAttr));
4324 wxCheckSetFont(dc, textFont);
4325 int charHeight = dc.GetCharHeight();
4326
4327 int x, y;
4328 if ( textFont.Ok() )
4329 {
4330 if ( textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUPERSCRIPT) )
4331 {
4332 double size = static_cast<double>(textFont.GetPointSize()) / wxSCRIPT_MUL_FACTOR;
4333 textFont.SetPointSize( static_cast<int>(size) );
4334 x = rect.x;
4335 y = rect.y;
4336 wxCheckSetFont(dc, textFont);
4337 }
4338 else if ( textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUBSCRIPT) )
4339 {
4340 double size = static_cast<double>(textFont.GetPointSize()) / wxSCRIPT_MUL_FACTOR;
4341 textFont.SetPointSize( static_cast<int>(size) );
4342 x = rect.x;
4343 int sub_height = static_cast<int>( static_cast<double>(charHeight) / wxSCRIPT_MUL_FACTOR);
4344 y = rect.y + (rect.height - sub_height + (descent - m_descent));
4345 wxCheckSetFont(dc, textFont);
4346 }
4347 else
4348 {
4349 x = rect.x;
4350 y = rect.y + (rect.height - charHeight - (descent - m_descent));
4351 }
4352 }
4353 else
4354 {
4355 x = rect.x;
4356 y = rect.y + (rect.height - charHeight - (descent - m_descent));
4357 }
5d7836c4
JS
4358
4359 // (a) All selected.
4360 if (selectionRange.GetStart() <= range.GetStart() && selectionRange.GetEnd() >= range.GetEnd())
ab14c7aa 4361 {
fe5aa22c 4362 DrawTabbedString(dc, textAttr, rect, stringChunk, x, y, true);
5d7836c4
JS
4363 }
4364 // (b) None selected.
4365 else if (selectionRange.GetEnd() < range.GetStart() || selectionRange.GetStart() > range.GetEnd())
4366 {
4367 // Draw all unselected
fe5aa22c 4368 DrawTabbedString(dc, textAttr, rect, stringChunk, x, y, false);
5d7836c4
JS
4369 }
4370 else
4371 {
4372 // (c) Part selected, part not
4373 // Let's draw unselected chunk, selected chunk, then unselected chunk.
4374
04ee05f9 4375 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
7fe8059f 4376
5d7836c4
JS
4377 // 1. Initial unselected chunk, if any, up until start of selection.
4378 if (selectionRange.GetStart() > range.GetStart() && selectionRange.GetStart() <= range.GetEnd())
4379 {
4380 int r1 = range.GetStart();
4381 int s1 = selectionRange.GetStart()-1;
4382 int fragmentLen = s1 - r1 + 1;
4383 if (fragmentLen < 0)
4384 wxLogDebug(wxT("Mid(%d, %d"), (int)(r1 - offset), (int)fragmentLen);
ff76711f 4385 wxString stringFragment = str.Mid(r1 - offset, fragmentLen);
5d7836c4 4386
fe5aa22c 4387 DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, false);
cfa3b256
JS
4388
4389#if USE_KERNING_FIX
4390 if (stringChunk.Find(wxT("\t")) == wxNOT_FOUND)
4391 {
4392 // Compensate for kerning difference
ff76711f
JS
4393 wxString stringFragment2(str.Mid(r1 - offset, fragmentLen+1));
4394 wxString stringFragment3(str.Mid(r1 - offset + fragmentLen, 1));
41a85215 4395
cfa3b256
JS
4396 wxCoord w1, h1, w2, h2, w3, h3;
4397 dc.GetTextExtent(stringFragment, & w1, & h1);
4398 dc.GetTextExtent(stringFragment2, & w2, & h2);
4399 dc.GetTextExtent(stringFragment3, & w3, & h3);
41a85215 4400
cfa3b256
JS
4401 int kerningDiff = (w1 + w3) - w2;
4402 x = x - kerningDiff;
4403 }
4404#endif
5d7836c4
JS
4405 }
4406
4407 // 2. Selected chunk, if any.
4408 if (selectionRange.GetEnd() >= range.GetStart())
4409 {
4410 int s1 = wxMax(selectionRange.GetStart(), range.GetStart());
4411 int s2 = wxMin(selectionRange.GetEnd(), range.GetEnd());
4412
4413 int fragmentLen = s2 - s1 + 1;
4414 if (fragmentLen < 0)
4415 wxLogDebug(wxT("Mid(%d, %d"), (int)(s1 - offset), (int)fragmentLen);
ff76711f 4416 wxString stringFragment = str.Mid(s1 - offset, fragmentLen);
5d7836c4 4417
fe5aa22c 4418 DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, true);
cfa3b256
JS
4419
4420#if USE_KERNING_FIX
4421 if (stringChunk.Find(wxT("\t")) == wxNOT_FOUND)
4422 {
4423 // Compensate for kerning difference
ff76711f
JS
4424 wxString stringFragment2(str.Mid(s1 - offset, fragmentLen+1));
4425 wxString stringFragment3(str.Mid(s1 - offset + fragmentLen, 1));
41a85215 4426
cfa3b256
JS
4427 wxCoord w1, h1, w2, h2, w3, h3;
4428 dc.GetTextExtent(stringFragment, & w1, & h1);
4429 dc.GetTextExtent(stringFragment2, & w2, & h2);
4430 dc.GetTextExtent(stringFragment3, & w3, & h3);
41a85215 4431
cfa3b256
JS
4432 int kerningDiff = (w1 + w3) - w2;
4433 x = x - kerningDiff;
4434 }
4435#endif
5d7836c4
JS
4436 }
4437
4438 // 3. Remaining unselected chunk, if any
4439 if (selectionRange.GetEnd() < range.GetEnd())
4440 {
4441 int s2 = wxMin(selectionRange.GetEnd()+1, range.GetEnd());
4442 int r2 = range.GetEnd();
4443
4444 int fragmentLen = r2 - s2 + 1;
4445 if (fragmentLen < 0)
4446 wxLogDebug(wxT("Mid(%d, %d"), (int)(s2 - offset), (int)fragmentLen);
ff76711f 4447 wxString stringFragment = str.Mid(s2 - offset, fragmentLen);
ab14c7aa 4448
fe5aa22c 4449 DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, false);
7fe8059f 4450 }
5d7836c4
JS
4451 }
4452
4453 return true;
4454}
61399247 4455
44cc96a8 4456bool wxRichTextPlainText::DrawTabbedString(wxDC& dc, const wxTextAttr& attr, const wxRect& rect,wxString& str, wxCoord& x, wxCoord& y, bool selected)
7f0d9d71 4457{
cfa3b256
JS
4458 bool hasTabs = (str.Find(wxT('\t')) != wxNOT_FOUND);
4459
4460 wxArrayInt tabArray;
4461 int tabCount;
4462 if (hasTabs)
ab14c7aa 4463 {
cfa3b256
JS
4464 if (attr.GetTabs().IsEmpty())
4465 tabArray = wxRichTextParagraph::GetDefaultTabs();
4466 else
4467 tabArray = attr.GetTabs();
4468 tabCount = tabArray.GetCount();
4469
4470 for (int i = 0; i < tabCount; ++i)
ab14c7aa 4471 {
cfa3b256
JS
4472 int pos = tabArray[i];
4473 pos = ConvertTenthsMMToPixels(dc, pos);
4474 tabArray[i] = pos;
7f0d9d71
JS
4475 }
4476 }
cfa3b256
JS
4477 else
4478 tabCount = 0;
ab14c7aa 4479
cfa3b256
JS
4480 int nextTabPos = -1;
4481 int tabPos = -1;
7f0d9d71 4482 wxCoord w, h;
ab14c7aa 4483
cfa3b256 4484 if (selected)
ab14c7aa 4485 {
0ec6da02
JS
4486 wxColour highlightColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT));
4487 wxColour highlightTextColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT));
4488
ecb5fbf1
JS
4489 wxCheckSetBrush(dc, wxBrush(highlightColour));
4490 wxCheckSetPen(dc, wxPen(highlightColour));
0ec6da02 4491 dc.SetTextForeground(highlightTextColour);
04ee05f9 4492 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
7f0d9d71 4493 }
ab14c7aa
JS
4494 else
4495 {
fe5aa22c 4496 dc.SetTextForeground(attr.GetTextColour());
ab14c7aa 4497
f0e9eda2
JS
4498 if (attr.HasFlag(wxTEXT_ATTR_BACKGROUND_COLOUR) && attr.GetBackgroundColour().IsOk())
4499 {
04ee05f9 4500 dc.SetBackgroundMode(wxBRUSHSTYLE_SOLID);
f0e9eda2
JS
4501 dc.SetTextBackground(attr.GetBackgroundColour());
4502 }
4503 else
04ee05f9 4504 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
3e541562 4505 }
3e541562 4506
cfa3b256 4507 while (hasTabs)
ab14c7aa
JS
4508 {
4509 // the string has a tab
7f0d9d71
JS
4510 // break up the string at the Tab
4511 wxString stringChunk = str.BeforeFirst(wxT('\t'));
4512 str = str.AfterFirst(wxT('\t'));
4513 dc.GetTextExtent(stringChunk, & w, & h);
cfa3b256 4514 tabPos = x + w;
7f0d9d71 4515 bool not_found = true;
cfa3b256 4516 for (int i = 0; i < tabCount && not_found; ++i)
ab14c7aa 4517 {
cfa3b256 4518 nextTabPos = tabArray.Item(i);
4794d69c
JS
4519
4520 // Find the next tab position.
4521 // Even if we're at the end of the tab array, we must still draw the chunk.
4522
4523 if (nextTabPos > tabPos || (i == (tabCount - 1)))
ab14c7aa 4524 {
4794d69c
JS
4525 if (nextTabPos <= tabPos)
4526 {
4527 int defaultTabWidth = ConvertTenthsMMToPixels(dc, WIDTH_FOR_DEFAULT_TABS);
4528 nextTabPos = tabPos + defaultTabWidth;
4529 }
4530
7f0d9d71 4531 not_found = false;
ab14c7aa
JS
4532 if (selected)
4533 {
cfa3b256 4534 w = nextTabPos - x;
7f0d9d71 4535 wxRect selRect(x, rect.y, w, rect.GetHeight());
61399247 4536 dc.DrawRectangle(selRect);
7f0d9d71
JS
4537 }
4538 dc.DrawText(stringChunk, x, y);
42688aea
JS
4539
4540 if (attr.HasTextEffects() && (attr.GetTextEffects() & wxTEXT_ATTR_EFFECT_STRIKETHROUGH))
4541 {
4542 wxPen oldPen = dc.GetPen();
ecb5fbf1 4543 wxCheckSetPen(dc, wxPen(attr.GetTextColour(), 1));
42688aea 4544 dc.DrawLine(x, (int) (y+(h/2)+0.5), x+w, (int) (y+(h/2)+0.5));
ecb5fbf1 4545 wxCheckSetPen(dc, oldPen);
42688aea
JS
4546 }
4547
cfa3b256 4548 x = nextTabPos;
7f0d9d71
JS
4549 }
4550 }
cfa3b256 4551 hasTabs = (str.Find(wxT('\t')) != wxNOT_FOUND);
7f0d9d71 4552 }
61399247 4553
cfa3b256 4554 if (!str.IsEmpty())
ab14c7aa 4555 {
cfa3b256
JS
4556 dc.GetTextExtent(str, & w, & h);
4557 if (selected)
4558 {
4559 wxRect selRect(x, rect.y, w, rect.GetHeight());
4560 dc.DrawRectangle(selRect);
4561 }
4562 dc.DrawText(str, x, y);
42688aea
JS
4563
4564 if (attr.HasTextEffects() && (attr.GetTextEffects() & wxTEXT_ATTR_EFFECT_STRIKETHROUGH))
4565 {
4566 wxPen oldPen = dc.GetPen();
ecb5fbf1 4567 wxCheckSetPen(dc, wxPen(attr.GetTextColour(), 1));
42688aea 4568 dc.DrawLine(x, (int) (y+(h/2)+0.5), x+w, (int) (y+(h/2)+0.5));
ecb5fbf1 4569 wxCheckSetPen(dc, oldPen);
42688aea
JS
4570 }
4571
cfa3b256 4572 x += w;
7f0d9d71 4573 }
7f0d9d71 4574 return true;
5d7836c4 4575
7f0d9d71 4576}
fe5aa22c 4577
5d7836c4 4578/// Lay the item out
38113684 4579bool wxRichTextPlainText::Layout(wxDC& dc, const wxRect& WXUNUSED(rect), int WXUNUSED(style))
5d7836c4 4580{
ecb5fbf1
JS
4581 // Only lay out if we haven't already cached the size
4582 if (m_size.x == -1)
4583 GetRangeSize(GetRange(), m_size, m_descent, dc, 0, wxPoint(0, 0));
5d7836c4
JS
4584
4585 return true;
4586}
4587
4588/// Copy
4589void wxRichTextPlainText::Copy(const wxRichTextPlainText& obj)
4590{
4591 wxRichTextObject::Copy(obj);
4592
4593 m_text = obj.m_text;
4594}
4595
4596/// Get/set the object size for the given range. Returns false if the range
4597/// is invalid for this object.
7f0d9d71 4598bool wxRichTextPlainText::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, int WXUNUSED(flags), wxPoint position) const
5d7836c4
JS
4599{
4600 if (!range.IsWithin(GetRange()))
4601 return false;
4602
fe5aa22c
JS
4603 wxRichTextParagraph* para = wxDynamicCast(GetParent(), wxRichTextParagraph);
4604 wxASSERT (para != NULL);
4605
44cc96a8 4606 wxTextAttr textAttr(para ? para->GetCombinedAttributes(GetAttributes()) : GetAttributes());
fe5aa22c 4607
5d7836c4
JS
4608 // Always assume unformatted text, since at this level we have no knowledge
4609 // of line breaks - and we don't need it, since we'll calculate size within
4610 // formatted text by doing it in chunks according to the line ranges
4611
30bf7630 4612 bool bScript(false);
44cc96a8 4613 wxFont font(GetBuffer()->GetFontTable().FindFont(textAttr));
30bf7630
JS
4614 if (font.Ok())
4615 {
4616 if ( textAttr.HasTextEffects() && ( (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUPERSCRIPT)
4617 || (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUBSCRIPT) ) )
4618 {
4619 wxFont textFont = font;
4620 double size = static_cast<double>(textFont.GetPointSize()) / wxSCRIPT_MUL_FACTOR;
4621 textFont.SetPointSize( static_cast<int>(size) );
4622 wxCheckSetFont(dc, textFont);
4623 bScript = true;
4624 }
4625 else
4626 {
4627 wxCheckSetFont(dc, font);
4628 }
4629 }
5d7836c4
JS
4630
4631 int startPos = range.GetStart() - GetRange().GetStart();
4632 long len = range.GetLength();
3e541562 4633
ff76711f
JS
4634 wxString str(m_text);
4635 wxString toReplace = wxRichTextLineBreakChar;
4636 str.Replace(toReplace, wxT(" "));
4637
4638 wxString stringChunk = str.Mid(startPos, (size_t) len);
42688aea
JS
4639
4640 if (textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_CAPITALS))
4641 stringChunk.MakeUpper();
4642
5d7836c4 4643 wxCoord w, h;
7f0d9d71 4644 int width = 0;
cfa3b256 4645 if (stringChunk.Find(wxT('\t')) != wxNOT_FOUND)
ab14c7aa
JS
4646 {
4647 // the string has a tab
cfa3b256
JS
4648 wxArrayInt tabArray;
4649 if (textAttr.GetTabs().IsEmpty())
4650 tabArray = wxRichTextParagraph::GetDefaultTabs();
4651 else
4652 tabArray = textAttr.GetTabs();
ab14c7aa 4653
cfa3b256 4654 int tabCount = tabArray.GetCount();
41a85215 4655
cfa3b256 4656 for (int i = 0; i < tabCount; ++i)
61399247 4657 {
cfa3b256
JS
4658 int pos = tabArray[i];
4659 pos = ((wxRichTextPlainText*) this)->ConvertTenthsMMToPixels(dc, pos);
4660 tabArray[i] = pos;
7f0d9d71 4661 }
41a85215 4662
cfa3b256 4663 int nextTabPos = -1;
61399247 4664
ab14c7aa
JS
4665 while (stringChunk.Find(wxT('\t')) >= 0)
4666 {
4667 // the string has a tab
7f0d9d71
JS
4668 // break up the string at the Tab
4669 wxString stringFragment = stringChunk.BeforeFirst(wxT('\t'));
4670 stringChunk = stringChunk.AfterFirst(wxT('\t'));
4671 dc.GetTextExtent(stringFragment, & w, & h);
4672 width += w;
cfa3b256 4673 int absoluteWidth = width + position.x;
4794d69c 4674
cfa3b256
JS
4675 bool notFound = true;
4676 for (int i = 0; i < tabCount && notFound; ++i)
ab14c7aa 4677 {
cfa3b256 4678 nextTabPos = tabArray.Item(i);
4794d69c
JS
4679
4680 // Find the next tab position.
4681 // Even if we're at the end of the tab array, we must still process the chunk.
4682
4683 if (nextTabPos > absoluteWidth || (i == (tabCount - 1)))
ab14c7aa 4684 {
4794d69c
JS
4685 if (nextTabPos <= absoluteWidth)
4686 {
4687 int defaultTabWidth = ((wxRichTextPlainText*) this)->ConvertTenthsMMToPixels(dc, WIDTH_FOR_DEFAULT_TABS);
4688 nextTabPos = absoluteWidth + defaultTabWidth;
4689 }
4690
cfa3b256
JS
4691 notFound = false;
4692 width = nextTabPos - position.x;
7f0d9d71
JS
4693 }
4694 }
4695 }
4696 }
30bf7630 4697
5d7836c4 4698 dc.GetTextExtent(stringChunk, & w, & h, & descent);
7f0d9d71 4699 width += w;
30bf7630
JS
4700
4701 if ( bScript )
4702 dc.SetFont(font);
4703
7f0d9d71 4704 size = wxSize(width, dc.GetCharHeight());
61399247 4705
5d7836c4
JS
4706 return true;
4707}
4708
4709/// Do a split, returning an object containing the second part, and setting
4710/// the first part in 'this'.
4711wxRichTextObject* wxRichTextPlainText::DoSplit(long pos)
4712{
ff76711f 4713 long index = pos - GetRange().GetStart();
3e541562 4714
28f92d74 4715 if (index < 0 || index >= (int) m_text.length())
5d7836c4
JS
4716 return NULL;
4717
4718 wxString firstPart = m_text.Mid(0, index);
4719 wxString secondPart = m_text.Mid(index);
4720
4721 m_text = firstPart;
4722
4723 wxRichTextPlainText* newObject = new wxRichTextPlainText(secondPart);
4724 newObject->SetAttributes(GetAttributes());
4725
4726 newObject->SetRange(wxRichTextRange(pos, GetRange().GetEnd()));
4727 GetRange().SetEnd(pos-1);
3e541562 4728
5d7836c4
JS
4729 return newObject;
4730}
4731
4732/// Calculate range
4733void wxRichTextPlainText::CalculateRange(long start, long& end)
4734{
28f92d74 4735 end = start + m_text.length() - 1;
5d7836c4
JS
4736 m_range.SetRange(start, end);
4737}
4738
4739/// Delete range
4740bool wxRichTextPlainText::DeleteRange(const wxRichTextRange& range)
4741{
4742 wxRichTextRange r = range;
4743
4744 r.LimitTo(GetRange());
4745
4746 if (r.GetStart() == GetRange().GetStart() && r.GetEnd() == GetRange().GetEnd())
4747 {
4748 m_text.Empty();
4749 return true;
4750 }
4751
4752 long startIndex = r.GetStart() - GetRange().GetStart();
4753 long len = r.GetLength();
4754
4755 m_text = m_text.Mid(0, startIndex) + m_text.Mid(startIndex+len);
4756 return true;
4757}
4758
4759/// Get text for the given range.
4760wxString wxRichTextPlainText::GetTextForRange(const wxRichTextRange& range) const
4761{
4762 wxRichTextRange r = range;
4763
4764 r.LimitTo(GetRange());
4765
4766 long startIndex = r.GetStart() - GetRange().GetStart();
4767 long len = r.GetLength();
4768
4769 return m_text.Mid(startIndex, len);
4770}
4771
4772/// Returns true if this object can merge itself with the given one.
4773bool wxRichTextPlainText::CanMerge(wxRichTextObject* object) const
4774{
4775 return object->GetClassInfo() == CLASSINFO(wxRichTextPlainText) &&
7fe8059f 4776 (m_text.empty() || wxTextAttrEq(GetAttributes(), object->GetAttributes()));
5d7836c4
JS
4777}
4778
4779/// Returns true if this object merged itself with the given one.
4780/// The calling code will then delete the given object.
4781bool wxRichTextPlainText::Merge(wxRichTextObject* object)
4782{
4783 wxRichTextPlainText* textObject = wxDynamicCast(object, wxRichTextPlainText);
4784 wxASSERT( textObject != NULL );
4785
4786 if (textObject)
4787 {
4788 m_text += textObject->GetText();
99404ab0 4789 wxRichTextApplyStyle(m_attributes, textObject->GetAttributes());
5d7836c4
JS
4790 return true;
4791 }
4792 else
4793 return false;
4794}
4795
4796/// Dump to output stream for debugging
4797void wxRichTextPlainText::Dump(wxTextOutputStream& stream)
4798{
4799 wxRichTextObject::Dump(stream);
4800 stream << m_text << wxT("\n");
4801}
4802
ff76711f
JS
4803/// Get the first position from pos that has a line break character.
4804long wxRichTextPlainText::GetFirstLineBreakPosition(long pos)
4805{
4806 int i;
4807 int len = m_text.length();
4808 int startPos = pos - m_range.GetStart();
4809 for (i = startPos; i < len; i++)
4810 {
4811 wxChar ch = m_text[i];
4812 if (ch == wxRichTextLineBreakChar)
4813 {
4814 return i + m_range.GetStart();
4815 }
4816 }
4817 return -1;
4818}
4819
5d7836c4
JS
4820/*!
4821 * wxRichTextBuffer
4822 * This is a kind of box, used to represent the whole buffer
4823 */
4824
4825IMPLEMENT_DYNAMIC_CLASS(wxRichTextBuffer, wxRichTextParagraphLayoutBox)
4826
d2d0adc7
JS
4827wxList wxRichTextBuffer::sm_handlers;
4828wxRichTextRenderer* wxRichTextBuffer::sm_renderer = NULL;
4829int wxRichTextBuffer::sm_bulletRightMargin = 20;
4830float wxRichTextBuffer::sm_bulletProportion = (float) 0.3;
5d7836c4
JS
4831
4832/// Initialisation
4833void wxRichTextBuffer::Init()
4834{
4835 m_commandProcessor = new wxCommandProcessor;
4836 m_styleSheet = NULL;
4837 m_modified = false;
4838 m_batchedCommandDepth = 0;
4839 m_batchedCommand = NULL;
4840 m_suppressUndo = 0;
d2d0adc7 4841 m_handlerFlags = 0;
44219ff0 4842 m_scale = 1.0;
5d7836c4
JS
4843}
4844
4845/// Initialisation
4846wxRichTextBuffer::~wxRichTextBuffer()
4847{
4848 delete m_commandProcessor;
4849 delete m_batchedCommand;
4850
4851 ClearStyleStack();
d2d0adc7 4852 ClearEventHandlers();
5d7836c4
JS
4853}
4854
85d8909b 4855void wxRichTextBuffer::ResetAndClearCommands()
5d7836c4 4856{
85d8909b 4857 Reset();
3e541562 4858
5d7836c4 4859 GetCommandProcessor()->ClearCommands();
5d7836c4 4860
5d7836c4 4861 Modify(false);
1e967276 4862 Invalidate(wxRICHTEXT_ALL);
5d7836c4
JS
4863}
4864
0ca07313
JS
4865void wxRichTextBuffer::Copy(const wxRichTextBuffer& obj)
4866{
4867 wxRichTextParagraphLayoutBox::Copy(obj);
4868
4869 m_styleSheet = obj.m_styleSheet;
4870 m_modified = obj.m_modified;
4871 m_batchedCommandDepth = obj.m_batchedCommandDepth;
4872 m_batchedCommand = obj.m_batchedCommand;
4873 m_suppressUndo = obj.m_suppressUndo;
4874}
4875
38f833b1
JS
4876/// Push style sheet to top of stack
4877bool wxRichTextBuffer::PushStyleSheet(wxRichTextStyleSheet* styleSheet)
4878{
4879 if (m_styleSheet)
4880 styleSheet->InsertSheet(m_styleSheet);
4881
4882 SetStyleSheet(styleSheet);
41a85215 4883
38f833b1
JS
4884 return true;
4885}
4886
4887/// Pop style sheet from top of stack
4888wxRichTextStyleSheet* wxRichTextBuffer::PopStyleSheet()
4889{
4890 if (m_styleSheet)
4891 {
4892 wxRichTextStyleSheet* oldSheet = m_styleSheet;
4893 m_styleSheet = oldSheet->GetNextSheet();
4894 oldSheet->Unlink();
41a85215 4895
38f833b1
JS
4896 return oldSheet;
4897 }
4898 else
4899 return NULL;
4900}
4901
0ca07313
JS
4902/// Submit command to insert paragraphs
4903bool wxRichTextBuffer::InsertParagraphsWithUndo(long pos, const wxRichTextParagraphLayoutBox& paragraphs, wxRichTextCtrl* ctrl, int flags)
4904{
4905 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, this, ctrl, false);
4906
44cc96a8 4907 wxTextAttr attr(GetDefaultStyle());
4f32b3cf 4908
44cc96a8
JS
4909 wxTextAttr* p = NULL;
4910 wxTextAttr paraAttr;
0ca07313
JS
4911 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
4912 {
4913 paraAttr = GetStyleForNewParagraph(pos);
4914 if (!paraAttr.IsDefault())
4915 p = & paraAttr;
4916 }
4f32b3cf
JS
4917 else
4918 p = & attr;
0ca07313
JS
4919
4920 action->GetNewParagraphs() = paragraphs;
59509217 4921
0ca07313
JS
4922 action->SetPosition(pos);
4923
99404ab0
JS
4924 wxRichTextRange range = wxRichTextRange(pos, pos + paragraphs.GetRange().GetEnd() - 1);
4925 if (!paragraphs.GetPartialParagraph())
4926 range.SetEnd(range.GetEnd()+1);
4927
0ca07313 4928 // Set the range we'll need to delete in Undo
99404ab0 4929 action->SetRange(range);
0ca07313
JS
4930
4931 SubmitAction(action);
4932
4933 return true;
4934}
4935
5d7836c4 4936/// Submit command to insert the given text
fe5aa22c 4937bool wxRichTextBuffer::InsertTextWithUndo(long pos, const wxString& text, wxRichTextCtrl* ctrl, int flags)
5d7836c4
JS
4938{
4939 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, this, ctrl, false);
4940
44cc96a8
JS
4941 wxTextAttr* p = NULL;
4942 wxTextAttr paraAttr;
fe5aa22c
JS
4943 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
4944 {
7c081bd2
JS
4945 // Get appropriate paragraph style
4946 paraAttr = GetStyleForNewParagraph(pos, false, false);
fe5aa22c
JS
4947 if (!paraAttr.IsDefault())
4948 p = & paraAttr;
4949 }
4950
fe5aa22c 4951 action->GetNewParagraphs().AddParagraphs(text, p);
0ca07313
JS
4952
4953 int length = action->GetNewParagraphs().GetRange().GetLength();
4954
4955 if (text.length() > 0 && text.Last() != wxT('\n'))
4956 {
4957 // Don't count the newline when undoing
4958 length --;
5d7836c4 4959 action->GetNewParagraphs().SetPartialParagraph(true);
0ca07313 4960 }
46ee0e5b
JS
4961 else if (text.length() > 0 && text.Last() == wxT('\n'))
4962 length --;
5d7836c4
JS
4963
4964 action->SetPosition(pos);
4965
4966 // Set the range we'll need to delete in Undo
0ca07313 4967 action->SetRange(wxRichTextRange(pos, pos + length - 1));
7fe8059f 4968
5d7836c4 4969 SubmitAction(action);
7fe8059f 4970
5d7836c4
JS
4971 return true;
4972}
4973
4974/// Submit command to insert the given text
fe5aa22c 4975bool wxRichTextBuffer::InsertNewlineWithUndo(long pos, wxRichTextCtrl* ctrl, int flags)
5d7836c4
JS
4976{
4977 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, this, ctrl, false);
4978
44cc96a8
JS
4979 wxTextAttr* p = NULL;
4980 wxTextAttr paraAttr;
fe5aa22c
JS
4981 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
4982 {
7c081bd2 4983 paraAttr = GetStyleForNewParagraph(pos, false, true /* look for next paragraph style */);
fe5aa22c
JS
4984 if (!paraAttr.IsDefault())
4985 p = & paraAttr;
4986 }
4987
44cc96a8 4988 wxTextAttr attr(GetDefaultStyle());
7fe8059f
WS
4989
4990 wxRichTextParagraph* newPara = new wxRichTextParagraph(wxEmptyString, this, & attr);
5d7836c4
JS
4991 action->GetNewParagraphs().AppendChild(newPara);
4992 action->GetNewParagraphs().UpdateRanges();
4993 action->GetNewParagraphs().SetPartialParagraph(false);
c025e094
JS
4994 wxRichTextParagraph* para = GetParagraphAtPosition(pos, false);
4995 long pos1 = pos;
4996
6c0ea513
JS
4997 if (p)
4998 newPara->SetAttributes(*p);
4999
c025e094
JS
5000 if (flags & wxRICHTEXT_INSERT_INTERACTIVE)
5001 {
5002 if (para && para->GetRange().GetEnd() == pos)
5003 pos1 ++;
6c0ea513
JS
5004 if (newPara->GetAttributes().HasBulletNumber())
5005 newPara->GetAttributes().SetBulletNumber(newPara->GetAttributes().GetBulletNumber()+1);
c025e094
JS
5006 }
5007
5d7836c4
JS
5008 action->SetPosition(pos);
5009
c025e094 5010 // Use the default character style
99404ab0
JS
5011 // Use the default character style
5012 if (!GetDefaultStyle().IsDefault() && newPara->GetChildren().GetFirst())
c025e094
JS
5013 {
5014 // Check whether the default style merely reflects the paragraph/basic style,
5015 // in which case don't apply it.
5016 wxTextAttrEx defaultStyle(GetDefaultStyle());
5017 wxTextAttrEx toApply;
5018 if (para)
5019 {
5020 wxRichTextAttr combinedAttr = para->GetCombinedAttributes();
5021 wxTextAttrEx newAttr;
5022 // This filters out attributes that are accounted for by the current
5023 // paragraph/basic style
5024 wxRichTextApplyStyle(toApply, defaultStyle, & combinedAttr);
5025 }
5026 else
5027 toApply = defaultStyle;
5028
5029 if (!toApply.IsDefault())
5030 newPara->GetChildren().GetFirst()->GetData()->SetAttributes(toApply);
5031 }
99404ab0 5032
5d7836c4 5033 // Set the range we'll need to delete in Undo
c025e094 5034 action->SetRange(wxRichTextRange(pos1, pos1));
7fe8059f 5035
5d7836c4 5036 SubmitAction(action);
7fe8059f 5037
5d7836c4
JS
5038 return true;
5039}
5040
5041/// Submit command to insert the given image
fe5aa22c 5042bool wxRichTextBuffer::InsertImageWithUndo(long pos, const wxRichTextImageBlock& imageBlock, wxRichTextCtrl* ctrl, int flags)
5d7836c4
JS
5043{
5044 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Image"), wxRICHTEXT_INSERT, this, ctrl, false);
5045
44cc96a8
JS
5046 wxTextAttr* p = NULL;
5047 wxTextAttr paraAttr;
fe5aa22c
JS
5048 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
5049 {
5050 paraAttr = GetStyleForNewParagraph(pos);
5051 if (!paraAttr.IsDefault())
5052 p = & paraAttr;
5053 }
5054
44cc96a8 5055 wxTextAttr attr(GetDefaultStyle());
7fe8059f 5056
5d7836c4 5057 wxRichTextParagraph* newPara = new wxRichTextParagraph(this, & attr);
fe5aa22c
JS
5058 if (p)
5059 newPara->SetAttributes(*p);
5060
5d7836c4
JS
5061 wxRichTextImage* imageObject = new wxRichTextImage(imageBlock, newPara);
5062 newPara->AppendChild(imageObject);
5063 action->GetNewParagraphs().AppendChild(newPara);
5064 action->GetNewParagraphs().UpdateRanges();
5065
5066 action->GetNewParagraphs().SetPartialParagraph(true);
5067
5068 action->SetPosition(pos);
5069
5070 // Set the range we'll need to delete in Undo
5071 action->SetRange(wxRichTextRange(pos, pos));
7fe8059f 5072
5d7836c4 5073 SubmitAction(action);
7fe8059f 5074
5d7836c4
JS
5075 return true;
5076}
5077
fe5aa22c
JS
5078/// Get the style that is appropriate for a new paragraph at this position.
5079/// If the previous paragraph has a paragraph style name, look up the next-paragraph
5080/// style.
44cc96a8 5081wxTextAttr wxRichTextBuffer::GetStyleForNewParagraph(long pos, bool caretPosition, bool lookUpNewParaStyle) const
fe5aa22c
JS
5082{
5083 wxRichTextParagraph* para = GetParagraphAtPosition(pos, caretPosition);
5084 if (para)
5085 {
44cc96a8 5086 wxTextAttr attr;
d2d0adc7 5087 bool foundAttributes = false;
3e541562 5088
d2d0adc7 5089 // Look for a matching paragraph style
7c081bd2 5090 if (lookUpNewParaStyle && !para->GetAttributes().GetParagraphStyleName().IsEmpty() && GetStyleSheet())
fe5aa22c
JS
5091 {
5092 wxRichTextParagraphStyleDefinition* paraDef = GetStyleSheet()->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName());
d2d0adc7 5093 if (paraDef)
fe5aa22c 5094 {
caad0109
JS
5095 // If we're not at the end of the paragraph, then we apply THIS style, and not the designated next style.
5096 if (para->GetRange().GetEnd() == pos && !paraDef->GetNextStyle().IsEmpty())
d2d0adc7
JS
5097 {
5098 wxRichTextParagraphStyleDefinition* nextParaDef = GetStyleSheet()->FindParagraphStyle(paraDef->GetNextStyle());
5099 if (nextParaDef)
5100 {
5101 foundAttributes = true;
336d8ae9 5102 attr = nextParaDef->GetStyleMergedWithBase(GetStyleSheet());
d2d0adc7
JS
5103 }
5104 }
3e541562 5105
d2d0adc7
JS
5106 // If we didn't find the 'next style', use this style instead.
5107 if (!foundAttributes)
5108 {
5109 foundAttributes = true;
336d8ae9 5110 attr = paraDef->GetStyleMergedWithBase(GetStyleSheet());
d2d0adc7 5111 }
fe5aa22c
JS
5112 }
5113 }
d2d0adc7
JS
5114 if (!foundAttributes)
5115 {
5116 attr = para->GetAttributes();
5117 int flags = attr.GetFlags();
fe5aa22c 5118
d2d0adc7
JS
5119 // Eliminate character styles
5120 flags &= ( (~ wxTEXT_ATTR_FONT) |
fe5aa22c
JS
5121 (~ wxTEXT_ATTR_TEXT_COLOUR) |
5122 (~ wxTEXT_ATTR_BACKGROUND_COLOUR) );
d2d0adc7
JS
5123 attr.SetFlags(flags);
5124 }
3e541562 5125
d2d0adc7
JS
5126 // Now see if we need to number the paragraph.
5127 if (attr.HasBulletStyle())
5128 {
44cc96a8 5129 wxTextAttr numberingAttr;
d2d0adc7 5130 if (FindNextParagraphNumber(para, numberingAttr))
44cc96a8 5131 wxRichTextApplyStyle(attr, (const wxTextAttr&) numberingAttr);
d2d0adc7 5132 }
fe5aa22c
JS
5133
5134 return attr;
5135 }
5136 else
44cc96a8 5137 return wxTextAttr();
fe5aa22c
JS
5138}
5139
5d7836c4 5140/// Submit command to delete this range
12cc29c5 5141bool wxRichTextBuffer::DeleteRangeWithUndo(const wxRichTextRange& range, wxRichTextCtrl* ctrl)
5d7836c4
JS
5142{
5143 wxRichTextAction* action = new wxRichTextAction(NULL, _("Delete"), wxRICHTEXT_DELETE, this, ctrl);
7fe8059f 5144
12cc29c5 5145 action->SetPosition(ctrl->GetCaretPosition());
5d7836c4
JS
5146
5147 // Set the range to delete
5148 action->SetRange(range);
7fe8059f 5149
5d7836c4
JS
5150 // Copy the fragment that we'll need to restore in Undo
5151 CopyFragment(range, action->GetOldParagraphs());
5152
6c0ea513
JS
5153 // See if we're deleting a paragraph marker, in which case we need to
5154 // make a note not to copy the attributes from the 2nd paragraph to the 1st.
5155 if (range.GetStart() == range.GetEnd())
5d7836c4 5156 {
6c0ea513
JS
5157 wxRichTextParagraph* para = GetParagraphAtPosition(range.GetStart());
5158 if (para && para->GetRange().GetEnd() == range.GetEnd())
5d7836c4 5159 {
6c0ea513
JS
5160 wxRichTextParagraph* nextPara = GetParagraphAtPosition(range.GetStart()+1);
5161 if (nextPara && nextPara != para)
5d7836c4 5162 {
6c0ea513
JS
5163 action->GetOldParagraphs().GetChildren().GetFirst()->GetData()->SetAttributes(nextPara->GetAttributes());
5164 action->GetOldParagraphs().GetAttributes().SetFlags(action->GetOldParagraphs().GetAttributes().GetFlags() | wxTEXT_ATTR_KEEP_FIRST_PARA_STYLE);
5d7836c4
JS
5165 }
5166 }
5167 }
5168
5169 SubmitAction(action);
7fe8059f 5170
5d7836c4
JS
5171 return true;
5172}
5173
5174/// Collapse undo/redo commands
5175bool wxRichTextBuffer::BeginBatchUndo(const wxString& cmdName)
5176{
5177 if (m_batchedCommandDepth == 0)
5178 {
5179 wxASSERT(m_batchedCommand == NULL);
5180 if (m_batchedCommand)
5181 {
0745f364 5182 GetCommandProcessor()->Store(m_batchedCommand);
5d7836c4
JS
5183 }
5184 m_batchedCommand = new wxRichTextCommand(cmdName);
5185 }
5186
7fe8059f 5187 m_batchedCommandDepth ++;
5d7836c4
JS
5188
5189 return true;
5190}
5191
5192/// Collapse undo/redo commands
5193bool wxRichTextBuffer::EndBatchUndo()
5194{
5195 m_batchedCommandDepth --;
5196
5197 wxASSERT(m_batchedCommandDepth >= 0);
5198 wxASSERT(m_batchedCommand != NULL);
5199
5200 if (m_batchedCommandDepth == 0)
5201 {
0745f364 5202 GetCommandProcessor()->Store(m_batchedCommand);
5d7836c4
JS
5203 m_batchedCommand = NULL;
5204 }
5205
5206 return true;
5207}
5208
5209/// Submit immediately, or delay according to whether collapsing is on
5210bool wxRichTextBuffer::SubmitAction(wxRichTextAction* action)
5211{
5212 if (BatchingUndo() && m_batchedCommand && !SuppressingUndo())
0745f364
JS
5213 {
5214 wxRichTextCommand* cmd = new wxRichTextCommand(action->GetName());
5215 cmd->AddAction(action);
5216 cmd->Do();
5217 cmd->GetActions().Clear();
5218 delete cmd;
5219
5d7836c4 5220 m_batchedCommand->AddAction(action);
0745f364 5221 }
5d7836c4
JS
5222 else
5223 {
5224 wxRichTextCommand* cmd = new wxRichTextCommand(action->GetName());
5225 cmd->AddAction(action);
5226
5227 // Only store it if we're not suppressing undo.
5228 return GetCommandProcessor()->Submit(cmd, !SuppressingUndo());
5229 }
5230
5231 return true;
5232}
5233
5234/// Begin suppressing undo/redo commands.
5235bool wxRichTextBuffer::BeginSuppressUndo()
5236{
7fe8059f 5237 m_suppressUndo ++;
5d7836c4
JS
5238
5239 return true;
5240}
5241
5242/// End suppressing undo/redo commands.
5243bool wxRichTextBuffer::EndSuppressUndo()
5244{
7fe8059f 5245 m_suppressUndo --;
5d7836c4
JS
5246
5247 return true;
5248}
5249
5250/// Begin using a style
44cc96a8 5251bool wxRichTextBuffer::BeginStyle(const wxTextAttr& style)
5d7836c4 5252{
44cc96a8 5253 wxTextAttr newStyle(GetDefaultStyle());
5d7836c4
JS
5254
5255 // Save the old default style
44cc96a8 5256 m_attributeStack.Append((wxObject*) new wxTextAttr(GetDefaultStyle()));
5d7836c4
JS
5257
5258 wxRichTextApplyStyle(newStyle, style);
5259 newStyle.SetFlags(style.GetFlags()|newStyle.GetFlags());
5260
5261 SetDefaultStyle(newStyle);
5262
5263 // wxLogDebug("Default style size = %d", GetDefaultStyle().GetFont().GetPointSize());
5264
5265 return true;
5266}
5267
5268/// End the style
5269bool wxRichTextBuffer::EndStyle()
5270{
63886f6d 5271 if (!m_attributeStack.GetFirst())
5d7836c4
JS
5272 {
5273 wxLogDebug(_("Too many EndStyle calls!"));
5274 return false;
5275 }
5276
09f14108 5277 wxList::compatibility_iterator node = m_attributeStack.GetLast();
44cc96a8 5278 wxTextAttr* attr = (wxTextAttr*)node->GetData();
9e31a660 5279 m_attributeStack.Erase(node);
5d7836c4
JS
5280
5281 SetDefaultStyle(*attr);
5282
5283 delete attr;
5284 return true;
5285}
5286
5287/// End all styles
5288bool wxRichTextBuffer::EndAllStyles()
5289{
5290 while (m_attributeStack.GetCount() != 0)
5291 EndStyle();
5292 return true;
5293}
5294
5295/// Clear the style stack
5296void wxRichTextBuffer::ClearStyleStack()
5297{
09f14108 5298 for (wxList::compatibility_iterator node = m_attributeStack.GetFirst(); node; node = node->GetNext())
44cc96a8 5299 delete (wxTextAttr*) node->GetData();
5d7836c4
JS
5300 m_attributeStack.Clear();
5301}
5302
5303/// Begin using bold
5304bool wxRichTextBuffer::BeginBold()
5305{
44cc96a8
JS
5306 wxTextAttr attr;
5307 attr.SetFontWeight(wxBOLD);
7fe8059f 5308
5d7836c4
JS
5309 return BeginStyle(attr);
5310}
5311
5312/// Begin using italic
5313bool wxRichTextBuffer::BeginItalic()
5314{
44cc96a8
JS
5315 wxTextAttr attr;
5316 attr.SetFontStyle(wxITALIC);
7fe8059f 5317
5d7836c4
JS
5318 return BeginStyle(attr);
5319}
5320
5321/// Begin using underline
5322bool wxRichTextBuffer::BeginUnderline()
5323{
44cc96a8
JS
5324 wxTextAttr attr;
5325 attr.SetFontUnderlined(true);
7fe8059f 5326
5d7836c4
JS
5327 return BeginStyle(attr);
5328}
5329
5330/// Begin using point size
5331bool wxRichTextBuffer::BeginFontSize(int pointSize)
5332{
44cc96a8
JS
5333 wxTextAttr attr;
5334 attr.SetFontSize(pointSize);
7fe8059f 5335
5d7836c4
JS
5336 return BeginStyle(attr);
5337}
5338
5339/// Begin using this font
5340bool wxRichTextBuffer::BeginFont(const wxFont& font)
5341{
44cc96a8 5342 wxTextAttr attr;
5d7836c4 5343 attr.SetFont(font);
7fe8059f 5344
5d7836c4
JS
5345 return BeginStyle(attr);
5346}
5347
5348/// Begin using this colour
5349bool wxRichTextBuffer::BeginTextColour(const wxColour& colour)
5350{
44cc96a8 5351 wxTextAttr attr;
5d7836c4
JS
5352 attr.SetFlags(wxTEXT_ATTR_TEXT_COLOUR);
5353 attr.SetTextColour(colour);
7fe8059f 5354
5d7836c4
JS
5355 return BeginStyle(attr);
5356}
5357
5358/// Begin using alignment
5359bool wxRichTextBuffer::BeginAlignment(wxTextAttrAlignment alignment)
5360{
44cc96a8 5361 wxTextAttr attr;
5d7836c4
JS
5362 attr.SetFlags(wxTEXT_ATTR_ALIGNMENT);
5363 attr.SetAlignment(alignment);
7fe8059f 5364
5d7836c4
JS
5365 return BeginStyle(attr);
5366}
5367
5368/// Begin left indent
5369bool wxRichTextBuffer::BeginLeftIndent(int leftIndent, int leftSubIndent)
5370{
44cc96a8 5371 wxTextAttr attr;
5d7836c4
JS
5372 attr.SetFlags(wxTEXT_ATTR_LEFT_INDENT);
5373 attr.SetLeftIndent(leftIndent, leftSubIndent);
7fe8059f 5374
5d7836c4
JS
5375 return BeginStyle(attr);
5376}
5377
5378/// Begin right indent
5379bool wxRichTextBuffer::BeginRightIndent(int rightIndent)
5380{
44cc96a8 5381 wxTextAttr attr;
5d7836c4
JS
5382 attr.SetFlags(wxTEXT_ATTR_RIGHT_INDENT);
5383 attr.SetRightIndent(rightIndent);
7fe8059f 5384
5d7836c4
JS
5385 return BeginStyle(attr);
5386}
5387
5388/// Begin paragraph spacing
5389bool wxRichTextBuffer::BeginParagraphSpacing(int before, int after)
5390{
5391 long flags = 0;
5392 if (before != 0)
5393 flags |= wxTEXT_ATTR_PARA_SPACING_BEFORE;
5394 if (after != 0)
5395 flags |= wxTEXT_ATTR_PARA_SPACING_AFTER;
5396
44cc96a8 5397 wxTextAttr attr;
5d7836c4
JS
5398 attr.SetFlags(flags);
5399 attr.SetParagraphSpacingBefore(before);
5400 attr.SetParagraphSpacingAfter(after);
7fe8059f 5401
5d7836c4
JS
5402 return BeginStyle(attr);
5403}
5404
5405/// Begin line spacing
5406bool wxRichTextBuffer::BeginLineSpacing(int lineSpacing)
5407{
44cc96a8 5408 wxTextAttr attr;
5d7836c4
JS
5409 attr.SetFlags(wxTEXT_ATTR_LINE_SPACING);
5410 attr.SetLineSpacing(lineSpacing);
7fe8059f 5411
5d7836c4
JS
5412 return BeginStyle(attr);
5413}
5414
5415/// Begin numbered bullet
5416bool wxRichTextBuffer::BeginNumberedBullet(int bulletNumber, int leftIndent, int leftSubIndent, int bulletStyle)
5417{
44cc96a8 5418 wxTextAttr attr;
f089713f 5419 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT);
5d7836c4
JS
5420 attr.SetBulletStyle(bulletStyle);
5421 attr.SetBulletNumber(bulletNumber);
5422 attr.SetLeftIndent(leftIndent, leftSubIndent);
7fe8059f 5423
5d7836c4
JS
5424 return BeginStyle(attr);
5425}
5426
5427/// Begin symbol bullet
d2d0adc7 5428bool wxRichTextBuffer::BeginSymbolBullet(const wxString& symbol, int leftIndent, int leftSubIndent, int bulletStyle)
5d7836c4 5429{
44cc96a8 5430 wxTextAttr attr;
f089713f 5431 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT);
5d7836c4
JS
5432 attr.SetBulletStyle(bulletStyle);
5433 attr.SetLeftIndent(leftIndent, leftSubIndent);
d2d0adc7 5434 attr.SetBulletText(symbol);
7fe8059f 5435
5d7836c4
JS
5436 return BeginStyle(attr);
5437}
5438
f089713f
JS
5439/// Begin standard bullet
5440bool wxRichTextBuffer::BeginStandardBullet(const wxString& bulletName, int leftIndent, int leftSubIndent, int bulletStyle)
5441{
44cc96a8 5442 wxTextAttr attr;
f089713f
JS
5443 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT);
5444 attr.SetBulletStyle(bulletStyle);
5445 attr.SetLeftIndent(leftIndent, leftSubIndent);
5446 attr.SetBulletName(bulletName);
5447
5448 return BeginStyle(attr);
5449}
5450
5d7836c4
JS
5451/// Begin named character style
5452bool wxRichTextBuffer::BeginCharacterStyle(const wxString& characterStyle)
5453{
5454 if (GetStyleSheet())
5455 {
5456 wxRichTextCharacterStyleDefinition* def = GetStyleSheet()->FindCharacterStyle(characterStyle);
5457 if (def)
5458 {
44cc96a8 5459 wxTextAttr attr = def->GetStyleMergedWithBase(GetStyleSheet());
5d7836c4
JS
5460 return BeginStyle(attr);
5461 }
5462 }
5463 return false;
5464}
5465
5466/// Begin named paragraph style
5467bool wxRichTextBuffer::BeginParagraphStyle(const wxString& paragraphStyle)
5468{
5469 if (GetStyleSheet())
5470 {
5471 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(paragraphStyle);
5472 if (def)
5473 {
44cc96a8 5474 wxTextAttr attr = def->GetStyleMergedWithBase(GetStyleSheet());
5d7836c4
JS
5475 return BeginStyle(attr);
5476 }
5477 }
5478 return false;
5479}
5480
f089713f
JS
5481/// Begin named list style
5482bool wxRichTextBuffer::BeginListStyle(const wxString& listStyle, int level, int number)
5483{
5484 if (GetStyleSheet())
5485 {
5486 wxRichTextListStyleDefinition* def = GetStyleSheet()->FindListStyle(listStyle);
5487 if (def)
5488 {
44cc96a8 5489 wxTextAttr attr(def->GetCombinedStyleForLevel(level));
f089713f
JS
5490
5491 attr.SetBulletNumber(number);
5492
5493 return BeginStyle(attr);
5494 }
5495 }
5496 return false;
5497}
5498
d2d0adc7
JS
5499/// Begin URL
5500bool wxRichTextBuffer::BeginURL(const wxString& url, const wxString& characterStyle)
5501{
44cc96a8 5502 wxTextAttr attr;
d2d0adc7
JS
5503
5504 if (!characterStyle.IsEmpty() && GetStyleSheet())
5505 {
5506 wxRichTextCharacterStyleDefinition* def = GetStyleSheet()->FindCharacterStyle(characterStyle);
5507 if (def)
5508 {
336d8ae9 5509 attr = def->GetStyleMergedWithBase(GetStyleSheet());
d2d0adc7
JS
5510 }
5511 }
5512 attr.SetURL(url);
5513
5514 return BeginStyle(attr);
5515}
5516
5d7836c4
JS
5517/// Adds a handler to the end
5518void wxRichTextBuffer::AddHandler(wxRichTextFileHandler *handler)
5519{
5520 sm_handlers.Append(handler);
5521}
5522
5523/// Inserts a handler at the front
5524void wxRichTextBuffer::InsertHandler(wxRichTextFileHandler *handler)
5525{
5526 sm_handlers.Insert( handler );
5527}
5528
5529/// Removes a handler
5530bool wxRichTextBuffer::RemoveHandler(const wxString& name)
5531{
5532 wxRichTextFileHandler *handler = FindHandler(name);
5533 if (handler)
5534 {
5535 sm_handlers.DeleteObject(handler);
5536 delete handler;
5537 return true;
5538 }
5539 else
5540 return false;
5541}
5542
5543/// Finds a handler by filename or, if supplied, type
5544wxRichTextFileHandler *wxRichTextBuffer::FindHandlerFilenameOrType(const wxString& filename, int imageType)
5545{
5546 if (imageType != wxRICHTEXT_TYPE_ANY)
5547 return FindHandler(imageType);
0ca07313 5548 else if (!filename.IsEmpty())
5d7836c4
JS
5549 {
5550 wxString path, file, ext;
5551 wxSplitPath(filename, & path, & file, & ext);
5552 return FindHandler(ext, imageType);
5553 }
0ca07313
JS
5554 else
5555 return NULL;
5d7836c4
JS
5556}
5557
5558
5559/// Finds a handler by name
5560wxRichTextFileHandler* wxRichTextBuffer::FindHandler(const wxString& name)
5561{
5562 wxList::compatibility_iterator node = sm_handlers.GetFirst();
5563 while (node)
5564 {
5565 wxRichTextFileHandler *handler = (wxRichTextFileHandler*)node->GetData();
5566 if (handler->GetName().Lower() == name.Lower()) return handler;
5567
5568 node = node->GetNext();
5569 }
5570 return NULL;
5571}
5572
5573/// Finds a handler by extension and type
5574wxRichTextFileHandler* wxRichTextBuffer::FindHandler(const wxString& extension, int type)
5575{
5576 wxList::compatibility_iterator node = sm_handlers.GetFirst();
5577 while (node)
5578 {
5579 wxRichTextFileHandler *handler = (wxRichTextFileHandler*)node->GetData();
5580 if ( handler->GetExtension().Lower() == extension.Lower() &&
5581 (type == wxRICHTEXT_TYPE_ANY || handler->GetType() == type) )
5582 return handler;
5583 node = node->GetNext();
5584 }
5585 return 0;
5586}
5587
5588/// Finds a handler by type
5589wxRichTextFileHandler* wxRichTextBuffer::FindHandler(int type)
5590{
5591 wxList::compatibility_iterator node = sm_handlers.GetFirst();
5592 while (node)
5593 {
5594 wxRichTextFileHandler *handler = (wxRichTextFileHandler *)node->GetData();
5595 if (handler->GetType() == type) return handler;
5596 node = node->GetNext();
5597 }
5598 return NULL;
5599}
5600
5601void wxRichTextBuffer::InitStandardHandlers()
5602{
5603 if (!FindHandler(wxRICHTEXT_TYPE_TEXT))
5604 AddHandler(new wxRichTextPlainTextHandler);
5605}
5606
5607void wxRichTextBuffer::CleanUpHandlers()
5608{
5609 wxList::compatibility_iterator node = sm_handlers.GetFirst();
5610 while (node)
5611 {
5612 wxRichTextFileHandler* handler = (wxRichTextFileHandler*)node->GetData();
5613 wxList::compatibility_iterator next = node->GetNext();
5614 delete handler;
5615 node = next;
5616 }
5617
5618 sm_handlers.Clear();
5619}
5620
1e967276 5621wxString wxRichTextBuffer::GetExtWildcard(bool combine, bool save, wxArrayInt* types)
5d7836c4 5622{
1e967276
JS
5623 if (types)
5624 types->Clear();
5625
5d7836c4
JS
5626 wxString wildcard;
5627
5628 wxList::compatibility_iterator node = GetHandlers().GetFirst();
5629 int count = 0;
5630 while (node)
5631 {
5632 wxRichTextFileHandler* handler = (wxRichTextFileHandler*) node->GetData();
5633 if (handler->IsVisible() && ((save && handler->CanSave()) || !save && handler->CanLoad()))
5634 {
5635 if (combine)
5636 {
5637 if (count > 0)
5638 wildcard += wxT(";");
5639 wildcard += wxT("*.") + handler->GetExtension();
5640 }
5641 else
5642 {
5643 if (count > 0)
5644 wildcard += wxT("|");
5645 wildcard += handler->GetName();
5646 wildcard += wxT(" ");
5647 wildcard += _("files");
5648 wildcard += wxT(" (*.");
5649 wildcard += handler->GetExtension();
5650 wildcard += wxT(")|*.");
5651 wildcard += handler->GetExtension();
1e967276
JS
5652 if (types)
5653 types->Add(handler->GetType());
5d7836c4
JS
5654 }
5655 count ++;
5656 }
5657
5658 node = node->GetNext();
5659 }
5660
5661 if (combine)
5662 wildcard = wxT("(") + wildcard + wxT(")|") + wildcard;
5663 return wildcard;
5664}
5665
5666/// Load a file
5667bool wxRichTextBuffer::LoadFile(const wxString& filename, int type)
5668{
5669 wxRichTextFileHandler* handler = FindHandlerFilenameOrType(filename, type);
5670 if (handler)
1e967276 5671 {
44cc96a8 5672 SetDefaultStyle(wxTextAttr());
d2d0adc7 5673 handler->SetFlags(GetHandlerFlags());
1e967276
JS
5674 bool success = handler->LoadFile(this, filename);
5675 Invalidate(wxRICHTEXT_ALL);
5676 return success;
5677 }
5d7836c4
JS
5678 else
5679 return false;
5680}
5681
5682/// Save a file
5683bool wxRichTextBuffer::SaveFile(const wxString& filename, int type)
5684{
5685 wxRichTextFileHandler* handler = FindHandlerFilenameOrType(filename, type);
5686 if (handler)
d2d0adc7
JS
5687 {
5688 handler->SetFlags(GetHandlerFlags());
5d7836c4 5689 return handler->SaveFile(this, filename);
d2d0adc7 5690 }
5d7836c4
JS
5691 else
5692 return false;
5693}
5694
5695/// Load from a stream
5696bool wxRichTextBuffer::LoadFile(wxInputStream& stream, int type)
5697{
5698 wxRichTextFileHandler* handler = FindHandler(type);
5699 if (handler)
1e967276 5700 {
44cc96a8 5701 SetDefaultStyle(wxTextAttr());
d2d0adc7 5702 handler->SetFlags(GetHandlerFlags());
1e967276
JS
5703 bool success = handler->LoadFile(this, stream);
5704 Invalidate(wxRICHTEXT_ALL);
5705 return success;
5706 }
5d7836c4
JS
5707 else
5708 return false;
5709}
5710
5711/// Save to a stream
5712bool wxRichTextBuffer::SaveFile(wxOutputStream& stream, int type)
5713{
5714 wxRichTextFileHandler* handler = FindHandler(type);
5715 if (handler)
d2d0adc7
JS
5716 {
5717 handler->SetFlags(GetHandlerFlags());
5d7836c4 5718 return handler->SaveFile(this, stream);
d2d0adc7 5719 }
5d7836c4
JS
5720 else
5721 return false;
5722}
5723
5724/// Copy the range to the clipboard
5725bool wxRichTextBuffer::CopyToClipboard(const wxRichTextRange& range)
5726{
5727 bool success = false;
11ef729d 5728#if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
0ca07313 5729
d2142335 5730 if (!wxTheClipboard->IsOpened() && wxTheClipboard->Open())
7fe8059f 5731 {
0ca07313
JS
5732 wxTheClipboard->Clear();
5733
5734 // Add composite object
5735
5736 wxDataObjectComposite* compositeObject = new wxDataObjectComposite();
5737
5738 {
5739 wxString text = GetTextForRange(range);
5740
5741#ifdef __WXMSW__
5742 text = wxTextFile::Translate(text, wxTextFileType_Dos);
5743#endif
5744
5745 compositeObject->Add(new wxTextDataObject(text), false /* not preferred */);
5746 }
5747
5748 // Add rich text buffer data object. This needs the XML handler to be present.
5749
5750 if (FindHandler(wxRICHTEXT_TYPE_XML))
5751 {
5752 wxRichTextBuffer* richTextBuf = new wxRichTextBuffer;
5753 CopyFragment(range, *richTextBuf);
5754
5755 compositeObject->Add(new wxRichTextBufferDataObject(richTextBuf), true /* preferred */);
5756 }
5757
5758 if (wxTheClipboard->SetData(compositeObject))
5759 success = true;
5760
5d7836c4
JS
5761 wxTheClipboard->Close();
5762 }
0ca07313 5763
39a1c2f2
WS
5764#else
5765 wxUnusedVar(range);
5766#endif
5d7836c4
JS
5767 return success;
5768}
5769
5770/// Paste the clipboard content to the buffer
5771bool wxRichTextBuffer::PasteFromClipboard(long position)
5772{
5773 bool success = false;
11ef729d 5774#if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
5d7836c4
JS
5775 if (CanPasteFromClipboard())
5776 {
5777 if (wxTheClipboard->Open())
5778 {
0ca07313
JS
5779 if (wxTheClipboard->IsSupported(wxDataFormat(wxRichTextBufferDataObject::GetRichTextBufferFormatId())))
5780 {
5781 wxRichTextBufferDataObject data;
5782 wxTheClipboard->GetData(data);
5783 wxRichTextBuffer* richTextBuffer = data.GetRichTextBuffer();
5784 if (richTextBuffer)
5785 {
5786 InsertParagraphsWithUndo(position+1, *richTextBuffer, GetRichTextCtrl(), wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE);
62381daa
JS
5787 if (GetRichTextCtrl())
5788 GetRichTextCtrl()->ShowPosition(position + richTextBuffer->GetRange().GetEnd());
0ca07313
JS
5789 delete richTextBuffer;
5790 }
5791 }
5792 else if (wxTheClipboard->IsSupported(wxDF_TEXT) || wxTheClipboard->IsSupported(wxDF_UNICODETEXT))
5d7836c4
JS
5793 {
5794 wxTextDataObject data;
5795 wxTheClipboard->GetData(data);
5796 wxString text(data.GetText());
c21f3211
JS
5797#ifdef __WXMSW__
5798 wxString text2;
5799 text2.Alloc(text.Length()+1);
5800 size_t i;
5801 for (i = 0; i < text.Length(); i++)
5802 {
5803 wxChar ch = text[i];
5804 if (ch != wxT('\r'))
5805 text2 += ch;
5806 }
5807#else
5808 wxString text2 = text;
5809#endif
5810 InsertTextWithUndo(position+1, text2, GetRichTextCtrl());
7fe8059f 5811
62381daa
JS
5812 if (GetRichTextCtrl())
5813 GetRichTextCtrl()->ShowPosition(position + text2.Length());
5814
5d7836c4
JS
5815 success = true;
5816 }
5817 else if (wxTheClipboard->IsSupported(wxDF_BITMAP))
5818 {
5819 wxBitmapDataObject data;
5820 wxTheClipboard->GetData(data);
5821 wxBitmap bitmap(data.GetBitmap());
5822 wxImage image(bitmap.ConvertToImage());
5823
5824 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Image"), wxRICHTEXT_INSERT, this, GetRichTextCtrl(), false);
7fe8059f 5825
5d7836c4
JS
5826 action->GetNewParagraphs().AddImage(image);
5827
5828 if (action->GetNewParagraphs().GetChildCount() == 1)
5829 action->GetNewParagraphs().SetPartialParagraph(true);
7fe8059f 5830
5d7836c4 5831 action->SetPosition(position);
7fe8059f 5832
5d7836c4
JS
5833 // Set the range we'll need to delete in Undo
5834 action->SetRange(wxRichTextRange(position, position));
7fe8059f 5835
5d7836c4
JS
5836 SubmitAction(action);
5837
5838 success = true;
5839 }
5840 wxTheClipboard->Close();
5841 }
5842 }
39a1c2f2
WS
5843#else
5844 wxUnusedVar(position);
5845#endif
5d7836c4
JS
5846 return success;
5847}
5848
5849/// Can we paste from the clipboard?
5850bool wxRichTextBuffer::CanPasteFromClipboard() const
5851{
7fe8059f 5852 bool canPaste = false;
11ef729d 5853#if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
d2142335 5854 if (!wxTheClipboard->IsOpened() && wxTheClipboard->Open())
5d7836c4 5855 {
0ca07313
JS
5856 if (wxTheClipboard->IsSupported(wxDF_TEXT) || wxTheClipboard->IsSupported(wxDF_UNICODETEXT) ||
5857 wxTheClipboard->IsSupported(wxDataFormat(wxRichTextBufferDataObject::GetRichTextBufferFormatId())) ||
5858 wxTheClipboard->IsSupported(wxDF_BITMAP))
5d7836c4 5859 {
7fe8059f 5860 canPaste = true;
5d7836c4
JS
5861 }
5862 wxTheClipboard->Close();
5863 }
39a1c2f2 5864#endif
5d7836c4
JS
5865 return canPaste;
5866}
5867
5868/// Dumps contents of buffer for debugging purposes
5869void wxRichTextBuffer::Dump()
5870{
5871 wxString text;
5872 {
5873 wxStringOutputStream stream(& text);
5874 wxTextOutputStream textStream(stream);
5875 Dump(textStream);
5876 }
5877
5878 wxLogDebug(text);
5879}
5880
d2d0adc7
JS
5881/// Add an event handler
5882bool wxRichTextBuffer::AddEventHandler(wxEvtHandler* handler)
5883{
5884 m_eventHandlers.Append(handler);
5885 return true;
5886}
5887
5888/// Remove an event handler
5889bool wxRichTextBuffer::RemoveEventHandler(wxEvtHandler* handler, bool deleteHandler)
5890{
5891 wxList::compatibility_iterator node = m_eventHandlers.Find(handler);
5892 if (node)
5893 {
5894 m_eventHandlers.Erase(node);
5895 if (deleteHandler)
5896 delete handler;
3e541562 5897
d2d0adc7
JS
5898 return true;
5899 }
5900 else
5901 return false;
5902}
5903
5904/// Clear event handlers
5905void wxRichTextBuffer::ClearEventHandlers()
5906{
5907 m_eventHandlers.Clear();
5908}
5909
5910/// Send event to event handlers. If sendToAll is true, will send to all event handlers,
5911/// otherwise will stop at the first successful one.
5912bool wxRichTextBuffer::SendEvent(wxEvent& event, bool sendToAll)
5913{
5914 bool success = false;
5915 for (wxList::compatibility_iterator node = m_eventHandlers.GetFirst(); node; node = node->GetNext())
5916 {
5917 wxEvtHandler* handler = (wxEvtHandler*) node->GetData();
5918 if (handler->ProcessEvent(event))
5919 {
5920 success = true;
5921 if (!sendToAll)
5922 return true;
5923 }
5924 }
5925 return success;
5926}
5927
5928/// Set style sheet and notify of the change
5929bool wxRichTextBuffer::SetStyleSheetAndNotify(wxRichTextStyleSheet* sheet)
5930{
5931 wxRichTextStyleSheet* oldSheet = GetStyleSheet();
3e541562 5932
d2d0adc7
JS
5933 wxWindowID id = wxID_ANY;
5934 if (GetRichTextCtrl())
5935 id = GetRichTextCtrl()->GetId();
3e541562 5936
d2d0adc7
JS
5937 wxRichTextEvent event(wxEVT_COMMAND_RICHTEXT_STYLESHEET_REPLACING, id);
5938 event.SetEventObject(GetRichTextCtrl());
5939 event.SetOldStyleSheet(oldSheet);
5940 event.SetNewStyleSheet(sheet);
5941 event.Allow();
3e541562 5942
d2d0adc7
JS
5943 if (SendEvent(event) && !event.IsAllowed())
5944 {
5945 if (sheet != oldSheet)
5946 delete sheet;
5947
5948 return false;
5949 }
5950
5951 if (oldSheet && oldSheet != sheet)
5952 delete oldSheet;
5953
5954 SetStyleSheet(sheet);
5955
5956 event.SetEventType(wxEVT_COMMAND_RICHTEXT_STYLESHEET_REPLACED);
5957 event.SetOldStyleSheet(NULL);
5958 event.Allow();
5959
5960 return SendEvent(event);
5961}
5962
5963/// Set renderer, deleting old one
5964void wxRichTextBuffer::SetRenderer(wxRichTextRenderer* renderer)
5965{
5966 if (sm_renderer)
5967 delete sm_renderer;
5968 sm_renderer = renderer;
5969}
5970
44cc96a8 5971bool wxRichTextStdRenderer::DrawStandardBullet(wxRichTextParagraph* paragraph, wxDC& dc, const wxTextAttr& bulletAttr, const wxRect& rect)
d2d0adc7
JS
5972{
5973 if (bulletAttr.GetTextColour().Ok())
5974 {
ecb5fbf1
JS
5975 wxCheckSetPen(dc, wxPen(bulletAttr.GetTextColour()));
5976 wxCheckSetBrush(dc, wxBrush(bulletAttr.GetTextColour()));
d2d0adc7
JS
5977 }
5978 else
5979 {
ecb5fbf1
JS
5980 wxCheckSetPen(dc, *wxBLACK_PEN);
5981 wxCheckSetBrush(dc, *wxBLACK_BRUSH);
d2d0adc7
JS
5982 }
5983
5984 wxFont font;
44cc96a8
JS
5985 if (bulletAttr.HasFont())
5986 {
5987 font = paragraph->GetBuffer()->GetFontTable().FindFont(bulletAttr);
5988 }
d2d0adc7
JS
5989 else
5990 font = (*wxNORMAL_FONT);
5991
ecb5fbf1 5992 wxCheckSetFont(dc, font);
d2d0adc7
JS
5993
5994 int charHeight = dc.GetCharHeight();
3e541562 5995
d2d0adc7
JS
5996 int bulletWidth = (int) (((float) charHeight) * wxRichTextBuffer::GetBulletProportion());
5997 int bulletHeight = bulletWidth;
5998
5999 int x = rect.x;
3e541562 6000
d2d0adc7
JS
6001 // Calculate the top position of the character (as opposed to the whole line height)
6002 int y = rect.y + (rect.height - charHeight);
3e541562 6003
d2d0adc7
JS
6004 // Calculate where the bullet should be positioned
6005 y = y + (charHeight+1)/2 - (bulletHeight+1)/2;
3e541562 6006
d2d0adc7 6007 // The margin between a bullet and text.
44219ff0 6008 int margin = paragraph->ConvertTenthsMMToPixels(dc, wxRichTextBuffer::GetBulletRightMargin());
3e541562 6009
d2d0adc7
JS
6010 if (bulletAttr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_RIGHT)
6011 x = rect.x + rect.width - bulletWidth - margin;
6012 else if (bulletAttr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_CENTRE)
6013 x = x + (rect.width)/2 - bulletWidth/2;
3e541562 6014
d2d0adc7
JS
6015 if (bulletAttr.GetBulletName() == wxT("standard/square"))
6016 {
6017 dc.DrawRectangle(x, y, bulletWidth, bulletHeight);
6018 }
6019 else if (bulletAttr.GetBulletName() == wxT("standard/diamond"))
6020 {
6021 wxPoint pts[5];
6022 pts[0].x = x; pts[0].y = y + bulletHeight/2;
6023 pts[1].x = x + bulletWidth/2; pts[1].y = y;
6024 pts[2].x = x + bulletWidth; pts[2].y = y + bulletHeight/2;
6025 pts[3].x = x + bulletWidth/2; pts[3].y = y + bulletHeight;
3e541562 6026
d2d0adc7
JS
6027 dc.DrawPolygon(4, pts);
6028 }
6029 else if (bulletAttr.GetBulletName() == wxT("standard/triangle"))
6030 {
6031 wxPoint pts[3];
6032 pts[0].x = x; pts[0].y = y;
6033 pts[1].x = x + bulletWidth; pts[1].y = y + bulletHeight/2;
6034 pts[2].x = x; pts[2].y = y + bulletHeight;
3e541562 6035
d2d0adc7
JS
6036 dc.DrawPolygon(3, pts);
6037 }
6038 else // "standard/circle", and catch-all
6039 {
6040 dc.DrawEllipse(x, y, bulletWidth, bulletHeight);
3e541562
JS
6041 }
6042
d2d0adc7
JS
6043 return true;
6044}
6045
44cc96a8 6046bool wxRichTextStdRenderer::DrawTextBullet(wxRichTextParagraph* paragraph, wxDC& dc, const wxTextAttr& attr, const wxRect& rect, const wxString& text)
d2d0adc7
JS
6047{
6048 if (!text.empty())
6049 {
6050 wxFont font;
44cc96a8
JS
6051 if ((attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_SYMBOL) && !attr.GetBulletFont().IsEmpty() && attr.HasFont())
6052 {
6053 wxTextAttr fontAttr;
6054 fontAttr.SetFontSize(attr.GetFontSize());
6055 fontAttr.SetFontStyle(attr.GetFontStyle());
6056 fontAttr.SetFontWeight(attr.GetFontWeight());
6057 fontAttr.SetFontUnderlined(attr.GetFontUnderlined());
6058 fontAttr.SetFontFaceName(attr.GetBulletFont());
6059 font = paragraph->GetBuffer()->GetFontTable().FindFont(fontAttr);
6060 }
6061 else if (attr.HasFont())
6062 font = paragraph->GetBuffer()->GetFontTable().FindFont(attr);
d2d0adc7
JS
6063 else
6064 font = (*wxNORMAL_FONT);
6065
ecb5fbf1 6066 wxCheckSetFont(dc, font);
d2d0adc7
JS
6067
6068 if (attr.GetTextColour().Ok())
6069 dc.SetTextForeground(attr.GetTextColour());
6070
04ee05f9 6071 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
d2d0adc7
JS
6072
6073 int charHeight = dc.GetCharHeight();
6074 wxCoord tw, th;
6075 dc.GetTextExtent(text, & tw, & th);
6076
6077 int x = rect.x;
6078
6079 // Calculate the top position of the character (as opposed to the whole line height)
3e541562 6080 int y = rect.y + (rect.height - charHeight);
d2d0adc7
JS
6081
6082 // The margin between a bullet and text.
44219ff0 6083 int margin = paragraph->ConvertTenthsMMToPixels(dc, wxRichTextBuffer::GetBulletRightMargin());
3e541562 6084
d2d0adc7
JS
6085 if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_RIGHT)
6086 x = (rect.x + rect.width) - tw - margin;
6087 else if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_CENTRE)
6088 x = x + (rect.width)/2 - tw/2;
6089
6090 dc.DrawText(text, x, y);
3e541562 6091
d2d0adc7
JS
6092 return true;
6093 }
6094 else
6095 return false;
6096}
6097
44cc96a8 6098bool wxRichTextStdRenderer::DrawBitmapBullet(wxRichTextParagraph* WXUNUSED(paragraph), wxDC& WXUNUSED(dc), const wxTextAttr& WXUNUSED(attr), const wxRect& WXUNUSED(rect))
d2d0adc7
JS
6099{
6100 // Currently unimplemented. The intention is to store bitmaps by name in a media store associated
6101 // with the buffer. The store will allow retrieval from memory, disk or other means.
6102 return false;
6103}
6104
6105/// Enumerate the standard bullet names currently supported
6106bool wxRichTextStdRenderer::EnumerateStandardBulletNames(wxArrayString& bulletNames)
6107{
6108 bulletNames.Add(wxT("standard/circle"));
6109 bulletNames.Add(wxT("standard/square"));
6110 bulletNames.Add(wxT("standard/diamond"));
6111 bulletNames.Add(wxT("standard/triangle"));
6112
6113 return true;
6114}
5d7836c4
JS
6115
6116/*
6117 * Module to initialise and clean up handlers
6118 */
6119
6120class wxRichTextModule: public wxModule
6121{
6122DECLARE_DYNAMIC_CLASS(wxRichTextModule)
6123public:
6124 wxRichTextModule() {}
cfa3b256
JS
6125 bool OnInit()
6126 {
d2d0adc7 6127 wxRichTextBuffer::SetRenderer(new wxRichTextStdRenderer);
cfa3b256
JS
6128 wxRichTextBuffer::InitStandardHandlers();
6129 wxRichTextParagraph::InitDefaultTabs();
6130 return true;
47b378bd 6131 }
cfa3b256
JS
6132 void OnExit()
6133 {
6134 wxRichTextBuffer::CleanUpHandlers();
6135 wxRichTextDecimalToRoman(-1);
6136 wxRichTextParagraph::ClearDefaultTabs();
dadd4f55 6137 wxRichTextCtrl::ClearAvailableFontNames();
d2d0adc7 6138 wxRichTextBuffer::SetRenderer(NULL);
47b378bd 6139 }
5d7836c4
JS
6140};
6141
6142IMPLEMENT_DYNAMIC_CLASS(wxRichTextModule, wxModule)
6143
6144
f1d6804f
RD
6145// If the richtext lib is dynamically loaded after the app has already started
6146// (such as from wxPython) then the built-in module system will not init this
6147// module. Provide this function to do it manually.
6148void wxRichTextModuleInit()
6149{
6150 wxModule* module = new wxRichTextModule;
6151 module->Init();
6152 wxModule::RegisterModule(module);
6153}
6154
6155
5d7836c4
JS
6156/*!
6157 * Commands for undo/redo
6158 *
6159 */
6160
6161wxRichTextCommand::wxRichTextCommand(const wxString& name, wxRichTextCommandId id, wxRichTextBuffer* buffer,
7fe8059f 6162 wxRichTextCtrl* ctrl, bool ignoreFirstTime): wxCommand(true, name)
5d7836c4
JS
6163{
6164 /* wxRichTextAction* action = */ new wxRichTextAction(this, name, id, buffer, ctrl, ignoreFirstTime);
6165}
6166
7fe8059f 6167wxRichTextCommand::wxRichTextCommand(const wxString& name): wxCommand(true, name)
5d7836c4
JS
6168{
6169}
6170
6171wxRichTextCommand::~wxRichTextCommand()
6172{
6173 ClearActions();
6174}
6175
6176void wxRichTextCommand::AddAction(wxRichTextAction* action)
6177{
6178 if (!m_actions.Member(action))
6179 m_actions.Append(action);
6180}
6181
6182bool wxRichTextCommand::Do()
6183{
09f14108 6184 for (wxList::compatibility_iterator node = m_actions.GetFirst(); node; node = node->GetNext())
5d7836c4
JS
6185 {
6186 wxRichTextAction* action = (wxRichTextAction*) node->GetData();
6187 action->Do();
6188 }
6189
6190 return true;
6191}
6192
6193bool wxRichTextCommand::Undo()
6194{
09f14108 6195 for (wxList::compatibility_iterator node = m_actions.GetLast(); node; node = node->GetPrevious())
5d7836c4
JS
6196 {
6197 wxRichTextAction* action = (wxRichTextAction*) node->GetData();
6198 action->Undo();
6199 }
6200
6201 return true;
6202}
6203
6204void wxRichTextCommand::ClearActions()
6205{
6206 WX_CLEAR_LIST(wxList, m_actions);
6207}
6208
6209/*!
6210 * Individual action
6211 *
6212 */
6213
6214wxRichTextAction::wxRichTextAction(wxRichTextCommand* cmd, const wxString& name, wxRichTextCommandId id, wxRichTextBuffer* buffer,
6215 wxRichTextCtrl* ctrl, bool ignoreFirstTime)
6216{
6217 m_buffer = buffer;
6218 m_ignoreThis = ignoreFirstTime;
6219 m_cmdId = id;
6220 m_position = -1;
6221 m_ctrl = ctrl;
6222 m_name = name;
6223 m_newParagraphs.SetDefaultStyle(buffer->GetDefaultStyle());
6224 m_newParagraphs.SetBasicStyle(buffer->GetBasicStyle());
6225 if (cmd)
6226 cmd->AddAction(this);
6227}
6228
6229wxRichTextAction::~wxRichTextAction()
6230{
6231}
6232
6233bool wxRichTextAction::Do()
6234{
6235 m_buffer->Modify(true);
6236
6237 switch (m_cmdId)
6238 {
6239 case wxRICHTEXT_INSERT:
6240 {
ea160b2e
JS
6241 // Store a list of line start character and y positions so we can figure out which area
6242 // we need to refresh
6243 wxArrayInt optimizationLineCharPositions;
6244 wxArrayInt optimizationLineYPositions;
6245
6246#if wxRICHTEXT_USE_OPTIMIZED_DRAWING
6247 // NOTE: we're assuming that the buffer is laid out correctly at this point.
6248 // If we had several actions, which only invalidate and leave layout until the
6249 // paint handler is called, then this might not be true. So we may need to switch
6250 // optimisation on only when we're simply adding text and not simultaneously
6251 // deleting a selection, for example. Or, we make sure the buffer is laid out correctly
6252 // first, but of course this means we'll be doing it twice.
6253 if (!m_buffer->GetDirty() && m_ctrl) // can only do optimisation if the buffer is already laid out correctly
6254 {
6255 wxSize clientSize = m_ctrl->GetClientSize();
6256 wxPoint firstVisiblePt = m_ctrl->GetFirstVisiblePoint();
6257 int lastY = firstVisiblePt.y + clientSize.y;
3e541562 6258
c025e094 6259 wxRichTextParagraph* para = m_buffer->GetParagraphAtPosition(GetRange().GetStart());
ea160b2e
JS
6260 wxRichTextObjectList::compatibility_iterator node = m_buffer->GetChildren().Find(para);
6261 while (node)
6262 {
6263 wxRichTextParagraph* child = (wxRichTextParagraph*) node->GetData();
6264 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
6265 while (node2)
6266 {
6267 wxRichTextLine* line = node2->GetData();
6268 wxPoint pt = line->GetAbsolutePosition();
6269 wxRichTextRange range = line->GetAbsoluteRange();
3e541562 6270
ea160b2e
JS
6271 if (pt.y > lastY)
6272 {
27b5dab3
JS
6273 node2 = wxRichTextLineList::compatibility_iterator();
6274 node = wxRichTextObjectList::compatibility_iterator();
ea160b2e
JS
6275 }
6276 else if (range.GetStart() > GetPosition() && pt.y >= firstVisiblePt.y)
3e541562 6277 {
ea160b2e
JS
6278 optimizationLineCharPositions.Add(range.GetStart());
6279 optimizationLineYPositions.Add(pt.y);
6280 }
6281
6282 if (node2)
6283 node2 = node2->GetNext();
6284 }
3e541562 6285
ea160b2e
JS
6286 if (node)
6287 node = node->GetNext();
6288 }
3e541562 6289 }
ea160b2e
JS
6290#endif
6291
c025e094 6292 m_buffer->InsertFragment(GetRange().GetStart(), m_newParagraphs);
5d7836c4 6293 m_buffer->UpdateRanges();
c025e094 6294 m_buffer->Invalidate(wxRichTextRange(GetRange().GetStart()-1, GetRange().GetEnd()));
5d7836c4 6295
0ca07313
JS
6296 long newCaretPosition = GetPosition() + m_newParagraphs.GetRange().GetLength();
6297
6298 // Character position to caret position
6299 newCaretPosition --;
6300
6301 // Don't take into account the last newline
5d7836c4
JS
6302 if (m_newParagraphs.GetPartialParagraph())
6303 newCaretPosition --;
46ee0e5b 6304 else
7c081bd2 6305 if (m_newParagraphs.GetChildren().GetCount() > 1)
46ee0e5b
JS
6306 {
6307 wxRichTextObject* p = (wxRichTextObject*) m_newParagraphs.GetChildren().GetLast()->GetData();
6308 if (p->GetRange().GetLength() == 1)
6309 newCaretPosition --;
6310 }
5d7836c4 6311
3e541562 6312 newCaretPosition = wxMin(newCaretPosition, (m_buffer->GetRange().GetEnd()-1));
0ca07313 6313
ea160b2e
JS
6314 if (optimizationLineCharPositions.GetCount() > 0)
6315 UpdateAppearance(newCaretPosition, true /* send update event */, & optimizationLineCharPositions, & optimizationLineYPositions);
6316 else
6317 UpdateAppearance(newCaretPosition, true /* send update event */);
3e541562 6318
5912d19e
JS
6319 wxRichTextEvent cmdEvent(
6320 wxEVT_COMMAND_RICHTEXT_CONTENT_INSERTED,
6321 m_ctrl ? m_ctrl->GetId() : -1);
6322 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
6323 cmdEvent.SetRange(GetRange());
6324 cmdEvent.SetPosition(GetRange().GetStart());
3e541562 6325
5912d19e 6326 m_buffer->SendEvent(cmdEvent);
5d7836c4
JS
6327
6328 break;
6329 }
6330 case wxRICHTEXT_DELETE:
6331 {
6332 m_buffer->DeleteRange(GetRange());
6333 m_buffer->UpdateRanges();
1e967276 6334 m_buffer->Invalidate(wxRichTextRange(GetRange().GetStart(), GetRange().GetStart()));
5d7836c4 6335
6ccbca24
JS
6336 long caretPos = GetRange().GetStart()-1;
6337 if (caretPos >= m_buffer->GetRange().GetEnd())
6338 caretPos --;
6339
6340 UpdateAppearance(caretPos, true /* send update event */);
5d7836c4 6341
5912d19e
JS
6342 wxRichTextEvent cmdEvent(
6343 wxEVT_COMMAND_RICHTEXT_CONTENT_DELETED,
6344 m_ctrl ? m_ctrl->GetId() : -1);
6345 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
6346 cmdEvent.SetRange(GetRange());
6347 cmdEvent.SetPosition(GetRange().GetStart());
3e541562 6348
5912d19e
JS
6349 m_buffer->SendEvent(cmdEvent);
6350
5d7836c4
JS
6351 break;
6352 }
6353 case wxRICHTEXT_CHANGE_STYLE:
6354 {
6355 ApplyParagraphs(GetNewParagraphs());
1e967276 6356 m_buffer->Invalidate(GetRange());
5d7836c4
JS
6357
6358 UpdateAppearance(GetPosition());
6359
5912d19e
JS
6360 wxRichTextEvent cmdEvent(
6361 wxEVT_COMMAND_RICHTEXT_STYLE_CHANGED,
6362 m_ctrl ? m_ctrl->GetId() : -1);
6363 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
6364 cmdEvent.SetRange(GetRange());
6365 cmdEvent.SetPosition(GetRange().GetStart());
3e541562 6366
5912d19e
JS
6367 m_buffer->SendEvent(cmdEvent);
6368
5d7836c4
JS
6369 break;
6370 }
6371 default:
6372 break;
6373 }
6374
6375 return true;
6376}
6377
6378bool wxRichTextAction::Undo()
6379{
6380 m_buffer->Modify(true);
6381
6382 switch (m_cmdId)
6383 {
6384 case wxRICHTEXT_INSERT:
6385 {
6386 m_buffer->DeleteRange(GetRange());
6387 m_buffer->UpdateRanges();
1e967276 6388 m_buffer->Invalidate(wxRichTextRange(GetRange().GetStart(), GetRange().GetStart()));
5d7836c4
JS
6389
6390 long newCaretPosition = GetPosition() - 1;
3e541562 6391
5d7836c4
JS
6392 UpdateAppearance(newCaretPosition, true /* send update event */);
6393
5912d19e
JS
6394 wxRichTextEvent cmdEvent(
6395 wxEVT_COMMAND_RICHTEXT_CONTENT_DELETED,
6396 m_ctrl ? m_ctrl->GetId() : -1);
6397 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
6398 cmdEvent.SetRange(GetRange());
6399 cmdEvent.SetPosition(GetRange().GetStart());
3e541562 6400
5912d19e
JS
6401 m_buffer->SendEvent(cmdEvent);
6402
5d7836c4
JS
6403 break;
6404 }
6405 case wxRICHTEXT_DELETE:
6406 {
6407 m_buffer->InsertFragment(GetRange().GetStart(), m_oldParagraphs);
6408 m_buffer->UpdateRanges();
1e967276 6409 m_buffer->Invalidate(GetRange());
5d7836c4
JS
6410
6411 UpdateAppearance(GetPosition(), true /* send update event */);
6412
5912d19e
JS
6413 wxRichTextEvent cmdEvent(
6414 wxEVT_COMMAND_RICHTEXT_CONTENT_INSERTED,
6415 m_ctrl ? m_ctrl->GetId() : -1);
6416 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
6417 cmdEvent.SetRange(GetRange());
6418 cmdEvent.SetPosition(GetRange().GetStart());
3e541562 6419
5912d19e
JS
6420 m_buffer->SendEvent(cmdEvent);
6421
5d7836c4
JS
6422 break;
6423 }
6424 case wxRICHTEXT_CHANGE_STYLE:
6425 {
6426 ApplyParagraphs(GetOldParagraphs());
1e967276 6427 m_buffer->Invalidate(GetRange());
5d7836c4
JS
6428
6429 UpdateAppearance(GetPosition());
6430
5912d19e
JS
6431 wxRichTextEvent cmdEvent(
6432 wxEVT_COMMAND_RICHTEXT_STYLE_CHANGED,
6433 m_ctrl ? m_ctrl->GetId() : -1);
6434 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
6435 cmdEvent.SetRange(GetRange());
6436 cmdEvent.SetPosition(GetRange().GetStart());
3e541562 6437
5912d19e
JS
6438 m_buffer->SendEvent(cmdEvent);
6439
5d7836c4
JS
6440 break;
6441 }
6442 default:
6443 break;
6444 }
6445
6446 return true;
6447}
6448
6449/// Update the control appearance
ea160b2e 6450void wxRichTextAction::UpdateAppearance(long caretPosition, bool sendUpdateEvent, wxArrayInt* optimizationLineCharPositions, wxArrayInt* optimizationLineYPositions)
5d7836c4
JS
6451{
6452 if (m_ctrl)
6453 {
6454 m_ctrl->SetCaretPosition(caretPosition);
6455 if (!m_ctrl->IsFrozen())
6456 {
2f36e8dc 6457 m_ctrl->LayoutContent();
5d7836c4 6458 m_ctrl->PositionCaret();
3e541562 6459
ea160b2e
JS
6460#if wxRICHTEXT_USE_OPTIMIZED_DRAWING
6461 // Find refresh rectangle if we are in a position to optimise refresh
6462 if (m_cmdId == wxRICHTEXT_INSERT && optimizationLineCharPositions && optimizationLineCharPositions->GetCount() > 0)
6463 {
6464 size_t i;
3e541562 6465
ea160b2e
JS
6466 wxSize clientSize = m_ctrl->GetClientSize();
6467 wxPoint firstVisiblePt = m_ctrl->GetFirstVisiblePoint();
3e541562 6468
ea160b2e
JS
6469 // Start/end positions
6470 int firstY = 0;
6471 int lastY = firstVisiblePt.y + clientSize.y;
3e541562 6472
ea160b2e
JS
6473 bool foundStart = false;
6474 bool foundEnd = false;
3e541562 6475
ea160b2e
JS
6476 // position offset - how many characters were inserted
6477 int positionOffset = GetRange().GetLength();
6478
6479 // find the first line which is being drawn at the same position as it was
6480 // before. Since we're talking about a simple insertion, we can assume
6481 // that the rest of the window does not need to be redrawn.
3e541562 6482
ea160b2e
JS
6483 wxRichTextParagraph* para = m_buffer->GetParagraphAtPosition(GetPosition());
6484 wxRichTextObjectList::compatibility_iterator node = m_buffer->GetChildren().Find(para);
6485 while (node)
6486 {
6487 wxRichTextParagraph* child = (wxRichTextParagraph*) node->GetData();
6488 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
6489 while (node2)
6490 {
6491 wxRichTextLine* line = node2->GetData();
6492 wxPoint pt = line->GetAbsolutePosition();
6493 wxRichTextRange range = line->GetAbsoluteRange();
3e541562 6494
ea160b2e
JS
6495 // we want to find the first line that is in the same position
6496 // as before. This will mean we're at the end of the changed text.
3e541562 6497
ea160b2e
JS
6498 if (pt.y > lastY) // going past the end of the window, no more info
6499 {
27b5dab3
JS
6500 node2 = wxRichTextLineList::compatibility_iterator();
6501 node = wxRichTextObjectList::compatibility_iterator();
ea160b2e
JS
6502 }
6503 else
6504 {
6505 if (!foundStart)
6506 {
6507 firstY = pt.y - firstVisiblePt.y;
6508 foundStart = true;
6509 }
6510
3e541562 6511 // search for this line being at the same position as before
ea160b2e
JS
6512 for (i = 0; i < optimizationLineCharPositions->GetCount(); i++)
6513 {
6514 if (((*optimizationLineCharPositions)[i] + positionOffset == range.GetStart()) &&
6515 ((*optimizationLineYPositions)[i] == pt.y))
6516 {
6517 // Stop, we're now the same as we were
6518 foundEnd = true;
6519 lastY = pt.y - firstVisiblePt.y;
6520
27b5dab3
JS
6521 node2 = wxRichTextLineList::compatibility_iterator();
6522 node = wxRichTextObjectList::compatibility_iterator();
6523
ea160b2e 6524 break;
3e541562 6525 }
ea160b2e
JS
6526 }
6527 }
6528
6529 if (node2)
6530 node2 = node2->GetNext();
6531 }
3e541562 6532
ea160b2e
JS
6533 if (node)
6534 node = node->GetNext();
6535 }
3e541562 6536
ea160b2e
JS
6537 if (!foundStart)
6538 firstY = firstVisiblePt.y;
6539 if (!foundEnd)
6540 lastY = firstVisiblePt.y + clientSize.y;
6541
6542 wxRect rect(firstVisiblePt.x, firstY, firstVisiblePt.x + clientSize.x, lastY - firstY);
6543 m_ctrl->RefreshRect(rect);
3e541562 6544
ea160b2e
JS
6545 // TODO: we need to make sure that lines are only drawn if in the update region. The rect
6546 // passed to Draw is currently used in different ways (to pass the position the content should
6547 // be drawn at as well as the relevant region).
6548 }
3e541562 6549 else
ea160b2e
JS
6550#endif
6551 m_ctrl->Refresh(false);
5d7836c4
JS
6552
6553 if (sendUpdateEvent)
0ec1179b 6554 wxTextCtrl::SendTextUpdatedEvent(m_ctrl);
5d7836c4 6555 }
7fe8059f 6556 }
5d7836c4
JS
6557}
6558
6559/// Replace the buffer paragraphs with the new ones.
0ca07313 6560void wxRichTextAction::ApplyParagraphs(const wxRichTextParagraphLayoutBox& fragment)
5d7836c4
JS
6561{
6562 wxRichTextObjectList::compatibility_iterator node = fragment.GetChildren().GetFirst();
6563 while (node)
6564 {
6565 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
6566 wxASSERT (para != NULL);
6567
6568 // We'll replace the existing paragraph by finding the paragraph at this position,
6569 // delete its node data, and setting a copy as the new node data.
6570 // TODO: make more efficient by simply swapping old and new paragraph objects.
6571
6572 wxRichTextParagraph* existingPara = m_buffer->GetParagraphAtPosition(para->GetRange().GetStart());
6573 if (existingPara)
6574 {
6575 wxRichTextObjectList::compatibility_iterator bufferParaNode = m_buffer->GetChildren().Find(existingPara);
6576 if (bufferParaNode)
6577 {
6578 wxRichTextParagraph* newPara = new wxRichTextParagraph(*para);
6579 newPara->SetParent(m_buffer);
6580
6581 bufferParaNode->SetData(newPara);
6582
6583 delete existingPara;
6584 }
6585 }
6586
6587 node = node->GetNext();
6588 }
6589}
6590
6591
6592/*!
6593 * wxRichTextRange
6594 * This stores beginning and end positions for a range of data.
6595 */
6596
6597/// Limit this range to be within 'range'
6598bool wxRichTextRange::LimitTo(const wxRichTextRange& range)
6599{
6600 if (m_start < range.m_start)
6601 m_start = range.m_start;
6602
6603 if (m_end > range.m_end)
6604 m_end = range.m_end;
6605
6606 return true;
6607}
6608
6609/*!
6610 * wxRichTextImage implementation
6611 * This object represents an image.
6612 */
6613
6614IMPLEMENT_DYNAMIC_CLASS(wxRichTextImage, wxRichTextObject)
6615
44cc96a8 6616wxRichTextImage::wxRichTextImage(const wxImage& image, wxRichTextObject* parent, wxTextAttr* charStyle):
5d7836c4
JS
6617 wxRichTextObject(parent)
6618{
6619 m_image = image;
4f32b3cf
JS
6620 if (charStyle)
6621 SetAttributes(*charStyle);
5d7836c4
JS
6622}
6623
44cc96a8 6624wxRichTextImage::wxRichTextImage(const wxRichTextImageBlock& imageBlock, wxRichTextObject* parent, wxTextAttr* charStyle):
5d7836c4
JS
6625 wxRichTextObject(parent)
6626{
6627 m_imageBlock = imageBlock;
6628 m_imageBlock.Load(m_image);
4f32b3cf
JS
6629 if (charStyle)
6630 SetAttributes(*charStyle);
5d7836c4
JS
6631}
6632
6633/// Load wxImage from the block
6634bool wxRichTextImage::LoadFromBlock()
6635{
6636 m_imageBlock.Load(m_image);
6637 return m_imageBlock.Ok();
6638}
6639
6640/// Make block from the wxImage
6641bool wxRichTextImage::MakeBlock()
6642{
6643 if (m_imageBlock.GetImageType() == wxBITMAP_TYPE_ANY || m_imageBlock.GetImageType() == -1)
6644 m_imageBlock.SetImageType(wxBITMAP_TYPE_PNG);
6645
6646 m_imageBlock.MakeImageBlock(m_image, m_imageBlock.GetImageType());
6647 return m_imageBlock.Ok();
6648}
6649
6650
6651/// Draw the item
6652bool wxRichTextImage::Draw(wxDC& dc, const wxRichTextRange& range, const wxRichTextRange& selectionRange, const wxRect& rect, int WXUNUSED(descent), int WXUNUSED(style))
6653{
6654 if (!m_image.Ok() && m_imageBlock.Ok())
6655 LoadFromBlock();
6656
6657 if (!m_image.Ok())
6658 return false;
6659
6660 if (m_image.Ok() && !m_bitmap.Ok())
6661 m_bitmap = wxBitmap(m_image);
6662
6663 int y = rect.y + (rect.height - m_image.GetHeight());
6664
6665 if (m_bitmap.Ok())
6666 dc.DrawBitmap(m_bitmap, rect.x, y, true);
6667
6668 if (selectionRange.Contains(range.GetStart()))
6669 {
ecb5fbf1
JS
6670 wxCheckSetBrush(dc, *wxBLACK_BRUSH);
6671 wxCheckSetPen(dc, *wxBLACK_PEN);
5d7836c4
JS
6672 dc.SetLogicalFunction(wxINVERT);
6673 dc.DrawRectangle(rect);
6674 dc.SetLogicalFunction(wxCOPY);
6675 }
6676
6677 return true;
6678}
6679
6680/// Lay the item out
38113684 6681bool wxRichTextImage::Layout(wxDC& WXUNUSED(dc), const wxRect& rect, int WXUNUSED(style))
5d7836c4
JS
6682{
6683 if (!m_image.Ok())
6684 LoadFromBlock();
6685
6686 if (m_image.Ok())
6687 {
6688 SetCachedSize(wxSize(m_image.GetWidth(), m_image.GetHeight()));
6689 SetPosition(rect.GetPosition());
6690 }
6691
6692 return true;
6693}
6694
6695/// Get/set the object size for the given range. Returns false if the range
6696/// is invalid for this object.
7f0d9d71 6697bool wxRichTextImage::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& WXUNUSED(descent), wxDC& WXUNUSED(dc), int WXUNUSED(flags), wxPoint WXUNUSED(position)) const
5d7836c4
JS
6698{
6699 if (!range.IsWithin(GetRange()))
6700 return false;
6701
6702 if (!m_image.Ok())
6703 return false;
6704
6705 size.x = m_image.GetWidth();
6706 size.y = m_image.GetHeight();
6707
6708 return true;
6709}
6710
6711/// Copy
6712void wxRichTextImage::Copy(const wxRichTextImage& obj)
6713{
59509217
JS
6714 wxRichTextObject::Copy(obj);
6715
5d7836c4
JS
6716 m_image = obj.m_image;
6717 m_imageBlock = obj.m_imageBlock;
6718}
6719
6720/*!
6721 * Utilities
6722 *
6723 */
6724
6725/// Compare two attribute objects
44cc96a8 6726bool wxTextAttrEq(const wxTextAttr& attr1, const wxTextAttr& attr2)
5d7836c4 6727{
38f833b1 6728 return (attr1 == attr2);
5d7836c4
JS
6729}
6730
44cc96a8
JS
6731// Partial equality test taking flags into account
6732bool wxTextAttrEqPartial(const wxTextAttr& attr1, const wxTextAttr& attr2, int flags)
6733{
6734 return attr1.EqPartial(attr2, flags);
6735}
5d7836c4 6736
44cc96a8
JS
6737/// Compare tabs
6738bool wxRichTextTabsEq(const wxArrayInt& tabs1, const wxArrayInt& tabs2)
6739{
6740 if (tabs1.GetCount() != tabs2.GetCount())
5d7836c4
JS
6741 return false;
6742
44cc96a8
JS
6743 size_t i;
6744 for (i = 0; i < tabs1.GetCount(); i++)
6745 {
6746 if (tabs1[i] != tabs2[i])
6747 return false;
6748 }
6749 return true;
6750}
5d7836c4 6751
44cc96a8
JS
6752bool wxRichTextApplyStyle(wxTextAttr& destStyle, const wxTextAttr& style, wxTextAttr* compareWith)
6753{
6754 return destStyle.Apply(style, compareWith);
6755}
5d7836c4 6756
44cc96a8
JS
6757// Remove attributes
6758bool wxRichTextRemoveStyle(wxTextAttr& destStyle, const wxTextAttr& style)
6759{
6760 return wxTextAttr::RemoveStyle(destStyle, style);
6761}
5d7836c4 6762
44cc96a8
JS
6763/// Combine two bitlists, specifying the bits of interest with separate flags.
6764bool wxRichTextCombineBitlists(int& valueA, int valueB, int& flagsA, int flagsB)
6765{
6766 return wxTextAttr::CombineBitlists(valueA, valueB, flagsA, flagsB);
6767}
5d7836c4 6768
44cc96a8
JS
6769/// Compare two bitlists
6770bool wxRichTextBitlistsEqPartial(int valueA, int valueB, int flags)
6771{
6772 return wxTextAttr::BitlistsEqPartial(valueA, valueB, flags);
6773}
38f833b1 6774
44cc96a8
JS
6775/// Split into paragraph and character styles
6776bool wxRichTextSplitParaCharStyles(const wxTextAttr& style, wxTextAttr& parStyle, wxTextAttr& charStyle)
6777{
6778 return wxTextAttr::SplitParaCharStyles(style, parStyle, charStyle);
6779}
5d7836c4 6780
44cc96a8
JS
6781/// Convert a decimal to Roman numerals
6782wxString wxRichTextDecimalToRoman(long n)
6783{
6784 static wxArrayInt decimalNumbers;
6785 static wxArrayString romanNumbers;
5d7836c4 6786
44cc96a8
JS
6787 // Clean up arrays
6788 if (n == -1)
6789 {
6790 decimalNumbers.Clear();
6791 romanNumbers.Clear();
6792 return wxEmptyString;
6793 }
5d7836c4 6794
44cc96a8
JS
6795 if (decimalNumbers.GetCount() == 0)
6796 {
6797 #define wxRichTextAddDecRom(n, r) decimalNumbers.Add(n); romanNumbers.Add(r);
59509217 6798
44cc96a8
JS
6799 wxRichTextAddDecRom(1000, wxT("M"));
6800 wxRichTextAddDecRom(900, wxT("CM"));
6801 wxRichTextAddDecRom(500, wxT("D"));
6802 wxRichTextAddDecRom(400, wxT("CD"));
6803 wxRichTextAddDecRom(100, wxT("C"));
6804 wxRichTextAddDecRom(90, wxT("XC"));
6805 wxRichTextAddDecRom(50, wxT("L"));
6806 wxRichTextAddDecRom(40, wxT("XL"));
6807 wxRichTextAddDecRom(10, wxT("X"));
6808 wxRichTextAddDecRom(9, wxT("IX"));
6809 wxRichTextAddDecRom(5, wxT("V"));
6810 wxRichTextAddDecRom(4, wxT("IV"));
6811 wxRichTextAddDecRom(1, wxT("I"));
6812 }
5d7836c4 6813
44cc96a8
JS
6814 int i = 0;
6815 wxString roman;
ea160b2e 6816
44cc96a8 6817 while (n > 0 && i < 13)
42688aea 6818 {
44cc96a8
JS
6819 if (n >= decimalNumbers[i])
6820 {
6821 n -= decimalNumbers[i];
6822 roman += romanNumbers[i];
6823 }
6824 else
6825 {
6826 i ++;
6827 }
42688aea 6828 }
44cc96a8
JS
6829 if (roman.IsEmpty())
6830 roman = wxT("0");
6831 return roman;
6832}
42688aea 6833
44cc96a8
JS
6834/*!
6835 * wxRichTextFileHandler
6836 * Base class for file handlers
6837 */
4d6d8bf4 6838
44cc96a8 6839IMPLEMENT_CLASS(wxRichTextFileHandler, wxObject)
5d7836c4 6840
44cc96a8
JS
6841#if wxUSE_FFILE && wxUSE_STREAMS
6842bool wxRichTextFileHandler::LoadFile(wxRichTextBuffer *buffer, const wxString& filename)
5d7836c4 6843{
44cc96a8
JS
6844 wxFFileInputStream stream(filename);
6845 if (stream.Ok())
6846 return LoadFile(buffer, stream);
5d7836c4 6847
44cc96a8
JS
6848 return false;
6849}
5d7836c4 6850
44cc96a8
JS
6851bool wxRichTextFileHandler::SaveFile(wxRichTextBuffer *buffer, const wxString& filename)
6852{
6853 wxFFileOutputStream stream(filename);
6854 if (stream.Ok())
6855 return SaveFile(buffer, stream);
5d7836c4 6856
44cc96a8
JS
6857 return false;
6858}
6859#endif // wxUSE_FFILE && wxUSE_STREAMS
5d7836c4 6860
44cc96a8
JS
6861/// Can we handle this filename (if using files)? By default, checks the extension.
6862bool wxRichTextFileHandler::CanHandle(const wxString& filename) const
6863{
6864 wxString path, file, ext;
6865 wxSplitPath(filename, & path, & file, & ext);
5d7836c4 6866
44cc96a8
JS
6867 return (ext.Lower() == GetExtension());
6868}
5d7836c4 6869
44cc96a8
JS
6870/*!
6871 * wxRichTextTextHandler
6872 * Plain text handler
6873 */
5d7836c4 6874
44cc96a8 6875IMPLEMENT_CLASS(wxRichTextPlainTextHandler, wxRichTextFileHandler)
5d7836c4 6876
44cc96a8
JS
6877#if wxUSE_STREAMS
6878bool wxRichTextPlainTextHandler::DoLoadFile(wxRichTextBuffer *buffer, wxInputStream& stream)
6879{
6880 if (!stream.IsOk())
797e38dd
JS
6881 return false;
6882
44cc96a8
JS
6883 wxString str;
6884 int lastCh = 0;
5d7836c4 6885
44cc96a8
JS
6886 while (!stream.Eof())
6887 {
6888 int ch = stream.GetC();
5d7836c4 6889
44cc96a8
JS
6890 if (!stream.Eof())
6891 {
6892 if (ch == 10 && lastCh != 13)
6893 str += wxT('\n');
5d7836c4 6894
44cc96a8
JS
6895 if (ch > 0 && ch != 10)
6896 str += wxChar(ch);
5d7836c4 6897
44cc96a8
JS
6898 lastCh = ch;
6899 }
6900 }
5d7836c4 6901
44cc96a8
JS
6902 buffer->ResetAndClearCommands();
6903 buffer->Clear();
6904 buffer->AddParagraphs(str);
6905 buffer->UpdateRanges();
5d7836c4 6906
44cc96a8
JS
6907 return true;
6908}
5d7836c4 6909
44cc96a8
JS
6910bool wxRichTextPlainTextHandler::DoSaveFile(wxRichTextBuffer *buffer, wxOutputStream& stream)
6911{
6912 if (!stream.IsOk())
5d7836c4
JS
6913 return false;
6914
44cc96a8 6915 wxString text = buffer->GetText();
38f833b1 6916
44cc96a8
JS
6917 wxString newLine = wxRichTextLineBreakChar;
6918 text.Replace(newLine, wxT("\n"));
5d7836c4 6919
44cc96a8 6920 wxCharBuffer buf = text.ToAscii();
5d7836c4 6921
44cc96a8
JS
6922 stream.Write((const char*) buf, text.length());
6923 return true;
6924}
6925#endif // wxUSE_STREAMS
5d7836c4 6926
44cc96a8
JS
6927/*
6928 * Stores information about an image, in binary in-memory form
6929 */
59509217 6930
44cc96a8
JS
6931wxRichTextImageBlock::wxRichTextImageBlock()
6932{
6933 Init();
6934}
5d7836c4 6935
44cc96a8
JS
6936wxRichTextImageBlock::wxRichTextImageBlock(const wxRichTextImageBlock& block):wxObject()
6937{
6938 Init();
6939 Copy(block);
6940}
ea160b2e 6941
44cc96a8
JS
6942wxRichTextImageBlock::~wxRichTextImageBlock()
6943{
6944 if (m_data)
42688aea 6945 {
44cc96a8
JS
6946 delete[] m_data;
6947 m_data = NULL;
42688aea 6948 }
5d7836c4
JS
6949}
6950
44cc96a8 6951void wxRichTextImageBlock::Init()
5d7836c4
JS
6952{
6953 m_data = NULL;
6954 m_dataSize = 0;
6955 m_imageType = -1;
6956}
6957
6958void wxRichTextImageBlock::Clear()
6959{
b01ca8b6 6960 delete[] m_data;
5d7836c4
JS
6961 m_data = NULL;
6962 m_dataSize = 0;
6963 m_imageType = -1;
6964}
6965
6966
6967// Load the original image into a memory block.
6968// If the image is not a JPEG, we must convert it into a JPEG
6969// to conserve space.
6970// If it's not a JPEG we can make use of 'image', already scaled, so we don't have to
6971// load the image a 2nd time.
6972
6973bool wxRichTextImageBlock::MakeImageBlock(const wxString& filename, int imageType, wxImage& image, bool convertToJPEG)
6974{
6975 m_imageType = imageType;
6976
6977 wxString filenameToRead(filename);
7fe8059f 6978 bool removeFile = false;
5d7836c4
JS
6979
6980 if (imageType == -1)
7fe8059f 6981 return false; // Could not determine image type
5d7836c4
JS
6982
6983 if ((imageType != wxBITMAP_TYPE_JPEG) && convertToJPEG)
6984 {
6985 wxString tempFile;
6986 bool success = wxGetTempFileName(_("image"), tempFile) ;
6987
6988 wxASSERT(success);
6989
6990 wxUnusedVar(success);
6991
6992 image.SaveFile(tempFile, wxBITMAP_TYPE_JPEG);
6993 filenameToRead = tempFile;
7fe8059f 6994 removeFile = true;
5d7836c4
JS
6995
6996 m_imageType = wxBITMAP_TYPE_JPEG;
6997 }
6998 wxFile file;
6999 if (!file.Open(filenameToRead))
7fe8059f 7000 return false;
5d7836c4
JS
7001
7002 m_dataSize = (size_t) file.Length();
7003 file.Close();
7004
7005 if (m_data)
7006 delete[] m_data;
7007 m_data = ReadBlock(filenameToRead, m_dataSize);
7008
7009 if (removeFile)
7010 wxRemoveFile(filenameToRead);
7011
7012 return (m_data != NULL);
7013}
7014
7015// Make an image block from the wxImage in the given
7016// format.
7017bool wxRichTextImageBlock::MakeImageBlock(wxImage& image, int imageType, int quality)
7018{
7019 m_imageType = imageType;
7020 image.SetOption(wxT("quality"), quality);
7021
7022 if (imageType == -1)
7fe8059f 7023 return false; // Could not determine image type
5d7836c4
JS
7024
7025 wxString tempFile;
7026 bool success = wxGetTempFileName(_("image"), tempFile) ;
7fe8059f 7027
5d7836c4
JS
7028 wxASSERT(success);
7029 wxUnusedVar(success);
7fe8059f 7030
5d7836c4
JS
7031 if (!image.SaveFile(tempFile, m_imageType))
7032 {
7033 if (wxFileExists(tempFile))
7034 wxRemoveFile(tempFile);
7fe8059f 7035 return false;
5d7836c4
JS
7036 }
7037
7038 wxFile file;
7039 if (!file.Open(tempFile))
7fe8059f 7040 return false;
5d7836c4
JS
7041
7042 m_dataSize = (size_t) file.Length();
7043 file.Close();
7044
7045 if (m_data)
7046 delete[] m_data;
7047 m_data = ReadBlock(tempFile, m_dataSize);
7048
7049 wxRemoveFile(tempFile);
7050
7051 return (m_data != NULL);
7052}
7053
7054
7055// Write to a file
7056bool wxRichTextImageBlock::Write(const wxString& filename)
7057{
7058 return WriteBlock(filename, m_data, m_dataSize);
7059}
7060
7061void wxRichTextImageBlock::Copy(const wxRichTextImageBlock& block)
7062{
7063 m_imageType = block.m_imageType;
7064 if (m_data)
7065 {
7066 delete[] m_data;
7067 m_data = NULL;
7068 }
7069 m_dataSize = block.m_dataSize;
7070 if (m_dataSize == 0)
7071 return;
7072
7073 m_data = new unsigned char[m_dataSize];
7074 unsigned int i;
7075 for (i = 0; i < m_dataSize; i++)
7076 m_data[i] = block.m_data[i];
7077}
7078
7079//// Operators
7080void wxRichTextImageBlock::operator=(const wxRichTextImageBlock& block)
7081{
7082 Copy(block);
7083}
7084
7085// Load a wxImage from the block
7086bool wxRichTextImageBlock::Load(wxImage& image)
7087{
7088 if (!m_data)
7fe8059f 7089 return false;
5d7836c4
JS
7090
7091 // Read in the image.
0ca07313 7092#if wxUSE_STREAMS
5d7836c4
JS
7093 wxMemoryInputStream mstream(m_data, m_dataSize);
7094 bool success = image.LoadFile(mstream, GetImageType());
7095#else
7096 wxString tempFile;
7097 bool success = wxGetTempFileName(_("image"), tempFile) ;
7098 wxASSERT(success);
7099
7100 if (!WriteBlock(tempFile, m_data, m_dataSize))
7101 {
7fe8059f 7102 return false;
5d7836c4
JS
7103 }
7104 success = image.LoadFile(tempFile, GetImageType());
7105 wxRemoveFile(tempFile);
7106#endif
7107
7108 return success;
7109}
7110
7111// Write data in hex to a stream
7112bool wxRichTextImageBlock::WriteHex(wxOutputStream& stream)
7113{
351c0647
JS
7114 const int bufSize = 512;
7115 char buf[bufSize+1];
7116
7117 int left = m_dataSize;
7118 int n, i, j;
7119 j = 0;
7120 while (left > 0)
5d7836c4 7121 {
351c0647
JS
7122 if (left*2 > bufSize)
7123 {
7124 n = bufSize; left -= (bufSize/2);
7125 }
7126 else
7127 {
7128 n = left*2; left = 0;
7129 }
7fe8059f 7130
351c0647
JS
7131 char* b = buf;
7132 for (i = 0; i < (n/2); i++)
7133 {
f728025e 7134 wxDecToHex(m_data[j], b, b+1);
351c0647
JS
7135 b += 2; j ++;
7136 }
5d7836c4 7137
351c0647
JS
7138 buf[n] = 0;
7139 stream.Write((const char*) buf, n);
7140 }
5d7836c4
JS
7141 return true;
7142}
7143
7144// Read data in hex from a stream
7145bool wxRichTextImageBlock::ReadHex(wxInputStream& stream, int length, int imageType)
7146{
7147 int dataSize = length/2;
7148
7149 if (m_data)
7150 delete[] m_data;
7151
351c0647 7152 wxChar str[2];
5d7836c4
JS
7153 m_data = new unsigned char[dataSize];
7154 int i;
7155 for (i = 0; i < dataSize; i ++)
7156 {
c9f78968
VS
7157 str[0] = (char)stream.GetC();
7158 str[1] = (char)stream.GetC();
5d7836c4 7159
7fe8059f 7160 m_data[i] = (unsigned char)wxHexToDec(str);
5d7836c4
JS
7161 }
7162
7163 m_dataSize = dataSize;
7164 m_imageType = imageType;
7165
7166 return true;
7167}
7168
5d7836c4
JS
7169// Allocate and read from stream as a block of memory
7170unsigned char* wxRichTextImageBlock::ReadBlock(wxInputStream& stream, size_t size)
7171{
7172 unsigned char* block = new unsigned char[size];
7173 if (!block)
7174 return NULL;
7175
7176 stream.Read(block, size);
7177
7178 return block;
7179}
7180
7181unsigned char* wxRichTextImageBlock::ReadBlock(const wxString& filename, size_t size)
7182{
7183 wxFileInputStream stream(filename);
7184 if (!stream.Ok())
7185 return NULL;
7186
7187 return ReadBlock(stream, size);
7188}
7189
7190// Write memory block to stream
7191bool wxRichTextImageBlock::WriteBlock(wxOutputStream& stream, unsigned char* block, size_t size)
7192{
7193 stream.Write((void*) block, size);
7194 return stream.IsOk();
7195
7196}
7197
7198// Write memory block to file
7199bool wxRichTextImageBlock::WriteBlock(const wxString& filename, unsigned char* block, size_t size)
7200{
7201 wxFileOutputStream outStream(filename);
7202 if (!outStream.Ok())
7fe8059f 7203 return false;
5d7836c4
JS
7204
7205 return WriteBlock(outStream, block, size);
7206}
7207
d2d0adc7
JS
7208// Gets the extension for the block's type
7209wxString wxRichTextImageBlock::GetExtension() const
7210{
7211 wxImageHandler* handler = wxImage::FindHandler(GetImageType());
7212 if (handler)
7213 return handler->GetExtension();
7214 else
7215 return wxEmptyString;
7216}
7217
0ca07313
JS
7218#if wxUSE_DATAOBJ
7219
7220/*!
7221 * The data object for a wxRichTextBuffer
7222 */
7223
7224const wxChar *wxRichTextBufferDataObject::ms_richTextBufferFormatId = wxT("wxShape");
7225
7226wxRichTextBufferDataObject::wxRichTextBufferDataObject(wxRichTextBuffer* richTextBuffer)
7227{
7228 m_richTextBuffer = richTextBuffer;
7229
7230 // this string should uniquely identify our format, but is otherwise
7231 // arbitrary
7232 m_formatRichTextBuffer.SetId(GetRichTextBufferFormatId());
7233
7234 SetFormat(m_formatRichTextBuffer);
7235}
7236
7237wxRichTextBufferDataObject::~wxRichTextBufferDataObject()
7238{
7239 delete m_richTextBuffer;
7240}
7241
7242// after a call to this function, the richTextBuffer is owned by the caller and it
7243// is responsible for deleting it!
7244wxRichTextBuffer* wxRichTextBufferDataObject::GetRichTextBuffer()
7245{
7246 wxRichTextBuffer* richTextBuffer = m_richTextBuffer;
7247 m_richTextBuffer = NULL;
7248
7249 return richTextBuffer;
7250}
7251
7252wxDataFormat wxRichTextBufferDataObject::GetPreferredFormat(Direction WXUNUSED(dir)) const
7253{
7254 return m_formatRichTextBuffer;
7255}
7256
7257size_t wxRichTextBufferDataObject::GetDataSize() const
7258{
7259 if (!m_richTextBuffer)
7260 return 0;
7261
7262 wxString bufXML;
7263
7264 {
7265 wxStringOutputStream stream(& bufXML);
7266 if (!m_richTextBuffer->SaveFile(stream, wxRICHTEXT_TYPE_XML))
7267 {
7268 wxLogError(wxT("Could not write the buffer to an XML stream.\nYou may have forgotten to add the XML file handler."));
7269 return 0;
7270 }
7271 }
7272
7273#if wxUSE_UNICODE
7274 wxCharBuffer buffer = bufXML.mb_str(wxConvUTF8);
7275 return strlen(buffer) + 1;
7276#else
7277 return bufXML.Length()+1;
7278#endif
7279}
7280
7281bool wxRichTextBufferDataObject::GetDataHere(void *pBuf) const
7282{
7283 if (!pBuf || !m_richTextBuffer)
7284 return false;
7285
7286 wxString bufXML;
7287
7288 {
7289 wxStringOutputStream stream(& bufXML);
7290 if (!m_richTextBuffer->SaveFile(stream, wxRICHTEXT_TYPE_XML))
7291 {
7292 wxLogError(wxT("Could not write the buffer to an XML stream.\nYou may have forgotten to add the XML file handler."));
7293 return 0;
7294 }
7295 }
7296
7297#if wxUSE_UNICODE
7298 wxCharBuffer buffer = bufXML.mb_str(wxConvUTF8);
7299 size_t len = strlen(buffer);
7300 memcpy((char*) pBuf, (const char*) buffer, len);
7301 ((char*) pBuf)[len] = 0;
7302#else
7303 size_t len = bufXML.Length();
7304 memcpy((char*) pBuf, (const char*) bufXML.c_str(), len);
7305 ((char*) pBuf)[len] = 0;
7306#endif
7307
7308 return true;
7309}
7310
7311bool wxRichTextBufferDataObject::SetData(size_t WXUNUSED(len), const void *buf)
7312{
7313 delete m_richTextBuffer;
7314 m_richTextBuffer = NULL;
7315
7316 wxString bufXML((const char*) buf, wxConvUTF8);
7317
7318 m_richTextBuffer = new wxRichTextBuffer;
7319
7320 wxStringInputStream stream(bufXML);
7321 if (!m_richTextBuffer->LoadFile(stream, wxRICHTEXT_TYPE_XML))
7322 {
7323 wxLogError(wxT("Could not read the buffer from an XML stream.\nYou may have forgotten to add the XML file handler."));
7324
7325 delete m_richTextBuffer;
7326 m_richTextBuffer = NULL;
7327
7328 return false;
7329 }
7330 return true;
7331}
7332
7333#endif
7334 // wxUSE_DATAOBJ
7335
44cc96a8
JS
7336
7337/*
7338 * wxRichTextFontTable
7339 * Manages quick access to a pool of fonts for rendering rich text
7340 */
7341
d65381ac 7342WX_DECLARE_STRING_HASH_MAP_WITH_DECL(wxFont, wxRichTextFontTableHashMap, class WXDLLIMPEXP_RICHTEXT);
44cc96a8
JS
7343
7344class wxRichTextFontTableData: public wxObjectRefData
7345{
7346public:
7347 wxRichTextFontTableData() {}
7348
7349 wxFont FindFont(const wxTextAttr& fontSpec);
7350
7351 wxRichTextFontTableHashMap m_hashMap;
7352};
7353
7354wxFont wxRichTextFontTableData::FindFont(const wxTextAttr& fontSpec)
7355{
7356 wxString facename(fontSpec.GetFontFaceName());
7357 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()));
7358 wxRichTextFontTableHashMap::iterator entry = m_hashMap.find(spec);
7359
7360 if ( entry == m_hashMap.end() )
7361 {
7362 wxFont font(fontSpec.GetFontSize(), wxDEFAULT, fontSpec.GetFontStyle(), fontSpec.GetFontWeight(), fontSpec.GetFontUnderlined(), facename.c_str());
7363 m_hashMap[spec] = font;
7364 return font;
7365 }
7366 else
7367 {
7368 return entry->second;
7369 }
7370}
7371
7372IMPLEMENT_DYNAMIC_CLASS(wxRichTextFontTable, wxObject)
7373
7374wxRichTextFontTable::wxRichTextFontTable()
7375{
7376 m_refData = new wxRichTextFontTableData;
44cc96a8
JS
7377}
7378
7379wxRichTextFontTable::wxRichTextFontTable(const wxRichTextFontTable& table)
7380{
7381 (*this) = table;
7382}
7383
7384wxRichTextFontTable::~wxRichTextFontTable()
7385{
7386 UnRef();
7387}
7388
7389bool wxRichTextFontTable::operator == (const wxRichTextFontTable& table) const
7390{
7391 return (m_refData == table.m_refData);
7392}
7393
7394void wxRichTextFontTable::operator= (const wxRichTextFontTable& table)
7395{
7396 Ref(table);
7397}
7398
7399wxFont wxRichTextFontTable::FindFont(const wxTextAttr& fontSpec)
7400{
7401 wxRichTextFontTableData* data = (wxRichTextFontTableData*) m_refData;
7402 if (data)
7403 return data->FindFont(fontSpec);
7404 else
7405 return wxFont();
7406}
7407
7408void wxRichTextFontTable::Clear()
7409{
7410 wxRichTextFontTableData* data = (wxRichTextFontTableData*) m_refData;
7411 if (data)
7412 data->m_hashMap.clear();
7413}
7414
5d7836c4
JS
7415#endif
7416 // wxUSE_RICHTEXT