Resets scroll position on load
[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 // ClearLines();
1953
1954 // Increase the size of the paragraph due to spacing
1955 int spaceBeforePara = ConvertTenthsMMToPixels(dc, GetAttributes().GetParagraphSpacingBefore());
1956 int spaceAfterPara = ConvertTenthsMMToPixels(dc, GetAttributes().GetParagraphSpacingAfter());
1957 int leftIndent = ConvertTenthsMMToPixels(dc, GetAttributes().GetLeftIndent());
1958 int leftSubIndent = ConvertTenthsMMToPixels(dc, GetAttributes().GetLeftSubIndent());
1959 int rightIndent = ConvertTenthsMMToPixels(dc, GetAttributes().GetRightIndent());
1960
1961 int lineSpacing = 0;
1962
1963 // Let's assume line spacing of 10 is normal, 15 is 1.5, 20 is 2, etc.
1964 if (GetAttributes().GetLineSpacing() > 10 && GetAttributes().GetFont().Ok())
1965 {
1966 dc.SetFont(GetAttributes().GetFont());
1967 lineSpacing = (ConvertTenthsMMToPixels(dc, dc.GetCharHeight()) * GetAttributes().GetLineSpacing())/10;
1968 }
1969
1970 // Available space for text on each line differs.
1971 int availableTextSpaceFirstLine = rect.GetWidth() - leftIndent - rightIndent;
1972
1973 // Bullets start the text at the same position as subsequent lines
1974 if (GetAttributes().GetBulletStyle() != wxTEXT_ATTR_BULLET_STYLE_NONE)
1975 availableTextSpaceFirstLine -= leftSubIndent;
1976
1977 int availableTextSpaceSubsequentLines = rect.GetWidth() - leftIndent - rightIndent - leftSubIndent;
1978
1979 // Start position for each line relative to the paragraph
1980 int startPositionFirstLine = leftIndent;
1981 int startPositionSubsequentLines = leftIndent + leftSubIndent;
1982
1983 // If we have a bullet in this paragraph, the start position for the first line's text
1984 // is actually leftIndent + leftSubIndent.
1985 if (GetAttributes().GetBulletStyle() != wxTEXT_ATTR_BULLET_STYLE_NONE)
1986 startPositionFirstLine = startPositionSubsequentLines;
1987
1988 //bool restrictWidth = wxRichTextHasStyle(style, wxRICHTEXT_FIXED_WIDTH);
1989 //bool restrictHeight = wxRichTextHasStyle(style, wxRICHTEXT_FIXED_HEIGHT);
1990
1991 long lastEndPos = GetRange().GetStart()-1;
1992 long lastCompletedEndPos = lastEndPos;
1993
1994 int currentWidth = 0;
1995 SetPosition(rect.GetPosition());
1996
1997 wxPoint currentPosition(0, spaceBeforePara); // We will calculate lines relative to paragraph
1998 int lineHeight = 0;
1999 int maxWidth = 0;
2000 int maxDescent = 0;
2001
2002 int lineCount = 0;
2003
2004 // Split up lines
2005
2006 // We may need to go back to a previous child, in which case create the new line,
2007 // find the child corresponding to the start position of the string, and
2008 // continue.
2009
2010 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2011 while (node)
2012 {
2013 wxRichTextObject* child = node->GetData();
2014
2015 // If this is e.g. a composite text box, it will need to be laid out itself.
2016 // But if just a text fragment or image, for example, this will
2017 // do nothing. NB: won't we need to set the position after layout?
2018 // since for example if position is dependent on vertical line size, we
2019 // can't tell the position until the size is determined. So possibly introduce
2020 // another layout phase.
2021
2022 child->Layout(dc, rect, style);
2023
2024 // Available width depends on whether we're on the first or subsequent lines
2025 int availableSpaceForText = (lineCount == 0 ? availableTextSpaceFirstLine : availableTextSpaceSubsequentLines);
2026
2027 currentPosition.x = (lineCount == 0 ? startPositionFirstLine : startPositionSubsequentLines);
2028
2029 // We may only be looking at part of a child, if we searched back for wrapping
2030 // and found a suitable point some way into the child. So get the size for the fragment
2031 // if necessary.
2032
2033 wxSize childSize;
2034 int childDescent = 0;
2035 if (lastEndPos == child->GetRange().GetStart() - 1)
2036 {
2037 childSize = child->GetCachedSize();
2038 childDescent = child->GetDescent();
2039 }
2040 else
2041 GetRangeSize(wxRichTextRange(lastEndPos+1, child->GetRange().GetEnd()), childSize, childDescent, dc, wxRICHTEXT_UNFORMATTED);
2042
2043 if (childSize.x + currentWidth > availableSpaceForText)
2044 {
2045 long wrapPosition = 0;
2046
2047 // Find a place to wrap. This may walk back to previous children,
2048 // for example if a word spans several objects.
2049 if (!FindWrapPosition(wxRichTextRange(lastCompletedEndPos+1, child->GetRange().GetEnd()), dc, availableSpaceForText, wrapPosition))
2050 {
2051 // If the function failed, just cut it off at the end of this child.
2052 wrapPosition = child->GetRange().GetEnd();
2053 }
2054
2055 // FindWrapPosition can still return a value that will put us in an endless wrapping loop
2056 if (wrapPosition <= lastCompletedEndPos)
2057 wrapPosition = wxMax(lastCompletedEndPos+1,child->GetRange().GetEnd());
2058
2059 // wxLogDebug(wxT("Split at %ld"), wrapPosition);
2060
2061 // Let's find the actual size of the current line now
2062 wxSize actualSize;
2063 wxRichTextRange actualRange(lastCompletedEndPos+1, wrapPosition);
2064 GetRangeSize(actualRange, actualSize, childDescent, dc, wxRICHTEXT_UNFORMATTED);
2065 currentWidth = actualSize.x;
2066 lineHeight = wxMax(lineHeight, actualSize.y);
2067 maxDescent = wxMax(childDescent, maxDescent);
2068
2069 // Add a new line
2070 wxRichTextLine* line = AllocateLine(lineCount);
2071
2072 // Set relative range so we won't have to change line ranges when paragraphs are moved
2073 line->SetRange(wxRichTextRange(actualRange.GetStart() - GetRange().GetStart(), actualRange.GetEnd() - GetRange().GetStart()));
2074 line->SetPosition(currentPosition);
2075 line->SetSize(wxSize(currentWidth, lineHeight));
2076 line->SetDescent(maxDescent);
2077
2078 // Now move down a line. TODO: add margins, spacing
2079 currentPosition.y += lineHeight;
2080 currentPosition.y += lineSpacing;
2081 currentWidth = 0;
2082 maxDescent = 0;
2083 maxWidth = wxMax(maxWidth, currentWidth);
2084
2085 lineCount ++;
2086
2087 // TODO: account for zero-length objects, such as fields
2088 wxASSERT(wrapPosition > lastCompletedEndPos);
2089
2090 lastEndPos = wrapPosition;
2091 lastCompletedEndPos = lastEndPos;
2092
2093 lineHeight = 0;
2094
2095 // May need to set the node back to a previous one, due to searching back in wrapping
2096 wxRichTextObject* childAfterWrapPosition = FindObjectAtPosition(wrapPosition+1);
2097 if (childAfterWrapPosition)
2098 node = m_children.Find(childAfterWrapPosition);
2099 else
2100 node = node->GetNext();
2101 }
2102 else
2103 {
2104 // We still fit, so don't add a line, and keep going
2105 currentWidth += childSize.x;
2106 lineHeight = wxMax(lineHeight, childSize.y);
2107 maxDescent = wxMax(childDescent, maxDescent);
2108
2109 maxWidth = wxMax(maxWidth, currentWidth);
2110 lastEndPos = child->GetRange().GetEnd();
2111
2112 node = node->GetNext();
2113 }
2114 }
2115
2116 // Add the last line - it's the current pos -> last para pos
2117 // Substract -1 because the last position is always the end-paragraph position.
2118 if (lastCompletedEndPos <= GetRange().GetEnd()-1)
2119 {
2120 currentPosition.x = (lineCount == 0 ? startPositionFirstLine : startPositionSubsequentLines);
2121
2122 wxRichTextLine* line = AllocateLine(lineCount);
2123
2124 wxRichTextRange actualRange(lastCompletedEndPos+1, GetRange().GetEnd()-1);
2125
2126 // Set relative range so we won't have to change line ranges when paragraphs are moved
2127 line->SetRange(wxRichTextRange(actualRange.GetStart() - GetRange().GetStart(), actualRange.GetEnd() - GetRange().GetStart()));
2128
2129 line->SetPosition(currentPosition);
2130
2131 if (lineHeight == 0)
2132 {
2133 if (GetAttributes().GetFont().Ok())
2134 dc.SetFont(GetAttributes().GetFont());
2135 lineHeight = dc.GetCharHeight();
2136 }
2137 if (maxDescent == 0)
2138 {
2139 int w, h;
2140 dc.GetTextExtent(wxT("X"), & w, &h, & maxDescent);
2141 }
2142
2143 line->SetSize(wxSize(currentWidth, lineHeight));
2144 line->SetDescent(maxDescent);
2145 currentPosition.y += lineHeight;
2146 currentPosition.y += lineSpacing;
2147 lineCount ++;
2148 }
2149
2150 // Remove remaining unused line objects, if any
2151 ClearUnusedLines(lineCount);
2152
2153 // Apply styles to wrapped lines
2154 ApplyParagraphStyle(rect);
2155
2156 SetCachedSize(wxSize(maxWidth, currentPosition.y + spaceBeforePara + spaceAfterPara));
2157
2158 m_dirty = false;
2159
2160 return true;
2161 }
2162
2163 /// Apply paragraph styles, such as centering, to wrapped lines
2164 void wxRichTextParagraph::ApplyParagraphStyle(const wxRect& rect)
2165 {
2166 if (!GetAttributes().HasAlignment())
2167 return;
2168
2169 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
2170 while (node)
2171 {
2172 wxRichTextLine* line = node->GetData();
2173
2174 wxPoint pos = line->GetPosition();
2175 wxSize size = line->GetSize();
2176
2177 // centering, right-justification
2178 if (GetAttributes().HasAlignment() && GetAttributes().GetAlignment() == wxTEXT_ALIGNMENT_CENTRE)
2179 {
2180 pos.x = (rect.GetWidth() - size.x)/2 + pos.x;
2181 line->SetPosition(pos);
2182 }
2183 else if (GetAttributes().HasAlignment() && GetAttributes().GetAlignment() == wxTEXT_ALIGNMENT_RIGHT)
2184 {
2185 pos.x = rect.GetRight() - size.x;
2186 line->SetPosition(pos);
2187 }
2188
2189 node = node->GetNext();
2190 }
2191 }
2192
2193 /// Insert text at the given position
2194 bool wxRichTextParagraph::InsertText(long pos, const wxString& text)
2195 {
2196 wxRichTextObject* childToUse = NULL;
2197 wxRichTextObjectList::compatibility_iterator nodeToUse = NULL;
2198
2199 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2200 while (node)
2201 {
2202 wxRichTextObject* child = node->GetData();
2203 if (child->GetRange().Contains(pos) && child->GetRange().GetLength() > 0)
2204 {
2205 childToUse = child;
2206 nodeToUse = node;
2207 break;
2208 }
2209
2210 node = node->GetNext();
2211 }
2212
2213 if (childToUse)
2214 {
2215 wxRichTextPlainText* textObject = wxDynamicCast(childToUse, wxRichTextPlainText);
2216 if (textObject)
2217 {
2218 int posInString = pos - textObject->GetRange().GetStart();
2219
2220 wxString newText = textObject->GetText().Mid(0, posInString) +
2221 text + textObject->GetText().Mid(posInString);
2222 textObject->SetText(newText);
2223
2224 int textLength = text.Length();
2225
2226 textObject->SetRange(wxRichTextRange(textObject->GetRange().GetStart(),
2227 textObject->GetRange().GetEnd() + textLength));
2228
2229 // Increment the end range of subsequent fragments in this paragraph.
2230 // We'll set the paragraph range itself at a higher level.
2231
2232 wxRichTextObjectList::compatibility_iterator node = nodeToUse->GetNext();
2233 while (node)
2234 {
2235 wxRichTextObject* child = node->GetData();
2236 child->SetRange(wxRichTextRange(textObject->GetRange().GetStart() + textLength,
2237 textObject->GetRange().GetEnd() + textLength));
2238
2239 node = node->GetNext();
2240 }
2241
2242 return true;
2243 }
2244 else
2245 {
2246 // TODO: if not a text object, insert at closest position, e.g. in front of it
2247 }
2248 }
2249 else
2250 {
2251 // Add at end.
2252 // Don't pass parent initially to suppress auto-setting of parent range.
2253 // We'll do that at a higher level.
2254 wxRichTextPlainText* textObject = new wxRichTextPlainText(text, this);
2255
2256 AppendChild(textObject);
2257 return true;
2258 }
2259
2260 return false;
2261 }
2262
2263 void wxRichTextParagraph::Copy(const wxRichTextParagraph& obj)
2264 {
2265 wxRichTextBox::Copy(obj);
2266 }
2267
2268 /// Clear the cached lines
2269 void wxRichTextParagraph::ClearLines()
2270 {
2271 WX_CLEAR_LIST(wxRichTextLineList, m_cachedLines);
2272 }
2273
2274 /// Get/set the object size for the given range. Returns false if the range
2275 /// is invalid for this object.
2276 bool wxRichTextParagraph::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, int flags) const
2277 {
2278 if (!range.IsWithin(GetRange()))
2279 return false;
2280
2281 if (flags & wxRICHTEXT_UNFORMATTED)
2282 {
2283 // Just use unformatted data, assume no line breaks
2284 // TODO: take into account line breaks
2285
2286 wxSize sz;
2287
2288 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2289 while (node)
2290 {
2291 wxRichTextObject* child = node->GetData();
2292 if (!child->GetRange().IsOutside(range))
2293 {
2294 wxSize childSize;
2295
2296 wxRichTextRange rangeToUse = range;
2297 rangeToUse.LimitTo(child->GetRange());
2298 int childDescent = 0;
2299
2300 if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, flags))
2301 {
2302 sz.y = wxMax(sz.y, childSize.y);
2303 sz.x += childSize.x;
2304 descent = wxMax(descent, childDescent);
2305 }
2306 }
2307
2308 node = node->GetNext();
2309 }
2310 size = sz;
2311 }
2312 else
2313 {
2314 // Use formatted data, with line breaks
2315 wxSize sz;
2316
2317 // We're going to loop through each line, and then for each line,
2318 // call GetRangeSize for the fragment that comprises that line.
2319 // Only we have to do that multiple times within the line, because
2320 // the line may be broken into pieces. For now ignore line break commands
2321 // (so we can assume that getting the unformatted size for a fragment
2322 // within a line is the actual size)
2323
2324 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
2325 while (node)
2326 {
2327 wxRichTextLine* line = node->GetData();
2328 wxRichTextRange lineRange = line->GetAbsoluteRange();
2329 if (!lineRange.IsOutside(range))
2330 {
2331 wxSize lineSize;
2332
2333 wxRichTextObjectList::compatibility_iterator node2 = m_children.GetFirst();
2334 while (node2)
2335 {
2336 wxRichTextObject* child = node2->GetData();
2337
2338 if (!child->GetRange().IsOutside(lineRange))
2339 {
2340 wxRichTextRange rangeToUse = lineRange;
2341 rangeToUse.LimitTo(child->GetRange());
2342
2343 wxSize childSize;
2344 int childDescent = 0;
2345 if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, flags))
2346 {
2347 lineSize.y = wxMax(lineSize.y, childSize.y);
2348 lineSize.x += childSize.x;
2349 }
2350 descent = wxMax(descent, childDescent);
2351 }
2352
2353 node2 = node2->GetNext();
2354 }
2355
2356 // Increase size by a line (TODO: paragraph spacing)
2357 sz.y += lineSize.y;
2358 sz.x = wxMax(sz.x, lineSize.x);
2359 }
2360 node = node->GetNext();
2361 }
2362 size = sz;
2363 }
2364 return true;
2365 }
2366
2367 /// Finds the absolute position and row height for the given character position
2368 bool wxRichTextParagraph::FindPosition(wxDC& dc, long index, wxPoint& pt, int* height, bool forceLineStart)
2369 {
2370 if (index == -1)
2371 {
2372 wxRichTextLine* line = ((wxRichTextParagraphLayoutBox*)GetParent())->GetLineAtPosition(0);
2373 if (line)
2374 *height = line->GetSize().y;
2375 else
2376 *height = dc.GetCharHeight();
2377
2378 // -1 means 'the start of the buffer'.
2379 pt = GetPosition();
2380 if (line)
2381 pt = pt + line->GetPosition();
2382
2383 *height = dc.GetCharHeight();
2384
2385 return true;
2386 }
2387
2388 // The final position in a paragraph is taken to mean the position
2389 // at the start of the next paragraph.
2390 if (index == GetRange().GetEnd())
2391 {
2392 wxRichTextParagraphLayoutBox* parent = wxDynamicCast(GetParent(), wxRichTextParagraphLayoutBox);
2393 wxASSERT( parent != NULL );
2394
2395 // Find the height at the next paragraph, if any
2396 wxRichTextLine* line = parent->GetLineAtPosition(index + 1);
2397 if (line)
2398 {
2399 *height = line->GetSize().y;
2400 pt = line->GetAbsolutePosition();
2401 }
2402 else
2403 {
2404 *height = dc.GetCharHeight();
2405 int indent = ConvertTenthsMMToPixels(dc, m_attributes.GetLeftIndent());
2406 pt = wxPoint(indent, GetCachedSize().y);
2407 }
2408
2409 return true;
2410 }
2411
2412 if (index < GetRange().GetStart() || index > GetRange().GetEnd())
2413 return false;
2414
2415 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
2416 while (node)
2417 {
2418 wxRichTextLine* line = node->GetData();
2419 wxRichTextRange lineRange = line->GetAbsoluteRange();
2420 if (index >= lineRange.GetStart() && index <= lineRange.GetEnd())
2421 {
2422 // If this is the last point in the line, and we're forcing the
2423 // returned value to be the start of the next line, do the required
2424 // thing.
2425 if (index == lineRange.GetEnd() && forceLineStart)
2426 {
2427 if (node->GetNext())
2428 {
2429 wxRichTextLine* nextLine = node->GetNext()->GetData();
2430 *height = nextLine->GetSize().y;
2431 pt = nextLine->GetAbsolutePosition();
2432 return true;
2433 }
2434 }
2435
2436 pt.y = line->GetPosition().y + GetPosition().y;
2437
2438 wxRichTextRange r(lineRange.GetStart(), index);
2439 wxSize rangeSize;
2440 int descent = 0;
2441
2442 // We find the size of the line up to this point,
2443 // then we can add this size to the line start position and
2444 // paragraph start position to find the actual position.
2445
2446 if (GetRangeSize(r, rangeSize, descent, dc, wxRICHTEXT_UNFORMATTED))
2447 {
2448 pt.x = line->GetPosition().x + GetPosition().x + rangeSize.x;
2449 *height = line->GetSize().y;
2450
2451 return true;
2452 }
2453
2454 }
2455
2456 node = node->GetNext();
2457 }
2458
2459 return false;
2460 }
2461
2462 /// Hit-testing: returns a flag indicating hit test details, plus
2463 /// information about position
2464 int wxRichTextParagraph::HitTest(wxDC& dc, const wxPoint& pt, long& textPosition)
2465 {
2466 wxPoint paraPos = GetPosition();
2467
2468 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
2469 while (node)
2470 {
2471 wxRichTextLine* line = node->GetData();
2472 wxPoint linePos = paraPos + line->GetPosition();
2473 wxSize lineSize = line->GetSize();
2474 wxRichTextRange lineRange = line->GetAbsoluteRange();
2475
2476 if (pt.y >= linePos.y && pt.y <= linePos.y + lineSize.y)
2477 {
2478 if (pt.x < linePos.x)
2479 {
2480 textPosition = lineRange.GetStart();
2481 return wxRICHTEXT_HITTEST_BEFORE;
2482 }
2483 else if (pt.x >= (linePos.x + lineSize.x))
2484 {
2485 textPosition = lineRange.GetEnd();
2486 return wxRICHTEXT_HITTEST_AFTER;
2487 }
2488 else
2489 {
2490 long i;
2491 int lastX = linePos.x;
2492 for (i = lineRange.GetStart(); i <= lineRange.GetEnd(); i++)
2493 {
2494 wxSize childSize;
2495 int descent = 0;
2496
2497 wxRichTextRange rangeToUse(lineRange.GetStart(), i);
2498
2499 GetRangeSize(rangeToUse, childSize, descent, dc, wxRICHTEXT_UNFORMATTED);
2500
2501 int nextX = childSize.x + linePos.x;
2502
2503 if (pt.x >= lastX && pt.x <= nextX)
2504 {
2505 textPosition = i;
2506
2507 // So now we know it's between i-1 and i.
2508 // Let's see if we can be more precise about
2509 // which side of the position it's on.
2510
2511 int midPoint = (nextX - lastX)/2 + lastX;
2512 if (pt.x >= midPoint)
2513 return wxRICHTEXT_HITTEST_AFTER;
2514 else
2515 return wxRICHTEXT_HITTEST_BEFORE;
2516 }
2517 else
2518 {
2519 lastX = nextX;
2520 }
2521 }
2522 }
2523 }
2524
2525 node = node->GetNext();
2526 }
2527
2528 return wxRICHTEXT_HITTEST_NONE;
2529 }
2530
2531 /// Split an object at this position if necessary, and return
2532 /// the previous object, or NULL if inserting at beginning.
2533 wxRichTextObject* wxRichTextParagraph::SplitAt(long pos, wxRichTextObject** previousObject)
2534 {
2535 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2536 while (node)
2537 {
2538 wxRichTextObject* child = node->GetData();
2539
2540 if (pos == child->GetRange().GetStart())
2541 {
2542 if (previousObject)
2543 *previousObject = child;
2544
2545 return child;
2546 }
2547
2548 if (child->GetRange().Contains(pos))
2549 {
2550 // This should create a new object, transferring part of
2551 // the content to the old object and the rest to the new object.
2552 wxRichTextObject* newObject = child->DoSplit(pos);
2553
2554 // If we couldn't split this object, just insert in front of it.
2555 if (!newObject)
2556 {
2557 // Maybe this is an empty string, try the next one
2558 // return child;
2559 }
2560 else
2561 {
2562 // Insert the new object after 'child'
2563 if (node->GetNext())
2564 m_children.Insert(node->GetNext(), newObject);
2565 else
2566 m_children.Append(newObject);
2567 newObject->SetParent(this);
2568
2569 if (previousObject)
2570 *previousObject = child;
2571
2572 return newObject;
2573 }
2574 }
2575
2576 node = node->GetNext();
2577 }
2578 if (previousObject)
2579 *previousObject = NULL;
2580 return NULL;
2581 }
2582
2583 /// Move content to a list from obj on
2584 void wxRichTextParagraph::MoveToList(wxRichTextObject* obj, wxList& list)
2585 {
2586 wxRichTextObjectList::compatibility_iterator node = m_children.Find(obj);
2587 while (node)
2588 {
2589 wxRichTextObject* child = node->GetData();
2590 list.Append(child);
2591
2592 wxRichTextObjectList::compatibility_iterator oldNode = node;
2593
2594 node = node->GetNext();
2595
2596 m_children.DeleteNode(oldNode);
2597 }
2598 }
2599
2600 /// Add content back from list
2601 void wxRichTextParagraph::MoveFromList(wxList& list)
2602 {
2603 for (wxNode* node = list.GetFirst(); node; node = node->GetNext())
2604 {
2605 AppendChild((wxRichTextObject*) node->GetData());
2606 }
2607 }
2608
2609 /// Calculate range
2610 void wxRichTextParagraph::CalculateRange(long start, long& end)
2611 {
2612 wxRichTextCompositeObject::CalculateRange(start, end);
2613
2614 // Add one for end of paragraph
2615 end ++;
2616
2617 m_range.SetRange(start, end);
2618 }
2619
2620 /// Find the object at the given position
2621 wxRichTextObject* wxRichTextParagraph::FindObjectAtPosition(long position)
2622 {
2623 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2624 while (node)
2625 {
2626 wxRichTextObject* obj = node->GetData();
2627 if (obj->GetRange().Contains(position))
2628 return obj;
2629
2630 node = node->GetNext();
2631 }
2632 return NULL;
2633 }
2634
2635 /// Get the plain text searching from the start or end of the range.
2636 /// The resulting string may be shorter than the range given.
2637 bool wxRichTextParagraph::GetContiguousPlainText(wxString& text, const wxRichTextRange& range, bool fromStart)
2638 {
2639 text = wxEmptyString;
2640
2641 if (fromStart)
2642 {
2643 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2644 while (node)
2645 {
2646 wxRichTextObject* obj = node->GetData();
2647 if (!obj->GetRange().IsOutside(range))
2648 {
2649 wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText);
2650 if (textObj)
2651 {
2652 text += textObj->GetTextForRange(range);
2653 }
2654 else
2655 return true;
2656 }
2657
2658 node = node->GetNext();
2659 }
2660 }
2661 else
2662 {
2663 wxRichTextObjectList::compatibility_iterator node = m_children.GetLast();
2664 while (node)
2665 {
2666 wxRichTextObject* obj = node->GetData();
2667 if (!obj->GetRange().IsOutside(range))
2668 {
2669 wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText);
2670 if (textObj)
2671 {
2672 text = textObj->GetTextForRange(range) + text;
2673 }
2674 else
2675 return true;
2676 }
2677
2678 node = node->GetPrevious();
2679 }
2680 }
2681
2682 return true;
2683 }
2684
2685 /// Find a suitable wrap position.
2686 bool wxRichTextParagraph::FindWrapPosition(const wxRichTextRange& range, wxDC& dc, int availableSpace, long& wrapPosition)
2687 {
2688 // Find the first position where the line exceeds the available space.
2689 wxSize sz;
2690 long i;
2691 long breakPosition = range.GetEnd();
2692 for (i = range.GetStart(); i <= range.GetEnd(); i++)
2693 {
2694 int descent = 0;
2695 GetRangeSize(wxRichTextRange(range.GetStart(), i), sz, descent, dc, wxRICHTEXT_UNFORMATTED);
2696
2697 if (sz.x > availableSpace)
2698 {
2699 breakPosition = i-1;
2700 break;
2701 }
2702 }
2703
2704 // Now we know the last position on the line.
2705 // Let's try to find a word break.
2706
2707 wxString plainText;
2708 if (GetContiguousPlainText(plainText, wxRichTextRange(range.GetStart(), breakPosition), false))
2709 {
2710 int spacePos = plainText.Find(wxT(' '), true);
2711 if (spacePos != wxNOT_FOUND)
2712 {
2713 int positionsFromEndOfString = plainText.Length() - spacePos - 1;
2714 breakPosition = breakPosition - positionsFromEndOfString;
2715 }
2716 }
2717
2718 wrapPosition = breakPosition;
2719
2720 return true;
2721 }
2722
2723 /// Get the bullet text for this paragraph.
2724 wxString wxRichTextParagraph::GetBulletText()
2725 {
2726 if (GetAttributes().GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_NONE ||
2727 (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_BITMAP))
2728 return wxEmptyString;
2729
2730 int number = GetAttributes().GetBulletNumber();
2731
2732 wxString text;
2733 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ARABIC)
2734 {
2735 text.Printf(wxT("%d"), number);
2736 }
2737 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_LETTERS_UPPER)
2738 {
2739 // TODO: Unicode, and also check if number > 26
2740 text.Printf(wxT("%c"), (wxChar) (number+64));
2741 }
2742 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_LETTERS_LOWER)
2743 {
2744 // TODO: Unicode, and also check if number > 26
2745 text.Printf(wxT("%c"), (wxChar) (number+96));
2746 }
2747 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ROMAN_UPPER)
2748 {
2749 // TODO: convert from number to roman numeral
2750 if (number == 1)
2751 text = wxT("I");
2752 else if (number == 2)
2753 text = wxT("II");
2754 else if (number == 3)
2755 text = wxT("III");
2756 else if (number == 4)
2757 text = wxT("IV");
2758 else
2759 text = wxT("TODO");
2760 }
2761 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ROMAN_LOWER)
2762 {
2763 // TODO: convert from number to roman numeral
2764 if (number == 1)
2765 text = wxT("i");
2766 else if (number == 2)
2767 text = wxT("ii");
2768 else if (number == 3)
2769 text = wxT("iii");
2770 else if (number == 4)
2771 text = wxT("iv");
2772 else
2773 text = wxT("TODO");
2774 }
2775 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_SYMBOL)
2776 {
2777 text = GetAttributes().GetBulletSymbol();
2778 }
2779
2780 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_PARENTHESES)
2781 {
2782 text = wxT("(") + text + wxT(")");
2783 }
2784 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_PERIOD)
2785 {
2786 text += wxT(".");
2787 }
2788
2789 return text;
2790 }
2791
2792 /// Allocate or reuse a line object
2793 wxRichTextLine* wxRichTextParagraph::AllocateLine(int pos)
2794 {
2795 if (pos < (int) m_cachedLines.GetCount())
2796 {
2797 wxRichTextLine* line = m_cachedLines.Item(pos)->GetData();
2798 line->Init(this);
2799 return line;
2800 }
2801 else
2802 {
2803 wxRichTextLine* line = new wxRichTextLine(this);
2804 m_cachedLines.Append(line);
2805 return line;
2806 }
2807 }
2808
2809 /// Clear remaining unused line objects, if any
2810 bool wxRichTextParagraph::ClearUnusedLines(int lineCount)
2811 {
2812 int cachedLineCount = m_cachedLines.GetCount();
2813 if ((int) cachedLineCount > lineCount)
2814 {
2815 for (int i = 0; i < (int) (cachedLineCount - lineCount); i ++)
2816 {
2817 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetLast();
2818 wxRichTextLine* line = node->GetData();
2819 m_cachedLines.Erase(node);
2820 delete line;
2821 }
2822 }
2823 return true;
2824 }
2825
2826
2827 /*!
2828 * wxRichTextLine
2829 * This object represents a line in a paragraph, and stores
2830 * offsets from the start of the paragraph representing the
2831 * start and end positions of the line.
2832 */
2833
2834 wxRichTextLine::wxRichTextLine(wxRichTextParagraph* parent)
2835 {
2836 Init(parent);
2837 }
2838
2839 /// Initialisation
2840 void wxRichTextLine::Init(wxRichTextParagraph* parent)
2841 {
2842 m_parent = parent;
2843 m_range.SetRange(-1, -1);
2844 m_pos = wxPoint(0, 0);
2845 m_size = wxSize(0, 0);
2846 m_descent = 0;
2847 }
2848
2849 /// Copy
2850 void wxRichTextLine::Copy(const wxRichTextLine& obj)
2851 {
2852 m_range = obj.m_range;
2853 }
2854
2855 /// Get the absolute object position
2856 wxPoint wxRichTextLine::GetAbsolutePosition() const
2857 {
2858 return m_parent->GetPosition() + m_pos;
2859 }
2860
2861 /// Get the absolute range
2862 wxRichTextRange wxRichTextLine::GetAbsoluteRange() const
2863 {
2864 wxRichTextRange range(m_range.GetStart() + m_parent->GetRange().GetStart(), 0);
2865 range.SetEnd(range.GetStart() + m_range.GetLength()-1);
2866 return range;
2867 }
2868
2869 /*!
2870 * wxRichTextPlainText
2871 * This object represents a single piece of text.
2872 */
2873
2874 IMPLEMENT_DYNAMIC_CLASS(wxRichTextPlainText, wxRichTextObject)
2875
2876 wxRichTextPlainText::wxRichTextPlainText(const wxString& text, wxRichTextObject* parent, wxTextAttrEx* style):
2877 wxRichTextObject(parent)
2878 {
2879 if (parent && !style)
2880 SetAttributes(parent->GetAttributes());
2881 if (style)
2882 SetAttributes(*style);
2883
2884 m_text = text;
2885 }
2886
2887 /// Draw the item
2888 bool wxRichTextPlainText::Draw(wxDC& dc, const wxRichTextRange& range, const wxRichTextRange& selectionRange, const wxRect& rect, int descent, int WXUNUSED(style))
2889 {
2890 int offset = GetRange().GetStart();
2891
2892 long len = range.GetLength();
2893 wxString stringChunk = m_text.Mid(range.GetStart() - offset, (size_t) len);
2894
2895 int charHeight = dc.GetCharHeight();
2896
2897 int x = rect.x;
2898 int y = rect.y + (rect.height - charHeight - (descent - m_descent));
2899
2900 // Test for the optimized situations where all is selected, or none
2901 // is selected.
2902
2903 if (GetAttributes().GetFont().Ok())
2904 dc.SetFont(GetAttributes().GetFont());
2905
2906 // (a) All selected.
2907 if (selectionRange.GetStart() <= range.GetStart() && selectionRange.GetEnd() >= range.GetEnd())
2908 {
2909 // Draw all selected
2910 dc.SetBrush(*wxBLACK_BRUSH);
2911 dc.SetPen(*wxBLACK_PEN);
2912 wxCoord w, h;
2913 dc.GetTextExtent(stringChunk, & w, & h);
2914 wxRect selRect(x, rect.y, w, rect.GetHeight());
2915 dc.DrawRectangle(selRect);
2916 dc.SetTextForeground(*wxWHITE);
2917 dc.SetBackgroundMode(wxTRANSPARENT);
2918 dc.DrawText(stringChunk, x, y);
2919 }
2920 // (b) None selected.
2921 else if (selectionRange.GetEnd() < range.GetStart() || selectionRange.GetStart() > range.GetEnd())
2922 {
2923 // Draw all unselected
2924 dc.SetTextForeground(GetAttributes().GetTextColour());
2925 dc.SetBackgroundMode(wxTRANSPARENT);
2926 dc.DrawText(stringChunk, x, y);
2927 }
2928 else
2929 {
2930 // (c) Part selected, part not
2931 // Let's draw unselected chunk, selected chunk, then unselected chunk.
2932
2933 dc.SetBackgroundMode(wxTRANSPARENT);
2934
2935 // 1. Initial unselected chunk, if any, up until start of selection.
2936 if (selectionRange.GetStart() > range.GetStart() && selectionRange.GetStart() <= range.GetEnd())
2937 {
2938 int r1 = range.GetStart();
2939 int s1 = selectionRange.GetStart()-1;
2940 int fragmentLen = s1 - r1 + 1;
2941 if (fragmentLen < 0)
2942 wxLogDebug(wxT("Mid(%d, %d"), (int)(r1 - offset), (int)fragmentLen);
2943 wxString stringFragment = m_text.Mid(r1 - offset, fragmentLen);
2944
2945 dc.SetTextForeground(GetAttributes().GetTextColour());
2946 dc.DrawText(stringFragment, x, y);
2947
2948 wxCoord w, h;
2949 dc.GetTextExtent(stringFragment, & w, & h);
2950 x += w;
2951 }
2952
2953 // 2. Selected chunk, if any.
2954 if (selectionRange.GetEnd() >= range.GetStart())
2955 {
2956 int s1 = wxMax(selectionRange.GetStart(), range.GetStart());
2957 int s2 = wxMin(selectionRange.GetEnd(), range.GetEnd());
2958
2959 int fragmentLen = s2 - s1 + 1;
2960 if (fragmentLen < 0)
2961 wxLogDebug(wxT("Mid(%d, %d"), (int)(s1 - offset), (int)fragmentLen);
2962 wxString stringFragment = m_text.Mid(s1 - offset, fragmentLen);
2963
2964 wxCoord w, h;
2965 dc.GetTextExtent(stringFragment, & w, & h);
2966 wxRect selRect(x, rect.y, w, rect.GetHeight());
2967
2968 dc.SetBrush(*wxBLACK_BRUSH);
2969 dc.SetPen(*wxBLACK_PEN);
2970 dc.DrawRectangle(selRect);
2971 dc.SetTextForeground(*wxWHITE);
2972 dc.DrawText(stringFragment, x, y);
2973
2974 x += w;
2975 }
2976
2977 // 3. Remaining unselected chunk, if any
2978 if (selectionRange.GetEnd() < range.GetEnd())
2979 {
2980 int s2 = wxMin(selectionRange.GetEnd()+1, range.GetEnd());
2981 int r2 = range.GetEnd();
2982
2983 int fragmentLen = r2 - s2 + 1;
2984 if (fragmentLen < 0)
2985 wxLogDebug(wxT("Mid(%d, %d"), (int)(s2 - offset), (int)fragmentLen);
2986 wxString stringFragment = m_text.Mid(s2 - offset, fragmentLen);
2987
2988 dc.SetTextForeground(GetAttributes().GetTextColour());
2989 dc.DrawText(stringFragment, x, y);
2990 }
2991 }
2992
2993 return true;
2994 }
2995
2996 /// Lay the item out
2997 bool wxRichTextPlainText::Layout(wxDC& dc, const wxRect& WXUNUSED(rect), int WXUNUSED(style))
2998 {
2999 if (GetAttributes().GetFont().Ok())
3000 dc.SetFont(GetAttributes().GetFont());
3001
3002 wxCoord w, h;
3003 dc.GetTextExtent(m_text, & w, & h, & m_descent);
3004 m_size = wxSize(w, dc.GetCharHeight());
3005
3006 return true;
3007 }
3008
3009 /// Copy
3010 void wxRichTextPlainText::Copy(const wxRichTextPlainText& obj)
3011 {
3012 wxRichTextObject::Copy(obj);
3013
3014 m_text = obj.m_text;
3015 }
3016
3017 /// Get/set the object size for the given range. Returns false if the range
3018 /// is invalid for this object.
3019 bool wxRichTextPlainText::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, int WXUNUSED(flags)) const
3020 {
3021 if (!range.IsWithin(GetRange()))
3022 return false;
3023
3024 // Always assume unformatted text, since at this level we have no knowledge
3025 // of line breaks - and we don't need it, since we'll calculate size within
3026 // formatted text by doing it in chunks according to the line ranges
3027
3028 if (GetAttributes().GetFont().Ok())
3029 dc.SetFont(GetAttributes().GetFont());
3030
3031 int startPos = range.GetStart() - GetRange().GetStart();
3032 long len = range.GetLength();
3033 wxString stringChunk = m_text.Mid(startPos, (size_t) len);
3034 wxCoord w, h;
3035 dc.GetTextExtent(stringChunk, & w, & h, & descent);
3036 size = wxSize(w, dc.GetCharHeight());
3037
3038 return true;
3039 }
3040
3041 /// Do a split, returning an object containing the second part, and setting
3042 /// the first part in 'this'.
3043 wxRichTextObject* wxRichTextPlainText::DoSplit(long pos)
3044 {
3045 int index = pos - GetRange().GetStart();
3046 if (index < 0 || index >= (int) m_text.Length())
3047 return NULL;
3048
3049 wxString firstPart = m_text.Mid(0, index);
3050 wxString secondPart = m_text.Mid(index);
3051
3052 m_text = firstPart;
3053
3054 wxRichTextPlainText* newObject = new wxRichTextPlainText(secondPart);
3055 newObject->SetAttributes(GetAttributes());
3056
3057 newObject->SetRange(wxRichTextRange(pos, GetRange().GetEnd()));
3058 GetRange().SetEnd(pos-1);
3059
3060 return newObject;
3061 }
3062
3063 /// Calculate range
3064 void wxRichTextPlainText::CalculateRange(long start, long& end)
3065 {
3066 end = start + m_text.Length() - 1;
3067 m_range.SetRange(start, end);
3068 }
3069
3070 /// Delete range
3071 bool wxRichTextPlainText::DeleteRange(const wxRichTextRange& range)
3072 {
3073 wxRichTextRange r = range;
3074
3075 r.LimitTo(GetRange());
3076
3077 if (r.GetStart() == GetRange().GetStart() && r.GetEnd() == GetRange().GetEnd())
3078 {
3079 m_text.Empty();
3080 return true;
3081 }
3082
3083 long startIndex = r.GetStart() - GetRange().GetStart();
3084 long len = r.GetLength();
3085
3086 m_text = m_text.Mid(0, startIndex) + m_text.Mid(startIndex+len);
3087 return true;
3088 }
3089
3090 /// Get text for the given range.
3091 wxString wxRichTextPlainText::GetTextForRange(const wxRichTextRange& range) const
3092 {
3093 wxRichTextRange r = range;
3094
3095 r.LimitTo(GetRange());
3096
3097 long startIndex = r.GetStart() - GetRange().GetStart();
3098 long len = r.GetLength();
3099
3100 return m_text.Mid(startIndex, len);
3101 }
3102
3103 /// Returns true if this object can merge itself with the given one.
3104 bool wxRichTextPlainText::CanMerge(wxRichTextObject* object) const
3105 {
3106 return object->GetClassInfo() == CLASSINFO(wxRichTextPlainText) &&
3107 (m_text.empty() || wxTextAttrEq(GetAttributes(), object->GetAttributes()));
3108 }
3109
3110 /// Returns true if this object merged itself with the given one.
3111 /// The calling code will then delete the given object.
3112 bool wxRichTextPlainText::Merge(wxRichTextObject* object)
3113 {
3114 wxRichTextPlainText* textObject = wxDynamicCast(object, wxRichTextPlainText);
3115 wxASSERT( textObject != NULL );
3116
3117 if (textObject)
3118 {
3119 m_text += textObject->GetText();
3120 return true;
3121 }
3122 else
3123 return false;
3124 }
3125
3126 /// Dump to output stream for debugging
3127 void wxRichTextPlainText::Dump(wxTextOutputStream& stream)
3128 {
3129 wxRichTextObject::Dump(stream);
3130 stream << m_text << wxT("\n");
3131 }
3132
3133 /*!
3134 * wxRichTextBuffer
3135 * This is a kind of box, used to represent the whole buffer
3136 */
3137
3138 IMPLEMENT_DYNAMIC_CLASS(wxRichTextBuffer, wxRichTextParagraphLayoutBox)
3139
3140 wxList wxRichTextBuffer::sm_handlers;
3141
3142 /// Initialisation
3143 void wxRichTextBuffer::Init()
3144 {
3145 m_commandProcessor = new wxCommandProcessor;
3146 m_styleSheet = NULL;
3147 m_modified = false;
3148 m_batchedCommandDepth = 0;
3149 m_batchedCommand = NULL;
3150 m_suppressUndo = 0;
3151 }
3152
3153 /// Initialisation
3154 wxRichTextBuffer::~wxRichTextBuffer()
3155 {
3156 delete m_commandProcessor;
3157 delete m_batchedCommand;
3158
3159 ClearStyleStack();
3160 }
3161
3162 void wxRichTextBuffer::Clear()
3163 {
3164 DeleteChildren();
3165 GetCommandProcessor()->ClearCommands();
3166 Modify(false);
3167 Invalidate(wxRICHTEXT_ALL);
3168 }
3169
3170 void wxRichTextBuffer::Reset()
3171 {
3172 DeleteChildren();
3173 AddParagraph(wxEmptyString);
3174 GetCommandProcessor()->ClearCommands();
3175 Modify(false);
3176 Invalidate(wxRICHTEXT_ALL);
3177 }
3178
3179 /// Submit command to insert the given text
3180 bool wxRichTextBuffer::InsertTextWithUndo(long pos, const wxString& text, wxRichTextCtrl* ctrl)
3181 {
3182 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, this, ctrl, false);
3183
3184 action->GetNewParagraphs().AddParagraphs(text);
3185 if (action->GetNewParagraphs().GetChildCount() == 1)
3186 action->GetNewParagraphs().SetPartialParagraph(true);
3187
3188 action->SetPosition(pos);
3189
3190 // Set the range we'll need to delete in Undo
3191 action->SetRange(wxRichTextRange(pos, pos + text.Length() - 1));
3192
3193 SubmitAction(action);
3194
3195 return true;
3196 }
3197
3198 /// Submit command to insert the given text
3199 bool wxRichTextBuffer::InsertNewlineWithUndo(long pos, wxRichTextCtrl* ctrl)
3200 {
3201 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, this, ctrl, false);
3202
3203 wxTextAttrEx attr(GetBasicStyle());
3204 wxRichTextApplyStyle(attr, GetDefaultStyle());
3205
3206 wxRichTextParagraph* newPara = new wxRichTextParagraph(wxEmptyString, this, & attr);
3207 action->GetNewParagraphs().AppendChild(newPara);
3208 action->GetNewParagraphs().UpdateRanges();
3209 action->GetNewParagraphs().SetPartialParagraph(false);
3210 action->SetPosition(pos);
3211
3212 // Set the range we'll need to delete in Undo
3213 action->SetRange(wxRichTextRange(pos, pos));
3214
3215 SubmitAction(action);
3216
3217 return true;
3218 }
3219
3220 /// Submit command to insert the given image
3221 bool wxRichTextBuffer::InsertImageWithUndo(long pos, const wxRichTextImageBlock& imageBlock, wxRichTextCtrl* ctrl)
3222 {
3223 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Image"), wxRICHTEXT_INSERT, this, ctrl, false);
3224
3225 wxTextAttrEx attr(GetBasicStyle());
3226 wxRichTextApplyStyle(attr, GetDefaultStyle());
3227
3228 wxRichTextParagraph* newPara = new wxRichTextParagraph(this, & attr);
3229 wxRichTextImage* imageObject = new wxRichTextImage(imageBlock, newPara);
3230 newPara->AppendChild(imageObject);
3231 action->GetNewParagraphs().AppendChild(newPara);
3232 action->GetNewParagraphs().UpdateRanges();
3233
3234 action->GetNewParagraphs().SetPartialParagraph(true);
3235
3236 action->SetPosition(pos);
3237
3238 // Set the range we'll need to delete in Undo
3239 action->SetRange(wxRichTextRange(pos, pos));
3240
3241 SubmitAction(action);
3242
3243 return true;
3244 }
3245
3246 /// Submit command to delete this range
3247 bool wxRichTextBuffer::DeleteRangeWithUndo(const wxRichTextRange& range, long initialCaretPosition, long WXUNUSED(newCaretPositon), wxRichTextCtrl* ctrl)
3248 {
3249 wxRichTextAction* action = new wxRichTextAction(NULL, _("Delete"), wxRICHTEXT_DELETE, this, ctrl);
3250
3251 action->SetPosition(initialCaretPosition);
3252
3253 // Set the range to delete
3254 action->SetRange(range);
3255
3256 // Copy the fragment that we'll need to restore in Undo
3257 CopyFragment(range, action->GetOldParagraphs());
3258
3259 // Special case: if there is only one (non-partial) paragraph,
3260 // we must save the *next* paragraph's style, because that
3261 // is the style we must apply when inserting the content back
3262 // when undoing the delete. (This is because we're merging the
3263 // paragraph with the previous paragraph and throwing away
3264 // the style, and we need to restore it.)
3265 if (!action->GetOldParagraphs().GetPartialParagraph() && action->GetOldParagraphs().GetChildCount() == 1)
3266 {
3267 wxRichTextParagraph* lastPara = GetParagraphAtPosition(range.GetStart());
3268 if (lastPara)
3269 {
3270 wxRichTextParagraph* nextPara = GetParagraphAtPosition(range.GetEnd()+1);
3271 if (nextPara)
3272 {
3273 wxRichTextParagraph* para = (wxRichTextParagraph*) action->GetOldParagraphs().GetChild(0);
3274 para->SetAttributes(nextPara->GetAttributes());
3275 }
3276 }
3277 }
3278
3279 SubmitAction(action);
3280
3281 return true;
3282 }
3283
3284 /// Collapse undo/redo commands
3285 bool wxRichTextBuffer::BeginBatchUndo(const wxString& cmdName)
3286 {
3287 if (m_batchedCommandDepth == 0)
3288 {
3289 wxASSERT(m_batchedCommand == NULL);
3290 if (m_batchedCommand)
3291 {
3292 GetCommandProcessor()->Submit(m_batchedCommand);
3293 }
3294 m_batchedCommand = new wxRichTextCommand(cmdName);
3295 }
3296
3297 m_batchedCommandDepth ++;
3298
3299 return true;
3300 }
3301
3302 /// Collapse undo/redo commands
3303 bool wxRichTextBuffer::EndBatchUndo()
3304 {
3305 m_batchedCommandDepth --;
3306
3307 wxASSERT(m_batchedCommandDepth >= 0);
3308 wxASSERT(m_batchedCommand != NULL);
3309
3310 if (m_batchedCommandDepth == 0)
3311 {
3312 GetCommandProcessor()->Submit(m_batchedCommand);
3313 m_batchedCommand = NULL;
3314 }
3315
3316 return true;
3317 }
3318
3319 /// Submit immediately, or delay according to whether collapsing is on
3320 bool wxRichTextBuffer::SubmitAction(wxRichTextAction* action)
3321 {
3322 if (BatchingUndo() && m_batchedCommand && !SuppressingUndo())
3323 m_batchedCommand->AddAction(action);
3324 else
3325 {
3326 wxRichTextCommand* cmd = new wxRichTextCommand(action->GetName());
3327 cmd->AddAction(action);
3328
3329 // Only store it if we're not suppressing undo.
3330 return GetCommandProcessor()->Submit(cmd, !SuppressingUndo());
3331 }
3332
3333 return true;
3334 }
3335
3336 /// Begin suppressing undo/redo commands.
3337 bool wxRichTextBuffer::BeginSuppressUndo()
3338 {
3339 m_suppressUndo ++;
3340
3341 return true;
3342 }
3343
3344 /// End suppressing undo/redo commands.
3345 bool wxRichTextBuffer::EndSuppressUndo()
3346 {
3347 m_suppressUndo --;
3348
3349 return true;
3350 }
3351
3352 /// Begin using a style
3353 bool wxRichTextBuffer::BeginStyle(const wxTextAttrEx& style)
3354 {
3355 wxTextAttrEx newStyle(GetDefaultStyle());
3356
3357 // Save the old default style
3358 m_attributeStack.Append((wxObject*) new wxTextAttrEx(GetDefaultStyle()));
3359
3360 wxRichTextApplyStyle(newStyle, style);
3361 newStyle.SetFlags(style.GetFlags()|newStyle.GetFlags());
3362
3363 SetDefaultStyle(newStyle);
3364
3365 // wxLogDebug("Default style size = %d", GetDefaultStyle().GetFont().GetPointSize());
3366
3367 return true;
3368 }
3369
3370 /// End the style
3371 bool wxRichTextBuffer::EndStyle()
3372 {
3373 if (m_attributeStack.GetFirst() == NULL)
3374 {
3375 wxLogDebug(_("Too many EndStyle calls!"));
3376 return false;
3377 }
3378
3379 wxNode* node = m_attributeStack.GetLast();
3380 wxTextAttrEx* attr = (wxTextAttrEx*)node->GetData();
3381 delete node;
3382
3383 SetDefaultStyle(*attr);
3384
3385 delete attr;
3386 return true;
3387 }
3388
3389 /// End all styles
3390 bool wxRichTextBuffer::EndAllStyles()
3391 {
3392 while (m_attributeStack.GetCount() != 0)
3393 EndStyle();
3394 return true;
3395 }
3396
3397 /// Clear the style stack
3398 void wxRichTextBuffer::ClearStyleStack()
3399 {
3400 for (wxNode* node = m_attributeStack.GetFirst(); node; node = node->GetNext())
3401 delete (wxTextAttrEx*) node->GetData();
3402 m_attributeStack.Clear();
3403 }
3404
3405 /// Begin using bold
3406 bool wxRichTextBuffer::BeginBold()
3407 {
3408 wxFont font(GetBasicStyle().GetFont());
3409 font.SetWeight(wxBOLD);
3410
3411 wxTextAttrEx attr;
3412 attr.SetFont(font,wxTEXT_ATTR_FONT_WEIGHT);
3413
3414 return BeginStyle(attr);
3415 }
3416
3417 /// Begin using italic
3418 bool wxRichTextBuffer::BeginItalic()
3419 {
3420 wxFont font(GetBasicStyle().GetFont());
3421 font.SetStyle(wxITALIC);
3422
3423 wxTextAttrEx attr;
3424 attr.SetFont(font, wxTEXT_ATTR_FONT_ITALIC);
3425
3426 return BeginStyle(attr);
3427 }
3428
3429 /// Begin using underline
3430 bool wxRichTextBuffer::BeginUnderline()
3431 {
3432 wxFont font(GetBasicStyle().GetFont());
3433 font.SetUnderlined(true);
3434
3435 wxTextAttrEx attr;
3436 attr.SetFont(font, wxTEXT_ATTR_FONT_UNDERLINE);
3437
3438 return BeginStyle(attr);
3439 }
3440
3441 /// Begin using point size
3442 bool wxRichTextBuffer::BeginFontSize(int pointSize)
3443 {
3444 wxFont font(GetBasicStyle().GetFont());
3445 font.SetPointSize(pointSize);
3446
3447 wxTextAttrEx attr;
3448 attr.SetFont(font, wxTEXT_ATTR_FONT_SIZE);
3449
3450 return BeginStyle(attr);
3451 }
3452
3453 /// Begin using this font
3454 bool wxRichTextBuffer::BeginFont(const wxFont& font)
3455 {
3456 wxTextAttrEx attr;
3457 attr.SetFlags(wxTEXT_ATTR_FONT);
3458 attr.SetFont(font);
3459
3460 return BeginStyle(attr);
3461 }
3462
3463 /// Begin using this colour
3464 bool wxRichTextBuffer::BeginTextColour(const wxColour& colour)
3465 {
3466 wxTextAttrEx attr;
3467 attr.SetFlags(wxTEXT_ATTR_TEXT_COLOUR);
3468 attr.SetTextColour(colour);
3469
3470 return BeginStyle(attr);
3471 }
3472
3473 /// Begin using alignment
3474 bool wxRichTextBuffer::BeginAlignment(wxTextAttrAlignment alignment)
3475 {
3476 wxTextAttrEx attr;
3477 attr.SetFlags(wxTEXT_ATTR_ALIGNMENT);
3478 attr.SetAlignment(alignment);
3479
3480 return BeginStyle(attr);
3481 }
3482
3483 /// Begin left indent
3484 bool wxRichTextBuffer::BeginLeftIndent(int leftIndent, int leftSubIndent)
3485 {
3486 wxTextAttrEx attr;
3487 attr.SetFlags(wxTEXT_ATTR_LEFT_INDENT);
3488 attr.SetLeftIndent(leftIndent, leftSubIndent);
3489
3490 return BeginStyle(attr);
3491 }
3492
3493 /// Begin right indent
3494 bool wxRichTextBuffer::BeginRightIndent(int rightIndent)
3495 {
3496 wxTextAttrEx attr;
3497 attr.SetFlags(wxTEXT_ATTR_RIGHT_INDENT);
3498 attr.SetRightIndent(rightIndent);
3499
3500 return BeginStyle(attr);
3501 }
3502
3503 /// Begin paragraph spacing
3504 bool wxRichTextBuffer::BeginParagraphSpacing(int before, int after)
3505 {
3506 long flags = 0;
3507 if (before != 0)
3508 flags |= wxTEXT_ATTR_PARA_SPACING_BEFORE;
3509 if (after != 0)
3510 flags |= wxTEXT_ATTR_PARA_SPACING_AFTER;
3511
3512 wxTextAttrEx attr;
3513 attr.SetFlags(flags);
3514 attr.SetParagraphSpacingBefore(before);
3515 attr.SetParagraphSpacingAfter(after);
3516
3517 return BeginStyle(attr);
3518 }
3519
3520 /// Begin line spacing
3521 bool wxRichTextBuffer::BeginLineSpacing(int lineSpacing)
3522 {
3523 wxTextAttrEx attr;
3524 attr.SetFlags(wxTEXT_ATTR_LINE_SPACING);
3525 attr.SetLineSpacing(lineSpacing);
3526
3527 return BeginStyle(attr);
3528 }
3529
3530 /// Begin numbered bullet
3531 bool wxRichTextBuffer::BeginNumberedBullet(int bulletNumber, int leftIndent, int leftSubIndent, int bulletStyle)
3532 {
3533 wxTextAttrEx attr;
3534 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_BULLET_NUMBER|wxTEXT_ATTR_LEFT_INDENT);
3535 attr.SetBulletStyle(bulletStyle);
3536 attr.SetBulletNumber(bulletNumber);
3537 attr.SetLeftIndent(leftIndent, leftSubIndent);
3538
3539 return BeginStyle(attr);
3540 }
3541
3542 /// Begin symbol bullet
3543 bool wxRichTextBuffer::BeginSymbolBullet(wxChar symbol, int leftIndent, int leftSubIndent, int bulletStyle)
3544 {
3545 wxTextAttrEx attr;
3546 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_BULLET_SYMBOL|wxTEXT_ATTR_LEFT_INDENT);
3547 attr.SetBulletStyle(bulletStyle);
3548 attr.SetLeftIndent(leftIndent, leftSubIndent);
3549 attr.SetBulletSymbol(symbol);
3550
3551 return BeginStyle(attr);
3552 }
3553
3554 /// Begin named character style
3555 bool wxRichTextBuffer::BeginCharacterStyle(const wxString& characterStyle)
3556 {
3557 if (GetStyleSheet())
3558 {
3559 wxRichTextCharacterStyleDefinition* def = GetStyleSheet()->FindCharacterStyle(characterStyle);
3560 if (def)
3561 {
3562 wxTextAttrEx attr;
3563 def->GetStyle().CopyTo(attr);
3564 return BeginStyle(attr);
3565 }
3566 }
3567 return false;
3568 }
3569
3570 /// Begin named paragraph style
3571 bool wxRichTextBuffer::BeginParagraphStyle(const wxString& paragraphStyle)
3572 {
3573 if (GetStyleSheet())
3574 {
3575 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(paragraphStyle);
3576 if (def)
3577 {
3578 wxTextAttrEx attr;
3579 def->GetStyle().CopyTo(attr);
3580 return BeginStyle(attr);
3581 }
3582 }
3583 return false;
3584 }
3585
3586 /// Adds a handler to the end
3587 void wxRichTextBuffer::AddHandler(wxRichTextFileHandler *handler)
3588 {
3589 sm_handlers.Append(handler);
3590 }
3591
3592 /// Inserts a handler at the front
3593 void wxRichTextBuffer::InsertHandler(wxRichTextFileHandler *handler)
3594 {
3595 sm_handlers.Insert( handler );
3596 }
3597
3598 /// Removes a handler
3599 bool wxRichTextBuffer::RemoveHandler(const wxString& name)
3600 {
3601 wxRichTextFileHandler *handler = FindHandler(name);
3602 if (handler)
3603 {
3604 sm_handlers.DeleteObject(handler);
3605 delete handler;
3606 return true;
3607 }
3608 else
3609 return false;
3610 }
3611
3612 /// Finds a handler by filename or, if supplied, type
3613 wxRichTextFileHandler *wxRichTextBuffer::FindHandlerFilenameOrType(const wxString& filename, int imageType)
3614 {
3615 if (imageType != wxRICHTEXT_TYPE_ANY)
3616 return FindHandler(imageType);
3617 else
3618 {
3619 wxString path, file, ext;
3620 wxSplitPath(filename, & path, & file, & ext);
3621 return FindHandler(ext, imageType);
3622 }
3623 }
3624
3625
3626 /// Finds a handler by name
3627 wxRichTextFileHandler* wxRichTextBuffer::FindHandler(const wxString& name)
3628 {
3629 wxList::compatibility_iterator node = sm_handlers.GetFirst();
3630 while (node)
3631 {
3632 wxRichTextFileHandler *handler = (wxRichTextFileHandler*)node->GetData();
3633 if (handler->GetName().Lower() == name.Lower()) return handler;
3634
3635 node = node->GetNext();
3636 }
3637 return NULL;
3638 }
3639
3640 /// Finds a handler by extension and type
3641 wxRichTextFileHandler* wxRichTextBuffer::FindHandler(const wxString& extension, int type)
3642 {
3643 wxList::compatibility_iterator node = sm_handlers.GetFirst();
3644 while (node)
3645 {
3646 wxRichTextFileHandler *handler = (wxRichTextFileHandler*)node->GetData();
3647 if ( handler->GetExtension().Lower() == extension.Lower() &&
3648 (type == wxRICHTEXT_TYPE_ANY || handler->GetType() == type) )
3649 return handler;
3650 node = node->GetNext();
3651 }
3652 return 0;
3653 }
3654
3655 /// Finds a handler by type
3656 wxRichTextFileHandler* wxRichTextBuffer::FindHandler(int type)
3657 {
3658 wxList::compatibility_iterator node = sm_handlers.GetFirst();
3659 while (node)
3660 {
3661 wxRichTextFileHandler *handler = (wxRichTextFileHandler *)node->GetData();
3662 if (handler->GetType() == type) return handler;
3663 node = node->GetNext();
3664 }
3665 return NULL;
3666 }
3667
3668 void wxRichTextBuffer::InitStandardHandlers()
3669 {
3670 if (!FindHandler(wxRICHTEXT_TYPE_TEXT))
3671 AddHandler(new wxRichTextPlainTextHandler);
3672 }
3673
3674 void wxRichTextBuffer::CleanUpHandlers()
3675 {
3676 wxList::compatibility_iterator node = sm_handlers.GetFirst();
3677 while (node)
3678 {
3679 wxRichTextFileHandler* handler = (wxRichTextFileHandler*)node->GetData();
3680 wxList::compatibility_iterator next = node->GetNext();
3681 delete handler;
3682 node = next;
3683 }
3684
3685 sm_handlers.Clear();
3686 }
3687
3688 wxString wxRichTextBuffer::GetExtWildcard(bool combine, bool save, wxArrayInt* types)
3689 {
3690 if (types)
3691 types->Clear();
3692
3693 wxString wildcard;
3694
3695 wxList::compatibility_iterator node = GetHandlers().GetFirst();
3696 int count = 0;
3697 while (node)
3698 {
3699 wxRichTextFileHandler* handler = (wxRichTextFileHandler*) node->GetData();
3700 if (handler->IsVisible() && ((save && handler->CanSave()) || !save && handler->CanLoad()))
3701 {
3702 if (combine)
3703 {
3704 if (count > 0)
3705 wildcard += wxT(";");
3706 wildcard += wxT("*.") + handler->GetExtension();
3707 }
3708 else
3709 {
3710 if (count > 0)
3711 wildcard += wxT("|");
3712 wildcard += handler->GetName();
3713 wildcard += wxT(" ");
3714 wildcard += _("files");
3715 wildcard += wxT(" (*.");
3716 wildcard += handler->GetExtension();
3717 wildcard += wxT(")|*.");
3718 wildcard += handler->GetExtension();
3719 if (types)
3720 types->Add(handler->GetType());
3721 }
3722 count ++;
3723 }
3724
3725 node = node->GetNext();
3726 }
3727
3728 if (combine)
3729 wildcard = wxT("(") + wildcard + wxT(")|") + wildcard;
3730 return wildcard;
3731 }
3732
3733 /// Load a file
3734 bool wxRichTextBuffer::LoadFile(const wxString& filename, int type)
3735 {
3736 wxRichTextFileHandler* handler = FindHandlerFilenameOrType(filename, type);
3737 if (handler)
3738 {
3739 SetDefaultStyle(wxTextAttrEx());
3740
3741 bool success = handler->LoadFile(this, filename);
3742 Invalidate(wxRICHTEXT_ALL);
3743 return success;
3744 }
3745 else
3746 return false;
3747 }
3748
3749 /// Save a file
3750 bool wxRichTextBuffer::SaveFile(const wxString& filename, int type)
3751 {
3752 wxRichTextFileHandler* handler = FindHandlerFilenameOrType(filename, type);
3753 if (handler)
3754 return handler->SaveFile(this, filename);
3755 else
3756 return false;
3757 }
3758
3759 /// Load from a stream
3760 bool wxRichTextBuffer::LoadFile(wxInputStream& stream, int type)
3761 {
3762 wxRichTextFileHandler* handler = FindHandler(type);
3763 if (handler)
3764 {
3765 SetDefaultStyle(wxTextAttrEx());
3766 bool success = handler->LoadFile(this, stream);
3767 Invalidate(wxRICHTEXT_ALL);
3768 return success;
3769 }
3770 else
3771 return false;
3772 }
3773
3774 /// Save to a stream
3775 bool wxRichTextBuffer::SaveFile(wxOutputStream& stream, int type)
3776 {
3777 wxRichTextFileHandler* handler = FindHandler(type);
3778 if (handler)
3779 return handler->SaveFile(this, stream);
3780 else
3781 return false;
3782 }
3783
3784 /// Copy the range to the clipboard
3785 bool wxRichTextBuffer::CopyToClipboard(const wxRichTextRange& range)
3786 {
3787 bool success = false;
3788 wxString text = GetTextForRange(range);
3789 if (wxTheClipboard->Open())
3790 {
3791 success = wxTheClipboard->SetData(new wxTextDataObject(text));
3792 wxTheClipboard->Close();
3793 }
3794 return success;
3795 }
3796
3797 /// Paste the clipboard content to the buffer
3798 bool wxRichTextBuffer::PasteFromClipboard(long position)
3799 {
3800 bool success = false;
3801 if (CanPasteFromClipboard())
3802 {
3803 if (wxTheClipboard->Open())
3804 {
3805 if (wxTheClipboard->IsSupported(wxDF_TEXT))
3806 {
3807 wxTextDataObject data;
3808 wxTheClipboard->GetData(data);
3809 wxString text(data.GetText());
3810
3811 InsertTextWithUndo(position+1, text, GetRichTextCtrl());
3812
3813 success = true;
3814 }
3815 else if (wxTheClipboard->IsSupported(wxDF_BITMAP))
3816 {
3817 wxBitmapDataObject data;
3818 wxTheClipboard->GetData(data);
3819 wxBitmap bitmap(data.GetBitmap());
3820 wxImage image(bitmap.ConvertToImage());
3821
3822 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Image"), wxRICHTEXT_INSERT, this, GetRichTextCtrl(), false);
3823
3824 action->GetNewParagraphs().AddImage(image);
3825
3826 if (action->GetNewParagraphs().GetChildCount() == 1)
3827 action->GetNewParagraphs().SetPartialParagraph(true);
3828
3829 action->SetPosition(position);
3830
3831 // Set the range we'll need to delete in Undo
3832 action->SetRange(wxRichTextRange(position, position));
3833
3834 SubmitAction(action);
3835
3836 success = true;
3837 }
3838 wxTheClipboard->Close();
3839 }
3840 }
3841 return success;
3842 }
3843
3844 /// Can we paste from the clipboard?
3845 bool wxRichTextBuffer::CanPasteFromClipboard() const
3846 {
3847 bool canPaste = false;
3848 if (wxTheClipboard->Open())
3849 {
3850 if (wxTheClipboard->IsSupported(wxDF_TEXT) || wxTheClipboard->IsSupported(wxDF_BITMAP))
3851 {
3852 canPaste = true;
3853 }
3854 wxTheClipboard->Close();
3855 }
3856 return canPaste;
3857 }
3858
3859 /// Dumps contents of buffer for debugging purposes
3860 void wxRichTextBuffer::Dump()
3861 {
3862 wxString text;
3863 {
3864 wxStringOutputStream stream(& text);
3865 wxTextOutputStream textStream(stream);
3866 Dump(textStream);
3867 }
3868
3869 wxLogDebug(text);
3870 }
3871
3872
3873 /*
3874 * Module to initialise and clean up handlers
3875 */
3876
3877 class wxRichTextModule: public wxModule
3878 {
3879 DECLARE_DYNAMIC_CLASS(wxRichTextModule)
3880 public:
3881 wxRichTextModule() {}
3882 bool OnInit() { wxRichTextBuffer::InitStandardHandlers(); return true; };
3883 void OnExit() { wxRichTextBuffer::CleanUpHandlers(); };
3884 };
3885
3886 IMPLEMENT_DYNAMIC_CLASS(wxRichTextModule, wxModule)
3887
3888
3889 /*!
3890 * Commands for undo/redo
3891 *
3892 */
3893
3894 wxRichTextCommand::wxRichTextCommand(const wxString& name, wxRichTextCommandId id, wxRichTextBuffer* buffer,
3895 wxRichTextCtrl* ctrl, bool ignoreFirstTime): wxCommand(true, name)
3896 {
3897 /* wxRichTextAction* action = */ new wxRichTextAction(this, name, id, buffer, ctrl, ignoreFirstTime);
3898 }
3899
3900 wxRichTextCommand::wxRichTextCommand(const wxString& name): wxCommand(true, name)
3901 {
3902 }
3903
3904 wxRichTextCommand::~wxRichTextCommand()
3905 {
3906 ClearActions();
3907 }
3908
3909 void wxRichTextCommand::AddAction(wxRichTextAction* action)
3910 {
3911 if (!m_actions.Member(action))
3912 m_actions.Append(action);
3913 }
3914
3915 bool wxRichTextCommand::Do()
3916 {
3917 for (wxNode* node = m_actions.GetFirst(); node; node = node->GetNext())
3918 {
3919 wxRichTextAction* action = (wxRichTextAction*) node->GetData();
3920 action->Do();
3921 }
3922
3923 return true;
3924 }
3925
3926 bool wxRichTextCommand::Undo()
3927 {
3928 for (wxNode* node = m_actions.GetLast(); node; node = node->GetPrevious())
3929 {
3930 wxRichTextAction* action = (wxRichTextAction*) node->GetData();
3931 action->Undo();
3932 }
3933
3934 return true;
3935 }
3936
3937 void wxRichTextCommand::ClearActions()
3938 {
3939 WX_CLEAR_LIST(wxList, m_actions);
3940 }
3941
3942 /*!
3943 * Individual action
3944 *
3945 */
3946
3947 wxRichTextAction::wxRichTextAction(wxRichTextCommand* cmd, const wxString& name, wxRichTextCommandId id, wxRichTextBuffer* buffer,
3948 wxRichTextCtrl* ctrl, bool ignoreFirstTime)
3949 {
3950 m_buffer = buffer;
3951 m_ignoreThis = ignoreFirstTime;
3952 m_cmdId = id;
3953 m_position = -1;
3954 m_ctrl = ctrl;
3955 m_name = name;
3956 m_newParagraphs.SetDefaultStyle(buffer->GetDefaultStyle());
3957 m_newParagraphs.SetBasicStyle(buffer->GetBasicStyle());
3958 if (cmd)
3959 cmd->AddAction(this);
3960 }
3961
3962 wxRichTextAction::~wxRichTextAction()
3963 {
3964 }
3965
3966 bool wxRichTextAction::Do()
3967 {
3968 m_buffer->Modify(true);
3969
3970 switch (m_cmdId)
3971 {
3972 case wxRICHTEXT_INSERT:
3973 {
3974 m_buffer->InsertFragment(GetPosition(), m_newParagraphs);
3975 m_buffer->UpdateRanges();
3976 m_buffer->Invalidate(GetRange());
3977
3978 long newCaretPosition = GetPosition() + m_newParagraphs.GetRange().GetLength() - 1;
3979 if (m_newParagraphs.GetPartialParagraph())
3980 newCaretPosition --;
3981
3982 UpdateAppearance(newCaretPosition, true /* send update event */);
3983
3984 break;
3985 }
3986 case wxRICHTEXT_DELETE:
3987 {
3988 m_buffer->DeleteRange(GetRange());
3989 m_buffer->UpdateRanges();
3990 m_buffer->Invalidate(wxRichTextRange(GetRange().GetStart(), GetRange().GetStart()));
3991
3992 UpdateAppearance(GetRange().GetStart()-1, true /* send update event */);
3993
3994 break;
3995 }
3996 case wxRICHTEXT_CHANGE_STYLE:
3997 {
3998 ApplyParagraphs(GetNewParagraphs());
3999 m_buffer->Invalidate(GetRange());
4000
4001 UpdateAppearance(GetPosition());
4002
4003 break;
4004 }
4005 default:
4006 break;
4007 }
4008
4009 return true;
4010 }
4011
4012 bool wxRichTextAction::Undo()
4013 {
4014 m_buffer->Modify(true);
4015
4016 switch (m_cmdId)
4017 {
4018 case wxRICHTEXT_INSERT:
4019 {
4020 m_buffer->DeleteRange(GetRange());
4021 m_buffer->UpdateRanges();
4022 m_buffer->Invalidate(wxRichTextRange(GetRange().GetStart(), GetRange().GetStart()));
4023
4024 long newCaretPosition = GetPosition() - 1;
4025 // if (m_newParagraphs.GetPartialParagraph())
4026 // newCaretPosition --;
4027
4028 UpdateAppearance(newCaretPosition, true /* send update event */);
4029
4030 break;
4031 }
4032 case wxRICHTEXT_DELETE:
4033 {
4034 m_buffer->InsertFragment(GetRange().GetStart(), m_oldParagraphs);
4035 m_buffer->UpdateRanges();
4036 m_buffer->Invalidate(GetRange());
4037
4038 UpdateAppearance(GetPosition(), true /* send update event */);
4039
4040 break;
4041 }
4042 case wxRICHTEXT_CHANGE_STYLE:
4043 {
4044 ApplyParagraphs(GetOldParagraphs());
4045 m_buffer->Invalidate(GetRange());
4046
4047 UpdateAppearance(GetPosition());
4048
4049 break;
4050 }
4051 default:
4052 break;
4053 }
4054
4055 return true;
4056 }
4057
4058 /// Update the control appearance
4059 void wxRichTextAction::UpdateAppearance(long caretPosition, bool sendUpdateEvent)
4060 {
4061 if (m_ctrl)
4062 {
4063 m_ctrl->SetCaretPosition(caretPosition);
4064 if (!m_ctrl->IsFrozen())
4065 {
4066 m_ctrl->Layout();
4067 m_ctrl->PositionCaret();
4068 m_ctrl->Refresh();
4069
4070 if (sendUpdateEvent)
4071 m_ctrl->SendUpdateEvent();
4072 }
4073 }
4074 }
4075
4076 /// Replace the buffer paragraphs with the new ones.
4077 void wxRichTextAction::ApplyParagraphs(const wxRichTextFragment& fragment)
4078 {
4079 wxRichTextObjectList::compatibility_iterator node = fragment.GetChildren().GetFirst();
4080 while (node)
4081 {
4082 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
4083 wxASSERT (para != NULL);
4084
4085 // We'll replace the existing paragraph by finding the paragraph at this position,
4086 // delete its node data, and setting a copy as the new node data.
4087 // TODO: make more efficient by simply swapping old and new paragraph objects.
4088
4089 wxRichTextParagraph* existingPara = m_buffer->GetParagraphAtPosition(para->GetRange().GetStart());
4090 if (existingPara)
4091 {
4092 wxRichTextObjectList::compatibility_iterator bufferParaNode = m_buffer->GetChildren().Find(existingPara);
4093 if (bufferParaNode)
4094 {
4095 wxRichTextParagraph* newPara = new wxRichTextParagraph(*para);
4096 newPara->SetParent(m_buffer);
4097
4098 bufferParaNode->SetData(newPara);
4099
4100 delete existingPara;
4101 }
4102 }
4103
4104 node = node->GetNext();
4105 }
4106 }
4107
4108
4109 /*!
4110 * wxRichTextRange
4111 * This stores beginning and end positions for a range of data.
4112 */
4113
4114 /// Limit this range to be within 'range'
4115 bool wxRichTextRange::LimitTo(const wxRichTextRange& range)
4116 {
4117 if (m_start < range.m_start)
4118 m_start = range.m_start;
4119
4120 if (m_end > range.m_end)
4121 m_end = range.m_end;
4122
4123 return true;
4124 }
4125
4126 /*!
4127 * wxRichTextImage implementation
4128 * This object represents an image.
4129 */
4130
4131 IMPLEMENT_DYNAMIC_CLASS(wxRichTextImage, wxRichTextObject)
4132
4133 wxRichTextImage::wxRichTextImage(const wxImage& image, wxRichTextObject* parent):
4134 wxRichTextObject(parent)
4135 {
4136 m_image = image;
4137 }
4138
4139 wxRichTextImage::wxRichTextImage(const wxRichTextImageBlock& imageBlock, wxRichTextObject* parent):
4140 wxRichTextObject(parent)
4141 {
4142 m_imageBlock = imageBlock;
4143 m_imageBlock.Load(m_image);
4144 }
4145
4146 /// Load wxImage from the block
4147 bool wxRichTextImage::LoadFromBlock()
4148 {
4149 m_imageBlock.Load(m_image);
4150 return m_imageBlock.Ok();
4151 }
4152
4153 /// Make block from the wxImage
4154 bool wxRichTextImage::MakeBlock()
4155 {
4156 if (m_imageBlock.GetImageType() == wxBITMAP_TYPE_ANY || m_imageBlock.GetImageType() == -1)
4157 m_imageBlock.SetImageType(wxBITMAP_TYPE_PNG);
4158
4159 m_imageBlock.MakeImageBlock(m_image, m_imageBlock.GetImageType());
4160 return m_imageBlock.Ok();
4161 }
4162
4163
4164 /// Draw the item
4165 bool wxRichTextImage::Draw(wxDC& dc, const wxRichTextRange& range, const wxRichTextRange& selectionRange, const wxRect& rect, int WXUNUSED(descent), int WXUNUSED(style))
4166 {
4167 if (!m_image.Ok() && m_imageBlock.Ok())
4168 LoadFromBlock();
4169
4170 if (!m_image.Ok())
4171 return false;
4172
4173 if (m_image.Ok() && !m_bitmap.Ok())
4174 m_bitmap = wxBitmap(m_image);
4175
4176 int y = rect.y + (rect.height - m_image.GetHeight());
4177
4178 if (m_bitmap.Ok())
4179 dc.DrawBitmap(m_bitmap, rect.x, y, true);
4180
4181 if (selectionRange.Contains(range.GetStart()))
4182 {
4183 dc.SetBrush(*wxBLACK_BRUSH);
4184 dc.SetPen(*wxBLACK_PEN);
4185 dc.SetLogicalFunction(wxINVERT);
4186 dc.DrawRectangle(rect);
4187 dc.SetLogicalFunction(wxCOPY);
4188 }
4189
4190 return true;
4191 }
4192
4193 /// Lay the item out
4194 bool wxRichTextImage::Layout(wxDC& WXUNUSED(dc), const wxRect& rect, int WXUNUSED(style))
4195 {
4196 if (!m_image.Ok())
4197 LoadFromBlock();
4198
4199 if (m_image.Ok())
4200 {
4201 SetCachedSize(wxSize(m_image.GetWidth(), m_image.GetHeight()));
4202 SetPosition(rect.GetPosition());
4203 }
4204
4205 return true;
4206 }
4207
4208 /// Get/set the object size for the given range. Returns false if the range
4209 /// is invalid for this object.
4210 bool wxRichTextImage::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& WXUNUSED(descent), wxDC& WXUNUSED(dc), int WXUNUSED(flags)) const
4211 {
4212 if (!range.IsWithin(GetRange()))
4213 return false;
4214
4215 if (!m_image.Ok())
4216 return false;
4217
4218 size.x = m_image.GetWidth();
4219 size.y = m_image.GetHeight();
4220
4221 return true;
4222 }
4223
4224 /// Copy
4225 void wxRichTextImage::Copy(const wxRichTextImage& obj)
4226 {
4227 m_image = obj.m_image;
4228 m_imageBlock = obj.m_imageBlock;
4229 }
4230
4231 /*!
4232 * Utilities
4233 *
4234 */
4235
4236 /// Compare two attribute objects
4237 bool wxTextAttrEq(const wxTextAttrEx& attr1, const wxTextAttrEx& attr2)
4238 {
4239 return (
4240 attr1.GetTextColour() == attr2.GetTextColour() &&
4241 attr1.GetBackgroundColour() == attr2.GetBackgroundColour() &&
4242 attr1.GetFont() == attr2.GetFont() &&
4243 attr1.GetAlignment() == attr2.GetAlignment() &&
4244 attr1.GetLeftIndent() == attr2.GetLeftIndent() &&
4245 attr1.GetRightIndent() == attr2.GetRightIndent() &&
4246 attr1.GetLeftSubIndent() == attr2.GetLeftSubIndent() &&
4247 attr1.GetTabs().GetCount() == attr2.GetTabs().GetCount() && // heuristic
4248 attr1.GetLineSpacing() == attr2.GetLineSpacing() &&
4249 attr1.GetParagraphSpacingAfter() == attr2.GetParagraphSpacingAfter() &&
4250 attr1.GetParagraphSpacingBefore() == attr2.GetParagraphSpacingBefore() &&
4251 attr1.GetBulletStyle() == attr2.GetBulletStyle() &&
4252 attr1.GetBulletNumber() == attr2.GetBulletNumber() &&
4253 attr1.GetBulletSymbol() == attr2.GetBulletSymbol() &&
4254 attr1.GetCharacterStyleName() == attr2.GetCharacterStyleName() &&
4255 attr1.GetParagraphStyleName() == attr2.GetParagraphStyleName());
4256 }
4257
4258 bool wxTextAttrEq(const wxTextAttrEx& attr1, const wxRichTextAttr& attr2)
4259 {
4260 return (
4261 attr1.GetTextColour() == attr2.GetTextColour() &&
4262 attr1.GetBackgroundColour() == attr2.GetBackgroundColour() &&
4263 attr1.GetFont().GetPointSize() == attr2.GetFontSize() &&
4264 attr1.GetFont().GetStyle() == attr2.GetFontStyle() &&
4265 attr1.GetFont().GetWeight() == attr2.GetFontWeight() &&
4266 attr1.GetFont().GetFaceName() == attr2.GetFontFaceName() &&
4267 attr1.GetFont().GetUnderlined() == attr2.GetFontUnderlined() &&
4268 attr1.GetAlignment() == attr2.GetAlignment() &&
4269 attr1.GetLeftIndent() == attr2.GetLeftIndent() &&
4270 attr1.GetRightIndent() == attr2.GetRightIndent() &&
4271 attr1.GetLeftSubIndent() == attr2.GetLeftSubIndent() &&
4272 attr1.GetTabs().GetCount() == attr2.GetTabs().GetCount() && // heuristic
4273 attr1.GetLineSpacing() == attr2.GetLineSpacing() &&
4274 attr1.GetParagraphSpacingAfter() == attr2.GetParagraphSpacingAfter() &&
4275 attr1.GetParagraphSpacingBefore() == attr2.GetParagraphSpacingBefore() &&
4276 attr1.GetBulletStyle() == attr2.GetBulletStyle() &&
4277 attr1.GetBulletNumber() == attr2.GetBulletNumber() &&
4278 attr1.GetBulletSymbol() == attr2.GetBulletSymbol() &&
4279 attr1.GetCharacterStyleName() == attr2.GetCharacterStyleName() &&
4280 attr1.GetParagraphStyleName() == attr2.GetParagraphStyleName());
4281 }
4282
4283 /// Compare two attribute objects, but take into account the flags
4284 /// specifying attributes of interest.
4285 bool wxTextAttrEqPartial(const wxTextAttrEx& attr1, const wxTextAttrEx& attr2, int flags)
4286 {
4287 if ((flags & wxTEXT_ATTR_TEXT_COLOUR) && attr1.GetTextColour() != attr2.GetTextColour())
4288 return false;
4289
4290 if ((flags & wxTEXT_ATTR_BACKGROUND_COLOUR) && attr1.GetBackgroundColour() != attr2.GetBackgroundColour())
4291 return false;
4292
4293 if ((flags & wxTEXT_ATTR_FONT_FACE) && attr1.GetFont().Ok() && attr2.GetFont().Ok() &&
4294 attr1.GetFont().GetFaceName() != attr2.GetFont().GetFaceName())
4295 return false;
4296
4297 if ((flags & wxTEXT_ATTR_FONT_SIZE) && attr1.GetFont().Ok() && attr2.GetFont().Ok() &&
4298 attr1.GetFont().GetPointSize() != attr2.GetFont().GetPointSize())
4299 return false;
4300
4301 if ((flags & wxTEXT_ATTR_FONT_WEIGHT) && attr1.GetFont().Ok() && attr2.GetFont().Ok() &&
4302 attr1.GetFont().GetWeight() != attr2.GetFont().GetWeight())
4303 return false;
4304
4305 if ((flags & wxTEXT_ATTR_FONT_ITALIC) && attr1.GetFont().Ok() && attr2.GetFont().Ok() &&
4306 attr1.GetFont().GetStyle() != attr2.GetFont().GetStyle())
4307 return false;
4308
4309 if ((flags & wxTEXT_ATTR_FONT_UNDERLINE) && attr1.GetFont().Ok() && attr2.GetFont().Ok() &&
4310 attr1.GetFont().GetUnderlined() != attr2.GetFont().GetUnderlined())
4311 return false;
4312
4313 if ((flags & wxTEXT_ATTR_ALIGNMENT) && attr1.GetAlignment() != attr2.GetAlignment())
4314 return false;
4315
4316 if ((flags & wxTEXT_ATTR_LEFT_INDENT) &&
4317 ((attr1.GetLeftIndent() != attr2.GetLeftIndent()) || (attr1.GetLeftSubIndent() != attr2.GetLeftSubIndent())))
4318 return false;
4319
4320 if ((flags & wxTEXT_ATTR_RIGHT_INDENT) &&
4321 (attr1.GetRightIndent() != attr2.GetRightIndent()))
4322 return false;
4323
4324 if ((flags && wxTEXT_ATTR_PARA_SPACING_AFTER) &&
4325 (attr1.GetParagraphSpacingAfter() != attr2.GetParagraphSpacingAfter()))
4326 return false;
4327
4328 if ((flags && wxTEXT_ATTR_PARA_SPACING_BEFORE) &&
4329 (attr1.GetParagraphSpacingBefore() != attr2.GetParagraphSpacingBefore()))
4330 return false;
4331
4332 if ((flags && wxTEXT_ATTR_LINE_SPACING) &&
4333 (attr1.GetLineSpacing() != attr2.GetLineSpacing()))
4334 return false;
4335
4336 if ((flags && wxTEXT_ATTR_CHARACTER_STYLE_NAME) &&
4337 (attr1.GetCharacterStyleName() != attr2.GetCharacterStyleName()))
4338 return false;
4339
4340 if ((flags && wxTEXT_ATTR_PARAGRAPH_STYLE_NAME) &&
4341 (attr1.GetParagraphStyleName() != attr2.GetParagraphStyleName()))
4342 return false;
4343
4344 if ((flags & wxTEXT_ATTR_BULLET_STYLE) &&
4345 (attr1.GetBulletStyle() != attr2.GetBulletStyle()))
4346 return false;
4347
4348 if ((flags & wxTEXT_ATTR_BULLET_NUMBER) &&
4349 (attr1.GetBulletNumber() != attr2.GetBulletNumber()))
4350 return false;
4351
4352 if ((flags & wxTEXT_ATTR_BULLET_SYMBOL) &&
4353 (attr1.GetBulletSymbol() != attr2.GetBulletSymbol()))
4354 return false;
4355
4356 /* TODO
4357 if ((flags & wxTEXT_ATTR_TABS) &&
4358 return false;
4359 */
4360
4361 return true;
4362 }
4363
4364 bool wxTextAttrEqPartial(const wxTextAttrEx& attr1, const wxRichTextAttr& attr2, int flags)
4365 {
4366 if ((flags & wxTEXT_ATTR_TEXT_COLOUR) && attr1.GetTextColour() != attr2.GetTextColour())
4367 return false;
4368
4369 if ((flags & wxTEXT_ATTR_BACKGROUND_COLOUR) && attr1.GetBackgroundColour() != attr2.GetBackgroundColour())
4370 return false;
4371
4372 if ((flags & (wxTEXT_ATTR_FONT)) && !attr1.GetFont().Ok())
4373 return false;
4374
4375 if ((flags & wxTEXT_ATTR_FONT_FACE) && attr1.GetFont().Ok() &&
4376 attr1.GetFont().GetFaceName() != attr2.GetFontFaceName())
4377 return false;
4378
4379 if ((flags & wxTEXT_ATTR_FONT_SIZE) && attr1.GetFont().Ok() &&
4380 attr1.GetFont().GetPointSize() != attr2.GetFontSize())
4381 return false;
4382
4383 if ((flags & wxTEXT_ATTR_FONT_WEIGHT) && attr1.GetFont().Ok() &&
4384 attr1.GetFont().GetWeight() != attr2.GetFontWeight())
4385 return false;
4386
4387 if ((flags & wxTEXT_ATTR_FONT_ITALIC) && attr1.GetFont().Ok() &&
4388 attr1.GetFont().GetStyle() != attr2.GetFontStyle())
4389 return false;
4390
4391 if ((flags & wxTEXT_ATTR_FONT_UNDERLINE) && attr1.GetFont().Ok() &&
4392 attr1.GetFont().GetUnderlined() != attr2.GetFontUnderlined())
4393 return false;
4394
4395 if ((flags & wxTEXT_ATTR_ALIGNMENT) && attr1.GetAlignment() != attr2.GetAlignment())
4396 return false;
4397
4398 if ((flags & wxTEXT_ATTR_LEFT_INDENT) &&
4399 ((attr1.GetLeftIndent() != attr2.GetLeftIndent()) || (attr1.GetLeftSubIndent() != attr2.GetLeftSubIndent())))
4400 return false;
4401
4402 if ((flags & wxTEXT_ATTR_RIGHT_INDENT) &&
4403 (attr1.GetRightIndent() != attr2.GetRightIndent()))
4404 return false;
4405
4406 if ((flags && wxTEXT_ATTR_PARA_SPACING_AFTER) &&
4407 (attr1.GetParagraphSpacingAfter() != attr2.GetParagraphSpacingAfter()))
4408 return false;
4409
4410 if ((flags && wxTEXT_ATTR_PARA_SPACING_BEFORE) &&
4411 (attr1.GetParagraphSpacingBefore() != attr2.GetParagraphSpacingBefore()))
4412 return false;
4413
4414 if ((flags && wxTEXT_ATTR_LINE_SPACING) &&
4415 (attr1.GetLineSpacing() != attr2.GetLineSpacing()))
4416 return false;
4417
4418 if ((flags && wxTEXT_ATTR_CHARACTER_STYLE_NAME) &&
4419 (attr1.GetCharacterStyleName() != attr2.GetCharacterStyleName()))
4420 return false;
4421
4422 if ((flags && wxTEXT_ATTR_PARAGRAPH_STYLE_NAME) &&
4423 (attr1.GetParagraphStyleName() != attr2.GetParagraphStyleName()))
4424 return false;
4425
4426 if ((flags & wxTEXT_ATTR_BULLET_STYLE) &&
4427 (attr1.GetBulletStyle() != attr2.GetBulletStyle()))
4428 return false;
4429
4430 if ((flags & wxTEXT_ATTR_BULLET_NUMBER) &&
4431 (attr1.GetBulletNumber() != attr2.GetBulletNumber()))
4432 return false;
4433
4434 if ((flags & wxTEXT_ATTR_BULLET_SYMBOL) &&
4435 (attr1.GetBulletSymbol() != attr2.GetBulletSymbol()))
4436 return false;
4437
4438 /* TODO
4439 if ((flags & wxTEXT_ATTR_TABS) &&
4440 return false;
4441 */
4442
4443 return true;
4444 }
4445
4446
4447 /// Apply one style to another
4448 bool wxRichTextApplyStyle(wxTextAttrEx& destStyle, const wxTextAttrEx& style)
4449 {
4450 // Whole font
4451 if (style.GetFont().Ok() && ((style.GetFlags() & (wxTEXT_ATTR_FONT)) == (wxTEXT_ATTR_FONT)))
4452 destStyle.SetFont(style.GetFont());
4453 else if (style.GetFont().Ok())
4454 {
4455 wxFont font = destStyle.GetFont();
4456
4457 if (style.GetFlags() & wxTEXT_ATTR_FONT_FACE)
4458 font.SetFaceName(style.GetFont().GetFaceName());
4459
4460 if (style.GetFlags() & wxTEXT_ATTR_FONT_SIZE)
4461 font.SetPointSize(style.GetFont().GetPointSize());
4462
4463 if (style.GetFlags() & wxTEXT_ATTR_FONT_ITALIC)
4464 font.SetStyle(style.GetFont().GetStyle());
4465
4466 if (style.GetFlags() & wxTEXT_ATTR_FONT_WEIGHT)
4467 font.SetWeight(style.GetFont().GetWeight());
4468
4469 if (style.GetFlags() & wxTEXT_ATTR_FONT_UNDERLINE)
4470 font.SetUnderlined(style.GetFont().GetUnderlined());
4471
4472 if (font != destStyle.GetFont())
4473 destStyle.SetFont(font);
4474 }
4475
4476 if ( style.GetTextColour().Ok() && style.HasTextColour())
4477 destStyle.SetTextColour(style.GetTextColour());
4478
4479 if ( style.GetBackgroundColour().Ok() && style.HasBackgroundColour())
4480 destStyle.SetBackgroundColour(style.GetBackgroundColour());
4481
4482 if (style.HasAlignment())
4483 destStyle.SetAlignment(style.GetAlignment());
4484
4485 if (style.HasTabs())
4486 destStyle.SetTabs(style.GetTabs());
4487
4488 if (style.HasLeftIndent())
4489 destStyle.SetLeftIndent(style.GetLeftIndent(), style.GetLeftSubIndent());
4490
4491 if (style.HasRightIndent())
4492 destStyle.SetRightIndent(style.GetRightIndent());
4493
4494 if (style.HasParagraphSpacingAfter())
4495 destStyle.SetParagraphSpacingAfter(style.GetParagraphSpacingAfter());
4496
4497 if (style.HasParagraphSpacingBefore())
4498 destStyle.SetParagraphSpacingBefore(style.GetParagraphSpacingBefore());
4499
4500 if (style.HasLineSpacing())
4501 destStyle.SetLineSpacing(style.GetLineSpacing());
4502
4503 if (style.HasCharacterStyleName())
4504 destStyle.SetCharacterStyleName(style.GetCharacterStyleName());
4505
4506 if (style.HasParagraphStyleName())
4507 destStyle.SetParagraphStyleName(style.GetParagraphStyleName());
4508
4509 if (style.HasBulletStyle())
4510 {
4511 destStyle.SetBulletStyle(style.GetBulletStyle());
4512 destStyle.SetBulletSymbol(style.GetBulletSymbol());
4513 }
4514
4515 if (style.HasBulletNumber())
4516 destStyle.SetBulletNumber(style.GetBulletNumber());
4517
4518 return true;
4519 }
4520
4521 bool wxRichTextApplyStyle(wxRichTextAttr& destStyle, const wxTextAttrEx& style)
4522 {
4523 wxTextAttrEx destStyle2;
4524 destStyle.CopyTo(destStyle2);
4525 wxRichTextApplyStyle(destStyle2, style);
4526 destStyle = destStyle2;
4527 return true;
4528 }
4529
4530 bool wxRichTextApplyStyle(wxTextAttrEx& destStyle, const wxRichTextAttr& style)
4531 {
4532
4533 // Whole font. Avoiding setting individual attributes if possible, since
4534 // it recreates the font each time.
4535 if ((style.GetFlags() & (wxTEXT_ATTR_FONT)) == (wxTEXT_ATTR_FONT))
4536 {
4537 destStyle.SetFont(wxFont(style.GetFontSize(), destStyle.GetFont().Ok() ? destStyle.GetFont().GetFamily() : wxDEFAULT,
4538 style.GetFontStyle(), style.GetFontWeight(), style.GetFontUnderlined(), style.GetFontFaceName()));
4539 }
4540 else if (style.GetFlags() & (wxTEXT_ATTR_FONT))
4541 {
4542 wxFont font = destStyle.GetFont();
4543
4544 if (style.GetFlags() & wxTEXT_ATTR_FONT_FACE)
4545 font.SetFaceName(style.GetFontFaceName());
4546
4547 if (style.GetFlags() & wxTEXT_ATTR_FONT_SIZE)
4548 font.SetPointSize(style.GetFontSize());
4549
4550 if (style.GetFlags() & wxTEXT_ATTR_FONT_ITALIC)
4551 font.SetStyle(style.GetFontStyle());
4552
4553 if (style.GetFlags() & wxTEXT_ATTR_FONT_WEIGHT)
4554 font.SetWeight(style.GetFontWeight());
4555
4556 if (style.GetFlags() & wxTEXT_ATTR_FONT_UNDERLINE)
4557 font.SetUnderlined(style.GetFontUnderlined());
4558
4559 if (font != destStyle.GetFont())
4560 destStyle.SetFont(font);
4561 }
4562
4563 if ( style.GetTextColour().Ok() && style.HasTextColour())
4564 destStyle.SetTextColour(style.GetTextColour());
4565
4566 if ( style.GetBackgroundColour().Ok() && style.HasBackgroundColour())
4567 destStyle.SetBackgroundColour(style.GetBackgroundColour());
4568
4569 if (style.HasAlignment())
4570 destStyle.SetAlignment(style.GetAlignment());
4571
4572 if (style.HasTabs())
4573 destStyle.SetTabs(style.GetTabs());
4574
4575 if (style.HasLeftIndent())
4576 destStyle.SetLeftIndent(style.GetLeftIndent(), style.GetLeftSubIndent());
4577
4578 if (style.HasRightIndent())
4579 destStyle.SetRightIndent(style.GetRightIndent());
4580
4581 if (style.HasParagraphSpacingAfter())
4582 destStyle.SetParagraphSpacingAfter(style.GetParagraphSpacingAfter());
4583
4584 if (style.HasParagraphSpacingBefore())
4585 destStyle.SetParagraphSpacingBefore(style.GetParagraphSpacingBefore());
4586
4587 if (style.HasLineSpacing())
4588 destStyle.SetLineSpacing(style.GetLineSpacing());
4589
4590 if (style.HasCharacterStyleName())
4591 destStyle.SetCharacterStyleName(style.GetCharacterStyleName());
4592
4593 if (style.HasParagraphStyleName())
4594 destStyle.SetParagraphStyleName(style.GetParagraphStyleName());
4595
4596 if (style.HasBulletStyle())
4597 {
4598 destStyle.SetBulletStyle(style.GetBulletStyle());
4599 destStyle.SetBulletSymbol(style.GetBulletSymbol());
4600 }
4601
4602 if (style.HasBulletNumber())
4603 destStyle.SetBulletNumber(style.GetBulletNumber());
4604
4605 return true;
4606 }
4607
4608
4609 /*!
4610 * wxRichTextAttr stores attributes without a wxFont object, so is a much more
4611 * efficient way to query styles.
4612 */
4613
4614 // ctors
4615 wxRichTextAttr::wxRichTextAttr(const wxColour& colText,
4616 const wxColour& colBack,
4617 wxTextAttrAlignment alignment): m_textAlignment(alignment), m_colText(colText), m_colBack(colBack)
4618 {
4619 Init();
4620
4621 if (m_colText.Ok()) m_flags |= wxTEXT_ATTR_TEXT_COLOUR;
4622 if (m_colBack.Ok()) m_flags |= wxTEXT_ATTR_BACKGROUND_COLOUR;
4623 if (alignment != wxTEXT_ALIGNMENT_DEFAULT)
4624 m_flags |= wxTEXT_ATTR_ALIGNMENT;
4625 }
4626
4627 wxRichTextAttr::wxRichTextAttr(const wxTextAttrEx& attr)
4628 {
4629 Init();
4630
4631 (*this) = attr;
4632 }
4633
4634 // operations
4635 void wxRichTextAttr::Init()
4636 {
4637 m_textAlignment = wxTEXT_ALIGNMENT_DEFAULT;
4638 m_flags = 0;
4639 m_leftIndent = 0;
4640 m_leftSubIndent = 0;
4641 m_rightIndent = 0;
4642
4643 m_fontSize = 12;
4644 m_fontStyle = wxNORMAL;
4645 m_fontWeight = wxNORMAL;
4646 m_fontUnderlined = false;
4647
4648 m_paragraphSpacingAfter = 0;
4649 m_paragraphSpacingBefore = 0;
4650 m_lineSpacing = 0;
4651 m_bulletStyle = wxTEXT_ATTR_BULLET_STYLE_NONE;
4652 m_bulletNumber = 0;
4653 m_bulletSymbol = wxT('*');
4654 }
4655
4656 // operators
4657 void wxRichTextAttr::operator= (const wxRichTextAttr& attr)
4658 {
4659 m_colText = attr.m_colText;
4660 m_colBack = attr.m_colBack;
4661 m_textAlignment = attr.m_textAlignment;
4662 m_leftIndent = attr.m_leftIndent;
4663 m_leftSubIndent = attr.m_leftSubIndent;
4664 m_rightIndent = attr.m_rightIndent;
4665 m_tabs = attr.m_tabs;
4666 m_flags = attr.m_flags;
4667
4668 m_fontSize = attr.m_fontSize;
4669 m_fontStyle = attr.m_fontStyle;
4670 m_fontWeight = attr.m_fontWeight;
4671 m_fontUnderlined = attr.m_fontUnderlined;
4672 m_fontFaceName = attr.m_fontFaceName;
4673
4674 m_paragraphSpacingAfter = attr.m_paragraphSpacingAfter;
4675 m_paragraphSpacingBefore = attr.m_paragraphSpacingBefore;
4676 m_lineSpacing = attr.m_lineSpacing;
4677 m_characterStyleName = attr.m_characterStyleName;
4678 m_paragraphStyleName = attr.m_paragraphStyleName;
4679 m_bulletStyle = attr.m_bulletStyle;
4680 m_bulletNumber = attr.m_bulletNumber;
4681 m_bulletSymbol = attr.m_bulletSymbol;
4682 }
4683
4684 // operators
4685 void wxRichTextAttr::operator= (const wxTextAttrEx& attr)
4686 {
4687 m_colText = attr.GetTextColour();
4688 m_colBack = attr.GetBackgroundColour();
4689 m_textAlignment = attr.GetAlignment();
4690 m_leftIndent = attr.GetLeftIndent();
4691 m_leftSubIndent = attr.GetLeftSubIndent();
4692 m_rightIndent = attr.GetRightIndent();
4693 m_tabs = attr.GetTabs();
4694 m_flags = attr.GetFlags();
4695
4696 m_paragraphSpacingAfter = attr.GetParagraphSpacingAfter();
4697 m_paragraphSpacingBefore = attr.GetParagraphSpacingBefore();
4698 m_lineSpacing = attr.GetLineSpacing();
4699 m_characterStyleName = attr.GetCharacterStyleName();
4700 m_paragraphStyleName = attr.GetParagraphStyleName();
4701
4702 if (attr.GetFont().Ok())
4703 GetFontAttributes(attr.GetFont());
4704 }
4705
4706 // Making a wxTextAttrEx object.
4707 wxRichTextAttr::operator wxTextAttrEx () const
4708 {
4709 wxTextAttrEx attr;
4710 CopyTo(attr);
4711 return attr;
4712 }
4713
4714 // Copy to a wxTextAttr
4715 void wxRichTextAttr::CopyTo(wxTextAttrEx& attr) const
4716 {
4717 attr.SetTextColour(GetTextColour());
4718 attr.SetBackgroundColour(GetBackgroundColour());
4719 attr.SetAlignment(GetAlignment());
4720 attr.SetTabs(GetTabs());
4721 attr.SetLeftIndent(GetLeftIndent(), GetLeftSubIndent());
4722 attr.SetRightIndent(GetRightIndent());
4723 attr.SetFont(CreateFont());
4724 attr.SetFlags(GetFlags()); // Important: set after SetFont, since SetFont sets flags
4725
4726 attr.SetParagraphSpacingAfter(m_paragraphSpacingAfter);
4727 attr.SetParagraphSpacingBefore(m_paragraphSpacingBefore);
4728 attr.SetLineSpacing(m_lineSpacing);
4729 attr.SetBulletStyle(m_bulletStyle);
4730 attr.SetBulletNumber(m_bulletNumber);
4731 attr.SetBulletSymbol(m_bulletSymbol);
4732 attr.SetCharacterStyleName(m_characterStyleName);
4733 attr.SetParagraphStyleName(m_paragraphStyleName);
4734
4735 }
4736
4737 // Create font from font attributes.
4738 wxFont wxRichTextAttr::CreateFont() const
4739 {
4740 wxFont font(m_fontSize, wxDEFAULT, m_fontStyle, m_fontWeight, m_fontUnderlined, m_fontFaceName);
4741 return font;
4742 }
4743
4744 // Get attributes from font.
4745 bool wxRichTextAttr::GetFontAttributes(const wxFont& font)
4746 {
4747 if (!font.Ok())
4748 return false;
4749
4750 m_fontSize = font.GetPointSize();
4751 m_fontStyle = font.GetStyle();
4752 m_fontWeight = font.GetWeight();
4753 m_fontUnderlined = font.GetUnderlined();
4754 m_fontFaceName = font.GetFaceName();
4755
4756 return true;
4757 }
4758
4759 /*!
4760 * wxTextAttrEx is an extended version of wxTextAttr with more paragraph attributes.
4761 */
4762
4763 wxTextAttrEx::wxTextAttrEx(const wxTextAttrEx& attr): wxTextAttr(attr)
4764 {
4765 m_paragraphSpacingAfter = attr.m_paragraphSpacingAfter;
4766 m_paragraphSpacingBefore = attr.m_paragraphSpacingBefore;
4767 m_lineSpacing = attr.m_lineSpacing;
4768 m_paragraphStyleName = attr.m_paragraphStyleName;
4769 m_characterStyleName = attr.m_characterStyleName;
4770 m_bulletStyle = attr.m_bulletStyle;
4771 m_bulletNumber = attr.m_bulletNumber;
4772 m_bulletSymbol = attr.m_bulletSymbol;
4773 }
4774
4775 // Initialise this object.
4776 void wxTextAttrEx::Init()
4777 {
4778 m_paragraphSpacingAfter = 0;
4779 m_paragraphSpacingBefore = 0;
4780 m_lineSpacing = 0;
4781 m_bulletStyle = wxTEXT_ATTR_BULLET_STYLE_NONE;
4782 m_bulletNumber = 0;
4783 m_bulletSymbol = 0;
4784 m_bulletSymbol = wxT('*');
4785 }
4786
4787 // Assignment from a wxTextAttrEx object
4788 void wxTextAttrEx::operator= (const wxTextAttrEx& attr)
4789 {
4790 wxTextAttr::operator= (attr);
4791
4792 m_paragraphSpacingAfter = attr.m_paragraphSpacingAfter;
4793 m_paragraphSpacingBefore = attr.m_paragraphSpacingBefore;
4794 m_lineSpacing = attr.m_lineSpacing;
4795 m_characterStyleName = attr.m_characterStyleName;
4796 m_paragraphStyleName = attr.m_paragraphStyleName;
4797 m_bulletStyle = attr.m_bulletStyle;
4798 m_bulletNumber = attr.m_bulletNumber;
4799 m_bulletSymbol = attr.m_bulletSymbol;
4800 }
4801
4802 // Assignment from a wxTextAttr object.
4803 void wxTextAttrEx::operator= (const wxTextAttr& attr)
4804 {
4805 wxTextAttr::operator= (attr);
4806 }
4807
4808 /*!
4809 * wxRichTextFileHandler
4810 * Base class for file handlers
4811 */
4812
4813 IMPLEMENT_CLASS(wxRichTextFileHandler, wxObject)
4814
4815 #if wxUSE_STREAMS
4816 bool wxRichTextFileHandler::LoadFile(wxRichTextBuffer *buffer, const wxString& filename)
4817 {
4818 wxFFileInputStream stream(filename);
4819 if (stream.Ok())
4820 return LoadFile(buffer, stream);
4821 else
4822 return false;
4823 }
4824
4825 bool wxRichTextFileHandler::SaveFile(wxRichTextBuffer *buffer, const wxString& filename)
4826 {
4827 wxFFileOutputStream stream(filename);
4828 if (stream.Ok())
4829 return SaveFile(buffer, stream);
4830 else
4831 return false;
4832 }
4833 #endif // wxUSE_STREAMS
4834
4835 /// Can we handle this filename (if using files)? By default, checks the extension.
4836 bool wxRichTextFileHandler::CanHandle(const wxString& filename) const
4837 {
4838 wxString path, file, ext;
4839 wxSplitPath(filename, & path, & file, & ext);
4840
4841 return (ext.Lower() == GetExtension());
4842 }
4843
4844 /*!
4845 * wxRichTextTextHandler
4846 * Plain text handler
4847 */
4848
4849 IMPLEMENT_CLASS(wxRichTextPlainTextHandler, wxRichTextFileHandler)
4850
4851 #if wxUSE_STREAMS
4852 bool wxRichTextPlainTextHandler::DoLoadFile(wxRichTextBuffer *buffer, wxInputStream& stream)
4853 {
4854 if (!stream.IsOk())
4855 return false;
4856
4857 wxString str;
4858 int lastCh = 0;
4859
4860 while (!stream.Eof())
4861 {
4862 int ch = stream.GetC();
4863
4864 if (!stream.Eof())
4865 {
4866 if (ch == 10 && lastCh != 13)
4867 str += wxT('\n');
4868
4869 if (ch > 0 && ch != 10)
4870 str += wxChar(ch);
4871
4872 lastCh = ch;
4873 }
4874 }
4875
4876 buffer->Clear();
4877 buffer->AddParagraphs(str);
4878 buffer->UpdateRanges();
4879
4880 return true;
4881
4882 }
4883
4884 bool wxRichTextPlainTextHandler::DoSaveFile(wxRichTextBuffer *buffer, wxOutputStream& stream)
4885 {
4886 if (!stream.IsOk())
4887 return false;
4888
4889 wxString text = buffer->GetText();
4890 wxCharBuffer buf = text.ToAscii();
4891
4892 stream.Write((const char*) buf, text.Length());
4893 return true;
4894 }
4895 #endif // wxUSE_STREAMS
4896
4897 /*
4898 * Stores information about an image, in binary in-memory form
4899 */
4900
4901 wxRichTextImageBlock::wxRichTextImageBlock()
4902 {
4903 Init();
4904 }
4905
4906 wxRichTextImageBlock::wxRichTextImageBlock(const wxRichTextImageBlock& block):wxObject()
4907 {
4908 Init();
4909 Copy(block);
4910 }
4911
4912 wxRichTextImageBlock::~wxRichTextImageBlock()
4913 {
4914 if (m_data)
4915 {
4916 delete[] m_data;
4917 m_data = NULL;
4918 }
4919 }
4920
4921 void wxRichTextImageBlock::Init()
4922 {
4923 m_data = NULL;
4924 m_dataSize = 0;
4925 m_imageType = -1;
4926 }
4927
4928 void wxRichTextImageBlock::Clear()
4929 {
4930 if (m_data)
4931 delete m_data;
4932 m_data = NULL;
4933 m_dataSize = 0;
4934 m_imageType = -1;
4935 }
4936
4937
4938 // Load the original image into a memory block.
4939 // If the image is not a JPEG, we must convert it into a JPEG
4940 // to conserve space.
4941 // If it's not a JPEG we can make use of 'image', already scaled, so we don't have to
4942 // load the image a 2nd time.
4943
4944 bool wxRichTextImageBlock::MakeImageBlock(const wxString& filename, int imageType, wxImage& image, bool convertToJPEG)
4945 {
4946 m_imageType = imageType;
4947
4948 wxString filenameToRead(filename);
4949 bool removeFile = false;
4950
4951 if (imageType == -1)
4952 return false; // Could not determine image type
4953
4954 if ((imageType != wxBITMAP_TYPE_JPEG) && convertToJPEG)
4955 {
4956 wxString tempFile;
4957 bool success = wxGetTempFileName(_("image"), tempFile) ;
4958
4959 wxASSERT(success);
4960
4961 wxUnusedVar(success);
4962
4963 image.SaveFile(tempFile, wxBITMAP_TYPE_JPEG);
4964 filenameToRead = tempFile;
4965 removeFile = true;
4966
4967 m_imageType = wxBITMAP_TYPE_JPEG;
4968 }
4969 wxFile file;
4970 if (!file.Open(filenameToRead))
4971 return false;
4972
4973 m_dataSize = (size_t) file.Length();
4974 file.Close();
4975
4976 if (m_data)
4977 delete[] m_data;
4978 m_data = ReadBlock(filenameToRead, m_dataSize);
4979
4980 if (removeFile)
4981 wxRemoveFile(filenameToRead);
4982
4983 return (m_data != NULL);
4984 }
4985
4986 // Make an image block from the wxImage in the given
4987 // format.
4988 bool wxRichTextImageBlock::MakeImageBlock(wxImage& image, int imageType, int quality)
4989 {
4990 m_imageType = imageType;
4991 image.SetOption(wxT("quality"), quality);
4992
4993 if (imageType == -1)
4994 return false; // Could not determine image type
4995
4996 wxString tempFile;
4997 bool success = wxGetTempFileName(_("image"), tempFile) ;
4998
4999 wxASSERT(success);
5000 wxUnusedVar(success);
5001
5002 if (!image.SaveFile(tempFile, m_imageType))
5003 {
5004 if (wxFileExists(tempFile))
5005 wxRemoveFile(tempFile);
5006 return false;
5007 }
5008
5009 wxFile file;
5010 if (!file.Open(tempFile))
5011 return false;
5012
5013 m_dataSize = (size_t) file.Length();
5014 file.Close();
5015
5016 if (m_data)
5017 delete[] m_data;
5018 m_data = ReadBlock(tempFile, m_dataSize);
5019
5020 wxRemoveFile(tempFile);
5021
5022 return (m_data != NULL);
5023 }
5024
5025
5026 // Write to a file
5027 bool wxRichTextImageBlock::Write(const wxString& filename)
5028 {
5029 return WriteBlock(filename, m_data, m_dataSize);
5030 }
5031
5032 void wxRichTextImageBlock::Copy(const wxRichTextImageBlock& block)
5033 {
5034 m_imageType = block.m_imageType;
5035 if (m_data)
5036 {
5037 delete[] m_data;
5038 m_data = NULL;
5039 }
5040 m_dataSize = block.m_dataSize;
5041 if (m_dataSize == 0)
5042 return;
5043
5044 m_data = new unsigned char[m_dataSize];
5045 unsigned int i;
5046 for (i = 0; i < m_dataSize; i++)
5047 m_data[i] = block.m_data[i];
5048 }
5049
5050 //// Operators
5051 void wxRichTextImageBlock::operator=(const wxRichTextImageBlock& block)
5052 {
5053 Copy(block);
5054 }
5055
5056 // Load a wxImage from the block
5057 bool wxRichTextImageBlock::Load(wxImage& image)
5058 {
5059 if (!m_data)
5060 return false;
5061
5062 // Read in the image.
5063 #if 1
5064 wxMemoryInputStream mstream(m_data, m_dataSize);
5065 bool success = image.LoadFile(mstream, GetImageType());
5066 #else
5067 wxString tempFile;
5068 bool success = wxGetTempFileName(_("image"), tempFile) ;
5069 wxASSERT(success);
5070
5071 if (!WriteBlock(tempFile, m_data, m_dataSize))
5072 {
5073 return false;
5074 }
5075 success = image.LoadFile(tempFile, GetImageType());
5076 wxRemoveFile(tempFile);
5077 #endif
5078
5079 return success;
5080 }
5081
5082 // Write data in hex to a stream
5083 bool wxRichTextImageBlock::WriteHex(wxOutputStream& stream)
5084 {
5085 wxString hex;
5086 int i;
5087 for (i = 0; i < (int) m_dataSize; i++)
5088 {
5089 hex = wxDecToHex(m_data[i]);
5090 wxCharBuffer buf = hex.ToAscii();
5091
5092 stream.Write((const char*) buf, hex.Length());
5093 }
5094
5095 return true;
5096 }
5097
5098 // Read data in hex from a stream
5099 bool wxRichTextImageBlock::ReadHex(wxInputStream& stream, int length, int imageType)
5100 {
5101 int dataSize = length/2;
5102
5103 if (m_data)
5104 delete[] m_data;
5105
5106 wxString str(wxT(" "));
5107 m_data = new unsigned char[dataSize];
5108 int i;
5109 for (i = 0; i < dataSize; i ++)
5110 {
5111 str[0] = stream.GetC();
5112 str[1] = stream.GetC();
5113
5114 m_data[i] = (unsigned char)wxHexToDec(str);
5115 }
5116
5117 m_dataSize = dataSize;
5118 m_imageType = imageType;
5119
5120 return true;
5121 }
5122
5123
5124 // Allocate and read from stream as a block of memory
5125 unsigned char* wxRichTextImageBlock::ReadBlock(wxInputStream& stream, size_t size)
5126 {
5127 unsigned char* block = new unsigned char[size];
5128 if (!block)
5129 return NULL;
5130
5131 stream.Read(block, size);
5132
5133 return block;
5134 }
5135
5136 unsigned char* wxRichTextImageBlock::ReadBlock(const wxString& filename, size_t size)
5137 {
5138 wxFileInputStream stream(filename);
5139 if (!stream.Ok())
5140 return NULL;
5141
5142 return ReadBlock(stream, size);
5143 }
5144
5145 // Write memory block to stream
5146 bool wxRichTextImageBlock::WriteBlock(wxOutputStream& stream, unsigned char* block, size_t size)
5147 {
5148 stream.Write((void*) block, size);
5149 return stream.IsOk();
5150
5151 }
5152
5153 // Write memory block to file
5154 bool wxRichTextImageBlock::WriteBlock(const wxString& filename, unsigned char* block, size_t size)
5155 {
5156 wxFileOutputStream outStream(filename);
5157 if (!outStream.Ok())
5158 return false;
5159
5160 return WriteBlock(outStream, block, size);
5161 }
5162
5163 #endif
5164 // wxUSE_RICHTEXT
5165