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