]> git.saurik.com Git - wxWidgets.git/blame - src/richtext/richtextbuffer.cpp
Use GetFont not m_font for GetCharHeight
[wxWidgets.git] / src / richtext / richtextbuffer.cpp
CommitLineData
5d7836c4 1/////////////////////////////////////////////////////////////////////////////
7fe8059f 2// Name: 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__
16 #pragma hdrstop
17#endif
18
19#ifndef WX_PRECOMP
20 #include "wx/wx.h"
21#endif
22
23#include "wx/image.h"
24
25#if wxUSE_RICHTEXT
26
27#include "wx/filename.h"
28#include "wx/clipbrd.h"
29#include "wx/wfstream.h"
30#include "wx/module.h"
31#include "wx/mstream.h"
32#include "wx/sstream.h"
33
34#include "wx/richtext/richtextbuffer.h"
35#include "wx/richtext/richtextctrl.h"
36#include "wx/richtext/richtextstyles.h"
37
38#include "wx/listimpl.cpp"
39
40WX_DEFINE_LIST(wxRichTextObjectList);
41WX_DEFINE_LIST(wxRichTextLineList);
42
43/*!
44 * wxRichTextObject
45 * This is the base for drawable objects.
46 */
47
48IMPLEMENT_CLASS(wxRichTextObject, wxObject)
49
50wxRichTextObject::wxRichTextObject(wxRichTextObject* parent)
51{
52 m_dirty = false;
53 m_refCount = 1;
54 m_parent = parent;
55 m_leftMargin = 0;
56 m_rightMargin = 0;
57 m_topMargin = 0;
58 m_bottomMargin = 0;
59 m_descent = 0;
60}
61
62wxRichTextObject::~wxRichTextObject()
63{
64}
65
66void wxRichTextObject::Dereference()
67{
68 m_refCount --;
69 if (m_refCount <= 0)
70 delete this;
71}
72
73/// Copy
74void wxRichTextObject::Copy(const wxRichTextObject& obj)
75{
76 m_size = obj.m_size;
77 m_pos = obj.m_pos;
78 m_dirty = obj.m_dirty;
79 m_range = obj.m_range;
80 m_attributes = obj.m_attributes;
81 m_descent = obj.m_descent;
82
83 if (!m_attributes.GetFont().Ok())
84 wxLogDebug(wxT("No font!"));
85 if (!obj.m_attributes.GetFont().Ok())
86 wxLogDebug(wxT("Parent has no font!"));
87}
88
89void wxRichTextObject::SetMargins(int margin)
90{
91 m_leftMargin = m_rightMargin = m_topMargin = m_bottomMargin = margin;
92}
93
94void wxRichTextObject::SetMargins(int leftMargin, int rightMargin, int topMargin, int bottomMargin)
95{
96 m_leftMargin = leftMargin;
97 m_rightMargin = rightMargin;
98 m_topMargin = topMargin;
99 m_bottomMargin = bottomMargin;
100}
101
102// Convert units in tends of a millimetre to device units
103int wxRichTextObject::ConvertTenthsMMToPixels(wxDC& dc, int units)
104{
105 int ppi = dc.GetPPI().x;
106
107 // There are ppi pixels in 254.1 "1/10 mm"
108
109 double pixels = ((double) units * (double)ppi) / 254.1;
110
111 return (int) pixels;
112}
113
114/// Dump to output stream for debugging
115void wxRichTextObject::Dump(wxTextOutputStream& stream)
116{
117 stream << GetClassInfo()->GetClassName() << wxT("\n");
118 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");
119 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");
120}
121
122
123/*!
124 * wxRichTextCompositeObject
125 * This is the base for drawable objects.
126 */
127
128IMPLEMENT_CLASS(wxRichTextCompositeObject, wxRichTextObject)
129
130wxRichTextCompositeObject::wxRichTextCompositeObject(wxRichTextObject* parent):
131 wxRichTextObject(parent)
132{
133}
134
135wxRichTextCompositeObject::~wxRichTextCompositeObject()
136{
137 DeleteChildren();
138}
139
140/// Get the nth child
141wxRichTextObject* wxRichTextCompositeObject::GetChild(size_t n) const
142{
143 wxASSERT ( n < m_children.GetCount() );
144
145 return m_children.Item(n)->GetData();
146}
147
148/// Append a child, returning the position
149size_t wxRichTextCompositeObject::AppendChild(wxRichTextObject* child)
150{
151 m_children.Append(child);
152 child->SetParent(this);
153 return m_children.GetCount() - 1;
154}
155
156/// Insert the child in front of the given object, or at the beginning
157bool wxRichTextCompositeObject::InsertChild(wxRichTextObject* child, wxRichTextObject* inFrontOf)
158{
159 if (inFrontOf)
160 {
161 wxRichTextObjectList::compatibility_iterator node = m_children.Find(inFrontOf);
162 m_children.Insert(node, child);
163 }
164 else
165 m_children.Insert(child);
166 child->SetParent(this);
167
168 return true;
169}
170
171/// Delete the child
172bool wxRichTextCompositeObject::RemoveChild(wxRichTextObject* child, bool deleteChild)
173{
174 wxRichTextObjectList::compatibility_iterator node = m_children.Find(child);
175 if (node)
176 {
efbf6735
JS
177 wxRichTextObject* obj = node->GetData();
178 m_children.Erase(node);
5d7836c4 179 if (deleteChild)
efbf6735 180 delete obj;
5d7836c4
JS
181
182 return true;
183 }
184 return false;
185}
186
187/// Delete all children
188bool wxRichTextCompositeObject::DeleteChildren()
189{
190 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
191 while (node)
192 {
193 wxRichTextObjectList::compatibility_iterator oldNode = node;
194
195 wxRichTextObject* child = node->GetData();
196 child->Dereference(); // Only delete if reference count is zero
197
198 node = node->GetNext();
efbf6735 199 m_children.Erase(oldNode);
5d7836c4
JS
200 }
201
202 return true;
203}
204
205/// Get the child count
206size_t wxRichTextCompositeObject::GetChildCount() const
207{
208 return m_children.GetCount();
209}
210
211/// Copy
212void wxRichTextCompositeObject::Copy(const wxRichTextCompositeObject& obj)
213{
214 wxRichTextObject::Copy(obj);
215
216 DeleteChildren();
217
218 wxRichTextObjectList::compatibility_iterator node = obj.m_children.GetFirst();
219 while (node)
220 {
221 wxRichTextObject* child = node->GetData();
222 m_children.Append(child->Clone());
223
224 node = node->GetNext();
225 }
226}
227
228/// Hit-testing: returns a flag indicating hit test details, plus
229/// information about position
230int wxRichTextCompositeObject::HitTest(wxDC& dc, const wxPoint& pt, long& textPosition)
231{
232 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
233 while (node)
234 {
235 wxRichTextObject* child = node->GetData();
236
237 int ret = child->HitTest(dc, pt, textPosition);
238 if (ret != wxRICHTEXT_HITTEST_NONE)
239 return ret;
240
241 node = node->GetNext();
242 }
243
244 return wxRICHTEXT_HITTEST_NONE;
245}
246
247/// Finds the absolute position and row height for the given character position
248bool wxRichTextCompositeObject::FindPosition(wxDC& dc, long index, wxPoint& pt, int* height, bool forceLineStart)
249{
250 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
251 while (node)
252 {
253 wxRichTextObject* child = node->GetData();
254
255 if (child->FindPosition(dc, index, pt, height, forceLineStart))
256 return true;
257
258 node = node->GetNext();
259 }
260
261 return false;
262}
263
264/// Calculate range
265void wxRichTextCompositeObject::CalculateRange(long start, long& end)
266{
267 long current = start;
268 long lastEnd = current;
269
270 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
271 while (node)
272 {
273 wxRichTextObject* child = node->GetData();
274 long childEnd = 0;
275
276 child->CalculateRange(current, childEnd);
277 lastEnd = childEnd;
278
279 current = childEnd + 1;
280
281 node = node->GetNext();
282 }
283
284 end = lastEnd;
285
286 // An object with no children has zero length
287 if (m_children.GetCount() == 0)
288 end --;
289
290 m_range.SetRange(start, end);
291}
292
293/// Delete range from layout.
294bool wxRichTextCompositeObject::DeleteRange(const wxRichTextRange& range)
295{
296 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
7fe8059f 297
5d7836c4
JS
298 while (node)
299 {
300 wxRichTextObject* obj = (wxRichTextObject*) node->GetData();
301 wxRichTextObjectList::compatibility_iterator next = node->GetNext();
7fe8059f 302
5d7836c4
JS
303 // Delete the range in each paragraph
304
305 // When a chunk has been deleted, internally the content does not
306 // now match the ranges.
307 // However, so long as deletion is not done on the same object twice this is OK.
308 // If you may delete content from the same object twice, recalculate
309 // the ranges inbetween DeleteRange calls by calling CalculateRanges, and
310 // adjust the range you're deleting accordingly.
7fe8059f 311
5d7836c4
JS
312 if (!obj->GetRange().IsOutside(range))
313 {
314 obj->DeleteRange(range);
315
316 // Delete an empty object, or paragraph within this range.
317 if (obj->IsEmpty() ||
318 (range.GetStart() <= obj->GetRange().GetStart() && range.GetEnd() >= obj->GetRange().GetEnd()))
319 {
320 // An empty paragraph has length 1, so won't be deleted unless the
321 // whole range is deleted.
7fe8059f 322 RemoveChild(obj, true);
5d7836c4
JS
323 }
324 }
7fe8059f 325
5d7836c4
JS
326 node = next;
327 }
7fe8059f 328
5d7836c4
JS
329 return true;
330}
331
332/// Get any text in this object for the given range
333wxString wxRichTextCompositeObject::GetTextForRange(const wxRichTextRange& range) const
334{
335 wxString text;
336 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
337 while (node)
338 {
339 wxRichTextObject* child = node->GetData();
340 wxRichTextRange childRange = range;
341 if (!child->GetRange().IsOutside(range))
342 {
343 childRange.LimitTo(child->GetRange());
7fe8059f 344
5d7836c4 345 wxString childText = child->GetTextForRange(childRange);
7fe8059f 346
5d7836c4
JS
347 text += childText;
348 }
349 node = node->GetNext();
350 }
351
352 return text;
353}
354
355/// Recursively merge all pieces that can be merged.
356bool wxRichTextCompositeObject::Defragment()
357{
358 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
359 while (node)
360 {
361 wxRichTextObject* child = node->GetData();
362 wxRichTextCompositeObject* composite = wxDynamicCast(child, wxRichTextCompositeObject);
7fe8059f 363 if (composite)
5d7836c4
JS
364 composite->Defragment();
365
366 if (node->GetNext())
367 {
368 wxRichTextObject* nextChild = node->GetNext()->GetData();
369 if (child->CanMerge(nextChild) && child->Merge(nextChild))
370 {
371 nextChild->Dereference();
372 delete node->GetNext();
373
374 // Don't set node -- we'll see if we can merge again with the next
375 // child.
376 }
377 else
378 node = node->GetNext();
379 }
380 else
381 node = node->GetNext();
382 }
383
384 return true;
385}
386
387/// Dump to output stream for debugging
388void wxRichTextCompositeObject::Dump(wxTextOutputStream& stream)
389{
390 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
391 while (node)
392 {
393 wxRichTextObject* child = node->GetData();
394 child->Dump(stream);
395 node = node->GetNext();
396 }
397}
398
399
400/*!
401 * wxRichTextBox
402 * This defines a 2D space to lay out objects
403 */
404
405IMPLEMENT_DYNAMIC_CLASS(wxRichTextBox, wxRichTextCompositeObject)
406
407wxRichTextBox::wxRichTextBox(wxRichTextObject* parent):
408 wxRichTextCompositeObject(parent)
409{
410}
411
412/// Draw the item
413bool wxRichTextBox::Draw(wxDC& dc, const wxRichTextRange& range, const wxRichTextRange& selectionRange, const wxRect& WXUNUSED(rect), int descent, int style)
414{
415 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
416 while (node)
417 {
418 wxRichTextObject* child = node->GetData();
419
420 wxRect childRect = wxRect(child->GetPosition(), child->GetCachedSize());
421 child->Draw(dc, range, selectionRange, childRect, descent, style);
422
423 node = node->GetNext();
424 }
425 return true;
426}
427
428/// Lay the item out
38113684 429bool wxRichTextBox::Layout(wxDC& dc, const wxRect& rect, int style)
5d7836c4
JS
430{
431 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
432 while (node)
433 {
434 wxRichTextObject* child = node->GetData();
38113684 435 child->Layout(dc, rect, style);
5d7836c4
JS
436
437 node = node->GetNext();
438 }
439 m_dirty = false;
440 return true;
441}
442
443/// Get/set the size for the given range. Assume only has one child.
444bool wxRichTextBox::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, int flags) const
445{
446 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
447 if (node)
448 {
449 wxRichTextObject* child = node->GetData();
450 return child->GetRangeSize(range, size, descent, dc, flags);
451 }
452 else
453 return false;
454}
455
456/// Copy
457void wxRichTextBox::Copy(const wxRichTextBox& obj)
458{
459 wxRichTextCompositeObject::Copy(obj);
460}
461
462
463/*!
464 * wxRichTextParagraphLayoutBox
465 * This box knows how to lay out paragraphs.
466 */
467
468IMPLEMENT_DYNAMIC_CLASS(wxRichTextParagraphLayoutBox, wxRichTextBox)
469
470wxRichTextParagraphLayoutBox::wxRichTextParagraphLayoutBox(wxRichTextObject* parent):
471 wxRichTextBox(parent)
472{
473 Init();
474}
475
476/// Initialize the object.
477void wxRichTextParagraphLayoutBox::Init()
478{
479 m_ctrl = NULL;
480
481 // For now, assume is the only box and has no initial size.
482 m_range = wxRichTextRange(0, -1);
483
38113684 484 m_invalidRange.SetRange(-1, -1);
5d7836c4
JS
485 m_leftMargin = 4;
486 m_rightMargin = 4;
487 m_topMargin = 4;
488 m_bottomMargin = 4;
489}
490
491/// Draw the item
011b3dcb 492bool wxRichTextParagraphLayoutBox::Draw(wxDC& dc, const wxRichTextRange& range, const wxRichTextRange& selectionRange, const wxRect& rect, int descent, int style)
5d7836c4
JS
493{
494 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
495 while (node)
496 {
497 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
498 wxASSERT (child != NULL);
7fe8059f 499
5d7836c4
JS
500 if (child && !child->GetRange().IsOutside(range))
501 {
502 wxRect childRect(child->GetPosition(), child->GetCachedSize());
7fe8059f 503
011b3dcb
JS
504 if (childRect.GetTop() > rect.GetBottom() || childRect.GetBottom() < rect.GetTop())
505 {
506 // Skip
507 }
508 else
509 child->Draw(dc, child->GetRange(), selectionRange, childRect, descent, style);
5d7836c4
JS
510 }
511
512 node = node->GetNext();
513 }
514 return true;
515}
516
517/// Lay the item out
38113684 518bool wxRichTextParagraphLayoutBox::Layout(wxDC& dc, const wxRect& rect, int style)
5d7836c4 519{
4d551ad5
JS
520 wxRect availableSpace;
521 bool formatRect = (style & wxRICHTEXT_LAYOUT_SPECIFIED_RECT) == wxRICHTEXT_LAYOUT_SPECIFIED_RECT;
522
523 // If only laying out a specific area, the passed rect has a different meaning:
524 // the visible part of the buffer.
525 if (formatRect)
526 {
527 availableSpace = wxRect(0 + m_leftMargin,
528 0 + m_topMargin,
529 rect.width - m_leftMargin - m_rightMargin,
530 rect.height);
531
532 // Invalidate the part of the buffer from the first visible line
533 // to the end. If other parts of the buffer are currently invalid,
534 // then they too will be taken into account if they are above
535 // the visible point.
536 long startPos = 0;
537 wxRichTextLine* line = GetLineAtYPosition(rect.y);
538 if (line)
539 startPos = line->GetAbsoluteRange().GetStart();
540
541 Invalidate(wxRichTextRange(startPos, GetRange().GetEnd()));
542 }
543 else
544 availableSpace = wxRect(rect.x + m_leftMargin,
5d7836c4
JS
545 rect.y + m_topMargin,
546 rect.width - m_leftMargin - m_rightMargin,
547 rect.height - m_topMargin - m_bottomMargin);
548
549 int maxWidth = 0;
7fe8059f 550
5d7836c4 551 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
39a1c2f2 552
38113684 553 bool layoutAll = true;
1e967276 554
38113684
JS
555 // Get invalid range, rounding to paragraph start/end.
556 wxRichTextRange invalidRange = GetInvalidRange(true);
557
4d551ad5 558 if (invalidRange == wxRICHTEXT_NONE && !formatRect)
1e967276
JS
559 return true;
560
561 if (invalidRange == wxRICHTEXT_ALL)
562 layoutAll = true;
38113684
JS
563 else // If we know what range is affected, start laying out from that point on.
564 if (invalidRange.GetStart() > GetRange().GetStart())
2c375f42 565 {
38113684 566 wxRichTextParagraph* firstParagraph = GetParagraphAtPosition(invalidRange.GetStart());
2c375f42
JS
567 if (firstParagraph)
568 {
569 wxRichTextObjectList::compatibility_iterator firstNode = m_children.Find(firstParagraph);
1e967276 570 wxRichTextObjectList::compatibility_iterator previousNode = firstNode ? firstNode->GetPrevious() : (wxRichTextObjectList::compatibility_iterator) NULL;
2c375f42
JS
571 if (firstNode && previousNode)
572 {
573 wxRichTextParagraph* previousParagraph = wxDynamicCast(previousNode->GetData(), wxRichTextParagraph);
574 availableSpace.y = previousParagraph->GetPosition().y + previousParagraph->GetCachedSize().y;
7fe8059f 575
2c375f42
JS
576 // Now we're going to start iterating from the first affected paragraph.
577 node = firstNode;
1e967276
JS
578
579 layoutAll = false;
2c375f42
JS
580 }
581 }
582 }
583
4d551ad5
JS
584 // A way to force speedy rest-of-buffer layout (the 'else' below)
585 bool forceQuickLayout = false;
39a1c2f2 586
5d7836c4
JS
587 while (node)
588 {
589 // Assume this box only contains paragraphs
590
591 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
592 wxASSERT (child != NULL);
7fe8059f 593
1e967276 594 // TODO: what if the child hasn't been laid out (e.g. involved in Undo) but still has 'old' lines
4d551ad5 595 if (child && !forceQuickLayout && (layoutAll || child->GetLines().GetCount() == 0 || !child->GetRange().IsOutside(invalidRange)))
2c375f42 596 {
38113684 597 child->Layout(dc, availableSpace, style);
5d7836c4 598
2c375f42
JS
599 // Layout must set the cached size
600 availableSpace.y += child->GetCachedSize().y;
601 maxWidth = wxMax(maxWidth, child->GetCachedSize().x);
4d551ad5
JS
602
603 // If we're just formatting the visible part of the buffer,
604 // and we're now past the bottom of the window, start quick
605 // layout.
606 if (formatRect && child->GetPosition().y > rect.GetBottom())
607 forceQuickLayout = true;
2c375f42
JS
608 }
609 else
610 {
611 // We're outside the immediately affected range, so now let's just
612 // move everything up or down. This assumes that all the children have previously
613 // been laid out and have wrapped line lists associated with them.
614 // TODO: check all paragraphs before the affected range.
7fe8059f 615
2c375f42 616 int inc = availableSpace.y - child->GetPosition().y;
7fe8059f 617
2c375f42
JS
618 while (node)
619 {
620 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
621 if (child)
622 {
623 if (child->GetLines().GetCount() == 0)
38113684 624 child->Layout(dc, availableSpace, style);
2c375f42
JS
625 else
626 child->SetPosition(wxPoint(child->GetPosition().x, child->GetPosition().y + inc));
7fe8059f 627
2c375f42
JS
628 availableSpace.y += child->GetCachedSize().y;
629 maxWidth = wxMax(maxWidth, child->GetCachedSize().x);
630 }
7fe8059f
WS
631
632 node = node->GetNext();
2c375f42
JS
633 }
634 break;
635 }
5d7836c4
JS
636
637 node = node->GetNext();
638 }
639
640 SetCachedSize(wxSize(maxWidth, availableSpace.y));
641
642 m_dirty = false;
1e967276 643 m_invalidRange = wxRICHTEXT_NONE;
5d7836c4
JS
644
645 return true;
646}
647
648/// Copy
649void wxRichTextParagraphLayoutBox::Copy(const wxRichTextParagraphLayoutBox& obj)
650{
651 wxRichTextBox::Copy(obj);
652}
653
654/// Get/set the size for the given range.
655bool wxRichTextParagraphLayoutBox::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, int flags) const
656{
657 wxSize sz;
658
659 wxRichTextObjectList::compatibility_iterator startPara = NULL;
660 wxRichTextObjectList::compatibility_iterator endPara = NULL;
661
662 // First find the first paragraph whose starting position is within the range.
663 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
664 while (node)
665 {
666 // child is a paragraph
667 wxRichTextObject* child = node->GetData();
668 const wxRichTextRange& r = child->GetRange();
669
670 if (r.GetStart() <= range.GetStart() && r.GetEnd() >= range.GetStart())
671 {
672 startPara = node;
673 break;
674 }
675
676 node = node->GetNext();
677 }
678
679 // Next find the last paragraph containing part of the range
680 node = m_children.GetFirst();
681 while (node)
682 {
683 // child is a paragraph
684 wxRichTextObject* child = node->GetData();
685 const wxRichTextRange& r = child->GetRange();
686
687 if (r.GetStart() <= range.GetEnd() && r.GetEnd() >= range.GetEnd())
688 {
689 endPara = node;
690 break;
691 }
692
693 node = node->GetNext();
694 }
695
696 if (!startPara || !endPara)
697 return false;
698
699 // Now we can add up the sizes
700 for (node = startPara; node ; node = node->GetNext())
701 {
702 // child is a paragraph
703 wxRichTextObject* child = node->GetData();
704 const wxRichTextRange& childRange = child->GetRange();
705 wxRichTextRange rangeToFind = range;
706 rangeToFind.LimitTo(childRange);
707
708 wxSize childSize;
709
710 int childDescent = 0;
711 child->GetRangeSize(rangeToFind, childSize, childDescent, dc, flags);
712
713 descent = wxMax(childDescent, descent);
714
715 sz.x = wxMax(sz.x, childSize.x);
716 sz.y += childSize.y;
717
718 if (node == endPara)
719 break;
720 }
721
722 size = sz;
723
724 return true;
725}
726
727/// Get the paragraph at the given position
728wxRichTextParagraph* wxRichTextParagraphLayoutBox::GetParagraphAtPosition(long pos, bool caretPosition) const
729{
730 if (caretPosition)
731 pos ++;
732
733 // First find the first paragraph whose starting position is within the range.
734 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
735 while (node)
736 {
737 // child is a paragraph
738 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
739 wxASSERT (child != NULL);
740
741 // Return first child in buffer if position is -1
742 // if (pos == -1)
743 // return child;
744
745 if (child->GetRange().Contains(pos))
746 return child;
747
748 node = node->GetNext();
749 }
750 return NULL;
751}
752
753/// Get the line at the given position
754wxRichTextLine* wxRichTextParagraphLayoutBox::GetLineAtPosition(long pos, bool caretPosition) const
755{
756 if (caretPosition)
757 pos ++;
758
759 // First find the first paragraph whose starting position is within the range.
760 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
761 while (node)
762 {
763 // child is a paragraph
764 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
765 wxASSERT (child != NULL);
766
767 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
768 while (node2)
769 {
770 wxRichTextLine* line = node2->GetData();
771
1e967276
JS
772 wxRichTextRange range = line->GetAbsoluteRange();
773
774 if (range.Contains(pos) ||
5d7836c4
JS
775
776 // If the position is end-of-paragraph, then return the last line of
777 // of the paragraph.
1e967276 778 (range.GetEnd() == child->GetRange().GetEnd()-1) && (pos == child->GetRange().GetEnd()))
5d7836c4
JS
779 return line;
780
781 node2 = node2->GetNext();
7fe8059f 782 }
5d7836c4
JS
783
784 node = node->GetNext();
785 }
786
787 int lineCount = GetLineCount();
788 if (lineCount > 0)
789 return GetLineForVisibleLineNumber(lineCount-1);
790 else
791 return NULL;
792}
793
794/// Get the line at the given y pixel position, or the last line.
795wxRichTextLine* wxRichTextParagraphLayoutBox::GetLineAtYPosition(int y) const
796{
797 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
798 while (node)
799 {
800 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
801 wxASSERT (child != NULL);
802
803 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
804 while (node2)
805 {
806 wxRichTextLine* line = node2->GetData();
807
808 wxRect rect(line->GetRect());
809
810 if (y <= rect.GetBottom())
811 return line;
812
813 node2 = node2->GetNext();
7fe8059f 814 }
5d7836c4
JS
815
816 node = node->GetNext();
817 }
818
819 // Return last line
820 int lineCount = GetLineCount();
821 if (lineCount > 0)
822 return GetLineForVisibleLineNumber(lineCount-1);
823 else
824 return NULL;
825}
826
827/// Get the number of visible lines
828int wxRichTextParagraphLayoutBox::GetLineCount() const
829{
830 int count = 0;
831
832 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
833 while (node)
834 {
835 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
836 wxASSERT (child != NULL);
837
838 count += child->GetLines().GetCount();
839 node = node->GetNext();
840 }
841 return count;
842}
843
844
845/// Get the paragraph for a given line
846wxRichTextParagraph* wxRichTextParagraphLayoutBox::GetParagraphForLine(wxRichTextLine* line) const
847{
1e967276 848 return GetParagraphAtPosition(line->GetAbsoluteRange().GetStart());
5d7836c4
JS
849}
850
851/// Get the line size at the given position
852wxSize wxRichTextParagraphLayoutBox::GetLineSizeAtPosition(long pos, bool caretPosition) const
853{
854 wxRichTextLine* line = GetLineAtPosition(pos, caretPosition);
855 if (line)
856 {
857 return line->GetSize();
858 }
859 else
860 return wxSize(0, 0);
861}
862
863
864/// Convenience function to add a paragraph of text
865wxRichTextRange wxRichTextParagraphLayoutBox::AddParagraph(const wxString& text)
866{
867 wxTextAttrEx style(GetAttributes());
868
869 // Apply default style. If the style has no attributes set,
870 // then the attributes will remain the 'basic style' (i.e. the
871 // layout box's style).
872 wxRichTextApplyStyle(style, GetDefaultStyle());
873
874 wxRichTextParagraph* para = new wxRichTextParagraph(text, this, & style);
875
876 AppendChild(para);
877
878 UpdateRanges();
879 SetDirty(true);
880
881 return para->GetRange();
882}
883
884/// Adds multiple paragraphs, based on newlines.
885wxRichTextRange wxRichTextParagraphLayoutBox::AddParagraphs(const wxString& text)
886{
887 wxTextAttrEx style(GetAttributes());
888 //wxLogDebug("Initial style = %s", style.GetFont().GetFaceName());
889 //wxLogDebug("Initial size = %d", style.GetFont().GetPointSize());
890
891 // Apply default style. If the style has no attributes set,
892 // then the attributes will remain the 'basic style' (i.e. the
893 // layout box's style).
894 wxRichTextApplyStyle(style, GetDefaultStyle());
895
896 //wxLogDebug("Style after applying default style = %s", style.GetFont().GetFaceName());
897 //wxLogDebug("Size after applying default style = %d", style.GetFont().GetPointSize());
898
899 wxRichTextParagraph* firstPara = NULL;
900 wxRichTextParagraph* lastPara = NULL;
901
902 wxRichTextRange range(-1, -1);
903 size_t i = 0;
904 size_t len = text.Length();
905 wxString line;
906 while (i < len)
907 {
908 wxChar ch = text[i];
909 if (ch == wxT('\n') || ch == wxT('\r'))
910 {
911 wxRichTextParagraph* para = new wxRichTextParagraph(line, this, & style);
912
913 AppendChild(para);
914 if (!firstPara)
915 firstPara = para;
916 lastPara = para;
917 line = wxEmptyString;
918 }
919 else
920 line += ch;
921
922 i ++;
923 }
7fe8059f 924 if (!line.empty())
5d7836c4
JS
925 {
926 lastPara = new wxRichTextParagraph(line, this, & style);
927 //wxLogDebug("Para Face = %s", lastPara->GetAttributes().GetFont().GetFaceName());
928 AppendChild(lastPara);
929 }
930
931 if (firstPara)
932 range.SetStart(firstPara->GetRange().GetStart());
933 else if (lastPara)
934 range.SetStart(lastPara->GetRange().GetStart());
935
936 if (lastPara)
937 range.SetEnd(lastPara->GetRange().GetEnd());
938 else if (firstPara)
939 range.SetEnd(firstPara->GetRange().GetEnd());
940
941 UpdateRanges();
942 SetDirty(false);
943
944 return GetRange();
945}
946
947/// Convenience function to add an image
948wxRichTextRange wxRichTextParagraphLayoutBox::AddImage(const wxImage& image)
949{
950 wxTextAttrEx style(GetAttributes());
951
952 // Apply default style. If the style has no attributes set,
953 // then the attributes will remain the 'basic style' (i.e. the
954 // layout box's style).
955 wxRichTextApplyStyle(style, GetDefaultStyle());
956
957 wxRichTextParagraph* para = new wxRichTextParagraph(this, & style);
958 AppendChild(para);
959 para->AppendChild(new wxRichTextImage(image, this));
960
961 UpdateRanges();
962 SetDirty(true);
963
964 return para->GetRange();
965}
966
967
968/// Insert fragment into this box at the given position. If partialParagraph is true,
969/// it is assumed that the last (or only) paragraph is just a piece of data with no paragraph
970/// marker.
971/// TODO: if fragment is inserted inside styled fragment, must apply that style to
972/// to the data (if it has a default style, anyway).
973
974bool wxRichTextParagraphLayoutBox::InsertFragment(long position, wxRichTextFragment& fragment)
975{
976 SetDirty(true);
977
978 // First, find the first paragraph whose starting position is within the range.
979 wxRichTextParagraph* para = GetParagraphAtPosition(position);
980 if (para)
981 {
982 wxRichTextObjectList::compatibility_iterator node = m_children.Find(para);
983
984 // Now split at this position, returning the object to insert the new
985 // ones in front of.
986 wxRichTextObject* nextObject = para->SplitAt(position);
987
988 // Special case: partial paragraph, just one paragraph. Might be a small amount of
989 // text, for example, so let's optimize.
990
991 if (fragment.GetPartialParagraph() && fragment.GetChildren().GetCount() == 1)
992 {
993 // Add the first para to this para...
994 wxRichTextObjectList::compatibility_iterator firstParaNode = fragment.GetChildren().GetFirst();
995 if (!firstParaNode)
996 return false;
997
998 // Iterate through the fragment paragraph inserting the content into this paragraph.
999 wxRichTextParagraph* firstPara = wxDynamicCast(firstParaNode->GetData(), wxRichTextParagraph);
1000 wxASSERT (firstPara != NULL);
1001
1002 wxRichTextObjectList::compatibility_iterator objectNode = firstPara->GetChildren().GetFirst();
1003 while (objectNode)
1004 {
1005 wxRichTextObject* newObj = objectNode->GetData()->Clone();
7fe8059f 1006
5d7836c4
JS
1007 if (!nextObject)
1008 {
1009 // Append
1010 para->AppendChild(newObj);
1011 }
1012 else
1013 {
1014 // Insert before nextObject
1015 para->InsertChild(newObj, nextObject);
1016 }
7fe8059f 1017
5d7836c4
JS
1018 objectNode = objectNode->GetNext();
1019 }
1020
1021 return true;
1022 }
1023 else
1024 {
1025 // Procedure for inserting a fragment consisting of a number of
1026 // paragraphs:
1027 //
1028 // 1. Remove and save the content that's after the insertion point, for adding
1029 // back once we've added the fragment.
1030 // 2. Add the content from the first fragment paragraph to the current
1031 // paragraph.
1032 // 3. Add remaining fragment paragraphs after the current paragraph.
1033 // 4. Add back the saved content from the first paragraph. If partialParagraph
1034 // is true, add it to the last paragraph added and not a new one.
1035
1036 // 1. Remove and save objects after split point.
1037 wxList savedObjects;
1038 if (nextObject)
1039 para->MoveToList(nextObject, savedObjects);
1040
1041 // 2. Add the content from the 1st fragment paragraph.
1042 wxRichTextObjectList::compatibility_iterator firstParaNode = fragment.GetChildren().GetFirst();
1043 if (!firstParaNode)
1044 return false;
1045
1046 wxRichTextParagraph* firstPara = wxDynamicCast(firstParaNode->GetData(), wxRichTextParagraph);
1047 wxASSERT(firstPara != NULL);
1048
1049 wxRichTextObjectList::compatibility_iterator objectNode = firstPara->GetChildren().GetFirst();
1050 while (objectNode)
1051 {
1052 wxRichTextObject* newObj = objectNode->GetData()->Clone();
7fe8059f 1053
5d7836c4
JS
1054 // Append
1055 para->AppendChild(newObj);
7fe8059f 1056
5d7836c4
JS
1057 objectNode = objectNode->GetNext();
1058 }
1059
1060 // 3. Add remaining fragment paragraphs after the current paragraph.
1061 wxRichTextObjectList::compatibility_iterator nextParagraphNode = node->GetNext();
1062 wxRichTextObject* nextParagraph = NULL;
1063 if (nextParagraphNode)
1064 nextParagraph = nextParagraphNode->GetData();
1065
1066 wxRichTextObjectList::compatibility_iterator i = fragment.GetChildren().GetFirst()->GetNext();
1067 wxRichTextParagraph* finalPara = para;
1068
1069 // If there was only one paragraph, we need to insert a new one.
1070 if (!i)
1071 {
1072 finalPara = new wxRichTextParagraph;
1073
1074 // TODO: These attributes should come from the subsequent paragraph
1075 // when originally deleted, since the subsequent para takes on
1076 // the previous para's attributes.
1077 finalPara->SetAttributes(firstPara->GetAttributes());
1078
1079 if (nextParagraph)
1080 InsertChild(finalPara, nextParagraph);
1081 else
7fe8059f 1082 AppendChild(finalPara);
5d7836c4
JS
1083 }
1084 else while (i)
1085 {
1086 wxRichTextParagraph* para = wxDynamicCast(i->GetData(), wxRichTextParagraph);
1087 wxASSERT( para != NULL );
1088
1089 finalPara = (wxRichTextParagraph*) para->Clone();
1090
1091 if (nextParagraph)
1092 InsertChild(finalPara, nextParagraph);
1093 else
1094 AppendChild(finalPara);
7fe8059f 1095
5d7836c4
JS
1096 i = i->GetNext();
1097 }
1098
1099 // 4. Add back the remaining content.
1100 if (finalPara)
1101 {
1102 finalPara->MoveFromList(savedObjects);
1103
1104 // Ensure there's at least one object
1105 if (finalPara->GetChildCount() == 0)
1106 {
7fe8059f 1107 wxRichTextPlainText* text = new wxRichTextPlainText(wxEmptyString);
5d7836c4
JS
1108 text->SetAttributes(finalPara->GetAttributes());
1109
1110 finalPara->AppendChild(text);
1111 }
1112 }
1113
1114 return true;
1115 }
1116 }
1117 else
1118 {
1119 // Append
1120 wxRichTextObjectList::compatibility_iterator i = fragment.GetChildren().GetFirst();
1121 while (i)
1122 {
1123 wxRichTextParagraph* para = wxDynamicCast(i->GetData(), wxRichTextParagraph);
1124 wxASSERT( para != NULL );
7fe8059f 1125
5d7836c4 1126 AppendChild(para->Clone());
7fe8059f 1127
5d7836c4
JS
1128 i = i->GetNext();
1129 }
1130
1131 return true;
1132 }
1133
1134 return false;
1135}
1136
1137/// Make a copy of the fragment corresponding to the given range, putting it in 'fragment'.
1138/// If there was an incomplete paragraph at the end, partialParagraph is set to true.
1139bool wxRichTextParagraphLayoutBox::CopyFragment(const wxRichTextRange& range, wxRichTextFragment& fragment)
1140{
1141 wxRichTextObjectList::compatibility_iterator i = GetChildren().GetFirst();
1142 while (i)
1143 {
1144 wxRichTextParagraph* para = wxDynamicCast(i->GetData(), wxRichTextParagraph);
1145 wxASSERT( para != NULL );
1146
1147 if (!para->GetRange().IsOutside(range))
1148 {
1149 fragment.AppendChild(para->Clone());
7fe8059f 1150 }
5d7836c4
JS
1151 i = i->GetNext();
1152 }
1153
1154 // Now top and tail the first and last paragraphs in our new fragment (which might be the same).
1155 if (!fragment.IsEmpty())
1156 {
1157 wxRichTextRange topTailRange(range);
1158
1159 wxRichTextParagraph* firstPara = wxDynamicCast(fragment.GetChildren().GetFirst()->GetData(), wxRichTextParagraph);
1160 wxASSERT( firstPara != NULL );
1161
1162 // Chop off the start of the paragraph
1163 if (topTailRange.GetStart() > firstPara->GetRange().GetStart())
1164 {
1165 wxRichTextRange r(firstPara->GetRange().GetStart(), topTailRange.GetStart()-1);
1166 firstPara->DeleteRange(r);
1167
1168 // Make sure the numbering is correct
1169 long end;
1170 fragment.CalculateRange(firstPara->GetRange().GetStart(), end);
1171
1172 // Now, we've deleted some positions, so adjust the range
1173 // accordingly.
1174 topTailRange.SetEnd(topTailRange.GetEnd() - r.GetLength());
1175 }
1176
1177 wxRichTextParagraph* lastPara = wxDynamicCast(fragment.GetChildren().GetLast()->GetData(), wxRichTextParagraph);
1178 wxASSERT( lastPara != NULL );
1179
1180 if (topTailRange.GetEnd() < (lastPara->GetRange().GetEnd()-1))
1181 {
1182 wxRichTextRange r(topTailRange.GetEnd()+1, lastPara->GetRange().GetEnd()-1); /* -1 since actual text ends 1 position before end of para marker */
1183 lastPara->DeleteRange(r);
1184
1185 // Make sure the numbering is correct
1186 long end;
1187 fragment.CalculateRange(firstPara->GetRange().GetStart(), end);
1188
1189 // We only have part of a paragraph at the end
1190 fragment.SetPartialParagraph(true);
1191 }
1192 else
1193 {
1194 if (topTailRange.GetEnd() == (lastPara->GetRange().GetEnd() - 1))
1195 // We have a partial paragraph (don't save last new paragraph marker)
1196 fragment.SetPartialParagraph(true);
1197 else
1198 // We have a complete paragraph
1199 fragment.SetPartialParagraph(false);
1200 }
1201 }
1202
1203 return true;
1204}
1205
1206/// Given a position, get the number of the visible line (potentially many to a paragraph),
1207/// starting from zero at the start of the buffer.
1208long wxRichTextParagraphLayoutBox::GetVisibleLineNumber(long pos, bool caretPosition, bool startOfLine) const
1209{
1210 if (caretPosition)
1211 pos ++;
1212
1213 int lineCount = 0;
1214
1215 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1216 while (node)
1217 {
1218 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
1219 wxASSERT( child != NULL );
1220
1221 if (child->GetRange().Contains(pos))
1222 {
1223 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
1224 while (node2)
1225 {
1226 wxRichTextLine* line = node2->GetData();
1e967276 1227 wxRichTextRange lineRange = line->GetAbsoluteRange();
7fe8059f 1228
1e967276 1229 if (lineRange.Contains(pos))
5d7836c4
JS
1230 {
1231 // If the caret is displayed at the end of the previous wrapped line,
1232 // we want to return the line it's _displayed_ at (not the actual line
1233 // containing the position).
1e967276 1234 if (lineRange.GetStart() == pos && !startOfLine && child->GetRange().GetStart() != pos)
5d7836c4
JS
1235 return lineCount - 1;
1236 else
1237 return lineCount;
1238 }
1239
1240 lineCount ++;
7fe8059f 1241
5d7836c4
JS
1242 node2 = node2->GetNext();
1243 }
1244 // If we didn't find it in the lines, it must be
1245 // the last position of the paragraph. So return the last line.
1246 return lineCount-1;
1247 }
1248 else
1249 lineCount += child->GetLines().GetCount();
1250
1251 node = node->GetNext();
1252 }
1253
1254 // Not found
1255 return -1;
1256}
1257
1258/// Given a line number, get the corresponding wxRichTextLine object.
1259wxRichTextLine* wxRichTextParagraphLayoutBox::GetLineForVisibleLineNumber(long lineNumber) const
1260{
1261 int lineCount = 0;
1262
1263 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1264 while (node)
1265 {
1266 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
1267 wxASSERT(child != NULL);
1268
1269 if (lineNumber < (int) (child->GetLines().GetCount() + lineCount))
1270 {
1271 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
1272 while (node2)
1273 {
1274 wxRichTextLine* line = node2->GetData();
7fe8059f 1275
5d7836c4
JS
1276 if (lineCount == lineNumber)
1277 return line;
1278
1279 lineCount ++;
7fe8059f 1280
5d7836c4 1281 node2 = node2->GetNext();
7fe8059f 1282 }
5d7836c4
JS
1283 }
1284 else
1285 lineCount += child->GetLines().GetCount();
1286
1287 node = node->GetNext();
1288 }
1289
1290 // Didn't find it
1291 return NULL;
1292}
1293
1294/// Delete range from layout.
1295bool wxRichTextParagraphLayoutBox::DeleteRange(const wxRichTextRange& range)
1296{
1297 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
7fe8059f 1298
5d7836c4
JS
1299 while (node)
1300 {
1301 wxRichTextParagraph* obj = wxDynamicCast(node->GetData(), wxRichTextParagraph);
1302 wxASSERT (obj != NULL);
1303
1304 wxRichTextObjectList::compatibility_iterator next = node->GetNext();
7fe8059f 1305
5d7836c4
JS
1306 // Delete the range in each paragraph
1307
1308 if (!obj->GetRange().IsOutside(range))
1309 {
1310 // Deletes the content of this object within the given range
1311 obj->DeleteRange(range);
1312
1313 // If the whole paragraph is within the range to delete,
1314 // delete the whole thing.
1315 if (range.GetStart() <= obj->GetRange().GetStart() && range.GetEnd() >= obj->GetRange().GetEnd())
1316 {
1317 // Delete the whole object
1318 RemoveChild(obj, true);
1319 }
1320 // If the range includes the paragraph end, we need to join this
1321 // and the next paragraph.
1322 else if (range.Contains(obj->GetRange().GetEnd()))
1323 {
1324 // We need to move the objects from the next paragraph
1325 // to this paragraph
1326
1327 if (next)
1328 {
1329 wxRichTextParagraph* nextParagraph = wxDynamicCast(next->GetData(), wxRichTextParagraph);
1330 next = next->GetNext();
1331 if (nextParagraph)
1332 {
1333 // Delete the stuff we need to delete
1334 nextParagraph->DeleteRange(range);
1335
1336 // Move the objects to the previous para
1337 wxRichTextObjectList::compatibility_iterator node1 = nextParagraph->GetChildren().GetFirst();
1338
1339 while (node1)
1340 {
1341 wxRichTextObject* obj1 = node1->GetData();
1342
1343 // If the object is empty, optimise it out
1344 if (obj1->IsEmpty())
1345 {
1346 delete obj1;
1347 }
1348 else
1349 {
1350 obj->AppendChild(obj1);
1351 }
1352
1353 wxRichTextObjectList::compatibility_iterator next1 = node1->GetNext();
1354 delete node1;
1355
1356 node1 = next1;
1357 }
1358
1359 // Delete the paragraph
1360 RemoveChild(nextParagraph, true);
1361
1362 }
7fe8059f 1363 }
5d7836c4
JS
1364
1365 }
1366 }
7fe8059f 1367
5d7836c4
JS
1368 node = next;
1369 }
7fe8059f 1370
5d7836c4
JS
1371 return true;
1372}
1373
1374/// Get any text in this object for the given range
1375wxString wxRichTextParagraphLayoutBox::GetTextForRange(const wxRichTextRange& range) const
1376{
1377 int lineCount = 0;
1378 wxString text;
1379 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1380 while (node)
1381 {
1382 wxRichTextObject* child = node->GetData();
1383 if (!child->GetRange().IsOutside(range))
1384 {
1385 if (lineCount > 0)
1386 text += wxT("\n");
1387 wxRichTextRange childRange = range;
1388 childRange.LimitTo(child->GetRange());
7fe8059f 1389
5d7836c4 1390 wxString childText = child->GetTextForRange(childRange);
7fe8059f 1391
5d7836c4
JS
1392 text += childText;
1393
1394 lineCount ++;
1395 }
1396 node = node->GetNext();
1397 }
1398
1399 return text;
1400}
1401
1402/// Get all the text
1403wxString wxRichTextParagraphLayoutBox::GetText() const
1404{
1405 return GetTextForRange(GetRange());
1406}
1407
1408/// Get the paragraph by number
1409wxRichTextParagraph* wxRichTextParagraphLayoutBox::GetParagraphAtLine(long paragraphNumber) const
1410{
1411 if ((size_t) paragraphNumber <= GetChildCount())
1412 return NULL;
1413
1414 return (wxRichTextParagraph*) GetChild((size_t) paragraphNumber);
1415}
1416
1417/// Get the length of the paragraph
1418int wxRichTextParagraphLayoutBox::GetParagraphLength(long paragraphNumber) const
1419{
1420 wxRichTextParagraph* para = GetParagraphAtLine(paragraphNumber);
1421 if (para)
1422 return para->GetRange().GetLength() - 1; // don't include newline
1423 else
1424 return 0;
1425}
1426
1427/// Get the text of the paragraph
1428wxString wxRichTextParagraphLayoutBox::GetParagraphText(long paragraphNumber) const
1429{
1430 wxRichTextParagraph* para = GetParagraphAtLine(paragraphNumber);
1431 if (para)
1432 return para->GetTextForRange(para->GetRange());
1433 else
1434 return wxEmptyString;
1435}
1436
1437/// Convert zero-based line column and paragraph number to a position.
1438long wxRichTextParagraphLayoutBox::XYToPosition(long x, long y) const
1439{
1440 wxRichTextParagraph* para = GetParagraphAtLine(y);
1441 if (para)
1442 {
1443 return para->GetRange().GetStart() + x;
1444 }
1445 else
1446 return -1;
1447}
1448
1449/// Convert zero-based position to line column and paragraph number
1450bool wxRichTextParagraphLayoutBox::PositionToXY(long pos, long* x, long* y) const
1451{
1452 wxRichTextParagraph* para = GetParagraphAtPosition(pos);
1453 if (para)
1454 {
1455 int count = 0;
1456 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1457 while (node)
1458 {
1459 wxRichTextObject* child = node->GetData();
1460 if (child == para)
1461 break;
1462 count ++;
1463 node = node->GetNext();
1464 }
1465
1466 *y = count;
1467 *x = pos - para->GetRange().GetStart();
1468
1469 return true;
1470 }
1471 else
1472 return false;
1473}
1474
1475/// Get the leaf object in a paragraph at this position.
1476/// Given a line number, get the corresponding wxRichTextLine object.
1477wxRichTextObject* wxRichTextParagraphLayoutBox::GetLeafObjectAtPosition(long position) const
1478{
1479 wxRichTextParagraph* para = GetParagraphAtPosition(position);
1480 if (para)
1481 {
1482 wxRichTextObjectList::compatibility_iterator node = para->GetChildren().GetFirst();
7fe8059f 1483
5d7836c4
JS
1484 while (node)
1485 {
1486 wxRichTextObject* child = node->GetData();
1487 if (child->GetRange().Contains(position))
1488 return child;
7fe8059f 1489
5d7836c4
JS
1490 node = node->GetNext();
1491 }
1492 if (position == para->GetRange().GetEnd() && para->GetChildCount() > 0)
1493 return para->GetChildren().GetLast()->GetData();
1494 }
1495 return NULL;
1496}
1497
1498/// Set character or paragraph text attributes: apply character styles only to immediate text nodes
1499bool wxRichTextParagraphLayoutBox::SetStyle(const wxRichTextRange& range, const wxRichTextAttr& style, bool withUndo)
1500{
1501 bool characterStyle = false;
1502 bool paragraphStyle = false;
1503
1504 if (style.IsCharacterStyle())
1505 characterStyle = true;
1506 if (style.IsParagraphStyle())
1507 paragraphStyle = true;
1508
1509 // If we are associated with a control, make undoable; otherwise, apply immediately
1510 // to the data.
1511
1512 bool haveControl = (GetRichTextCtrl() != NULL);
1513
1514 wxRichTextAction* action = NULL;
7fe8059f 1515
5d7836c4
JS
1516 if (haveControl && withUndo)
1517 {
1518 action = new wxRichTextAction(NULL, _("Change Style"), wxRICHTEXT_CHANGE_STYLE, & GetRichTextCtrl()->GetBuffer(), GetRichTextCtrl());
1519 action->SetRange(range);
1520 action->SetPosition(GetRichTextCtrl()->GetCaretPosition());
1521 }
1522
1523 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1524 while (node)
1525 {
1526 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
1527 wxASSERT (para != NULL);
1528
1529 if (para && para->GetChildCount() > 0)
1530 {
1531 // Stop searching if we're beyond the range of interest
1532 if (para->GetRange().GetStart() > range.GetEnd())
1533 break;
1534
1535 if (!para->GetRange().IsOutside(range))
1536 {
1537 // We'll be using a copy of the paragraph to make style changes,
1538 // not updating the buffer directly.
4e09ebe8 1539 wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL);
7fe8059f 1540
5d7836c4
JS
1541 if (haveControl && withUndo)
1542 {
1543 newPara = new wxRichTextParagraph(*para);
1544 action->GetNewParagraphs().AppendChild(newPara);
1545
1546 // Also store the old ones for Undo
1547 action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para));
1548 }
1549 else
1550 newPara = para;
1551
1552 if (paragraphStyle)
1553 wxRichTextApplyStyle(newPara->GetAttributes(), style);
1554
1555 if (characterStyle && range.GetStart() != newPara->GetRange().GetEnd())
1556 {
1557 wxRichTextRange childRange(range);
1558 childRange.LimitTo(newPara->GetRange());
7fe8059f 1559
5d7836c4
JS
1560 // Find the starting position and if necessary split it so
1561 // we can start applying a different style.
1562 // TODO: check that the style actually changes or is different
1563 // from style outside of range
4e09ebe8
JS
1564 wxRichTextObject* firstObject wxDUMMY_INITIALIZE(NULL);
1565 wxRichTextObject* lastObject wxDUMMY_INITIALIZE(NULL);
7fe8059f 1566
5d7836c4
JS
1567 if (childRange.GetStart() == newPara->GetRange().GetStart())
1568 firstObject = newPara->GetChildren().GetFirst()->GetData();
1569 else
1570 firstObject = newPara->SplitAt(range.GetStart());
7fe8059f 1571
5d7836c4
JS
1572 // Increment by 1 because we're apply the style one _after_ the split point
1573 long splitPoint = childRange.GetEnd();
1574 if (splitPoint != newPara->GetRange().GetEnd())
1575 splitPoint ++;
7fe8059f 1576
5d7836c4
JS
1577 // Find last object
1578 if (splitPoint == newPara->GetRange().GetEnd() || splitPoint == (newPara->GetRange().GetEnd() - 1))
1579 lastObject = newPara->GetChildren().GetLast()->GetData();
1580 else
1581 // lastObject is set as a side-effect of splitting. It's
1582 // returned as the object before the new object.
1583 (void) newPara->SplitAt(splitPoint, & lastObject);
7fe8059f 1584
5d7836c4
JS
1585 wxASSERT(firstObject != NULL);
1586 wxASSERT(lastObject != NULL);
7fe8059f 1587
5d7836c4
JS
1588 if (!firstObject || !lastObject)
1589 continue;
7fe8059f 1590
5d7836c4
JS
1591 wxRichTextObjectList::compatibility_iterator firstNode = newPara->GetChildren().Find(firstObject);
1592 wxRichTextObjectList::compatibility_iterator lastNode = newPara->GetChildren().Find(lastObject);
7fe8059f 1593
5d7836c4
JS
1594 wxASSERT(firstNode != NULL);
1595 wxASSERT(lastNode != NULL);
7fe8059f 1596
5d7836c4 1597 wxRichTextObjectList::compatibility_iterator node2 = firstNode;
7fe8059f 1598
5d7836c4
JS
1599 while (node2)
1600 {
1601 wxRichTextObject* child = node2->GetData();
7fe8059f 1602
5d7836c4
JS
1603 wxRichTextApplyStyle(child->GetAttributes(), style);
1604 if (node2 == lastNode)
1605 break;
7fe8059f 1606
5d7836c4
JS
1607 node2 = node2->GetNext();
1608 }
1609 }
1610 }
1611 }
1612
1613 node = node->GetNext();
1614 }
1615
1616 // Do action, or delay it until end of batch.
1617 if (haveControl && withUndo)
1618 GetRichTextCtrl()->GetBuffer().SubmitAction(action);
1619
1620 return true;
1621}
1622
1623/// Set text attributes
1624bool wxRichTextParagraphLayoutBox::SetStyle(const wxRichTextRange& range, const wxTextAttrEx& style, bool withUndo)
1625{
1626 wxRichTextAttr richStyle = style;
1627 return SetStyle(range, richStyle, withUndo);
1628}
1629
1630/// Get the text attributes for this position.
1631bool wxRichTextParagraphLayoutBox::GetStyle(long position, wxTextAttrEx& style) const
1632{
4e09ebe8 1633 wxRichTextObject* obj wxDUMMY_INITIALIZE(NULL);
e191ee87 1634
5d7836c4
JS
1635 if (style.IsParagraphStyle())
1636 obj = GetParagraphAtPosition(position);
1637 else
1638 obj = GetLeafObjectAtPosition(position);
e191ee87 1639
5d7836c4
JS
1640 if (obj)
1641 {
1642 style = obj->GetAttributes();
1643 return true;
1644 }
1645 else
1646 return false;
1647}
1648
1649/// Get the text attributes for this position.
1650bool wxRichTextParagraphLayoutBox::GetStyle(long position, wxRichTextAttr& style) const
1651{
4e09ebe8 1652 wxRichTextObject* obj wxDUMMY_INITIALIZE(NULL);
e191ee87 1653
5d7836c4
JS
1654 if (style.IsParagraphStyle())
1655 obj = GetParagraphAtPosition(position);
1656 else
1657 obj = GetLeafObjectAtPosition(position);
e191ee87 1658
5d7836c4
JS
1659 if (obj)
1660 {
1661 style = obj->GetAttributes();
1662 return true;
1663 }
1664 else
1665 return false;
1666}
1667
1668/// Set default style
1669bool wxRichTextParagraphLayoutBox::SetDefaultStyle(const wxTextAttrEx& style)
1670{
1671 m_defaultAttributes = style;
1672
1673 return true;
1674}
1675
1676/// Test if this whole range has character attributes of the specified kind. If any
1677/// of the attributes are different within the range, the test fails. You
1678/// can use this to implement, for example, bold button updating. style must have
1679/// flags indicating which attributes are of interest.
1680bool wxRichTextParagraphLayoutBox::HasCharacterAttributes(const wxRichTextRange& range, const wxRichTextAttr& style) const
1681{
1682 int foundCount = 0;
1683 int matchingCount = 0;
1684
1685 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1686 while (node)
1687 {
1688 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
1689 wxASSERT (para != NULL);
1690
1691 if (para)
1692 {
1693 // Stop searching if we're beyond the range of interest
1694 if (para->GetRange().GetStart() > range.GetEnd())
1695 return foundCount == matchingCount;
1696
1697 if (!para->GetRange().IsOutside(range))
1698 {
1699 wxRichTextObjectList::compatibility_iterator node2 = para->GetChildren().GetFirst();
1700
1701 while (node2)
1702 {
1703 wxRichTextObject* child = node2->GetData();
1704 if (!child->GetRange().IsOutside(range) && child->IsKindOf(CLASSINFO(wxRichTextPlainText)))
1705 {
1706 foundCount ++;
1707 if (wxTextAttrEqPartial(child->GetAttributes(), style, style.GetFlags()))
1708 matchingCount ++;
1709 }
1710
1711 node2 = node2->GetNext();
1712 }
1713 }
1714 }
1715
1716 node = node->GetNext();
1717 }
1718
1719 return foundCount == matchingCount;
1720}
1721
1722bool wxRichTextParagraphLayoutBox::HasCharacterAttributes(const wxRichTextRange& range, const wxTextAttrEx& style) const
1723{
1724 wxRichTextAttr richStyle = style;
1725 return HasCharacterAttributes(range, richStyle);
1726}
1727
1728/// Test if this whole range has paragraph attributes of the specified kind. If any
1729/// of the attributes are different within the range, the test fails. You
1730/// can use this to implement, for example, centering button updating. style must have
1731/// flags indicating which attributes are of interest.
1732bool wxRichTextParagraphLayoutBox::HasParagraphAttributes(const wxRichTextRange& range, const wxRichTextAttr& style) const
1733{
1734 int foundCount = 0;
1735 int matchingCount = 0;
1736
1737 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1738 while (node)
1739 {
1740 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
1741 wxASSERT (para != NULL);
1742
1743 if (para)
1744 {
1745 // Stop searching if we're beyond the range of interest
1746 if (para->GetRange().GetStart() > range.GetEnd())
1747 return foundCount == matchingCount;
1748
1749 if (!para->GetRange().IsOutside(range))
1750 {
1751 foundCount ++;
1752 if (wxTextAttrEqPartial(para->GetAttributes(), style, style.GetFlags()))
1753 matchingCount ++;
1754 }
1755 }
1756
1757 node = node->GetNext();
1758 }
1759 return foundCount == matchingCount;
1760}
1761
1762bool wxRichTextParagraphLayoutBox::HasParagraphAttributes(const wxRichTextRange& range, const wxTextAttrEx& style) const
1763{
1764 wxRichTextAttr richStyle = style;
1765 return HasParagraphAttributes(range, richStyle);
1766}
1767
1768void wxRichTextParagraphLayoutBox::Clear()
1769{
1770 DeleteChildren();
1771}
1772
1773void wxRichTextParagraphLayoutBox::Reset()
1774{
1775 Clear();
1776
7fe8059f 1777 AddParagraph(wxEmptyString);
5d7836c4
JS
1778}
1779
38113684
JS
1780/// Invalidate the buffer. With no argument, invalidates whole buffer.
1781void wxRichTextParagraphLayoutBox::Invalidate(const wxRichTextRange& invalidRange)
1782{
1783 SetDirty(true);
39a1c2f2 1784
1e967276 1785 if (invalidRange == wxRICHTEXT_ALL)
38113684 1786 {
1e967276 1787 m_invalidRange = wxRICHTEXT_ALL;
38113684
JS
1788 return;
1789 }
1e967276
JS
1790
1791 // Already invalidating everything
1792 if (m_invalidRange == wxRICHTEXT_ALL)
1793 return;
39a1c2f2 1794
1e967276 1795 if ((invalidRange.GetStart() < m_invalidRange.GetStart()) || m_invalidRange.GetStart() == -1)
38113684
JS
1796 m_invalidRange.SetStart(invalidRange.GetStart());
1797 if (invalidRange.GetEnd() > m_invalidRange.GetEnd())
1798 m_invalidRange.SetEnd(invalidRange.GetEnd());
1799}
1800
1801/// Get invalid range, rounding to entire paragraphs if argument is true.
1802wxRichTextRange wxRichTextParagraphLayoutBox::GetInvalidRange(bool wholeParagraphs) const
1803{
1e967276 1804 if (m_invalidRange == wxRICHTEXT_ALL || m_invalidRange == wxRICHTEXT_NONE)
38113684 1805 return m_invalidRange;
39a1c2f2 1806
38113684 1807 wxRichTextRange range = m_invalidRange;
39a1c2f2 1808
38113684
JS
1809 if (wholeParagraphs)
1810 {
1811 wxRichTextParagraph* para1 = GetParagraphAtPosition(range.GetStart());
1812 wxRichTextParagraph* para2 = GetParagraphAtPosition(range.GetEnd());
1813 if (para1)
1814 range.SetStart(para1->GetRange().GetStart());
1815 if (para2)
1816 range.SetEnd(para2->GetRange().GetEnd());
1817 }
1818 return range;
1819}
1820
5d7836c4
JS
1821/*!
1822 * wxRichTextFragment class declaration
1823 * This is a lind of paragraph layout box used for storing
1824 * paragraphs for Undo/Redo, for example.
1825 */
1826
1827IMPLEMENT_DYNAMIC_CLASS(wxRichTextFragment, wxRichTextParagraphLayoutBox)
1828
1829/// Initialise
1830void wxRichTextFragment::Init()
1831{
1832 m_partialParagraph = false;
1833}
1834
1835/// Copy
1836void wxRichTextFragment::Copy(const wxRichTextFragment& obj)
1837{
1838 wxRichTextParagraphLayoutBox::Copy(obj);
1839
1840 m_partialParagraph = obj.m_partialParagraph;
1841}
1842
1843/*!
1844 * wxRichTextParagraph
1845 * This object represents a single paragraph (or in a straight text editor, a line).
1846 */
1847
1848IMPLEMENT_DYNAMIC_CLASS(wxRichTextParagraph, wxRichTextBox)
1849
1850wxRichTextParagraph::wxRichTextParagraph(wxRichTextObject* parent, wxTextAttrEx* style):
1851 wxRichTextBox(parent)
1852{
1853 if (parent && !style)
1854 SetAttributes(parent->GetAttributes());
1855 if (style)
1856 SetAttributes(*style);
1857}
1858
1859wxRichTextParagraph::wxRichTextParagraph(const wxString& text, wxRichTextObject* parent, wxTextAttrEx* style):
1860 wxRichTextBox(parent)
1861{
1862 if (parent && !style)
1863 SetAttributes(parent->GetAttributes());
1864 if (style)
1865 SetAttributes(*style);
1866
1867 AppendChild(new wxRichTextPlainText(text, this));
1868}
1869
1870wxRichTextParagraph::~wxRichTextParagraph()
1871{
1872 ClearLines();
1873}
1874
1875/// Draw the item
1876bool wxRichTextParagraph::Draw(wxDC& dc, const wxRichTextRange& WXUNUSED(range), const wxRichTextRange& selectionRange, const wxRect& WXUNUSED(rect), int WXUNUSED(descent), int style)
1877{
1878 // Draw the bullet, if any
1879 if (GetAttributes().GetBulletStyle() != wxTEXT_ATTR_BULLET_STYLE_NONE)
1880 {
1881 if (GetAttributes().GetLeftSubIndent() != 0)
1882 {
1883 int spaceBeforePara = ConvertTenthsMMToPixels(dc, GetAttributes().GetParagraphSpacingBefore());
1884 // int spaceAfterPara = ConvertTenthsMMToPixels(dc, GetAttributes().GetParagraphSpacingAfter());
1885 int leftIndent = ConvertTenthsMMToPixels(dc, GetAttributes().GetLeftIndent());
1886 // int leftSubIndent = ConvertTenthsMMToPixels(dc, GetAttributes().GetLeftSubIndent());
1887 // int rightIndent = ConvertTenthsMMToPixels(dc, GetAttributes().GetRightIndent());
1888
1889 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_BITMAP)
1890 {
1891 // TODO
1892 }
1893 else
1894 {
1895 wxString bulletText = GetBulletText();
7fe8059f 1896 if (!bulletText.empty())
5d7836c4
JS
1897 {
1898 if (GetAttributes().GetFont().Ok())
1899 dc.SetFont(GetAttributes().GetFont());
7fe8059f 1900
5d7836c4
JS
1901 if (GetAttributes().GetTextColour().Ok())
1902 dc.SetTextForeground(GetAttributes().GetTextColour());
7fe8059f 1903
5d7836c4 1904 dc.SetBackgroundMode(wxTRANSPARENT);
7fe8059f 1905
5d7836c4
JS
1906 // Get line height from first line, if any
1907 wxRichTextLine* line = m_cachedLines.GetFirst() ? (wxRichTextLine* ) m_cachedLines.GetFirst()->GetData() : (wxRichTextLine*) NULL;
7fe8059f 1908
5d7836c4 1909 wxPoint linePos;
4e09ebe8 1910 int lineHeight wxDUMMY_INITIALIZE(0);
5d7836c4
JS
1911 if (line)
1912 {
1913 lineHeight = line->GetSize().y;
1914 linePos = line->GetPosition() + GetPosition();
1915 }
1916 else
1917 {
1918 lineHeight = dc.GetCharHeight();
1919 linePos = GetPosition();
1920 linePos.y += spaceBeforePara;
1921 }
7fe8059f 1922
5d7836c4 1923 int charHeight = dc.GetCharHeight();
7fe8059f 1924
5d7836c4
JS
1925 int x = GetPosition().x + leftIndent;
1926 int y = linePos.y + (lineHeight - charHeight);
7fe8059f 1927
5d7836c4
JS
1928 dc.DrawText(bulletText, x, y);
1929 }
1930 }
1931 }
1932 }
7fe8059f 1933
5d7836c4
JS
1934 // Draw the range for each line, one object at a time.
1935
1936 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
1937 while (node)
1938 {
1939 wxRichTextLine* line = node->GetData();
1e967276 1940 wxRichTextRange lineRange = line->GetAbsoluteRange();
5d7836c4
JS
1941
1942 int maxDescent = line->GetDescent();
1943
1944 // Lines are specified relative to the paragraph
1945
1946 wxPoint linePosition = line->GetPosition() + GetPosition();
1947 wxPoint objectPosition = linePosition;
1948
1949 // Loop through objects until we get to the one within range
1950 wxRichTextObjectList::compatibility_iterator node2 = m_children.GetFirst();
1951 while (node2)
1952 {
1953 wxRichTextObject* child = node2->GetData();
1e967276 1954 if (!child->GetRange().IsOutside(lineRange))
5d7836c4
JS
1955 {
1956 // Draw this part of the line at the correct position
1957 wxRichTextRange objectRange(child->GetRange());
1e967276 1958 objectRange.LimitTo(lineRange);
5d7836c4
JS
1959
1960 wxSize objectSize;
1961 int descent = 0;
1962 child->GetRangeSize(objectRange, objectSize, descent, dc, wxRICHTEXT_UNFORMATTED);
1963
1964 // Use the child object's width, but the whole line's height
1965 wxRect childRect(objectPosition, wxSize(objectSize.x, line->GetSize().y));
1966 child->Draw(dc, objectRange, selectionRange, childRect, maxDescent, style);
1967
1968 objectPosition.x += objectSize.x;
1969 }
1e967276 1970 else if (child->GetRange().GetStart() > lineRange.GetEnd())
5d7836c4
JS
1971 // Can break out of inner loop now since we've passed this line's range
1972 break;
1973
1974 node2 = node2->GetNext();
1975 }
1976
1977 node = node->GetNext();
7fe8059f 1978 }
5d7836c4
JS
1979
1980 return true;
1981}
1982
1983/// Lay the item out
38113684 1984bool wxRichTextParagraph::Layout(wxDC& dc, const wxRect& rect, int style)
5d7836c4 1985{
169adfa9
JS
1986 // ClearLines();
1987
5d7836c4
JS
1988 // Increase the size of the paragraph due to spacing
1989 int spaceBeforePara = ConvertTenthsMMToPixels(dc, GetAttributes().GetParagraphSpacingBefore());
1990 int spaceAfterPara = ConvertTenthsMMToPixels(dc, GetAttributes().GetParagraphSpacingAfter());
1991 int leftIndent = ConvertTenthsMMToPixels(dc, GetAttributes().GetLeftIndent());
1992 int leftSubIndent = ConvertTenthsMMToPixels(dc, GetAttributes().GetLeftSubIndent());
1993 int rightIndent = ConvertTenthsMMToPixels(dc, GetAttributes().GetRightIndent());
1994
1995 int lineSpacing = 0;
1996
1997 // Let's assume line spacing of 10 is normal, 15 is 1.5, 20 is 2, etc.
1998 if (GetAttributes().GetLineSpacing() > 10 && GetAttributes().GetFont().Ok())
1999 {
2000 dc.SetFont(GetAttributes().GetFont());
2001 lineSpacing = (ConvertTenthsMMToPixels(dc, dc.GetCharHeight()) * GetAttributes().GetLineSpacing())/10;
2002 }
2003
2004 // Available space for text on each line differs.
2005 int availableTextSpaceFirstLine = rect.GetWidth() - leftIndent - rightIndent;
2006
2007 // Bullets start the text at the same position as subsequent lines
2008 if (GetAttributes().GetBulletStyle() != wxTEXT_ATTR_BULLET_STYLE_NONE)
2009 availableTextSpaceFirstLine -= leftSubIndent;
2010
2011 int availableTextSpaceSubsequentLines = rect.GetWidth() - leftIndent - rightIndent - leftSubIndent;
2012
2013 // Start position for each line relative to the paragraph
2014 int startPositionFirstLine = leftIndent;
2015 int startPositionSubsequentLines = leftIndent + leftSubIndent;
2016
2017 // If we have a bullet in this paragraph, the start position for the first line's text
2018 // is actually leftIndent + leftSubIndent.
2019 if (GetAttributes().GetBulletStyle() != wxTEXT_ATTR_BULLET_STYLE_NONE)
2020 startPositionFirstLine = startPositionSubsequentLines;
2021
2022 //bool restrictWidth = wxRichTextHasStyle(style, wxRICHTEXT_FIXED_WIDTH);
2023 //bool restrictHeight = wxRichTextHasStyle(style, wxRICHTEXT_FIXED_HEIGHT);
2024
2025 long lastEndPos = GetRange().GetStart()-1;
2026 long lastCompletedEndPos = lastEndPos;
2027
2028 int currentWidth = 0;
2029 SetPosition(rect.GetPosition());
2030
2031 wxPoint currentPosition(0, spaceBeforePara); // We will calculate lines relative to paragraph
2032 int lineHeight = 0;
2033 int maxWidth = 0;
2034 int maxDescent = 0;
2035
2036 int lineCount = 0;
2037
2038 // Split up lines
2039
2040 // We may need to go back to a previous child, in which case create the new line,
2041 // find the child corresponding to the start position of the string, and
2042 // continue.
2043
2044 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2045 while (node)
2046 {
2047 wxRichTextObject* child = node->GetData();
2048
2049 // If this is e.g. a composite text box, it will need to be laid out itself.
2050 // But if just a text fragment or image, for example, this will
2051 // do nothing. NB: won't we need to set the position after layout?
2052 // since for example if position is dependent on vertical line size, we
2053 // can't tell the position until the size is determined. So possibly introduce
2054 // another layout phase.
2055
38113684 2056 child->Layout(dc, rect, style);
5d7836c4
JS
2057
2058 // Available width depends on whether we're on the first or subsequent lines
2059 int availableSpaceForText = (lineCount == 0 ? availableTextSpaceFirstLine : availableTextSpaceSubsequentLines);
2060
2061 currentPosition.x = (lineCount == 0 ? startPositionFirstLine : startPositionSubsequentLines);
2062
2063 // We may only be looking at part of a child, if we searched back for wrapping
2064 // and found a suitable point some way into the child. So get the size for the fragment
2065 // if necessary.
2066
2067 wxSize childSize;
2068 int childDescent = 0;
2069 if (lastEndPos == child->GetRange().GetStart() - 1)
2070 {
2071 childSize = child->GetCachedSize();
2072 childDescent = child->GetDescent();
2073 }
2074 else
2075 GetRangeSize(wxRichTextRange(lastEndPos+1, child->GetRange().GetEnd()), childSize, childDescent, dc, wxRICHTEXT_UNFORMATTED);
2076
2077 if (childSize.x + currentWidth > availableSpaceForText)
2078 {
2079 long wrapPosition = 0;
2080
2081 // Find a place to wrap. This may walk back to previous children,
2082 // for example if a word spans several objects.
2083 if (!FindWrapPosition(wxRichTextRange(lastCompletedEndPos+1, child->GetRange().GetEnd()), dc, availableSpaceForText, wrapPosition))
2084 {
2085 // If the function failed, just cut it off at the end of this child.
2086 wrapPosition = child->GetRange().GetEnd();
2087 }
2088
2089 // FindWrapPosition can still return a value that will put us in an endless wrapping loop
2090 if (wrapPosition <= lastCompletedEndPos)
2091 wrapPosition = wxMax(lastCompletedEndPos+1,child->GetRange().GetEnd());
2092
2093 // wxLogDebug(wxT("Split at %ld"), wrapPosition);
7fe8059f 2094
5d7836c4
JS
2095 // Let's find the actual size of the current line now
2096 wxSize actualSize;
2097 wxRichTextRange actualRange(lastCompletedEndPos+1, wrapPosition);
2098 GetRangeSize(actualRange, actualSize, childDescent, dc, wxRICHTEXT_UNFORMATTED);
2099 currentWidth = actualSize.x;
2100 lineHeight = wxMax(lineHeight, actualSize.y);
2101 maxDescent = wxMax(childDescent, maxDescent);
7fe8059f 2102
5d7836c4 2103 // Add a new line
1e967276 2104 wxRichTextLine* line = AllocateLine(lineCount);
39a1c2f2 2105
1e967276
JS
2106 // Set relative range so we won't have to change line ranges when paragraphs are moved
2107 line->SetRange(wxRichTextRange(actualRange.GetStart() - GetRange().GetStart(), actualRange.GetEnd() - GetRange().GetStart()));
5d7836c4
JS
2108 line->SetPosition(currentPosition);
2109 line->SetSize(wxSize(currentWidth, lineHeight));
2110 line->SetDescent(maxDescent);
2111
5d7836c4
JS
2112 // Now move down a line. TODO: add margins, spacing
2113 currentPosition.y += lineHeight;
2114 currentPosition.y += lineSpacing;
2115 currentWidth = 0;
2116 maxDescent = 0;
7fe8059f
WS
2117 maxWidth = wxMax(maxWidth, currentWidth);
2118
5d7836c4
JS
2119 lineCount ++;
2120
2121 // TODO: account for zero-length objects, such as fields
2122 wxASSERT(wrapPosition > lastCompletedEndPos);
7fe8059f 2123
5d7836c4
JS
2124 lastEndPos = wrapPosition;
2125 lastCompletedEndPos = lastEndPos;
2126
2127 lineHeight = 0;
2128
2129 // May need to set the node back to a previous one, due to searching back in wrapping
2130 wxRichTextObject* childAfterWrapPosition = FindObjectAtPosition(wrapPosition+1);
2131 if (childAfterWrapPosition)
2132 node = m_children.Find(childAfterWrapPosition);
2133 else
2134 node = node->GetNext();
2135 }
2136 else
2137 {
2138 // We still fit, so don't add a line, and keep going
2139 currentWidth += childSize.x;
2140 lineHeight = wxMax(lineHeight, childSize.y);
2141 maxDescent = wxMax(childDescent, maxDescent);
2142
2143 maxWidth = wxMax(maxWidth, currentWidth);
2144 lastEndPos = child->GetRange().GetEnd();
2145
2146 node = node->GetNext();
2147 }
2148 }
2149
2150 // Add the last line - it's the current pos -> last para pos
2151 // Substract -1 because the last position is always the end-paragraph position.
2152 if (lastCompletedEndPos <= GetRange().GetEnd()-1)
2153 {
2154 currentPosition.x = (lineCount == 0 ? startPositionFirstLine : startPositionSubsequentLines);
2155
1e967276
JS
2156 wxRichTextLine* line = AllocateLine(lineCount);
2157
2158 wxRichTextRange actualRange(lastCompletedEndPos+1, GetRange().GetEnd()-1);
2159
2160 // Set relative range so we won't have to change line ranges when paragraphs are moved
2161 line->SetRange(wxRichTextRange(actualRange.GetStart() - GetRange().GetStart(), actualRange.GetEnd() - GetRange().GetStart()));
7fe8059f 2162
5d7836c4
JS
2163 line->SetPosition(currentPosition);
2164
2165 if (lineHeight == 0)
2166 {
2167 if (GetAttributes().GetFont().Ok())
2168 dc.SetFont(GetAttributes().GetFont());
2169 lineHeight = dc.GetCharHeight();
2170 }
2171 if (maxDescent == 0)
2172 {
2173 int w, h;
2174 dc.GetTextExtent(wxT("X"), & w, &h, & maxDescent);
2175 }
2176
2177 line->SetSize(wxSize(currentWidth, lineHeight));
2178 line->SetDescent(maxDescent);
2179 currentPosition.y += lineHeight;
2180 currentPosition.y += lineSpacing;
2181 lineCount ++;
5d7836c4
JS
2182 }
2183
1e967276
JS
2184 // Remove remaining unused line objects, if any
2185 ClearUnusedLines(lineCount);
2186
5d7836c4
JS
2187 // Apply styles to wrapped lines
2188 ApplyParagraphStyle(rect);
2189
2190 SetCachedSize(wxSize(maxWidth, currentPosition.y + spaceBeforePara + spaceAfterPara));
2191
2192 m_dirty = false;
2193
2194 return true;
2195}
2196
2197/// Apply paragraph styles, such as centering, to wrapped lines
2198void wxRichTextParagraph::ApplyParagraphStyle(const wxRect& rect)
2199{
2200 if (!GetAttributes().HasAlignment())
2201 return;
2202
2203 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
2204 while (node)
2205 {
2206 wxRichTextLine* line = node->GetData();
2207
2208 wxPoint pos = line->GetPosition();
2209 wxSize size = line->GetSize();
2210
2211 // centering, right-justification
2212 if (GetAttributes().HasAlignment() && GetAttributes().GetAlignment() == wxTEXT_ALIGNMENT_CENTRE)
2213 {
2214 pos.x = (rect.GetWidth() - size.x)/2 + pos.x;
2215 line->SetPosition(pos);
2216 }
2217 else if (GetAttributes().HasAlignment() && GetAttributes().GetAlignment() == wxTEXT_ALIGNMENT_RIGHT)
2218 {
2219 pos.x = rect.GetRight() - size.x;
2220 line->SetPosition(pos);
2221 }
2222
2223 node = node->GetNext();
2224 }
2225}
2226
2227/// Insert text at the given position
2228bool wxRichTextParagraph::InsertText(long pos, const wxString& text)
2229{
2230 wxRichTextObject* childToUse = NULL;
2231 wxRichTextObjectList::compatibility_iterator nodeToUse = NULL;
2232
2233 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2234 while (node)
2235 {
2236 wxRichTextObject* child = node->GetData();
2237 if (child->GetRange().Contains(pos) && child->GetRange().GetLength() > 0)
2238 {
2239 childToUse = child;
2240 nodeToUse = node;
2241 break;
2242 }
2243
2244 node = node->GetNext();
2245 }
2246
2247 if (childToUse)
2248 {
2249 wxRichTextPlainText* textObject = wxDynamicCast(childToUse, wxRichTextPlainText);
2250 if (textObject)
2251 {
2252 int posInString = pos - textObject->GetRange().GetStart();
2253
2254 wxString newText = textObject->GetText().Mid(0, posInString) +
2255 text + textObject->GetText().Mid(posInString);
2256 textObject->SetText(newText);
2257
2258 int textLength = text.Length();
2259
2260 textObject->SetRange(wxRichTextRange(textObject->GetRange().GetStart(),
2261 textObject->GetRange().GetEnd() + textLength));
2262
2263 // Increment the end range of subsequent fragments in this paragraph.
2264 // We'll set the paragraph range itself at a higher level.
2265
2266 wxRichTextObjectList::compatibility_iterator node = nodeToUse->GetNext();
2267 while (node)
2268 {
2269 wxRichTextObject* child = node->GetData();
2270 child->SetRange(wxRichTextRange(textObject->GetRange().GetStart() + textLength,
2271 textObject->GetRange().GetEnd() + textLength));
7fe8059f 2272
5d7836c4
JS
2273 node = node->GetNext();
2274 }
2275
2276 return true;
2277 }
2278 else
2279 {
2280 // TODO: if not a text object, insert at closest position, e.g. in front of it
2281 }
2282 }
2283 else
2284 {
2285 // Add at end.
2286 // Don't pass parent initially to suppress auto-setting of parent range.
2287 // We'll do that at a higher level.
2288 wxRichTextPlainText* textObject = new wxRichTextPlainText(text, this);
2289
2290 AppendChild(textObject);
2291 return true;
2292 }
2293
2294 return false;
2295}
2296
2297void wxRichTextParagraph::Copy(const wxRichTextParagraph& obj)
2298{
2299 wxRichTextBox::Copy(obj);
2300}
2301
2302/// Clear the cached lines
2303void wxRichTextParagraph::ClearLines()
2304{
2305 WX_CLEAR_LIST(wxRichTextLineList, m_cachedLines);
2306}
2307
2308/// Get/set the object size for the given range. Returns false if the range
2309/// is invalid for this object.
2310bool wxRichTextParagraph::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, int flags) const
2311{
2312 if (!range.IsWithin(GetRange()))
2313 return false;
2314
2315 if (flags & wxRICHTEXT_UNFORMATTED)
2316 {
2317 // Just use unformatted data, assume no line breaks
2318 // TODO: take into account line breaks
2319
2320 wxSize sz;
2321
2322 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2323 while (node)
2324 {
2325 wxRichTextObject* child = node->GetData();
2326 if (!child->GetRange().IsOutside(range))
2327 {
2328 wxSize childSize;
7fe8059f 2329
5d7836c4
JS
2330 wxRichTextRange rangeToUse = range;
2331 rangeToUse.LimitTo(child->GetRange());
2332 int childDescent = 0;
7fe8059f 2333
5d7836c4
JS
2334 if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, flags))
2335 {
2336 sz.y = wxMax(sz.y, childSize.y);
2337 sz.x += childSize.x;
2338 descent = wxMax(descent, childDescent);
2339 }
2340 }
2341
2342 node = node->GetNext();
2343 }
2344 size = sz;
2345 }
2346 else
2347 {
2348 // Use formatted data, with line breaks
2349 wxSize sz;
2350
2351 // We're going to loop through each line, and then for each line,
2352 // call GetRangeSize for the fragment that comprises that line.
2353 // Only we have to do that multiple times within the line, because
2354 // the line may be broken into pieces. For now ignore line break commands
2355 // (so we can assume that getting the unformatted size for a fragment
2356 // within a line is the actual size)
2357
2358 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
2359 while (node)
2360 {
2361 wxRichTextLine* line = node->GetData();
1e967276
JS
2362 wxRichTextRange lineRange = line->GetAbsoluteRange();
2363 if (!lineRange.IsOutside(range))
5d7836c4
JS
2364 {
2365 wxSize lineSize;
7fe8059f 2366
5d7836c4
JS
2367 wxRichTextObjectList::compatibility_iterator node2 = m_children.GetFirst();
2368 while (node2)
2369 {
2370 wxRichTextObject* child = node2->GetData();
7fe8059f 2371
1e967276 2372 if (!child->GetRange().IsOutside(lineRange))
5d7836c4 2373 {
1e967276 2374 wxRichTextRange rangeToUse = lineRange;
5d7836c4 2375 rangeToUse.LimitTo(child->GetRange());
7fe8059f 2376
5d7836c4
JS
2377 wxSize childSize;
2378 int childDescent = 0;
2379 if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, flags))
2380 {
2381 lineSize.y = wxMax(lineSize.y, childSize.y);
2382 lineSize.x += childSize.x;
2383 }
2384 descent = wxMax(descent, childDescent);
2385 }
7fe8059f 2386
5d7836c4
JS
2387 node2 = node2->GetNext();
2388 }
2389
2390 // Increase size by a line (TODO: paragraph spacing)
2391 sz.y += lineSize.y;
2392 sz.x = wxMax(sz.x, lineSize.x);
2393 }
2394 node = node->GetNext();
2395 }
2396 size = sz;
2397 }
2398 return true;
2399}
2400
2401/// Finds the absolute position and row height for the given character position
2402bool wxRichTextParagraph::FindPosition(wxDC& dc, long index, wxPoint& pt, int* height, bool forceLineStart)
2403{
2404 if (index == -1)
2405 {
2406 wxRichTextLine* line = ((wxRichTextParagraphLayoutBox*)GetParent())->GetLineAtPosition(0);
2407 if (line)
2408 *height = line->GetSize().y;
2409 else
2410 *height = dc.GetCharHeight();
2411
2412 // -1 means 'the start of the buffer'.
2413 pt = GetPosition();
2414 if (line)
2415 pt = pt + line->GetPosition();
2416
2417 *height = dc.GetCharHeight();
2418
2419 return true;
2420 }
2421
2422 // The final position in a paragraph is taken to mean the position
2423 // at the start of the next paragraph.
2424 if (index == GetRange().GetEnd())
2425 {
2426 wxRichTextParagraphLayoutBox* parent = wxDynamicCast(GetParent(), wxRichTextParagraphLayoutBox);
2427 wxASSERT( parent != NULL );
2428
2429 // Find the height at the next paragraph, if any
2430 wxRichTextLine* line = parent->GetLineAtPosition(index + 1);
2431 if (line)
2432 {
2433 *height = line->GetSize().y;
2434 pt = line->GetAbsolutePosition();
2435 }
2436 else
2437 {
2438 *height = dc.GetCharHeight();
2439 int indent = ConvertTenthsMMToPixels(dc, m_attributes.GetLeftIndent());
2440 pt = wxPoint(indent, GetCachedSize().y);
2441 }
2442
2443 return true;
2444 }
2445
2446 if (index < GetRange().GetStart() || index > GetRange().GetEnd())
2447 return false;
2448
2449 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
2450 while (node)
2451 {
2452 wxRichTextLine* line = node->GetData();
1e967276
JS
2453 wxRichTextRange lineRange = line->GetAbsoluteRange();
2454 if (index >= lineRange.GetStart() && index <= lineRange.GetEnd())
5d7836c4
JS
2455 {
2456 // If this is the last point in the line, and we're forcing the
2457 // returned value to be the start of the next line, do the required
2458 // thing.
1e967276 2459 if (index == lineRange.GetEnd() && forceLineStart)
5d7836c4
JS
2460 {
2461 if (node->GetNext())
2462 {
2463 wxRichTextLine* nextLine = node->GetNext()->GetData();
2464 *height = nextLine->GetSize().y;
2465 pt = nextLine->GetAbsolutePosition();
2466 return true;
2467 }
2468 }
2469
2470 pt.y = line->GetPosition().y + GetPosition().y;
2471
1e967276 2472 wxRichTextRange r(lineRange.GetStart(), index);
5d7836c4
JS
2473 wxSize rangeSize;
2474 int descent = 0;
2475
2476 // We find the size of the line up to this point,
2477 // then we can add this size to the line start position and
2478 // paragraph start position to find the actual position.
2479
2480 if (GetRangeSize(r, rangeSize, descent, dc, wxRICHTEXT_UNFORMATTED))
2481 {
2482 pt.x = line->GetPosition().x + GetPosition().x + rangeSize.x;
2483 *height = line->GetSize().y;
2484
2485 return true;
2486 }
2487
2488 }
2489
2490 node = node->GetNext();
2491 }
2492
2493 return false;
2494}
2495
2496/// Hit-testing: returns a flag indicating hit test details, plus
2497/// information about position
2498int wxRichTextParagraph::HitTest(wxDC& dc, const wxPoint& pt, long& textPosition)
2499{
2500 wxPoint paraPos = GetPosition();
2501
2502 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
2503 while (node)
2504 {
2505 wxRichTextLine* line = node->GetData();
2506 wxPoint linePos = paraPos + line->GetPosition();
2507 wxSize lineSize = line->GetSize();
1e967276 2508 wxRichTextRange lineRange = line->GetAbsoluteRange();
5d7836c4
JS
2509
2510 if (pt.y >= linePos.y && pt.y <= linePos.y + lineSize.y)
2511 {
2512 if (pt.x < linePos.x)
2513 {
1e967276 2514 textPosition = lineRange.GetStart();
5d7836c4
JS
2515 return wxRICHTEXT_HITTEST_BEFORE;
2516 }
2517 else if (pt.x >= (linePos.x + lineSize.x))
2518 {
1e967276 2519 textPosition = lineRange.GetEnd();
5d7836c4
JS
2520 return wxRICHTEXT_HITTEST_AFTER;
2521 }
2522 else
2523 {
2524 long i;
2525 int lastX = linePos.x;
1e967276 2526 for (i = lineRange.GetStart(); i <= lineRange.GetEnd(); i++)
5d7836c4
JS
2527 {
2528 wxSize childSize;
2529 int descent = 0;
7fe8059f 2530
1e967276 2531 wxRichTextRange rangeToUse(lineRange.GetStart(), i);
7fe8059f 2532
5d7836c4
JS
2533 GetRangeSize(rangeToUse, childSize, descent, dc, wxRICHTEXT_UNFORMATTED);
2534
2535 int nextX = childSize.x + linePos.x;
2536
2537 if (pt.x >= lastX && pt.x <= nextX)
2538 {
2539 textPosition = i;
2540
2541 // So now we know it's between i-1 and i.
2542 // Let's see if we can be more precise about
2543 // which side of the position it's on.
2544
2545 int midPoint = (nextX - lastX)/2 + lastX;
2546 if (pt.x >= midPoint)
2547 return wxRICHTEXT_HITTEST_AFTER;
2548 else
2549 return wxRICHTEXT_HITTEST_BEFORE;
2550 }
2551 else
2552 {
2553 lastX = nextX;
2554 }
2555 }
2556 }
2557 }
7fe8059f 2558
5d7836c4
JS
2559 node = node->GetNext();
2560 }
2561
2562 return wxRICHTEXT_HITTEST_NONE;
2563}
2564
2565/// Split an object at this position if necessary, and return
2566/// the previous object, or NULL if inserting at beginning.
2567wxRichTextObject* wxRichTextParagraph::SplitAt(long pos, wxRichTextObject** previousObject)
2568{
2569 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2570 while (node)
2571 {
2572 wxRichTextObject* child = node->GetData();
2573
2574 if (pos == child->GetRange().GetStart())
2575 {
2576 if (previousObject)
4d551ad5
JS
2577 {
2578 if (node->GetPrevious())
2579 *previousObject = node->GetPrevious()->GetData();
2580 else
2581 *previousObject = NULL;
2582 }
5d7836c4
JS
2583
2584 return child;
2585 }
2586
2587 if (child->GetRange().Contains(pos))
2588 {
2589 // This should create a new object, transferring part of
2590 // the content to the old object and the rest to the new object.
2591 wxRichTextObject* newObject = child->DoSplit(pos);
2592
2593 // If we couldn't split this object, just insert in front of it.
2594 if (!newObject)
2595 {
2596 // Maybe this is an empty string, try the next one
2597 // return child;
2598 }
2599 else
2600 {
2601 // Insert the new object after 'child'
2602 if (node->GetNext())
2603 m_children.Insert(node->GetNext(), newObject);
2604 else
2605 m_children.Append(newObject);
2606 newObject->SetParent(this);
2607
2608 if (previousObject)
2609 *previousObject = child;
2610
2611 return newObject;
2612 }
2613 }
2614
2615 node = node->GetNext();
2616 }
2617 if (previousObject)
2618 *previousObject = NULL;
2619 return NULL;
2620}
2621
2622/// Move content to a list from obj on
2623void wxRichTextParagraph::MoveToList(wxRichTextObject* obj, wxList& list)
2624{
2625 wxRichTextObjectList::compatibility_iterator node = m_children.Find(obj);
2626 while (node)
2627 {
2628 wxRichTextObject* child = node->GetData();
2629 list.Append(child);
2630
2631 wxRichTextObjectList::compatibility_iterator oldNode = node;
2632
2633 node = node->GetNext();
2634
2635 m_children.DeleteNode(oldNode);
2636 }
2637}
2638
2639/// Add content back from list
2640void wxRichTextParagraph::MoveFromList(wxList& list)
2641{
2642 for (wxNode* node = list.GetFirst(); node; node = node->GetNext())
2643 {
2644 AppendChild((wxRichTextObject*) node->GetData());
2645 }
2646}
2647
2648/// Calculate range
2649void wxRichTextParagraph::CalculateRange(long start, long& end)
2650{
2651 wxRichTextCompositeObject::CalculateRange(start, end);
2652
2653 // Add one for end of paragraph
2654 end ++;
2655
2656 m_range.SetRange(start, end);
2657}
2658
2659/// Find the object at the given position
2660wxRichTextObject* wxRichTextParagraph::FindObjectAtPosition(long position)
2661{
2662 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2663 while (node)
2664 {
2665 wxRichTextObject* obj = node->GetData();
2666 if (obj->GetRange().Contains(position))
2667 return obj;
7fe8059f 2668
5d7836c4
JS
2669 node = node->GetNext();
2670 }
2671 return NULL;
2672}
2673
2674/// Get the plain text searching from the start or end of the range.
2675/// The resulting string may be shorter than the range given.
2676bool wxRichTextParagraph::GetContiguousPlainText(wxString& text, const wxRichTextRange& range, bool fromStart)
2677{
2678 text = wxEmptyString;
2679
2680 if (fromStart)
2681 {
2682 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2683 while (node)
2684 {
2685 wxRichTextObject* obj = node->GetData();
2686 if (!obj->GetRange().IsOutside(range))
2687 {
2688 wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText);
2689 if (textObj)
2690 {
2691 text += textObj->GetTextForRange(range);
2692 }
2693 else
2694 return true;
2695 }
2696
2697 node = node->GetNext();
2698 }
2699 }
2700 else
2701 {
2702 wxRichTextObjectList::compatibility_iterator node = m_children.GetLast();
2703 while (node)
2704 {
2705 wxRichTextObject* obj = node->GetData();
2706 if (!obj->GetRange().IsOutside(range))
2707 {
2708 wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText);
2709 if (textObj)
2710 {
2711 text = textObj->GetTextForRange(range) + text;
2712 }
2713 else
2714 return true;
2715 }
2716
2717 node = node->GetPrevious();
2718 }
2719 }
2720
2721 return true;
2722}
2723
2724/// Find a suitable wrap position.
2725bool wxRichTextParagraph::FindWrapPosition(const wxRichTextRange& range, wxDC& dc, int availableSpace, long& wrapPosition)
2726{
2727 // Find the first position where the line exceeds the available space.
2728 wxSize sz;
2729 long i;
2730 long breakPosition = range.GetEnd();
2731 for (i = range.GetStart(); i <= range.GetEnd(); i++)
2732 {
2733 int descent = 0;
2734 GetRangeSize(wxRichTextRange(range.GetStart(), i), sz, descent, dc, wxRICHTEXT_UNFORMATTED);
2735
2736 if (sz.x > availableSpace)
2737 {
2738 breakPosition = i-1;
2739 break;
2740 }
2741 }
2742
2743 // Now we know the last position on the line.
2744 // Let's try to find a word break.
2745
2746 wxString plainText;
2747 if (GetContiguousPlainText(plainText, wxRichTextRange(range.GetStart(), breakPosition), false))
2748 {
2749 int spacePos = plainText.Find(wxT(' '), true);
2750 if (spacePos != wxNOT_FOUND)
2751 {
2752 int positionsFromEndOfString = plainText.Length() - spacePos - 1;
2753 breakPosition = breakPosition - positionsFromEndOfString;
2754 }
2755 }
2756
2757 wrapPosition = breakPosition;
2758
2759 return true;
2760}
2761
2762/// Get the bullet text for this paragraph.
2763wxString wxRichTextParagraph::GetBulletText()
2764{
2765 if (GetAttributes().GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_NONE ||
2766 (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_BITMAP))
2767 return wxEmptyString;
2768
2769 int number = GetAttributes().GetBulletNumber();
2770
2771 wxString text;
2772 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ARABIC)
2773 {
2774 text.Printf(wxT("%d"), number);
2775 }
2776 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_LETTERS_UPPER)
2777 {
2778 // TODO: Unicode, and also check if number > 26
2779 text.Printf(wxT("%c"), (wxChar) (number+64));
2780 }
2781 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_LETTERS_LOWER)
2782 {
2783 // TODO: Unicode, and also check if number > 26
2784 text.Printf(wxT("%c"), (wxChar) (number+96));
2785 }
2786 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ROMAN_UPPER)
2787 {
2788 // TODO: convert from number to roman numeral
2789 if (number == 1)
2790 text = wxT("I");
2791 else if (number == 2)
2792 text = wxT("II");
2793 else if (number == 3)
2794 text = wxT("III");
2795 else if (number == 4)
2796 text = wxT("IV");
2797 else
2798 text = wxT("TODO");
2799 }
2800 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ROMAN_LOWER)
2801 {
2802 // TODO: convert from number to roman numeral
2803 if (number == 1)
2804 text = wxT("i");
2805 else if (number == 2)
2806 text = wxT("ii");
2807 else if (number == 3)
2808 text = wxT("iii");
2809 else if (number == 4)
2810 text = wxT("iv");
2811 else
2812 text = wxT("TODO");
2813 }
2814 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_SYMBOL)
2815 {
2816 text = GetAttributes().GetBulletSymbol();
2817 }
2818
2819 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_PARENTHESES)
2820 {
2821 text = wxT("(") + text + wxT(")");
2822 }
2823 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_PERIOD)
2824 {
2825 text += wxT(".");
2826 }
2827
2828 return text;
2829}
2830
1e967276
JS
2831/// Allocate or reuse a line object
2832wxRichTextLine* wxRichTextParagraph::AllocateLine(int pos)
2833{
2834 if (pos < (int) m_cachedLines.GetCount())
2835 {
2836 wxRichTextLine* line = m_cachedLines.Item(pos)->GetData();
2837 line->Init(this);
2838 return line;
2839 }
2840 else
2841 {
2842 wxRichTextLine* line = new wxRichTextLine(this);
2843 m_cachedLines.Append(line);
2844 return line;
2845 }
2846}
2847
2848/// Clear remaining unused line objects, if any
2849bool wxRichTextParagraph::ClearUnusedLines(int lineCount)
2850{
2851 int cachedLineCount = m_cachedLines.GetCount();
2852 if ((int) cachedLineCount > lineCount)
2853 {
2854 for (int i = 0; i < (int) (cachedLineCount - lineCount); i ++)
2855 {
2856 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetLast();
2857 wxRichTextLine* line = node->GetData();
2858 m_cachedLines.Erase(node);
2859 delete line;
2860 }
2861 }
2862 return true;
2863}
2864
5d7836c4
JS
2865
2866/*!
2867 * wxRichTextLine
2868 * This object represents a line in a paragraph, and stores
2869 * offsets from the start of the paragraph representing the
2870 * start and end positions of the line.
2871 */
2872
2873wxRichTextLine::wxRichTextLine(wxRichTextParagraph* parent)
2874{
1e967276 2875 Init(parent);
5d7836c4
JS
2876}
2877
2878/// Initialisation
1e967276 2879void wxRichTextLine::Init(wxRichTextParagraph* parent)
5d7836c4 2880{
1e967276
JS
2881 m_parent = parent;
2882 m_range.SetRange(-1, -1);
2883 m_pos = wxPoint(0, 0);
2884 m_size = wxSize(0, 0);
5d7836c4
JS
2885 m_descent = 0;
2886}
2887
2888/// Copy
2889void wxRichTextLine::Copy(const wxRichTextLine& obj)
2890{
2891 m_range = obj.m_range;
2892}
2893
2894/// Get the absolute object position
2895wxPoint wxRichTextLine::GetAbsolutePosition() const
2896{
2897 return m_parent->GetPosition() + m_pos;
2898}
2899
1e967276
JS
2900/// Get the absolute range
2901wxRichTextRange wxRichTextLine::GetAbsoluteRange() const
2902{
2903 wxRichTextRange range(m_range.GetStart() + m_parent->GetRange().GetStart(), 0);
2904 range.SetEnd(range.GetStart() + m_range.GetLength()-1);
2905 return range;
2906}
2907
5d7836c4
JS
2908/*!
2909 * wxRichTextPlainText
2910 * This object represents a single piece of text.
2911 */
2912
2913IMPLEMENT_DYNAMIC_CLASS(wxRichTextPlainText, wxRichTextObject)
2914
2915wxRichTextPlainText::wxRichTextPlainText(const wxString& text, wxRichTextObject* parent, wxTextAttrEx* style):
2916 wxRichTextObject(parent)
2917{
2918 if (parent && !style)
2919 SetAttributes(parent->GetAttributes());
2920 if (style)
2921 SetAttributes(*style);
2922
2923 m_text = text;
2924}
2925
2926/// Draw the item
2927bool wxRichTextPlainText::Draw(wxDC& dc, const wxRichTextRange& range, const wxRichTextRange& selectionRange, const wxRect& rect, int descent, int WXUNUSED(style))
2928{
2929 int offset = GetRange().GetStart();
2930
2931 long len = range.GetLength();
2932 wxString stringChunk = m_text.Mid(range.GetStart() - offset, (size_t) len);
2933
2934 int charHeight = dc.GetCharHeight();
2935
2936 int x = rect.x;
2937 int y = rect.y + (rect.height - charHeight - (descent - m_descent));
2938
2939 // Test for the optimized situations where all is selected, or none
2940 // is selected.
2941
2942 if (GetAttributes().GetFont().Ok())
2943 dc.SetFont(GetAttributes().GetFont());
2944
2945 // (a) All selected.
2946 if (selectionRange.GetStart() <= range.GetStart() && selectionRange.GetEnd() >= range.GetEnd())
2947 {
2948 // Draw all selected
2949 dc.SetBrush(*wxBLACK_BRUSH);
2950 dc.SetPen(*wxBLACK_PEN);
2951 wxCoord w, h;
2952 dc.GetTextExtent(stringChunk, & w, & h);
2953 wxRect selRect(x, rect.y, w, rect.GetHeight());
2954 dc.DrawRectangle(selRect);
2955 dc.SetTextForeground(*wxWHITE);
2956 dc.SetBackgroundMode(wxTRANSPARENT);
2957 dc.DrawText(stringChunk, x, y);
2958 }
2959 // (b) None selected.
2960 else if (selectionRange.GetEnd() < range.GetStart() || selectionRange.GetStart() > range.GetEnd())
2961 {
2962 // Draw all unselected
2963 dc.SetTextForeground(GetAttributes().GetTextColour());
2964 dc.SetBackgroundMode(wxTRANSPARENT);
2965 dc.DrawText(stringChunk, x, y);
2966 }
2967 else
2968 {
2969 // (c) Part selected, part not
2970 // Let's draw unselected chunk, selected chunk, then unselected chunk.
2971
2972 dc.SetBackgroundMode(wxTRANSPARENT);
7fe8059f 2973
5d7836c4
JS
2974 // 1. Initial unselected chunk, if any, up until start of selection.
2975 if (selectionRange.GetStart() > range.GetStart() && selectionRange.GetStart() <= range.GetEnd())
2976 {
2977 int r1 = range.GetStart();
2978 int s1 = selectionRange.GetStart()-1;
2979 int fragmentLen = s1 - r1 + 1;
2980 if (fragmentLen < 0)
2981 wxLogDebug(wxT("Mid(%d, %d"), (int)(r1 - offset), (int)fragmentLen);
2982 wxString stringFragment = m_text.Mid(r1 - offset, fragmentLen);
2983
2984 dc.SetTextForeground(GetAttributes().GetTextColour());
2985 dc.DrawText(stringFragment, x, y);
2986
2987 wxCoord w, h;
2988 dc.GetTextExtent(stringFragment, & w, & h);
2989 x += w;
2990 }
2991
2992 // 2. Selected chunk, if any.
2993 if (selectionRange.GetEnd() >= range.GetStart())
2994 {
2995 int s1 = wxMax(selectionRange.GetStart(), range.GetStart());
2996 int s2 = wxMin(selectionRange.GetEnd(), range.GetEnd());
2997
2998 int fragmentLen = s2 - s1 + 1;
2999 if (fragmentLen < 0)
3000 wxLogDebug(wxT("Mid(%d, %d"), (int)(s1 - offset), (int)fragmentLen);
3001 wxString stringFragment = m_text.Mid(s1 - offset, fragmentLen);
3002
3003 wxCoord w, h;
3004 dc.GetTextExtent(stringFragment, & w, & h);
3005 wxRect selRect(x, rect.y, w, rect.GetHeight());
3006
3007 dc.SetBrush(*wxBLACK_BRUSH);
3008 dc.SetPen(*wxBLACK_PEN);
3009 dc.DrawRectangle(selRect);
3010 dc.SetTextForeground(*wxWHITE);
3011 dc.DrawText(stringFragment, x, y);
3012
3013 x += w;
3014 }
3015
3016 // 3. Remaining unselected chunk, if any
3017 if (selectionRange.GetEnd() < range.GetEnd())
3018 {
3019 int s2 = wxMin(selectionRange.GetEnd()+1, range.GetEnd());
3020 int r2 = range.GetEnd();
3021
3022 int fragmentLen = r2 - s2 + 1;
3023 if (fragmentLen < 0)
3024 wxLogDebug(wxT("Mid(%d, %d"), (int)(s2 - offset), (int)fragmentLen);
3025 wxString stringFragment = m_text.Mid(s2 - offset, fragmentLen);
3026
3027 dc.SetTextForeground(GetAttributes().GetTextColour());
3028 dc.DrawText(stringFragment, x, y);
7fe8059f 3029 }
5d7836c4
JS
3030 }
3031
3032 return true;
3033}
3034
3035/// Lay the item out
38113684 3036bool wxRichTextPlainText::Layout(wxDC& dc, const wxRect& WXUNUSED(rect), int WXUNUSED(style))
5d7836c4
JS
3037{
3038 if (GetAttributes().GetFont().Ok())
3039 dc.SetFont(GetAttributes().GetFont());
3040
3041 wxCoord w, h;
3042 dc.GetTextExtent(m_text, & w, & h, & m_descent);
3043 m_size = wxSize(w, dc.GetCharHeight());
3044
3045 return true;
3046}
3047
3048/// Copy
3049void wxRichTextPlainText::Copy(const wxRichTextPlainText& obj)
3050{
3051 wxRichTextObject::Copy(obj);
3052
3053 m_text = obj.m_text;
3054}
3055
3056/// Get/set the object size for the given range. Returns false if the range
3057/// is invalid for this object.
3058bool wxRichTextPlainText::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, int WXUNUSED(flags)) const
3059{
3060 if (!range.IsWithin(GetRange()))
3061 return false;
3062
3063 // Always assume unformatted text, since at this level we have no knowledge
3064 // of line breaks - and we don't need it, since we'll calculate size within
3065 // formatted text by doing it in chunks according to the line ranges
3066
3067 if (GetAttributes().GetFont().Ok())
3068 dc.SetFont(GetAttributes().GetFont());
3069
3070 int startPos = range.GetStart() - GetRange().GetStart();
3071 long len = range.GetLength();
3072 wxString stringChunk = m_text.Mid(startPos, (size_t) len);
3073 wxCoord w, h;
3074 dc.GetTextExtent(stringChunk, & w, & h, & descent);
3075 size = wxSize(w, dc.GetCharHeight());
3076
3077 return true;
3078}
3079
3080/// Do a split, returning an object containing the second part, and setting
3081/// the first part in 'this'.
3082wxRichTextObject* wxRichTextPlainText::DoSplit(long pos)
3083{
3084 int index = pos - GetRange().GetStart();
3085 if (index < 0 || index >= (int) m_text.Length())
3086 return NULL;
3087
3088 wxString firstPart = m_text.Mid(0, index);
3089 wxString secondPart = m_text.Mid(index);
3090
3091 m_text = firstPart;
3092
3093 wxRichTextPlainText* newObject = new wxRichTextPlainText(secondPart);
3094 newObject->SetAttributes(GetAttributes());
3095
3096 newObject->SetRange(wxRichTextRange(pos, GetRange().GetEnd()));
3097 GetRange().SetEnd(pos-1);
3098
3099 return newObject;
3100}
3101
3102/// Calculate range
3103void wxRichTextPlainText::CalculateRange(long start, long& end)
3104{
3105 end = start + m_text.Length() - 1;
3106 m_range.SetRange(start, end);
3107}
3108
3109/// Delete range
3110bool wxRichTextPlainText::DeleteRange(const wxRichTextRange& range)
3111{
3112 wxRichTextRange r = range;
3113
3114 r.LimitTo(GetRange());
3115
3116 if (r.GetStart() == GetRange().GetStart() && r.GetEnd() == GetRange().GetEnd())
3117 {
3118 m_text.Empty();
3119 return true;
3120 }
3121
3122 long startIndex = r.GetStart() - GetRange().GetStart();
3123 long len = r.GetLength();
3124
3125 m_text = m_text.Mid(0, startIndex) + m_text.Mid(startIndex+len);
3126 return true;
3127}
3128
3129/// Get text for the given range.
3130wxString wxRichTextPlainText::GetTextForRange(const wxRichTextRange& range) const
3131{
3132 wxRichTextRange r = range;
3133
3134 r.LimitTo(GetRange());
3135
3136 long startIndex = r.GetStart() - GetRange().GetStart();
3137 long len = r.GetLength();
3138
3139 return m_text.Mid(startIndex, len);
3140}
3141
3142/// Returns true if this object can merge itself with the given one.
3143bool wxRichTextPlainText::CanMerge(wxRichTextObject* object) const
3144{
3145 return object->GetClassInfo() == CLASSINFO(wxRichTextPlainText) &&
7fe8059f 3146 (m_text.empty() || wxTextAttrEq(GetAttributes(), object->GetAttributes()));
5d7836c4
JS
3147}
3148
3149/// Returns true if this object merged itself with the given one.
3150/// The calling code will then delete the given object.
3151bool wxRichTextPlainText::Merge(wxRichTextObject* object)
3152{
3153 wxRichTextPlainText* textObject = wxDynamicCast(object, wxRichTextPlainText);
3154 wxASSERT( textObject != NULL );
3155
3156 if (textObject)
3157 {
3158 m_text += textObject->GetText();
3159 return true;
3160 }
3161 else
3162 return false;
3163}
3164
3165/// Dump to output stream for debugging
3166void wxRichTextPlainText::Dump(wxTextOutputStream& stream)
3167{
3168 wxRichTextObject::Dump(stream);
3169 stream << m_text << wxT("\n");
3170}
3171
3172/*!
3173 * wxRichTextBuffer
3174 * This is a kind of box, used to represent the whole buffer
3175 */
3176
3177IMPLEMENT_DYNAMIC_CLASS(wxRichTextBuffer, wxRichTextParagraphLayoutBox)
3178
3179wxList wxRichTextBuffer::sm_handlers;
3180
3181/// Initialisation
3182void wxRichTextBuffer::Init()
3183{
3184 m_commandProcessor = new wxCommandProcessor;
3185 m_styleSheet = NULL;
3186 m_modified = false;
3187 m_batchedCommandDepth = 0;
3188 m_batchedCommand = NULL;
3189 m_suppressUndo = 0;
3190}
3191
3192/// Initialisation
3193wxRichTextBuffer::~wxRichTextBuffer()
3194{
3195 delete m_commandProcessor;
3196 delete m_batchedCommand;
3197
3198 ClearStyleStack();
3199}
3200
3201void wxRichTextBuffer::Clear()
3202{
3203 DeleteChildren();
3204 GetCommandProcessor()->ClearCommands();
3205 Modify(false);
1e967276 3206 Invalidate(wxRICHTEXT_ALL);
5d7836c4
JS
3207}
3208
3209void wxRichTextBuffer::Reset()
3210{
3211 DeleteChildren();
7fe8059f 3212 AddParagraph(wxEmptyString);
5d7836c4
JS
3213 GetCommandProcessor()->ClearCommands();
3214 Modify(false);
1e967276 3215 Invalidate(wxRICHTEXT_ALL);
5d7836c4
JS
3216}
3217
3218/// Submit command to insert the given text
3219bool wxRichTextBuffer::InsertTextWithUndo(long pos, const wxString& text, wxRichTextCtrl* ctrl)
3220{
3221 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, this, ctrl, false);
3222
3223 action->GetNewParagraphs().AddParagraphs(text);
3224 if (action->GetNewParagraphs().GetChildCount() == 1)
3225 action->GetNewParagraphs().SetPartialParagraph(true);
3226
3227 action->SetPosition(pos);
3228
3229 // Set the range we'll need to delete in Undo
3230 action->SetRange(wxRichTextRange(pos, pos + text.Length() - 1));
7fe8059f 3231
5d7836c4 3232 SubmitAction(action);
7fe8059f 3233
5d7836c4
JS
3234 return true;
3235}
3236
3237/// Submit command to insert the given text
3238bool wxRichTextBuffer::InsertNewlineWithUndo(long pos, wxRichTextCtrl* ctrl)
3239{
3240 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, this, ctrl, false);
3241
3242 wxTextAttrEx attr(GetBasicStyle());
3243 wxRichTextApplyStyle(attr, GetDefaultStyle());
7fe8059f
WS
3244
3245 wxRichTextParagraph* newPara = new wxRichTextParagraph(wxEmptyString, this, & attr);
5d7836c4
JS
3246 action->GetNewParagraphs().AppendChild(newPara);
3247 action->GetNewParagraphs().UpdateRanges();
3248 action->GetNewParagraphs().SetPartialParagraph(false);
3249 action->SetPosition(pos);
3250
3251 // Set the range we'll need to delete in Undo
3252 action->SetRange(wxRichTextRange(pos, pos));
7fe8059f 3253
5d7836c4 3254 SubmitAction(action);
7fe8059f 3255
5d7836c4
JS
3256 return true;
3257}
3258
3259/// Submit command to insert the given image
3260bool wxRichTextBuffer::InsertImageWithUndo(long pos, const wxRichTextImageBlock& imageBlock, wxRichTextCtrl* ctrl)
3261{
3262 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Image"), wxRICHTEXT_INSERT, this, ctrl, false);
3263
3264 wxTextAttrEx attr(GetBasicStyle());
3265 wxRichTextApplyStyle(attr, GetDefaultStyle());
7fe8059f 3266
5d7836c4
JS
3267 wxRichTextParagraph* newPara = new wxRichTextParagraph(this, & attr);
3268 wxRichTextImage* imageObject = new wxRichTextImage(imageBlock, newPara);
3269 newPara->AppendChild(imageObject);
3270 action->GetNewParagraphs().AppendChild(newPara);
3271 action->GetNewParagraphs().UpdateRanges();
3272
3273 action->GetNewParagraphs().SetPartialParagraph(true);
3274
3275 action->SetPosition(pos);
3276
3277 // Set the range we'll need to delete in Undo
3278 action->SetRange(wxRichTextRange(pos, pos));
7fe8059f 3279
5d7836c4 3280 SubmitAction(action);
7fe8059f 3281
5d7836c4
JS
3282 return true;
3283}
3284
3285/// Submit command to delete this range
3286bool wxRichTextBuffer::DeleteRangeWithUndo(const wxRichTextRange& range, long initialCaretPosition, long WXUNUSED(newCaretPositon), wxRichTextCtrl* ctrl)
3287{
3288 wxRichTextAction* action = new wxRichTextAction(NULL, _("Delete"), wxRICHTEXT_DELETE, this, ctrl);
7fe8059f 3289
5d7836c4
JS
3290 action->SetPosition(initialCaretPosition);
3291
3292 // Set the range to delete
3293 action->SetRange(range);
7fe8059f 3294
5d7836c4
JS
3295 // Copy the fragment that we'll need to restore in Undo
3296 CopyFragment(range, action->GetOldParagraphs());
3297
3298 // Special case: if there is only one (non-partial) paragraph,
3299 // we must save the *next* paragraph's style, because that
3300 // is the style we must apply when inserting the content back
3301 // when undoing the delete. (This is because we're merging the
3302 // paragraph with the previous paragraph and throwing away
3303 // the style, and we need to restore it.)
3304 if (!action->GetOldParagraphs().GetPartialParagraph() && action->GetOldParagraphs().GetChildCount() == 1)
3305 {
3306 wxRichTextParagraph* lastPara = GetParagraphAtPosition(range.GetStart());
3307 if (lastPara)
3308 {
3309 wxRichTextParagraph* nextPara = GetParagraphAtPosition(range.GetEnd()+1);
3310 if (nextPara)
3311 {
3312 wxRichTextParagraph* para = (wxRichTextParagraph*) action->GetOldParagraphs().GetChild(0);
3313 para->SetAttributes(nextPara->GetAttributes());
3314 }
3315 }
3316 }
3317
3318 SubmitAction(action);
7fe8059f 3319
5d7836c4
JS
3320 return true;
3321}
3322
3323/// Collapse undo/redo commands
3324bool wxRichTextBuffer::BeginBatchUndo(const wxString& cmdName)
3325{
3326 if (m_batchedCommandDepth == 0)
3327 {
3328 wxASSERT(m_batchedCommand == NULL);
3329 if (m_batchedCommand)
3330 {
3331 GetCommandProcessor()->Submit(m_batchedCommand);
3332 }
3333 m_batchedCommand = new wxRichTextCommand(cmdName);
3334 }
3335
7fe8059f 3336 m_batchedCommandDepth ++;
5d7836c4
JS
3337
3338 return true;
3339}
3340
3341/// Collapse undo/redo commands
3342bool wxRichTextBuffer::EndBatchUndo()
3343{
3344 m_batchedCommandDepth --;
3345
3346 wxASSERT(m_batchedCommandDepth >= 0);
3347 wxASSERT(m_batchedCommand != NULL);
3348
3349 if (m_batchedCommandDepth == 0)
3350 {
3351 GetCommandProcessor()->Submit(m_batchedCommand);
3352 m_batchedCommand = NULL;
3353 }
3354
3355 return true;
3356}
3357
3358/// Submit immediately, or delay according to whether collapsing is on
3359bool wxRichTextBuffer::SubmitAction(wxRichTextAction* action)
3360{
3361 if (BatchingUndo() && m_batchedCommand && !SuppressingUndo())
3362 m_batchedCommand->AddAction(action);
3363 else
3364 {
3365 wxRichTextCommand* cmd = new wxRichTextCommand(action->GetName());
3366 cmd->AddAction(action);
3367
3368 // Only store it if we're not suppressing undo.
3369 return GetCommandProcessor()->Submit(cmd, !SuppressingUndo());
3370 }
3371
3372 return true;
3373}
3374
3375/// Begin suppressing undo/redo commands.
3376bool wxRichTextBuffer::BeginSuppressUndo()
3377{
7fe8059f 3378 m_suppressUndo ++;
5d7836c4
JS
3379
3380 return true;
3381}
3382
3383/// End suppressing undo/redo commands.
3384bool wxRichTextBuffer::EndSuppressUndo()
3385{
7fe8059f 3386 m_suppressUndo --;
5d7836c4
JS
3387
3388 return true;
3389}
3390
3391/// Begin using a style
3392bool wxRichTextBuffer::BeginStyle(const wxTextAttrEx& style)
3393{
3394 wxTextAttrEx newStyle(GetDefaultStyle());
3395
3396 // Save the old default style
3397 m_attributeStack.Append((wxObject*) new wxTextAttrEx(GetDefaultStyle()));
3398
3399 wxRichTextApplyStyle(newStyle, style);
3400 newStyle.SetFlags(style.GetFlags()|newStyle.GetFlags());
3401
3402 SetDefaultStyle(newStyle);
3403
3404 // wxLogDebug("Default style size = %d", GetDefaultStyle().GetFont().GetPointSize());
3405
3406 return true;
3407}
3408
3409/// End the style
3410bool wxRichTextBuffer::EndStyle()
3411{
3412 if (m_attributeStack.GetFirst() == NULL)
3413 {
3414 wxLogDebug(_("Too many EndStyle calls!"));
3415 return false;
3416 }
3417
3418 wxNode* node = m_attributeStack.GetLast();
3419 wxTextAttrEx* attr = (wxTextAttrEx*)node->GetData();
3420 delete node;
3421
3422 SetDefaultStyle(*attr);
3423
3424 delete attr;
3425 return true;
3426}
3427
3428/// End all styles
3429bool wxRichTextBuffer::EndAllStyles()
3430{
3431 while (m_attributeStack.GetCount() != 0)
3432 EndStyle();
3433 return true;
3434}
3435
3436/// Clear the style stack
3437void wxRichTextBuffer::ClearStyleStack()
3438{
3439 for (wxNode* node = m_attributeStack.GetFirst(); node; node = node->GetNext())
3440 delete (wxTextAttrEx*) node->GetData();
3441 m_attributeStack.Clear();
3442}
3443
3444/// Begin using bold
3445bool wxRichTextBuffer::BeginBold()
3446{
3447 wxFont font(GetBasicStyle().GetFont());
3448 font.SetWeight(wxBOLD);
3449
3450 wxTextAttrEx attr;
3451 attr.SetFont(font,wxTEXT_ATTR_FONT_WEIGHT);
7fe8059f 3452
5d7836c4
JS
3453 return BeginStyle(attr);
3454}
3455
3456/// Begin using italic
3457bool wxRichTextBuffer::BeginItalic()
3458{
3459 wxFont font(GetBasicStyle().GetFont());
3460 font.SetStyle(wxITALIC);
3461
3462 wxTextAttrEx attr;
3463 attr.SetFont(font, wxTEXT_ATTR_FONT_ITALIC);
7fe8059f 3464
5d7836c4
JS
3465 return BeginStyle(attr);
3466}
3467
3468/// Begin using underline
3469bool wxRichTextBuffer::BeginUnderline()
3470{
3471 wxFont font(GetBasicStyle().GetFont());
3472 font.SetUnderlined(true);
3473
3474 wxTextAttrEx attr;
3475 attr.SetFont(font, wxTEXT_ATTR_FONT_UNDERLINE);
7fe8059f 3476
5d7836c4
JS
3477 return BeginStyle(attr);
3478}
3479
3480/// Begin using point size
3481bool wxRichTextBuffer::BeginFontSize(int pointSize)
3482{
3483 wxFont font(GetBasicStyle().GetFont());
3484 font.SetPointSize(pointSize);
3485
3486 wxTextAttrEx attr;
3487 attr.SetFont(font, wxTEXT_ATTR_FONT_SIZE);
7fe8059f 3488
5d7836c4
JS
3489 return BeginStyle(attr);
3490}
3491
3492/// Begin using this font
3493bool wxRichTextBuffer::BeginFont(const wxFont& font)
3494{
3495 wxTextAttrEx attr;
3496 attr.SetFlags(wxTEXT_ATTR_FONT);
3497 attr.SetFont(font);
7fe8059f 3498
5d7836c4
JS
3499 return BeginStyle(attr);
3500}
3501
3502/// Begin using this colour
3503bool wxRichTextBuffer::BeginTextColour(const wxColour& colour)
3504{
3505 wxTextAttrEx attr;
3506 attr.SetFlags(wxTEXT_ATTR_TEXT_COLOUR);
3507 attr.SetTextColour(colour);
7fe8059f 3508
5d7836c4
JS
3509 return BeginStyle(attr);
3510}
3511
3512/// Begin using alignment
3513bool wxRichTextBuffer::BeginAlignment(wxTextAttrAlignment alignment)
3514{
3515 wxTextAttrEx attr;
3516 attr.SetFlags(wxTEXT_ATTR_ALIGNMENT);
3517 attr.SetAlignment(alignment);
7fe8059f 3518
5d7836c4
JS
3519 return BeginStyle(attr);
3520}
3521
3522/// Begin left indent
3523bool wxRichTextBuffer::BeginLeftIndent(int leftIndent, int leftSubIndent)
3524{
3525 wxTextAttrEx attr;
3526 attr.SetFlags(wxTEXT_ATTR_LEFT_INDENT);
3527 attr.SetLeftIndent(leftIndent, leftSubIndent);
7fe8059f 3528
5d7836c4
JS
3529 return BeginStyle(attr);
3530}
3531
3532/// Begin right indent
3533bool wxRichTextBuffer::BeginRightIndent(int rightIndent)
3534{
3535 wxTextAttrEx attr;
3536 attr.SetFlags(wxTEXT_ATTR_RIGHT_INDENT);
3537 attr.SetRightIndent(rightIndent);
7fe8059f 3538
5d7836c4
JS
3539 return BeginStyle(attr);
3540}
3541
3542/// Begin paragraph spacing
3543bool wxRichTextBuffer::BeginParagraphSpacing(int before, int after)
3544{
3545 long flags = 0;
3546 if (before != 0)
3547 flags |= wxTEXT_ATTR_PARA_SPACING_BEFORE;
3548 if (after != 0)
3549 flags |= wxTEXT_ATTR_PARA_SPACING_AFTER;
3550
3551 wxTextAttrEx attr;
3552 attr.SetFlags(flags);
3553 attr.SetParagraphSpacingBefore(before);
3554 attr.SetParagraphSpacingAfter(after);
7fe8059f 3555
5d7836c4
JS
3556 return BeginStyle(attr);
3557}
3558
3559/// Begin line spacing
3560bool wxRichTextBuffer::BeginLineSpacing(int lineSpacing)
3561{
3562 wxTextAttrEx attr;
3563 attr.SetFlags(wxTEXT_ATTR_LINE_SPACING);
3564 attr.SetLineSpacing(lineSpacing);
7fe8059f 3565
5d7836c4
JS
3566 return BeginStyle(attr);
3567}
3568
3569/// Begin numbered bullet
3570bool wxRichTextBuffer::BeginNumberedBullet(int bulletNumber, int leftIndent, int leftSubIndent, int bulletStyle)
3571{
3572 wxTextAttrEx attr;
3573 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_BULLET_NUMBER|wxTEXT_ATTR_LEFT_INDENT);
3574 attr.SetBulletStyle(bulletStyle);
3575 attr.SetBulletNumber(bulletNumber);
3576 attr.SetLeftIndent(leftIndent, leftSubIndent);
7fe8059f 3577
5d7836c4
JS
3578 return BeginStyle(attr);
3579}
3580
3581/// Begin symbol bullet
3582bool wxRichTextBuffer::BeginSymbolBullet(wxChar symbol, int leftIndent, int leftSubIndent, int bulletStyle)
3583{
3584 wxTextAttrEx attr;
3585 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_BULLET_SYMBOL|wxTEXT_ATTR_LEFT_INDENT);
3586 attr.SetBulletStyle(bulletStyle);
3587 attr.SetLeftIndent(leftIndent, leftSubIndent);
3588 attr.SetBulletSymbol(symbol);
7fe8059f 3589
5d7836c4
JS
3590 return BeginStyle(attr);
3591}
3592
3593/// Begin named character style
3594bool wxRichTextBuffer::BeginCharacterStyle(const wxString& characterStyle)
3595{
3596 if (GetStyleSheet())
3597 {
3598 wxRichTextCharacterStyleDefinition* def = GetStyleSheet()->FindCharacterStyle(characterStyle);
3599 if (def)
3600 {
3601 wxTextAttrEx attr;
3602 def->GetStyle().CopyTo(attr);
3603 return BeginStyle(attr);
3604 }
3605 }
3606 return false;
3607}
3608
3609/// Begin named paragraph style
3610bool wxRichTextBuffer::BeginParagraphStyle(const wxString& paragraphStyle)
3611{
3612 if (GetStyleSheet())
3613 {
3614 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(paragraphStyle);
3615 if (def)
3616 {
3617 wxTextAttrEx attr;
3618 def->GetStyle().CopyTo(attr);
3619 return BeginStyle(attr);
3620 }
3621 }
3622 return false;
3623}
3624
3625/// Adds a handler to the end
3626void wxRichTextBuffer::AddHandler(wxRichTextFileHandler *handler)
3627{
3628 sm_handlers.Append(handler);
3629}
3630
3631/// Inserts a handler at the front
3632void wxRichTextBuffer::InsertHandler(wxRichTextFileHandler *handler)
3633{
3634 sm_handlers.Insert( handler );
3635}
3636
3637/// Removes a handler
3638bool wxRichTextBuffer::RemoveHandler(const wxString& name)
3639{
3640 wxRichTextFileHandler *handler = FindHandler(name);
3641 if (handler)
3642 {
3643 sm_handlers.DeleteObject(handler);
3644 delete handler;
3645 return true;
3646 }
3647 else
3648 return false;
3649}
3650
3651/// Finds a handler by filename or, if supplied, type
3652wxRichTextFileHandler *wxRichTextBuffer::FindHandlerFilenameOrType(const wxString& filename, int imageType)
3653{
3654 if (imageType != wxRICHTEXT_TYPE_ANY)
3655 return FindHandler(imageType);
3656 else
3657 {
3658 wxString path, file, ext;
3659 wxSplitPath(filename, & path, & file, & ext);
3660 return FindHandler(ext, imageType);
3661 }
3662}
3663
3664
3665/// Finds a handler by name
3666wxRichTextFileHandler* wxRichTextBuffer::FindHandler(const wxString& name)
3667{
3668 wxList::compatibility_iterator node = sm_handlers.GetFirst();
3669 while (node)
3670 {
3671 wxRichTextFileHandler *handler = (wxRichTextFileHandler*)node->GetData();
3672 if (handler->GetName().Lower() == name.Lower()) return handler;
3673
3674 node = node->GetNext();
3675 }
3676 return NULL;
3677}
3678
3679/// Finds a handler by extension and type
3680wxRichTextFileHandler* wxRichTextBuffer::FindHandler(const wxString& extension, int type)
3681{
3682 wxList::compatibility_iterator node = sm_handlers.GetFirst();
3683 while (node)
3684 {
3685 wxRichTextFileHandler *handler = (wxRichTextFileHandler*)node->GetData();
3686 if ( handler->GetExtension().Lower() == extension.Lower() &&
3687 (type == wxRICHTEXT_TYPE_ANY || handler->GetType() == type) )
3688 return handler;
3689 node = node->GetNext();
3690 }
3691 return 0;
3692}
3693
3694/// Finds a handler by type
3695wxRichTextFileHandler* wxRichTextBuffer::FindHandler(int type)
3696{
3697 wxList::compatibility_iterator node = sm_handlers.GetFirst();
3698 while (node)
3699 {
3700 wxRichTextFileHandler *handler = (wxRichTextFileHandler *)node->GetData();
3701 if (handler->GetType() == type) return handler;
3702 node = node->GetNext();
3703 }
3704 return NULL;
3705}
3706
3707void wxRichTextBuffer::InitStandardHandlers()
3708{
3709 if (!FindHandler(wxRICHTEXT_TYPE_TEXT))
3710 AddHandler(new wxRichTextPlainTextHandler);
3711}
3712
3713void wxRichTextBuffer::CleanUpHandlers()
3714{
3715 wxList::compatibility_iterator node = sm_handlers.GetFirst();
3716 while (node)
3717 {
3718 wxRichTextFileHandler* handler = (wxRichTextFileHandler*)node->GetData();
3719 wxList::compatibility_iterator next = node->GetNext();
3720 delete handler;
3721 node = next;
3722 }
3723
3724 sm_handlers.Clear();
3725}
3726
1e967276 3727wxString wxRichTextBuffer::GetExtWildcard(bool combine, bool save, wxArrayInt* types)
5d7836c4 3728{
1e967276
JS
3729 if (types)
3730 types->Clear();
3731
5d7836c4
JS
3732 wxString wildcard;
3733
3734 wxList::compatibility_iterator node = GetHandlers().GetFirst();
3735 int count = 0;
3736 while (node)
3737 {
3738 wxRichTextFileHandler* handler = (wxRichTextFileHandler*) node->GetData();
3739 if (handler->IsVisible() && ((save && handler->CanSave()) || !save && handler->CanLoad()))
3740 {
3741 if (combine)
3742 {
3743 if (count > 0)
3744 wildcard += wxT(";");
3745 wildcard += wxT("*.") + handler->GetExtension();
3746 }
3747 else
3748 {
3749 if (count > 0)
3750 wildcard += wxT("|");
3751 wildcard += handler->GetName();
3752 wildcard += wxT(" ");
3753 wildcard += _("files");
3754 wildcard += wxT(" (*.");
3755 wildcard += handler->GetExtension();
3756 wildcard += wxT(")|*.");
3757 wildcard += handler->GetExtension();
1e967276
JS
3758 if (types)
3759 types->Add(handler->GetType());
5d7836c4
JS
3760 }
3761 count ++;
3762 }
3763
3764 node = node->GetNext();
3765 }
3766
3767 if (combine)
3768 wildcard = wxT("(") + wildcard + wxT(")|") + wildcard;
3769 return wildcard;
3770}
3771
3772/// Load a file
3773bool wxRichTextBuffer::LoadFile(const wxString& filename, int type)
3774{
3775 wxRichTextFileHandler* handler = FindHandlerFilenameOrType(filename, type);
3776 if (handler)
1e967276
JS
3777 {
3778 SetDefaultStyle(wxTextAttrEx());
3779
3780 bool success = handler->LoadFile(this, filename);
3781 Invalidate(wxRICHTEXT_ALL);
3782 return success;
3783 }
5d7836c4
JS
3784 else
3785 return false;
3786}
3787
3788/// Save a file
3789bool wxRichTextBuffer::SaveFile(const wxString& filename, int type)
3790{
3791 wxRichTextFileHandler* handler = FindHandlerFilenameOrType(filename, type);
3792 if (handler)
3793 return handler->SaveFile(this, filename);
3794 else
3795 return false;
3796}
3797
3798/// Load from a stream
3799bool wxRichTextBuffer::LoadFile(wxInputStream& stream, int type)
3800{
3801 wxRichTextFileHandler* handler = FindHandler(type);
3802 if (handler)
1e967276
JS
3803 {
3804 SetDefaultStyle(wxTextAttrEx());
3805 bool success = handler->LoadFile(this, stream);
3806 Invalidate(wxRICHTEXT_ALL);
3807 return success;
3808 }
5d7836c4
JS
3809 else
3810 return false;
3811}
3812
3813/// Save to a stream
3814bool wxRichTextBuffer::SaveFile(wxOutputStream& stream, int type)
3815{
3816 wxRichTextFileHandler* handler = FindHandler(type);
3817 if (handler)
3818 return handler->SaveFile(this, stream);
3819 else
3820 return false;
3821}
3822
3823/// Copy the range to the clipboard
3824bool wxRichTextBuffer::CopyToClipboard(const wxRichTextRange& range)
3825{
3826 bool success = false;
39a1c2f2 3827#if wxUSE_CLIPBOARD
5d7836c4 3828 wxString text = GetTextForRange(range);
d2142335 3829 if (!wxTheClipboard->IsOpened() && wxTheClipboard->Open())
7fe8059f 3830 {
5d7836c4
JS
3831 success = wxTheClipboard->SetData(new wxTextDataObject(text));
3832 wxTheClipboard->Close();
3833 }
39a1c2f2
WS
3834#else
3835 wxUnusedVar(range);
3836#endif
5d7836c4
JS
3837 return success;
3838}
3839
3840/// Paste the clipboard content to the buffer
3841bool wxRichTextBuffer::PasteFromClipboard(long position)
3842{
3843 bool success = false;
39a1c2f2 3844#if wxUSE_CLIPBOARD
5d7836c4
JS
3845 if (CanPasteFromClipboard())
3846 {
3847 if (wxTheClipboard->Open())
3848 {
3849 if (wxTheClipboard->IsSupported(wxDF_TEXT))
3850 {
3851 wxTextDataObject data;
3852 wxTheClipboard->GetData(data);
3853 wxString text(data.GetText());
3854
3855 InsertTextWithUndo(position+1, text, GetRichTextCtrl());
7fe8059f 3856
5d7836c4
JS
3857 success = true;
3858 }
3859 else if (wxTheClipboard->IsSupported(wxDF_BITMAP))
3860 {
3861 wxBitmapDataObject data;
3862 wxTheClipboard->GetData(data);
3863 wxBitmap bitmap(data.GetBitmap());
3864 wxImage image(bitmap.ConvertToImage());
3865
3866 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Image"), wxRICHTEXT_INSERT, this, GetRichTextCtrl(), false);
7fe8059f 3867
5d7836c4
JS
3868 action->GetNewParagraphs().AddImage(image);
3869
3870 if (action->GetNewParagraphs().GetChildCount() == 1)
3871 action->GetNewParagraphs().SetPartialParagraph(true);
7fe8059f 3872
5d7836c4 3873 action->SetPosition(position);
7fe8059f 3874
5d7836c4
JS
3875 // Set the range we'll need to delete in Undo
3876 action->SetRange(wxRichTextRange(position, position));
7fe8059f 3877
5d7836c4
JS
3878 SubmitAction(action);
3879
3880 success = true;
3881 }
3882 wxTheClipboard->Close();
3883 }
3884 }
39a1c2f2
WS
3885#else
3886 wxUnusedVar(position);
3887#endif
5d7836c4
JS
3888 return success;
3889}
3890
3891/// Can we paste from the clipboard?
3892bool wxRichTextBuffer::CanPasteFromClipboard() const
3893{
7fe8059f 3894 bool canPaste = false;
39a1c2f2 3895#if wxUSE_CLIPBOARD
d2142335 3896 if (!wxTheClipboard->IsOpened() && wxTheClipboard->Open())
5d7836c4
JS
3897 {
3898 if (wxTheClipboard->IsSupported(wxDF_TEXT) || wxTheClipboard->IsSupported(wxDF_BITMAP))
3899 {
7fe8059f 3900 canPaste = true;
5d7836c4
JS
3901 }
3902 wxTheClipboard->Close();
3903 }
39a1c2f2 3904#endif
5d7836c4
JS
3905 return canPaste;
3906}
3907
3908/// Dumps contents of buffer for debugging purposes
3909void wxRichTextBuffer::Dump()
3910{
3911 wxString text;
3912 {
3913 wxStringOutputStream stream(& text);
3914 wxTextOutputStream textStream(stream);
3915 Dump(textStream);
3916 }
3917
3918 wxLogDebug(text);
3919}
3920
3921
3922/*
3923 * Module to initialise and clean up handlers
3924 */
3925
3926class wxRichTextModule: public wxModule
3927{
3928DECLARE_DYNAMIC_CLASS(wxRichTextModule)
3929public:
3930 wxRichTextModule() {}
3931 bool OnInit() { wxRichTextBuffer::InitStandardHandlers(); return true; };
3932 void OnExit() { wxRichTextBuffer::CleanUpHandlers(); };
3933};
3934
3935IMPLEMENT_DYNAMIC_CLASS(wxRichTextModule, wxModule)
3936
3937
3938/*!
3939 * Commands for undo/redo
3940 *
3941 */
3942
3943wxRichTextCommand::wxRichTextCommand(const wxString& name, wxRichTextCommandId id, wxRichTextBuffer* buffer,
7fe8059f 3944 wxRichTextCtrl* ctrl, bool ignoreFirstTime): wxCommand(true, name)
5d7836c4
JS
3945{
3946 /* wxRichTextAction* action = */ new wxRichTextAction(this, name, id, buffer, ctrl, ignoreFirstTime);
3947}
3948
7fe8059f 3949wxRichTextCommand::wxRichTextCommand(const wxString& name): wxCommand(true, name)
5d7836c4
JS
3950{
3951}
3952
3953wxRichTextCommand::~wxRichTextCommand()
3954{
3955 ClearActions();
3956}
3957
3958void wxRichTextCommand::AddAction(wxRichTextAction* action)
3959{
3960 if (!m_actions.Member(action))
3961 m_actions.Append(action);
3962}
3963
3964bool wxRichTextCommand::Do()
3965{
3966 for (wxNode* node = m_actions.GetFirst(); node; node = node->GetNext())
3967 {
3968 wxRichTextAction* action = (wxRichTextAction*) node->GetData();
3969 action->Do();
3970 }
3971
3972 return true;
3973}
3974
3975bool wxRichTextCommand::Undo()
3976{
3977 for (wxNode* node = m_actions.GetLast(); node; node = node->GetPrevious())
3978 {
3979 wxRichTextAction* action = (wxRichTextAction*) node->GetData();
3980 action->Undo();
3981 }
3982
3983 return true;
3984}
3985
3986void wxRichTextCommand::ClearActions()
3987{
3988 WX_CLEAR_LIST(wxList, m_actions);
3989}
3990
3991/*!
3992 * Individual action
3993 *
3994 */
3995
3996wxRichTextAction::wxRichTextAction(wxRichTextCommand* cmd, const wxString& name, wxRichTextCommandId id, wxRichTextBuffer* buffer,
3997 wxRichTextCtrl* ctrl, bool ignoreFirstTime)
3998{
3999 m_buffer = buffer;
4000 m_ignoreThis = ignoreFirstTime;
4001 m_cmdId = id;
4002 m_position = -1;
4003 m_ctrl = ctrl;
4004 m_name = name;
4005 m_newParagraphs.SetDefaultStyle(buffer->GetDefaultStyle());
4006 m_newParagraphs.SetBasicStyle(buffer->GetBasicStyle());
4007 if (cmd)
4008 cmd->AddAction(this);
4009}
4010
4011wxRichTextAction::~wxRichTextAction()
4012{
4013}
4014
4015bool wxRichTextAction::Do()
4016{
4017 m_buffer->Modify(true);
4018
4019 switch (m_cmdId)
4020 {
4021 case wxRICHTEXT_INSERT:
4022 {
4023 m_buffer->InsertFragment(GetPosition(), m_newParagraphs);
4024 m_buffer->UpdateRanges();
1e967276 4025 m_buffer->Invalidate(GetRange());
5d7836c4
JS
4026
4027 long newCaretPosition = GetPosition() + m_newParagraphs.GetRange().GetLength() - 1;
4028 if (m_newParagraphs.GetPartialParagraph())
4029 newCaretPosition --;
4030
4031 UpdateAppearance(newCaretPosition, true /* send update event */);
4032
4033 break;
4034 }
4035 case wxRICHTEXT_DELETE:
4036 {
4037 m_buffer->DeleteRange(GetRange());
4038 m_buffer->UpdateRanges();
1e967276 4039 m_buffer->Invalidate(wxRichTextRange(GetRange().GetStart(), GetRange().GetStart()));
5d7836c4
JS
4040
4041 UpdateAppearance(GetRange().GetStart()-1, true /* send update event */);
4042
4043 break;
4044 }
4045 case wxRICHTEXT_CHANGE_STYLE:
4046 {
4047 ApplyParagraphs(GetNewParagraphs());
1e967276 4048 m_buffer->Invalidate(GetRange());
5d7836c4
JS
4049
4050 UpdateAppearance(GetPosition());
4051
4052 break;
4053 }
4054 default:
4055 break;
4056 }
4057
4058 return true;
4059}
4060
4061bool wxRichTextAction::Undo()
4062{
4063 m_buffer->Modify(true);
4064
4065 switch (m_cmdId)
4066 {
4067 case wxRICHTEXT_INSERT:
4068 {
4069 m_buffer->DeleteRange(GetRange());
4070 m_buffer->UpdateRanges();
1e967276 4071 m_buffer->Invalidate(wxRichTextRange(GetRange().GetStart(), GetRange().GetStart()));
5d7836c4
JS
4072
4073 long newCaretPosition = GetPosition() - 1;
4074 // if (m_newParagraphs.GetPartialParagraph())
4075 // newCaretPosition --;
4076
4077 UpdateAppearance(newCaretPosition, true /* send update event */);
4078
4079 break;
4080 }
4081 case wxRICHTEXT_DELETE:
4082 {
4083 m_buffer->InsertFragment(GetRange().GetStart(), m_oldParagraphs);
4084 m_buffer->UpdateRanges();
1e967276 4085 m_buffer->Invalidate(GetRange());
5d7836c4
JS
4086
4087 UpdateAppearance(GetPosition(), true /* send update event */);
4088
4089 break;
4090 }
4091 case wxRICHTEXT_CHANGE_STYLE:
4092 {
4093 ApplyParagraphs(GetOldParagraphs());
1e967276 4094 m_buffer->Invalidate(GetRange());
5d7836c4
JS
4095
4096 UpdateAppearance(GetPosition());
4097
4098 break;
4099 }
4100 default:
4101 break;
4102 }
4103
4104 return true;
4105}
4106
4107/// Update the control appearance
4108void wxRichTextAction::UpdateAppearance(long caretPosition, bool sendUpdateEvent)
4109{
4110 if (m_ctrl)
4111 {
4112 m_ctrl->SetCaretPosition(caretPosition);
4113 if (!m_ctrl->IsFrozen())
4114 {
4115 m_ctrl->Layout();
4116 m_ctrl->PositionCaret();
4117 m_ctrl->Refresh();
4118
4119 if (sendUpdateEvent)
4120 m_ctrl->SendUpdateEvent();
4121 }
7fe8059f 4122 }
5d7836c4
JS
4123}
4124
4125/// Replace the buffer paragraphs with the new ones.
4126void wxRichTextAction::ApplyParagraphs(const wxRichTextFragment& fragment)
4127{
4128 wxRichTextObjectList::compatibility_iterator node = fragment.GetChildren().GetFirst();
4129 while (node)
4130 {
4131 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
4132 wxASSERT (para != NULL);
4133
4134 // We'll replace the existing paragraph by finding the paragraph at this position,
4135 // delete its node data, and setting a copy as the new node data.
4136 // TODO: make more efficient by simply swapping old and new paragraph objects.
4137
4138 wxRichTextParagraph* existingPara = m_buffer->GetParagraphAtPosition(para->GetRange().GetStart());
4139 if (existingPara)
4140 {
4141 wxRichTextObjectList::compatibility_iterator bufferParaNode = m_buffer->GetChildren().Find(existingPara);
4142 if (bufferParaNode)
4143 {
4144 wxRichTextParagraph* newPara = new wxRichTextParagraph(*para);
4145 newPara->SetParent(m_buffer);
4146
4147 bufferParaNode->SetData(newPara);
4148
4149 delete existingPara;
4150 }
4151 }
4152
4153 node = node->GetNext();
4154 }
4155}
4156
4157
4158/*!
4159 * wxRichTextRange
4160 * This stores beginning and end positions for a range of data.
4161 */
4162
4163/// Limit this range to be within 'range'
4164bool wxRichTextRange::LimitTo(const wxRichTextRange& range)
4165{
4166 if (m_start < range.m_start)
4167 m_start = range.m_start;
4168
4169 if (m_end > range.m_end)
4170 m_end = range.m_end;
4171
4172 return true;
4173}
4174
4175/*!
4176 * wxRichTextImage implementation
4177 * This object represents an image.
4178 */
4179
4180IMPLEMENT_DYNAMIC_CLASS(wxRichTextImage, wxRichTextObject)
4181
4182wxRichTextImage::wxRichTextImage(const wxImage& image, wxRichTextObject* parent):
4183 wxRichTextObject(parent)
4184{
4185 m_image = image;
4186}
4187
4188wxRichTextImage::wxRichTextImage(const wxRichTextImageBlock& imageBlock, wxRichTextObject* parent):
4189 wxRichTextObject(parent)
4190{
4191 m_imageBlock = imageBlock;
4192 m_imageBlock.Load(m_image);
4193}
4194
4195/// Load wxImage from the block
4196bool wxRichTextImage::LoadFromBlock()
4197{
4198 m_imageBlock.Load(m_image);
4199 return m_imageBlock.Ok();
4200}
4201
4202/// Make block from the wxImage
4203bool wxRichTextImage::MakeBlock()
4204{
4205 if (m_imageBlock.GetImageType() == wxBITMAP_TYPE_ANY || m_imageBlock.GetImageType() == -1)
4206 m_imageBlock.SetImageType(wxBITMAP_TYPE_PNG);
4207
4208 m_imageBlock.MakeImageBlock(m_image, m_imageBlock.GetImageType());
4209 return m_imageBlock.Ok();
4210}
4211
4212
4213/// Draw the item
4214bool wxRichTextImage::Draw(wxDC& dc, const wxRichTextRange& range, const wxRichTextRange& selectionRange, const wxRect& rect, int WXUNUSED(descent), int WXUNUSED(style))
4215{
4216 if (!m_image.Ok() && m_imageBlock.Ok())
4217 LoadFromBlock();
4218
4219 if (!m_image.Ok())
4220 return false;
4221
4222 if (m_image.Ok() && !m_bitmap.Ok())
4223 m_bitmap = wxBitmap(m_image);
4224
4225 int y = rect.y + (rect.height - m_image.GetHeight());
4226
4227 if (m_bitmap.Ok())
4228 dc.DrawBitmap(m_bitmap, rect.x, y, true);
4229
4230 if (selectionRange.Contains(range.GetStart()))
4231 {
4232 dc.SetBrush(*wxBLACK_BRUSH);
4233 dc.SetPen(*wxBLACK_PEN);
4234 dc.SetLogicalFunction(wxINVERT);
4235 dc.DrawRectangle(rect);
4236 dc.SetLogicalFunction(wxCOPY);
4237 }
4238
4239 return true;
4240}
4241
4242/// Lay the item out
38113684 4243bool wxRichTextImage::Layout(wxDC& WXUNUSED(dc), const wxRect& rect, int WXUNUSED(style))
5d7836c4
JS
4244{
4245 if (!m_image.Ok())
4246 LoadFromBlock();
4247
4248 if (m_image.Ok())
4249 {
4250 SetCachedSize(wxSize(m_image.GetWidth(), m_image.GetHeight()));
4251 SetPosition(rect.GetPosition());
4252 }
4253
4254 return true;
4255}
4256
4257/// Get/set the object size for the given range. Returns false if the range
4258/// is invalid for this object.
4259bool wxRichTextImage::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& WXUNUSED(descent), wxDC& WXUNUSED(dc), int WXUNUSED(flags)) const
4260{
4261 if (!range.IsWithin(GetRange()))
4262 return false;
4263
4264 if (!m_image.Ok())
4265 return false;
4266
4267 size.x = m_image.GetWidth();
4268 size.y = m_image.GetHeight();
4269
4270 return true;
4271}
4272
4273/// Copy
4274void wxRichTextImage::Copy(const wxRichTextImage& obj)
4275{
4276 m_image = obj.m_image;
4277 m_imageBlock = obj.m_imageBlock;
4278}
4279
4280/*!
4281 * Utilities
4282 *
4283 */
4284
4285/// Compare two attribute objects
4286bool wxTextAttrEq(const wxTextAttrEx& attr1, const wxTextAttrEx& attr2)
4287{
4288 return (
4289 attr1.GetTextColour() == attr2.GetTextColour() &&
4290 attr1.GetBackgroundColour() == attr2.GetBackgroundColour() &&
4291 attr1.GetFont() == attr2.GetFont() &&
4292 attr1.GetAlignment() == attr2.GetAlignment() &&
4293 attr1.GetLeftIndent() == attr2.GetLeftIndent() &&
4294 attr1.GetRightIndent() == attr2.GetRightIndent() &&
4295 attr1.GetLeftSubIndent() == attr2.GetLeftSubIndent() &&
4296 attr1.GetTabs().GetCount() == attr2.GetTabs().GetCount() && // heuristic
4297 attr1.GetLineSpacing() == attr2.GetLineSpacing() &&
4298 attr1.GetParagraphSpacingAfter() == attr2.GetParagraphSpacingAfter() &&
4299 attr1.GetParagraphSpacingBefore() == attr2.GetParagraphSpacingBefore() &&
4300 attr1.GetBulletStyle() == attr2.GetBulletStyle() &&
4301 attr1.GetBulletNumber() == attr2.GetBulletNumber() &&
4302 attr1.GetBulletSymbol() == attr2.GetBulletSymbol() &&
4303 attr1.GetCharacterStyleName() == attr2.GetCharacterStyleName() &&
4304 attr1.GetParagraphStyleName() == attr2.GetParagraphStyleName());
4305}
4306
4307bool wxTextAttrEq(const wxTextAttrEx& attr1, const wxRichTextAttr& attr2)
4308{
4309 return (
4310 attr1.GetTextColour() == attr2.GetTextColour() &&
4311 attr1.GetBackgroundColour() == attr2.GetBackgroundColour() &&
4312 attr1.GetFont().GetPointSize() == attr2.GetFontSize() &&
4313 attr1.GetFont().GetStyle() == attr2.GetFontStyle() &&
4314 attr1.GetFont().GetWeight() == attr2.GetFontWeight() &&
4315 attr1.GetFont().GetFaceName() == attr2.GetFontFaceName() &&
4316 attr1.GetFont().GetUnderlined() == attr2.GetFontUnderlined() &&
4317 attr1.GetAlignment() == attr2.GetAlignment() &&
4318 attr1.GetLeftIndent() == attr2.GetLeftIndent() &&
4319 attr1.GetRightIndent() == attr2.GetRightIndent() &&
4320 attr1.GetLeftSubIndent() == attr2.GetLeftSubIndent() &&
4321 attr1.GetTabs().GetCount() == attr2.GetTabs().GetCount() && // heuristic
4322 attr1.GetLineSpacing() == attr2.GetLineSpacing() &&
4323 attr1.GetParagraphSpacingAfter() == attr2.GetParagraphSpacingAfter() &&
4324 attr1.GetParagraphSpacingBefore() == attr2.GetParagraphSpacingBefore() &&
4325 attr1.GetBulletStyle() == attr2.GetBulletStyle() &&
4326 attr1.GetBulletNumber() == attr2.GetBulletNumber() &&
4327 attr1.GetBulletSymbol() == attr2.GetBulletSymbol() &&
4328 attr1.GetCharacterStyleName() == attr2.GetCharacterStyleName() &&
4329 attr1.GetParagraphStyleName() == attr2.GetParagraphStyleName());
4330}
4331
4332/// Compare two attribute objects, but take into account the flags
4333/// specifying attributes of interest.
4334bool wxTextAttrEqPartial(const wxTextAttrEx& attr1, const wxTextAttrEx& attr2, int flags)
4335{
4336 if ((flags & wxTEXT_ATTR_TEXT_COLOUR) && attr1.GetTextColour() != attr2.GetTextColour())
4337 return false;
4338
4339 if ((flags & wxTEXT_ATTR_BACKGROUND_COLOUR) && attr1.GetBackgroundColour() != attr2.GetBackgroundColour())
4340 return false;
4341
4342 if ((flags & wxTEXT_ATTR_FONT_FACE) && attr1.GetFont().Ok() && attr2.GetFont().Ok() &&
4343 attr1.GetFont().GetFaceName() != attr2.GetFont().GetFaceName())
4344 return false;
4345
4346 if ((flags & wxTEXT_ATTR_FONT_SIZE) && attr1.GetFont().Ok() && attr2.GetFont().Ok() &&
4347 attr1.GetFont().GetPointSize() != attr2.GetFont().GetPointSize())
4348 return false;
4349
4350 if ((flags & wxTEXT_ATTR_FONT_WEIGHT) && attr1.GetFont().Ok() && attr2.GetFont().Ok() &&
4351 attr1.GetFont().GetWeight() != attr2.GetFont().GetWeight())
4352 return false;
4353
4354 if ((flags & wxTEXT_ATTR_FONT_ITALIC) && attr1.GetFont().Ok() && attr2.GetFont().Ok() &&
4355 attr1.GetFont().GetStyle() != attr2.GetFont().GetStyle())
4356 return false;
4357
4358 if ((flags & wxTEXT_ATTR_FONT_UNDERLINE) && attr1.GetFont().Ok() && attr2.GetFont().Ok() &&
4359 attr1.GetFont().GetUnderlined() != attr2.GetFont().GetUnderlined())
4360 return false;
4361
4362 if ((flags & wxTEXT_ATTR_ALIGNMENT) && attr1.GetAlignment() != attr2.GetAlignment())
4363 return false;
4364
4365 if ((flags & wxTEXT_ATTR_LEFT_INDENT) &&
4366 ((attr1.GetLeftIndent() != attr2.GetLeftIndent()) || (attr1.GetLeftSubIndent() != attr2.GetLeftSubIndent())))
4367 return false;
4368
4369 if ((flags & wxTEXT_ATTR_RIGHT_INDENT) &&
4370 (attr1.GetRightIndent() != attr2.GetRightIndent()))
4371 return false;
4372
4d551ad5 4373 if ((flags & wxTEXT_ATTR_PARA_SPACING_AFTER) &&
5d7836c4
JS
4374 (attr1.GetParagraphSpacingAfter() != attr2.GetParagraphSpacingAfter()))
4375 return false;
4376
4d551ad5 4377 if ((flags & wxTEXT_ATTR_PARA_SPACING_BEFORE) &&
5d7836c4
JS
4378 (attr1.GetParagraphSpacingBefore() != attr2.GetParagraphSpacingBefore()))
4379 return false;
4380
4d551ad5 4381 if ((flags & wxTEXT_ATTR_LINE_SPACING) &&
5d7836c4
JS
4382 (attr1.GetLineSpacing() != attr2.GetLineSpacing()))
4383 return false;
4384
4d551ad5 4385 if ((flags & wxTEXT_ATTR_CHARACTER_STYLE_NAME) &&
5d7836c4
JS
4386 (attr1.GetCharacterStyleName() != attr2.GetCharacterStyleName()))
4387 return false;
4388
4d551ad5 4389 if ((flags & wxTEXT_ATTR_PARAGRAPH_STYLE_NAME) &&
5d7836c4
JS
4390 (attr1.GetParagraphStyleName() != attr2.GetParagraphStyleName()))
4391 return false;
4392
4393 if ((flags & wxTEXT_ATTR_BULLET_STYLE) &&
4394 (attr1.GetBulletStyle() != attr2.GetBulletStyle()))
4395 return false;
4396
4397 if ((flags & wxTEXT_ATTR_BULLET_NUMBER) &&
4398 (attr1.GetBulletNumber() != attr2.GetBulletNumber()))
4399 return false;
4400
4401 if ((flags & wxTEXT_ATTR_BULLET_SYMBOL) &&
4402 (attr1.GetBulletSymbol() != attr2.GetBulletSymbol()))
4403 return false;
4404
4405/* TODO
4406 if ((flags & wxTEXT_ATTR_TABS) &&
4407 return false;
4408*/
4409
4410 return true;
4411}
4412
4413bool wxTextAttrEqPartial(const wxTextAttrEx& attr1, const wxRichTextAttr& attr2, int flags)
4414{
4415 if ((flags & wxTEXT_ATTR_TEXT_COLOUR) && attr1.GetTextColour() != attr2.GetTextColour())
4416 return false;
4417
4418 if ((flags & wxTEXT_ATTR_BACKGROUND_COLOUR) && attr1.GetBackgroundColour() != attr2.GetBackgroundColour())
4419 return false;
4420
4421 if ((flags & (wxTEXT_ATTR_FONT)) && !attr1.GetFont().Ok())
4422 return false;
4423
4424 if ((flags & wxTEXT_ATTR_FONT_FACE) && attr1.GetFont().Ok() &&
4425 attr1.GetFont().GetFaceName() != attr2.GetFontFaceName())
4426 return false;
4427
4428 if ((flags & wxTEXT_ATTR_FONT_SIZE) && attr1.GetFont().Ok() &&
4429 attr1.GetFont().GetPointSize() != attr2.GetFontSize())
4430 return false;
4431
4432 if ((flags & wxTEXT_ATTR_FONT_WEIGHT) && attr1.GetFont().Ok() &&
4433 attr1.GetFont().GetWeight() != attr2.GetFontWeight())
4434 return false;
4435
4436 if ((flags & wxTEXT_ATTR_FONT_ITALIC) && attr1.GetFont().Ok() &&
4437 attr1.GetFont().GetStyle() != attr2.GetFontStyle())
4438 return false;
4439
4440 if ((flags & wxTEXT_ATTR_FONT_UNDERLINE) && attr1.GetFont().Ok() &&
4441 attr1.GetFont().GetUnderlined() != attr2.GetFontUnderlined())
4442 return false;
4443
4444 if ((flags & wxTEXT_ATTR_ALIGNMENT) && attr1.GetAlignment() != attr2.GetAlignment())
4445 return false;
4446
4447 if ((flags & wxTEXT_ATTR_LEFT_INDENT) &&
4448 ((attr1.GetLeftIndent() != attr2.GetLeftIndent()) || (attr1.GetLeftSubIndent() != attr2.GetLeftSubIndent())))
4449 return false;
4450
4451 if ((flags & wxTEXT_ATTR_RIGHT_INDENT) &&
4452 (attr1.GetRightIndent() != attr2.GetRightIndent()))
4453 return false;
4454
4d551ad5 4455 if ((flags & wxTEXT_ATTR_PARA_SPACING_AFTER) &&
5d7836c4
JS
4456 (attr1.GetParagraphSpacingAfter() != attr2.GetParagraphSpacingAfter()))
4457 return false;
4458
4d551ad5 4459 if ((flags & wxTEXT_ATTR_PARA_SPACING_BEFORE) &&
5d7836c4
JS
4460 (attr1.GetParagraphSpacingBefore() != attr2.GetParagraphSpacingBefore()))
4461 return false;
4462
4d551ad5 4463 if ((flags & wxTEXT_ATTR_LINE_SPACING) &&
5d7836c4
JS
4464 (attr1.GetLineSpacing() != attr2.GetLineSpacing()))
4465 return false;
4466
4d551ad5 4467 if ((flags & wxTEXT_ATTR_CHARACTER_STYLE_NAME) &&
5d7836c4
JS
4468 (attr1.GetCharacterStyleName() != attr2.GetCharacterStyleName()))
4469 return false;
4470
4d551ad5 4471 if ((flags & wxTEXT_ATTR_PARAGRAPH_STYLE_NAME) &&
5d7836c4
JS
4472 (attr1.GetParagraphStyleName() != attr2.GetParagraphStyleName()))
4473 return false;
4474
4475 if ((flags & wxTEXT_ATTR_BULLET_STYLE) &&
4476 (attr1.GetBulletStyle() != attr2.GetBulletStyle()))
4477 return false;
4478
4479 if ((flags & wxTEXT_ATTR_BULLET_NUMBER) &&
4480 (attr1.GetBulletNumber() != attr2.GetBulletNumber()))
4481 return false;
4482
4483 if ((flags & wxTEXT_ATTR_BULLET_SYMBOL) &&
4484 (attr1.GetBulletSymbol() != attr2.GetBulletSymbol()))
4485 return false;
4486
4487/* TODO
4488 if ((flags & wxTEXT_ATTR_TABS) &&
4489 return false;
4490*/
4491
4492 return true;
4493}
4494
4495
4496/// Apply one style to another
4497bool wxRichTextApplyStyle(wxTextAttrEx& destStyle, const wxTextAttrEx& style)
4498{
4499 // Whole font
4500 if (style.GetFont().Ok() && ((style.GetFlags() & (wxTEXT_ATTR_FONT)) == (wxTEXT_ATTR_FONT)))
4501 destStyle.SetFont(style.GetFont());
4502 else if (style.GetFont().Ok())
4503 {
4504 wxFont font = destStyle.GetFont();
4505
4506 if (style.GetFlags() & wxTEXT_ATTR_FONT_FACE)
4507 font.SetFaceName(style.GetFont().GetFaceName());
4508
4509 if (style.GetFlags() & wxTEXT_ATTR_FONT_SIZE)
4510 font.SetPointSize(style.GetFont().GetPointSize());
4511
4512 if (style.GetFlags() & wxTEXT_ATTR_FONT_ITALIC)
4513 font.SetStyle(style.GetFont().GetStyle());
4514
4515 if (style.GetFlags() & wxTEXT_ATTR_FONT_WEIGHT)
4516 font.SetWeight(style.GetFont().GetWeight());
4517
4518 if (style.GetFlags() & wxTEXT_ATTR_FONT_UNDERLINE)
4519 font.SetUnderlined(style.GetFont().GetUnderlined());
4520
4521 if (font != destStyle.GetFont())
4522 destStyle.SetFont(font);
4523 }
4524
4525 if ( style.GetTextColour().Ok() && style.HasTextColour())
4526 destStyle.SetTextColour(style.GetTextColour());
4527
4528 if ( style.GetBackgroundColour().Ok() && style.HasBackgroundColour())
4529 destStyle.SetBackgroundColour(style.GetBackgroundColour());
4530
4531 if (style.HasAlignment())
4532 destStyle.SetAlignment(style.GetAlignment());
4533
4534 if (style.HasTabs())
4535 destStyle.SetTabs(style.GetTabs());
4536
4537 if (style.HasLeftIndent())
4538 destStyle.SetLeftIndent(style.GetLeftIndent(), style.GetLeftSubIndent());
4539
4540 if (style.HasRightIndent())
4541 destStyle.SetRightIndent(style.GetRightIndent());
4542
4543 if (style.HasParagraphSpacingAfter())
4544 destStyle.SetParagraphSpacingAfter(style.GetParagraphSpacingAfter());
4545
4546 if (style.HasParagraphSpacingBefore())
4547 destStyle.SetParagraphSpacingBefore(style.GetParagraphSpacingBefore());
4548
4549 if (style.HasLineSpacing())
4550 destStyle.SetLineSpacing(style.GetLineSpacing());
4551
4552 if (style.HasCharacterStyleName())
4553 destStyle.SetCharacterStyleName(style.GetCharacterStyleName());
4554
4555 if (style.HasParagraphStyleName())
4556 destStyle.SetParagraphStyleName(style.GetParagraphStyleName());
4557
4558 if (style.HasBulletStyle())
4559 {
4560 destStyle.SetBulletStyle(style.GetBulletStyle());
4561 destStyle.SetBulletSymbol(style.GetBulletSymbol());
4562 }
4563
4564 if (style.HasBulletNumber())
4565 destStyle.SetBulletNumber(style.GetBulletNumber());
4566
4567 return true;
4568}
4569
4570bool wxRichTextApplyStyle(wxRichTextAttr& destStyle, const wxTextAttrEx& style)
4571{
4572 wxTextAttrEx destStyle2;
4573 destStyle.CopyTo(destStyle2);
4574 wxRichTextApplyStyle(destStyle2, style);
4575 destStyle = destStyle2;
4576 return true;
4577}
4578
4579bool wxRichTextApplyStyle(wxTextAttrEx& destStyle, const wxRichTextAttr& style)
4580{
7fe8059f 4581
5d7836c4
JS
4582 // Whole font. Avoiding setting individual attributes if possible, since
4583 // it recreates the font each time.
4584 if ((style.GetFlags() & (wxTEXT_ATTR_FONT)) == (wxTEXT_ATTR_FONT))
4585 {
4586 destStyle.SetFont(wxFont(style.GetFontSize(), destStyle.GetFont().Ok() ? destStyle.GetFont().GetFamily() : wxDEFAULT,
4587 style.GetFontStyle(), style.GetFontWeight(), style.GetFontUnderlined(), style.GetFontFaceName()));
4588 }
4589 else if (style.GetFlags() & (wxTEXT_ATTR_FONT))
4590 {
4591 wxFont font = destStyle.GetFont();
4592
4593 if (style.GetFlags() & wxTEXT_ATTR_FONT_FACE)
4594 font.SetFaceName(style.GetFontFaceName());
7fe8059f 4595
5d7836c4
JS
4596 if (style.GetFlags() & wxTEXT_ATTR_FONT_SIZE)
4597 font.SetPointSize(style.GetFontSize());
7fe8059f 4598
5d7836c4
JS
4599 if (style.GetFlags() & wxTEXT_ATTR_FONT_ITALIC)
4600 font.SetStyle(style.GetFontStyle());
7fe8059f 4601
5d7836c4
JS
4602 if (style.GetFlags() & wxTEXT_ATTR_FONT_WEIGHT)
4603 font.SetWeight(style.GetFontWeight());
7fe8059f 4604
5d7836c4
JS
4605 if (style.GetFlags() & wxTEXT_ATTR_FONT_UNDERLINE)
4606 font.SetUnderlined(style.GetFontUnderlined());
4607
4608 if (font != destStyle.GetFont())
4609 destStyle.SetFont(font);
4610 }
4611
4612 if ( style.GetTextColour().Ok() && style.HasTextColour())
4613 destStyle.SetTextColour(style.GetTextColour());
4614
4615 if ( style.GetBackgroundColour().Ok() && style.HasBackgroundColour())
4616 destStyle.SetBackgroundColour(style.GetBackgroundColour());
4617
4618 if (style.HasAlignment())
4619 destStyle.SetAlignment(style.GetAlignment());
4620
4621 if (style.HasTabs())
4622 destStyle.SetTabs(style.GetTabs());
4623
4624 if (style.HasLeftIndent())
4625 destStyle.SetLeftIndent(style.GetLeftIndent(), style.GetLeftSubIndent());
4626
4627 if (style.HasRightIndent())
4628 destStyle.SetRightIndent(style.GetRightIndent());
4629
4630 if (style.HasParagraphSpacingAfter())
4631 destStyle.SetParagraphSpacingAfter(style.GetParagraphSpacingAfter());
4632
4633 if (style.HasParagraphSpacingBefore())
4634 destStyle.SetParagraphSpacingBefore(style.GetParagraphSpacingBefore());
4635
4636 if (style.HasLineSpacing())
4637 destStyle.SetLineSpacing(style.GetLineSpacing());
4638
4639 if (style.HasCharacterStyleName())
4640 destStyle.SetCharacterStyleName(style.GetCharacterStyleName());
4641
4642 if (style.HasParagraphStyleName())
4643 destStyle.SetParagraphStyleName(style.GetParagraphStyleName());
4644
4645 if (style.HasBulletStyle())
4646 {
4647 destStyle.SetBulletStyle(style.GetBulletStyle());
4648 destStyle.SetBulletSymbol(style.GetBulletSymbol());
4649 }
4650
4651 if (style.HasBulletNumber())
4652 destStyle.SetBulletNumber(style.GetBulletNumber());
4653
4654 return true;
4655}
4656
4657
4658/*!
4659 * wxRichTextAttr stores attributes without a wxFont object, so is a much more
4660 * efficient way to query styles.
4661 */
4662
4663// ctors
4664wxRichTextAttr::wxRichTextAttr(const wxColour& colText,
4665 const wxColour& colBack,
4666 wxTextAttrAlignment alignment): m_textAlignment(alignment), m_colText(colText), m_colBack(colBack)
4667{
4668 Init();
4669
4670 if (m_colText.Ok()) m_flags |= wxTEXT_ATTR_TEXT_COLOUR;
4671 if (m_colBack.Ok()) m_flags |= wxTEXT_ATTR_BACKGROUND_COLOUR;
4672 if (alignment != wxTEXT_ALIGNMENT_DEFAULT)
4673 m_flags |= wxTEXT_ATTR_ALIGNMENT;
4674}
4675
4676wxRichTextAttr::wxRichTextAttr(const wxTextAttrEx& attr)
4677{
4678 Init();
4679
4680 (*this) = attr;
4681}
4682
4683// operations
4684void wxRichTextAttr::Init()
4685{
4686 m_textAlignment = wxTEXT_ALIGNMENT_DEFAULT;
4687 m_flags = 0;
4688 m_leftIndent = 0;
4689 m_leftSubIndent = 0;
4690 m_rightIndent = 0;
4691
4692 m_fontSize = 12;
4693 m_fontStyle = wxNORMAL;
4694 m_fontWeight = wxNORMAL;
4695 m_fontUnderlined = false;
4696
4697 m_paragraphSpacingAfter = 0;
4698 m_paragraphSpacingBefore = 0;
4699 m_lineSpacing = 0;
4700 m_bulletStyle = wxTEXT_ATTR_BULLET_STYLE_NONE;
4701 m_bulletNumber = 0;
4702 m_bulletSymbol = wxT('*');
4703}
4704
4705// operators
4706void wxRichTextAttr::operator= (const wxRichTextAttr& attr)
4707{
4708 m_colText = attr.m_colText;
4709 m_colBack = attr.m_colBack;
4710 m_textAlignment = attr.m_textAlignment;
4711 m_leftIndent = attr.m_leftIndent;
4712 m_leftSubIndent = attr.m_leftSubIndent;
4713 m_rightIndent = attr.m_rightIndent;
4714 m_tabs = attr.m_tabs;
4715 m_flags = attr.m_flags;
4716
4717 m_fontSize = attr.m_fontSize;
4718 m_fontStyle = attr.m_fontStyle;
4719 m_fontWeight = attr.m_fontWeight;
4720 m_fontUnderlined = attr.m_fontUnderlined;
4721 m_fontFaceName = attr.m_fontFaceName;
4722
4723 m_paragraphSpacingAfter = attr.m_paragraphSpacingAfter;
4724 m_paragraphSpacingBefore = attr.m_paragraphSpacingBefore;
4725 m_lineSpacing = attr.m_lineSpacing;
4726 m_characterStyleName = attr.m_characterStyleName;
4727 m_paragraphStyleName = attr.m_paragraphStyleName;
4728 m_bulletStyle = attr.m_bulletStyle;
4729 m_bulletNumber = attr.m_bulletNumber;
4730 m_bulletSymbol = attr.m_bulletSymbol;
4731}
4732
4733// operators
4734void wxRichTextAttr::operator= (const wxTextAttrEx& attr)
4735{
4736 m_colText = attr.GetTextColour();
4737 m_colBack = attr.GetBackgroundColour();
4738 m_textAlignment = attr.GetAlignment();
4739 m_leftIndent = attr.GetLeftIndent();
4740 m_leftSubIndent = attr.GetLeftSubIndent();
4741 m_rightIndent = attr.GetRightIndent();
4742 m_tabs = attr.GetTabs();
4743 m_flags = attr.GetFlags();
4744
4745 m_paragraphSpacingAfter = attr.GetParagraphSpacingAfter();
4746 m_paragraphSpacingBefore = attr.GetParagraphSpacingBefore();
4747 m_lineSpacing = attr.GetLineSpacing();
4748 m_characterStyleName = attr.GetCharacterStyleName();
4749 m_paragraphStyleName = attr.GetParagraphStyleName();
4750
4751 if (attr.GetFont().Ok())
4752 GetFontAttributes(attr.GetFont());
4753}
4754
4755// Making a wxTextAttrEx object.
4756wxRichTextAttr::operator wxTextAttrEx () const
4757{
4758 wxTextAttrEx attr;
4759 CopyTo(attr);
4760 return attr;
4761}
4762
4763// Copy to a wxTextAttr
4764void wxRichTextAttr::CopyTo(wxTextAttrEx& attr) const
4765{
4766 attr.SetTextColour(GetTextColour());
4767 attr.SetBackgroundColour(GetBackgroundColour());
4768 attr.SetAlignment(GetAlignment());
4769 attr.SetTabs(GetTabs());
4770 attr.SetLeftIndent(GetLeftIndent(), GetLeftSubIndent());
4771 attr.SetRightIndent(GetRightIndent());
4772 attr.SetFont(CreateFont());
4773 attr.SetFlags(GetFlags()); // Important: set after SetFont, since SetFont sets flags
4774
4775 attr.SetParagraphSpacingAfter(m_paragraphSpacingAfter);
4776 attr.SetParagraphSpacingBefore(m_paragraphSpacingBefore);
4777 attr.SetLineSpacing(m_lineSpacing);
4778 attr.SetBulletStyle(m_bulletStyle);
4779 attr.SetBulletNumber(m_bulletNumber);
4780 attr.SetBulletSymbol(m_bulletSymbol);
4781 attr.SetCharacterStyleName(m_characterStyleName);
4782 attr.SetParagraphStyleName(m_paragraphStyleName);
4783
4784}
4785
4786// Create font from font attributes.
4787wxFont wxRichTextAttr::CreateFont() const
4788{
4789 wxFont font(m_fontSize, wxDEFAULT, m_fontStyle, m_fontWeight, m_fontUnderlined, m_fontFaceName);
ff2baa25
JS
4790#ifdef __WXMAC__
4791 font.SetNoAntiAliasing(true);
4792#endif
5d7836c4
JS
4793 return font;
4794}
4795
4796// Get attributes from font.
4797bool wxRichTextAttr::GetFontAttributes(const wxFont& font)
4798{
4799 if (!font.Ok())
4800 return false;
4801
4802 m_fontSize = font.GetPointSize();
4803 m_fontStyle = font.GetStyle();
4804 m_fontWeight = font.GetWeight();
4805 m_fontUnderlined = font.GetUnderlined();
4806 m_fontFaceName = font.GetFaceName();
4807
4808 return true;
4809}
4810
4811/*!
4812 * wxTextAttrEx is an extended version of wxTextAttr with more paragraph attributes.
4813 */
4814
4815wxTextAttrEx::wxTextAttrEx(const wxTextAttrEx& attr): wxTextAttr(attr)
4816{
4817 m_paragraphSpacingAfter = attr.m_paragraphSpacingAfter;
4818 m_paragraphSpacingBefore = attr.m_paragraphSpacingBefore;
4819 m_lineSpacing = attr.m_lineSpacing;
4820 m_paragraphStyleName = attr.m_paragraphStyleName;
4821 m_characterStyleName = attr.m_characterStyleName;
4822 m_bulletStyle = attr.m_bulletStyle;
4823 m_bulletNumber = attr.m_bulletNumber;
4824 m_bulletSymbol = attr.m_bulletSymbol;
4825}
4826
4827// Initialise this object.
4828void wxTextAttrEx::Init()
4829{
4830 m_paragraphSpacingAfter = 0;
4831 m_paragraphSpacingBefore = 0;
4832 m_lineSpacing = 0;
4833 m_bulletStyle = wxTEXT_ATTR_BULLET_STYLE_NONE;
4834 m_bulletNumber = 0;
4835 m_bulletSymbol = 0;
4836 m_bulletSymbol = wxT('*');
4837}
4838
4839// Assignment from a wxTextAttrEx object
4840void wxTextAttrEx::operator= (const wxTextAttrEx& attr)
4841{
4842 wxTextAttr::operator= (attr);
4843
4844 m_paragraphSpacingAfter = attr.m_paragraphSpacingAfter;
4845 m_paragraphSpacingBefore = attr.m_paragraphSpacingBefore;
4846 m_lineSpacing = attr.m_lineSpacing;
4847 m_characterStyleName = attr.m_characterStyleName;
4848 m_paragraphStyleName = attr.m_paragraphStyleName;
4849 m_bulletStyle = attr.m_bulletStyle;
4850 m_bulletNumber = attr.m_bulletNumber;
4851 m_bulletSymbol = attr.m_bulletSymbol;
4852}
4853
4854// Assignment from a wxTextAttr object.
4855void wxTextAttrEx::operator= (const wxTextAttr& attr)
4856{
4857 wxTextAttr::operator= (attr);
4858}
4859
4860/*!
4861 * wxRichTextFileHandler
4862 * Base class for file handlers
4863 */
4864
4865IMPLEMENT_CLASS(wxRichTextFileHandler, wxObject)
4866
7fe8059f 4867#if wxUSE_STREAMS
5d7836c4
JS
4868bool wxRichTextFileHandler::LoadFile(wxRichTextBuffer *buffer, const wxString& filename)
4869{
4870 wxFFileInputStream stream(filename);
4871 if (stream.Ok())
4872 return LoadFile(buffer, stream);
4873 else
4874 return false;
4875}
4876
4877bool wxRichTextFileHandler::SaveFile(wxRichTextBuffer *buffer, const wxString& filename)
4878{
4879 wxFFileOutputStream stream(filename);
4880 if (stream.Ok())
4881 return SaveFile(buffer, stream);
4882 else
4883 return false;
4884}
7fe8059f 4885#endif // wxUSE_STREAMS
5d7836c4
JS
4886
4887/// Can we handle this filename (if using files)? By default, checks the extension.
4888bool wxRichTextFileHandler::CanHandle(const wxString& filename) const
4889{
4890 wxString path, file, ext;
4891 wxSplitPath(filename, & path, & file, & ext);
4892
4893 return (ext.Lower() == GetExtension());
4894}
4895
4896/*!
4897 * wxRichTextTextHandler
4898 * Plain text handler
4899 */
4900
4901IMPLEMENT_CLASS(wxRichTextPlainTextHandler, wxRichTextFileHandler)
4902
4903#if wxUSE_STREAMS
7fe8059f 4904bool wxRichTextPlainTextHandler::DoLoadFile(wxRichTextBuffer *buffer, wxInputStream& stream)
5d7836c4
JS
4905{
4906 if (!stream.IsOk())
4907 return false;
4908
4909 wxString str;
1e967276 4910 int lastCh = 0;
5d7836c4
JS
4911
4912 while (!stream.Eof())
4913 {
e191ee87 4914 int ch = stream.GetC();
5d7836c4 4915
169adfa9
JS
4916 if (!stream.Eof())
4917 {
4918 if (ch == 10 && lastCh != 13)
4919 str += wxT('\n');
39a1c2f2 4920
169adfa9
JS
4921 if (ch > 0 && ch != 10)
4922 str += wxChar(ch);
39a1c2f2 4923
169adfa9
JS
4924 lastCh = ch;
4925 }
5d7836c4
JS
4926 }
4927
4928 buffer->Clear();
4929 buffer->AddParagraphs(str);
4930 buffer->UpdateRanges();
4931
4932 return true;
4933
4934}
4935
7fe8059f 4936bool wxRichTextPlainTextHandler::DoSaveFile(wxRichTextBuffer *buffer, wxOutputStream& stream)
5d7836c4
JS
4937{
4938 if (!stream.IsOk())
4939 return false;
4940
4941 wxString text = buffer->GetText();
4942 wxCharBuffer buf = text.ToAscii();
4943
4944 stream.Write((const char*) buf, text.Length());
4945 return true;
4946}
7fe8059f 4947#endif // wxUSE_STREAMS
5d7836c4
JS
4948
4949/*
4950 * Stores information about an image, in binary in-memory form
4951 */
4952
4953wxRichTextImageBlock::wxRichTextImageBlock()
4954{
4955 Init();
4956}
4957
4958wxRichTextImageBlock::wxRichTextImageBlock(const wxRichTextImageBlock& block):wxObject()
4959{
4960 Init();
4961 Copy(block);
4962}
4963
4964wxRichTextImageBlock::~wxRichTextImageBlock()
4965{
4966 if (m_data)
4967 {
4968 delete[] m_data;
4969 m_data = NULL;
4970 }
4971}
4972
4973void wxRichTextImageBlock::Init()
4974{
4975 m_data = NULL;
4976 m_dataSize = 0;
4977 m_imageType = -1;
4978}
4979
4980void wxRichTextImageBlock::Clear()
4981{
4982 if (m_data)
4983 delete m_data;
4984 m_data = NULL;
4985 m_dataSize = 0;
4986 m_imageType = -1;
4987}
4988
4989
4990// Load the original image into a memory block.
4991// If the image is not a JPEG, we must convert it into a JPEG
4992// to conserve space.
4993// If it's not a JPEG we can make use of 'image', already scaled, so we don't have to
4994// load the image a 2nd time.
4995
4996bool wxRichTextImageBlock::MakeImageBlock(const wxString& filename, int imageType, wxImage& image, bool convertToJPEG)
4997{
4998 m_imageType = imageType;
4999
5000 wxString filenameToRead(filename);
7fe8059f 5001 bool removeFile = false;
5d7836c4
JS
5002
5003 if (imageType == -1)
7fe8059f 5004 return false; // Could not determine image type
5d7836c4
JS
5005
5006 if ((imageType != wxBITMAP_TYPE_JPEG) && convertToJPEG)
5007 {
5008 wxString tempFile;
5009 bool success = wxGetTempFileName(_("image"), tempFile) ;
5010
5011 wxASSERT(success);
5012
5013 wxUnusedVar(success);
5014
5015 image.SaveFile(tempFile, wxBITMAP_TYPE_JPEG);
5016 filenameToRead = tempFile;
7fe8059f 5017 removeFile = true;
5d7836c4
JS
5018
5019 m_imageType = wxBITMAP_TYPE_JPEG;
5020 }
5021 wxFile file;
5022 if (!file.Open(filenameToRead))
7fe8059f 5023 return false;
5d7836c4
JS
5024
5025 m_dataSize = (size_t) file.Length();
5026 file.Close();
5027
5028 if (m_data)
5029 delete[] m_data;
5030 m_data = ReadBlock(filenameToRead, m_dataSize);
5031
5032 if (removeFile)
5033 wxRemoveFile(filenameToRead);
5034
5035 return (m_data != NULL);
5036}
5037
5038// Make an image block from the wxImage in the given
5039// format.
5040bool wxRichTextImageBlock::MakeImageBlock(wxImage& image, int imageType, int quality)
5041{
5042 m_imageType = imageType;
5043 image.SetOption(wxT("quality"), quality);
5044
5045 if (imageType == -1)
7fe8059f 5046 return false; // Could not determine image type
5d7836c4
JS
5047
5048 wxString tempFile;
5049 bool success = wxGetTempFileName(_("image"), tempFile) ;
7fe8059f 5050
5d7836c4
JS
5051 wxASSERT(success);
5052 wxUnusedVar(success);
7fe8059f 5053
5d7836c4
JS
5054 if (!image.SaveFile(tempFile, m_imageType))
5055 {
5056 if (wxFileExists(tempFile))
5057 wxRemoveFile(tempFile);
7fe8059f 5058 return false;
5d7836c4
JS
5059 }
5060
5061 wxFile file;
5062 if (!file.Open(tempFile))
7fe8059f 5063 return false;
5d7836c4
JS
5064
5065 m_dataSize = (size_t) file.Length();
5066 file.Close();
5067
5068 if (m_data)
5069 delete[] m_data;
5070 m_data = ReadBlock(tempFile, m_dataSize);
5071
5072 wxRemoveFile(tempFile);
5073
5074 return (m_data != NULL);
5075}
5076
5077
5078// Write to a file
5079bool wxRichTextImageBlock::Write(const wxString& filename)
5080{
5081 return WriteBlock(filename, m_data, m_dataSize);
5082}
5083
5084void wxRichTextImageBlock::Copy(const wxRichTextImageBlock& block)
5085{
5086 m_imageType = block.m_imageType;
5087 if (m_data)
5088 {
5089 delete[] m_data;
5090 m_data = NULL;
5091 }
5092 m_dataSize = block.m_dataSize;
5093 if (m_dataSize == 0)
5094 return;
5095
5096 m_data = new unsigned char[m_dataSize];
5097 unsigned int i;
5098 for (i = 0; i < m_dataSize; i++)
5099 m_data[i] = block.m_data[i];
5100}
5101
5102//// Operators
5103void wxRichTextImageBlock::operator=(const wxRichTextImageBlock& block)
5104{
5105 Copy(block);
5106}
5107
5108// Load a wxImage from the block
5109bool wxRichTextImageBlock::Load(wxImage& image)
5110{
5111 if (!m_data)
7fe8059f 5112 return false;
5d7836c4
JS
5113
5114 // Read in the image.
5115#if 1
5116 wxMemoryInputStream mstream(m_data, m_dataSize);
5117 bool success = image.LoadFile(mstream, GetImageType());
5118#else
5119 wxString tempFile;
5120 bool success = wxGetTempFileName(_("image"), tempFile) ;
5121 wxASSERT(success);
5122
5123 if (!WriteBlock(tempFile, m_data, m_dataSize))
5124 {
7fe8059f 5125 return false;
5d7836c4
JS
5126 }
5127 success = image.LoadFile(tempFile, GetImageType());
5128 wxRemoveFile(tempFile);
5129#endif
5130
5131 return success;
5132}
5133
5134// Write data in hex to a stream
5135bool wxRichTextImageBlock::WriteHex(wxOutputStream& stream)
5136{
5137 wxString hex;
5138 int i;
5139 for (i = 0; i < (int) m_dataSize; i++)
5140 {
5141 hex = wxDecToHex(m_data[i]);
5142 wxCharBuffer buf = hex.ToAscii();
7fe8059f 5143
5d7836c4
JS
5144 stream.Write((const char*) buf, hex.Length());
5145 }
5146
5147 return true;
5148}
5149
5150// Read data in hex from a stream
5151bool wxRichTextImageBlock::ReadHex(wxInputStream& stream, int length, int imageType)
5152{
5153 int dataSize = length/2;
5154
5155 if (m_data)
5156 delete[] m_data;
5157
5158 wxString str(wxT(" "));
5159 m_data = new unsigned char[dataSize];
5160 int i;
5161 for (i = 0; i < dataSize; i ++)
5162 {
5163 str[0] = stream.GetC();
5164 str[1] = stream.GetC();
5165
7fe8059f 5166 m_data[i] = (unsigned char)wxHexToDec(str);
5d7836c4
JS
5167 }
5168
5169 m_dataSize = dataSize;
5170 m_imageType = imageType;
5171
5172 return true;
5173}
5174
5175
5176// Allocate and read from stream as a block of memory
5177unsigned char* wxRichTextImageBlock::ReadBlock(wxInputStream& stream, size_t size)
5178{
5179 unsigned char* block = new unsigned char[size];
5180 if (!block)
5181 return NULL;
5182
5183 stream.Read(block, size);
5184
5185 return block;
5186}
5187
5188unsigned char* wxRichTextImageBlock::ReadBlock(const wxString& filename, size_t size)
5189{
5190 wxFileInputStream stream(filename);
5191 if (!stream.Ok())
5192 return NULL;
5193
5194 return ReadBlock(stream, size);
5195}
5196
5197// Write memory block to stream
5198bool wxRichTextImageBlock::WriteBlock(wxOutputStream& stream, unsigned char* block, size_t size)
5199{
5200 stream.Write((void*) block, size);
5201 return stream.IsOk();
5202
5203}
5204
5205// Write memory block to file
5206bool wxRichTextImageBlock::WriteBlock(const wxString& filename, unsigned char* block, size_t size)
5207{
5208 wxFileOutputStream outStream(filename);
5209 if (!outStream.Ok())
7fe8059f 5210 return false;
5d7836c4
JS
5211
5212 return WriteBlock(outStream, block, size);
5213}
5214
5215#endif
5216 // wxUSE_RICHTEXT