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