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