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