]> git.saurik.com Git - wxWidgets.git/blob - src/richtext/richtextbuffer.cpp
simplify/cleanup wxTheXXXList and wxGDIObject code (patch 1452023 from Paul Cornett)
[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 text.Replace(_T("\r\n"), _T("\n"));
3858
3859 InsertTextWithUndo(position+1, text, GetRichTextCtrl());
3860
3861 success = true;
3862 }
3863 else if (wxTheClipboard->IsSupported(wxDF_BITMAP))
3864 {
3865 wxBitmapDataObject data;
3866 wxTheClipboard->GetData(data);
3867 wxBitmap bitmap(data.GetBitmap());
3868 wxImage image(bitmap.ConvertToImage());
3869
3870 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Image"), wxRICHTEXT_INSERT, this, GetRichTextCtrl(), false);
3871
3872 action->GetNewParagraphs().AddImage(image);
3873
3874 if (action->GetNewParagraphs().GetChildCount() == 1)
3875 action->GetNewParagraphs().SetPartialParagraph(true);
3876
3877 action->SetPosition(position);
3878
3879 // Set the range we'll need to delete in Undo
3880 action->SetRange(wxRichTextRange(position, position));
3881
3882 SubmitAction(action);
3883
3884 success = true;
3885 }
3886 wxTheClipboard->Close();
3887 }
3888 }
3889 #else
3890 wxUnusedVar(position);
3891 #endif
3892 return success;
3893 }
3894
3895 /// Can we paste from the clipboard?
3896 bool wxRichTextBuffer::CanPasteFromClipboard() const
3897 {
3898 bool canPaste = false;
3899 #if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
3900 if (!wxTheClipboard->IsOpened() && wxTheClipboard->Open())
3901 {
3902 if (wxTheClipboard->IsSupported(wxDF_TEXT) || wxTheClipboard->IsSupported(wxDF_BITMAP))
3903 {
3904 canPaste = true;
3905 }
3906 wxTheClipboard->Close();
3907 }
3908 #endif
3909 return canPaste;
3910 }
3911
3912 /// Dumps contents of buffer for debugging purposes
3913 void wxRichTextBuffer::Dump()
3914 {
3915 wxString text;
3916 {
3917 wxStringOutputStream stream(& text);
3918 wxTextOutputStream textStream(stream);
3919 Dump(textStream);
3920 }
3921
3922 wxLogDebug(text);
3923 }
3924
3925
3926 /*
3927 * Module to initialise and clean up handlers
3928 */
3929
3930 class wxRichTextModule: public wxModule
3931 {
3932 DECLARE_DYNAMIC_CLASS(wxRichTextModule)
3933 public:
3934 wxRichTextModule() {}
3935 bool OnInit() { wxRichTextBuffer::InitStandardHandlers(); return true; };
3936 void OnExit() { wxRichTextBuffer::CleanUpHandlers(); };
3937 };
3938
3939 IMPLEMENT_DYNAMIC_CLASS(wxRichTextModule, wxModule)
3940
3941
3942 /*!
3943 * Commands for undo/redo
3944 *
3945 */
3946
3947 wxRichTextCommand::wxRichTextCommand(const wxString& name, wxRichTextCommandId id, wxRichTextBuffer* buffer,
3948 wxRichTextCtrl* ctrl, bool ignoreFirstTime): wxCommand(true, name)
3949 {
3950 /* wxRichTextAction* action = */ new wxRichTextAction(this, name, id, buffer, ctrl, ignoreFirstTime);
3951 }
3952
3953 wxRichTextCommand::wxRichTextCommand(const wxString& name): wxCommand(true, name)
3954 {
3955 }
3956
3957 wxRichTextCommand::~wxRichTextCommand()
3958 {
3959 ClearActions();
3960 }
3961
3962 void wxRichTextCommand::AddAction(wxRichTextAction* action)
3963 {
3964 if (!m_actions.Member(action))
3965 m_actions.Append(action);
3966 }
3967
3968 bool wxRichTextCommand::Do()
3969 {
3970 for (wxList::compatibility_iterator node = m_actions.GetFirst(); node; node = node->GetNext())
3971 {
3972 wxRichTextAction* action = (wxRichTextAction*) node->GetData();
3973 action->Do();
3974 }
3975
3976 return true;
3977 }
3978
3979 bool wxRichTextCommand::Undo()
3980 {
3981 for (wxList::compatibility_iterator node = m_actions.GetLast(); node; node = node->GetPrevious())
3982 {
3983 wxRichTextAction* action = (wxRichTextAction*) node->GetData();
3984 action->Undo();
3985 }
3986
3987 return true;
3988 }
3989
3990 void wxRichTextCommand::ClearActions()
3991 {
3992 WX_CLEAR_LIST(wxList, m_actions);
3993 }
3994
3995 /*!
3996 * Individual action
3997 *
3998 */
3999
4000 wxRichTextAction::wxRichTextAction(wxRichTextCommand* cmd, const wxString& name, wxRichTextCommandId id, wxRichTextBuffer* buffer,
4001 wxRichTextCtrl* ctrl, bool ignoreFirstTime)
4002 {
4003 m_buffer = buffer;
4004 m_ignoreThis = ignoreFirstTime;
4005 m_cmdId = id;
4006 m_position = -1;
4007 m_ctrl = ctrl;
4008 m_name = name;
4009 m_newParagraphs.SetDefaultStyle(buffer->GetDefaultStyle());
4010 m_newParagraphs.SetBasicStyle(buffer->GetBasicStyle());
4011 if (cmd)
4012 cmd->AddAction(this);
4013 }
4014
4015 wxRichTextAction::~wxRichTextAction()
4016 {
4017 }
4018
4019 bool wxRichTextAction::Do()
4020 {
4021 m_buffer->Modify(true);
4022
4023 switch (m_cmdId)
4024 {
4025 case wxRICHTEXT_INSERT:
4026 {
4027 m_buffer->InsertFragment(GetPosition(), m_newParagraphs);
4028 m_buffer->UpdateRanges();
4029 m_buffer->Invalidate(GetRange());
4030
4031 long newCaretPosition = GetPosition() + m_newParagraphs.GetRange().GetLength() - 1;
4032 if (m_newParagraphs.GetPartialParagraph())
4033 newCaretPosition --;
4034
4035 UpdateAppearance(newCaretPosition, true /* send update event */);
4036
4037 break;
4038 }
4039 case wxRICHTEXT_DELETE:
4040 {
4041 m_buffer->DeleteRange(GetRange());
4042 m_buffer->UpdateRanges();
4043 m_buffer->Invalidate(wxRichTextRange(GetRange().GetStart(), GetRange().GetStart()));
4044
4045 UpdateAppearance(GetRange().GetStart()-1, true /* send update event */);
4046
4047 break;
4048 }
4049 case wxRICHTEXT_CHANGE_STYLE:
4050 {
4051 ApplyParagraphs(GetNewParagraphs());
4052 m_buffer->Invalidate(GetRange());
4053
4054 UpdateAppearance(GetPosition());
4055
4056 break;
4057 }
4058 default:
4059 break;
4060 }
4061
4062 return true;
4063 }
4064
4065 bool wxRichTextAction::Undo()
4066 {
4067 m_buffer->Modify(true);
4068
4069 switch (m_cmdId)
4070 {
4071 case wxRICHTEXT_INSERT:
4072 {
4073 m_buffer->DeleteRange(GetRange());
4074 m_buffer->UpdateRanges();
4075 m_buffer->Invalidate(wxRichTextRange(GetRange().GetStart(), GetRange().GetStart()));
4076
4077 long newCaretPosition = GetPosition() - 1;
4078 // if (m_newParagraphs.GetPartialParagraph())
4079 // newCaretPosition --;
4080
4081 UpdateAppearance(newCaretPosition, true /* send update event */);
4082
4083 break;
4084 }
4085 case wxRICHTEXT_DELETE:
4086 {
4087 m_buffer->InsertFragment(GetRange().GetStart(), m_oldParagraphs);
4088 m_buffer->UpdateRanges();
4089 m_buffer->Invalidate(GetRange());
4090
4091 UpdateAppearance(GetPosition(), true /* send update event */);
4092
4093 break;
4094 }
4095 case wxRICHTEXT_CHANGE_STYLE:
4096 {
4097 ApplyParagraphs(GetOldParagraphs());
4098 m_buffer->Invalidate(GetRange());
4099
4100 UpdateAppearance(GetPosition());
4101
4102 break;
4103 }
4104 default:
4105 break;
4106 }
4107
4108 return true;
4109 }
4110
4111 /// Update the control appearance
4112 void wxRichTextAction::UpdateAppearance(long caretPosition, bool sendUpdateEvent)
4113 {
4114 if (m_ctrl)
4115 {
4116 m_ctrl->SetCaretPosition(caretPosition);
4117 if (!m_ctrl->IsFrozen())
4118 {
4119 m_ctrl->LayoutContent();
4120 m_ctrl->PositionCaret();
4121 m_ctrl->Refresh(false);
4122
4123 if (sendUpdateEvent)
4124 m_ctrl->SendUpdateEvent();
4125 }
4126 }
4127 }
4128
4129 /// Replace the buffer paragraphs with the new ones.
4130 void wxRichTextAction::ApplyParagraphs(const wxRichTextFragment& fragment)
4131 {
4132 wxRichTextObjectList::compatibility_iterator node = fragment.GetChildren().GetFirst();
4133 while (node)
4134 {
4135 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
4136 wxASSERT (para != NULL);
4137
4138 // We'll replace the existing paragraph by finding the paragraph at this position,
4139 // delete its node data, and setting a copy as the new node data.
4140 // TODO: make more efficient by simply swapping old and new paragraph objects.
4141
4142 wxRichTextParagraph* existingPara = m_buffer->GetParagraphAtPosition(para->GetRange().GetStart());
4143 if (existingPara)
4144 {
4145 wxRichTextObjectList::compatibility_iterator bufferParaNode = m_buffer->GetChildren().Find(existingPara);
4146 if (bufferParaNode)
4147 {
4148 wxRichTextParagraph* newPara = new wxRichTextParagraph(*para);
4149 newPara->SetParent(m_buffer);
4150
4151 bufferParaNode->SetData(newPara);
4152
4153 delete existingPara;
4154 }
4155 }
4156
4157 node = node->GetNext();
4158 }
4159 }
4160
4161
4162 /*!
4163 * wxRichTextRange
4164 * This stores beginning and end positions for a range of data.
4165 */
4166
4167 /// Limit this range to be within 'range'
4168 bool wxRichTextRange::LimitTo(const wxRichTextRange& range)
4169 {
4170 if (m_start < range.m_start)
4171 m_start = range.m_start;
4172
4173 if (m_end > range.m_end)
4174 m_end = range.m_end;
4175
4176 return true;
4177 }
4178
4179 /*!
4180 * wxRichTextImage implementation
4181 * This object represents an image.
4182 */
4183
4184 IMPLEMENT_DYNAMIC_CLASS(wxRichTextImage, wxRichTextObject)
4185
4186 wxRichTextImage::wxRichTextImage(const wxImage& image, wxRichTextObject* parent):
4187 wxRichTextObject(parent)
4188 {
4189 m_image = image;
4190 }
4191
4192 wxRichTextImage::wxRichTextImage(const wxRichTextImageBlock& imageBlock, wxRichTextObject* parent):
4193 wxRichTextObject(parent)
4194 {
4195 m_imageBlock = imageBlock;
4196 m_imageBlock.Load(m_image);
4197 }
4198
4199 /// Load wxImage from the block
4200 bool wxRichTextImage::LoadFromBlock()
4201 {
4202 m_imageBlock.Load(m_image);
4203 return m_imageBlock.Ok();
4204 }
4205
4206 /// Make block from the wxImage
4207 bool wxRichTextImage::MakeBlock()
4208 {
4209 if (m_imageBlock.GetImageType() == wxBITMAP_TYPE_ANY || m_imageBlock.GetImageType() == -1)
4210 m_imageBlock.SetImageType(wxBITMAP_TYPE_PNG);
4211
4212 m_imageBlock.MakeImageBlock(m_image, m_imageBlock.GetImageType());
4213 return m_imageBlock.Ok();
4214 }
4215
4216
4217 /// Draw the item
4218 bool wxRichTextImage::Draw(wxDC& dc, const wxRichTextRange& range, const wxRichTextRange& selectionRange, const wxRect& rect, int WXUNUSED(descent), int WXUNUSED(style))
4219 {
4220 if (!m_image.Ok() && m_imageBlock.Ok())
4221 LoadFromBlock();
4222
4223 if (!m_image.Ok())
4224 return false;
4225
4226 if (m_image.Ok() && !m_bitmap.Ok())
4227 m_bitmap = wxBitmap(m_image);
4228
4229 int y = rect.y + (rect.height - m_image.GetHeight());
4230
4231 if (m_bitmap.Ok())
4232 dc.DrawBitmap(m_bitmap, rect.x, y, true);
4233
4234 if (selectionRange.Contains(range.GetStart()))
4235 {
4236 dc.SetBrush(*wxBLACK_BRUSH);
4237 dc.SetPen(*wxBLACK_PEN);
4238 dc.SetLogicalFunction(wxINVERT);
4239 dc.DrawRectangle(rect);
4240 dc.SetLogicalFunction(wxCOPY);
4241 }
4242
4243 return true;
4244 }
4245
4246 /// Lay the item out
4247 bool wxRichTextImage::Layout(wxDC& WXUNUSED(dc), const wxRect& rect, int WXUNUSED(style))
4248 {
4249 if (!m_image.Ok())
4250 LoadFromBlock();
4251
4252 if (m_image.Ok())
4253 {
4254 SetCachedSize(wxSize(m_image.GetWidth(), m_image.GetHeight()));
4255 SetPosition(rect.GetPosition());
4256 }
4257
4258 return true;
4259 }
4260
4261 /// Get/set the object size for the given range. Returns false if the range
4262 /// is invalid for this object.
4263 bool wxRichTextImage::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& WXUNUSED(descent), wxDC& WXUNUSED(dc), int WXUNUSED(flags)) const
4264 {
4265 if (!range.IsWithin(GetRange()))
4266 return false;
4267
4268 if (!m_image.Ok())
4269 return false;
4270
4271 size.x = m_image.GetWidth();
4272 size.y = m_image.GetHeight();
4273
4274 return true;
4275 }
4276
4277 /// Copy
4278 void wxRichTextImage::Copy(const wxRichTextImage& obj)
4279 {
4280 m_image = obj.m_image;
4281 m_imageBlock = obj.m_imageBlock;
4282 }
4283
4284 /*!
4285 * Utilities
4286 *
4287 */
4288
4289 /// Compare two attribute objects
4290 bool wxTextAttrEq(const wxTextAttrEx& attr1, const wxTextAttrEx& attr2)
4291 {
4292 return (
4293 attr1.GetTextColour() == attr2.GetTextColour() &&
4294 attr1.GetBackgroundColour() == attr2.GetBackgroundColour() &&
4295 attr1.GetFont() == attr2.GetFont() &&
4296 attr1.GetAlignment() == attr2.GetAlignment() &&
4297 attr1.GetLeftIndent() == attr2.GetLeftIndent() &&
4298 attr1.GetRightIndent() == attr2.GetRightIndent() &&
4299 attr1.GetLeftSubIndent() == attr2.GetLeftSubIndent() &&
4300 attr1.GetTabs().GetCount() == attr2.GetTabs().GetCount() && // heuristic
4301 attr1.GetLineSpacing() == attr2.GetLineSpacing() &&
4302 attr1.GetParagraphSpacingAfter() == attr2.GetParagraphSpacingAfter() &&
4303 attr1.GetParagraphSpacingBefore() == attr2.GetParagraphSpacingBefore() &&
4304 attr1.GetBulletStyle() == attr2.GetBulletStyle() &&
4305 attr1.GetBulletNumber() == attr2.GetBulletNumber() &&
4306 attr1.GetBulletSymbol() == attr2.GetBulletSymbol() &&
4307 attr1.GetCharacterStyleName() == attr2.GetCharacterStyleName() &&
4308 attr1.GetParagraphStyleName() == attr2.GetParagraphStyleName());
4309 }
4310
4311 bool wxTextAttrEq(const wxTextAttrEx& attr1, const wxRichTextAttr& attr2)
4312 {
4313 return (
4314 attr1.GetTextColour() == attr2.GetTextColour() &&
4315 attr1.GetBackgroundColour() == attr2.GetBackgroundColour() &&
4316 attr1.GetFont().GetPointSize() == attr2.GetFontSize() &&
4317 attr1.GetFont().GetStyle() == attr2.GetFontStyle() &&
4318 attr1.GetFont().GetWeight() == attr2.GetFontWeight() &&
4319 attr1.GetFont().GetFaceName() == attr2.GetFontFaceName() &&
4320 attr1.GetFont().GetUnderlined() == attr2.GetFontUnderlined() &&
4321 attr1.GetAlignment() == attr2.GetAlignment() &&
4322 attr1.GetLeftIndent() == attr2.GetLeftIndent() &&
4323 attr1.GetRightIndent() == attr2.GetRightIndent() &&
4324 attr1.GetLeftSubIndent() == attr2.GetLeftSubIndent() &&
4325 attr1.GetTabs().GetCount() == attr2.GetTabs().GetCount() && // heuristic
4326 attr1.GetLineSpacing() == attr2.GetLineSpacing() &&
4327 attr1.GetParagraphSpacingAfter() == attr2.GetParagraphSpacingAfter() &&
4328 attr1.GetParagraphSpacingBefore() == attr2.GetParagraphSpacingBefore() &&
4329 attr1.GetBulletStyle() == attr2.GetBulletStyle() &&
4330 attr1.GetBulletNumber() == attr2.GetBulletNumber() &&
4331 attr1.GetBulletSymbol() == attr2.GetBulletSymbol() &&
4332 attr1.GetCharacterStyleName() == attr2.GetCharacterStyleName() &&
4333 attr1.GetParagraphStyleName() == attr2.GetParagraphStyleName());
4334 }
4335
4336 /// Compare two attribute objects, but take into account the flags
4337 /// specifying attributes of interest.
4338 bool wxTextAttrEqPartial(const wxTextAttrEx& attr1, const wxTextAttrEx& attr2, int flags)
4339 {
4340 if ((flags & wxTEXT_ATTR_TEXT_COLOUR) && attr1.GetTextColour() != attr2.GetTextColour())
4341 return false;
4342
4343 if ((flags & wxTEXT_ATTR_BACKGROUND_COLOUR) && attr1.GetBackgroundColour() != attr2.GetBackgroundColour())
4344 return false;
4345
4346 if ((flags & wxTEXT_ATTR_FONT_FACE) && attr1.GetFont().Ok() && attr2.GetFont().Ok() &&
4347 attr1.GetFont().GetFaceName() != attr2.GetFont().GetFaceName())
4348 return false;
4349
4350 if ((flags & wxTEXT_ATTR_FONT_SIZE) && attr1.GetFont().Ok() && attr2.GetFont().Ok() &&
4351 attr1.GetFont().GetPointSize() != attr2.GetFont().GetPointSize())
4352 return false;
4353
4354 if ((flags & wxTEXT_ATTR_FONT_WEIGHT) && attr1.GetFont().Ok() && attr2.GetFont().Ok() &&
4355 attr1.GetFont().GetWeight() != attr2.GetFont().GetWeight())
4356 return false;
4357
4358 if ((flags & wxTEXT_ATTR_FONT_ITALIC) && attr1.GetFont().Ok() && attr2.GetFont().Ok() &&
4359 attr1.GetFont().GetStyle() != attr2.GetFont().GetStyle())
4360 return false;
4361
4362 if ((flags & wxTEXT_ATTR_FONT_UNDERLINE) && attr1.GetFont().Ok() && attr2.GetFont().Ok() &&
4363 attr1.GetFont().GetUnderlined() != attr2.GetFont().GetUnderlined())
4364 return false;
4365
4366 if ((flags & wxTEXT_ATTR_ALIGNMENT) && attr1.GetAlignment() != attr2.GetAlignment())
4367 return false;
4368
4369 if ((flags & wxTEXT_ATTR_LEFT_INDENT) &&
4370 ((attr1.GetLeftIndent() != attr2.GetLeftIndent()) || (attr1.GetLeftSubIndent() != attr2.GetLeftSubIndent())))
4371 return false;
4372
4373 if ((flags & wxTEXT_ATTR_RIGHT_INDENT) &&
4374 (attr1.GetRightIndent() != attr2.GetRightIndent()))
4375 return false;
4376
4377 if ((flags & wxTEXT_ATTR_PARA_SPACING_AFTER) &&
4378 (attr1.GetParagraphSpacingAfter() != attr2.GetParagraphSpacingAfter()))
4379 return false;
4380
4381 if ((flags & wxTEXT_ATTR_PARA_SPACING_BEFORE) &&
4382 (attr1.GetParagraphSpacingBefore() != attr2.GetParagraphSpacingBefore()))
4383 return false;
4384
4385 if ((flags & wxTEXT_ATTR_LINE_SPACING) &&
4386 (attr1.GetLineSpacing() != attr2.GetLineSpacing()))
4387 return false;
4388
4389 if ((flags & wxTEXT_ATTR_CHARACTER_STYLE_NAME) &&
4390 (attr1.GetCharacterStyleName() != attr2.GetCharacterStyleName()))
4391 return false;
4392
4393 if ((flags & wxTEXT_ATTR_PARAGRAPH_STYLE_NAME) &&
4394 (attr1.GetParagraphStyleName() != attr2.GetParagraphStyleName()))
4395 return false;
4396
4397 if ((flags & wxTEXT_ATTR_BULLET_STYLE) &&
4398 (attr1.GetBulletStyle() != attr2.GetBulletStyle()))
4399 return false;
4400
4401 if ((flags & wxTEXT_ATTR_BULLET_NUMBER) &&
4402 (attr1.GetBulletNumber() != attr2.GetBulletNumber()))
4403 return false;
4404
4405 if ((flags & wxTEXT_ATTR_BULLET_SYMBOL) &&
4406 (attr1.GetBulletSymbol() != attr2.GetBulletSymbol()))
4407 return false;
4408
4409 /* TODO
4410 if ((flags & wxTEXT_ATTR_TABS) &&
4411 return false;
4412 */
4413
4414 return true;
4415 }
4416
4417 bool wxTextAttrEqPartial(const wxTextAttrEx& attr1, const wxRichTextAttr& attr2, int flags)
4418 {
4419 if ((flags & wxTEXT_ATTR_TEXT_COLOUR) && attr1.GetTextColour() != attr2.GetTextColour())
4420 return false;
4421
4422 if ((flags & wxTEXT_ATTR_BACKGROUND_COLOUR) && attr1.GetBackgroundColour() != attr2.GetBackgroundColour())
4423 return false;
4424
4425 if ((flags & (wxTEXT_ATTR_FONT)) && !attr1.GetFont().Ok())
4426 return false;
4427
4428 if ((flags & wxTEXT_ATTR_FONT_FACE) && attr1.GetFont().Ok() &&
4429 attr1.GetFont().GetFaceName() != attr2.GetFontFaceName())
4430 return false;
4431
4432 if ((flags & wxTEXT_ATTR_FONT_SIZE) && attr1.GetFont().Ok() &&
4433 attr1.GetFont().GetPointSize() != attr2.GetFontSize())
4434 return false;
4435
4436 if ((flags & wxTEXT_ATTR_FONT_WEIGHT) && attr1.GetFont().Ok() &&
4437 attr1.GetFont().GetWeight() != attr2.GetFontWeight())
4438 return false;
4439
4440 if ((flags & wxTEXT_ATTR_FONT_ITALIC) && attr1.GetFont().Ok() &&
4441 attr1.GetFont().GetStyle() != attr2.GetFontStyle())
4442 return false;
4443
4444 if ((flags & wxTEXT_ATTR_FONT_UNDERLINE) && attr1.GetFont().Ok() &&
4445 attr1.GetFont().GetUnderlined() != attr2.GetFontUnderlined())
4446 return false;
4447
4448 if ((flags & wxTEXT_ATTR_ALIGNMENT) && attr1.GetAlignment() != attr2.GetAlignment())
4449 return false;
4450
4451 if ((flags & wxTEXT_ATTR_LEFT_INDENT) &&
4452 ((attr1.GetLeftIndent() != attr2.GetLeftIndent()) || (attr1.GetLeftSubIndent() != attr2.GetLeftSubIndent())))
4453 return false;
4454
4455 if ((flags & wxTEXT_ATTR_RIGHT_INDENT) &&
4456 (attr1.GetRightIndent() != attr2.GetRightIndent()))
4457 return false;
4458
4459 if ((flags & wxTEXT_ATTR_PARA_SPACING_AFTER) &&
4460 (attr1.GetParagraphSpacingAfter() != attr2.GetParagraphSpacingAfter()))
4461 return false;
4462
4463 if ((flags & wxTEXT_ATTR_PARA_SPACING_BEFORE) &&
4464 (attr1.GetParagraphSpacingBefore() != attr2.GetParagraphSpacingBefore()))
4465 return false;
4466
4467 if ((flags & wxTEXT_ATTR_LINE_SPACING) &&
4468 (attr1.GetLineSpacing() != attr2.GetLineSpacing()))
4469 return false;
4470
4471 if ((flags & wxTEXT_ATTR_CHARACTER_STYLE_NAME) &&
4472 (attr1.GetCharacterStyleName() != attr2.GetCharacterStyleName()))
4473 return false;
4474
4475 if ((flags & wxTEXT_ATTR_PARAGRAPH_STYLE_NAME) &&
4476 (attr1.GetParagraphStyleName() != attr2.GetParagraphStyleName()))
4477 return false;
4478
4479 if ((flags & wxTEXT_ATTR_BULLET_STYLE) &&
4480 (attr1.GetBulletStyle() != attr2.GetBulletStyle()))
4481 return false;
4482
4483 if ((flags & wxTEXT_ATTR_BULLET_NUMBER) &&
4484 (attr1.GetBulletNumber() != attr2.GetBulletNumber()))
4485 return false;
4486
4487 if ((flags & wxTEXT_ATTR_BULLET_SYMBOL) &&
4488 (attr1.GetBulletSymbol() != attr2.GetBulletSymbol()))
4489 return false;
4490
4491 /* TODO
4492 if ((flags & wxTEXT_ATTR_TABS) &&
4493 return false;
4494 */
4495
4496 return true;
4497 }
4498
4499
4500 /// Apply one style to another
4501 bool wxRichTextApplyStyle(wxTextAttrEx& destStyle, const wxTextAttrEx& style)
4502 {
4503 // Whole font
4504 if (style.GetFont().Ok() && ((style.GetFlags() & (wxTEXT_ATTR_FONT)) == (wxTEXT_ATTR_FONT)))
4505 destStyle.SetFont(style.GetFont());
4506 else if (style.GetFont().Ok())
4507 {
4508 wxFont font = destStyle.GetFont();
4509
4510 if (style.GetFlags() & wxTEXT_ATTR_FONT_FACE)
4511 font.SetFaceName(style.GetFont().GetFaceName());
4512
4513 if (style.GetFlags() & wxTEXT_ATTR_FONT_SIZE)
4514 font.SetPointSize(style.GetFont().GetPointSize());
4515
4516 if (style.GetFlags() & wxTEXT_ATTR_FONT_ITALIC)
4517 font.SetStyle(style.GetFont().GetStyle());
4518
4519 if (style.GetFlags() & wxTEXT_ATTR_FONT_WEIGHT)
4520 font.SetWeight(style.GetFont().GetWeight());
4521
4522 if (style.GetFlags() & wxTEXT_ATTR_FONT_UNDERLINE)
4523 font.SetUnderlined(style.GetFont().GetUnderlined());
4524
4525 if (font != destStyle.GetFont())
4526 destStyle.SetFont(font);
4527 }
4528
4529 if ( style.GetTextColour().Ok() && style.HasTextColour())
4530 destStyle.SetTextColour(style.GetTextColour());
4531
4532 if ( style.GetBackgroundColour().Ok() && style.HasBackgroundColour())
4533 destStyle.SetBackgroundColour(style.GetBackgroundColour());
4534
4535 if (style.HasAlignment())
4536 destStyle.SetAlignment(style.GetAlignment());
4537
4538 if (style.HasTabs())
4539 destStyle.SetTabs(style.GetTabs());
4540
4541 if (style.HasLeftIndent())
4542 destStyle.SetLeftIndent(style.GetLeftIndent(), style.GetLeftSubIndent());
4543
4544 if (style.HasRightIndent())
4545 destStyle.SetRightIndent(style.GetRightIndent());
4546
4547 if (style.HasParagraphSpacingAfter())
4548 destStyle.SetParagraphSpacingAfter(style.GetParagraphSpacingAfter());
4549
4550 if (style.HasParagraphSpacingBefore())
4551 destStyle.SetParagraphSpacingBefore(style.GetParagraphSpacingBefore());
4552
4553 if (style.HasLineSpacing())
4554 destStyle.SetLineSpacing(style.GetLineSpacing());
4555
4556 if (style.HasCharacterStyleName())
4557 destStyle.SetCharacterStyleName(style.GetCharacterStyleName());
4558
4559 if (style.HasParagraphStyleName())
4560 destStyle.SetParagraphStyleName(style.GetParagraphStyleName());
4561
4562 if (style.HasBulletStyle())
4563 {
4564 destStyle.SetBulletStyle(style.GetBulletStyle());
4565 destStyle.SetBulletSymbol(style.GetBulletSymbol());
4566 }
4567
4568 if (style.HasBulletNumber())
4569 destStyle.SetBulletNumber(style.GetBulletNumber());
4570
4571 return true;
4572 }
4573
4574 bool wxRichTextApplyStyle(wxRichTextAttr& destStyle, const wxTextAttrEx& style)
4575 {
4576 wxTextAttrEx destStyle2;
4577 destStyle.CopyTo(destStyle2);
4578 wxRichTextApplyStyle(destStyle2, style);
4579 destStyle = destStyle2;
4580 return true;
4581 }
4582
4583 bool wxRichTextApplyStyle(wxTextAttrEx& destStyle, const wxRichTextAttr& style)
4584 {
4585
4586 // Whole font. Avoiding setting individual attributes if possible, since
4587 // it recreates the font each time.
4588 if ((style.GetFlags() & (wxTEXT_ATTR_FONT)) == (wxTEXT_ATTR_FONT))
4589 {
4590 destStyle.SetFont(wxFont(style.GetFontSize(), destStyle.GetFont().Ok() ? destStyle.GetFont().GetFamily() : wxDEFAULT,
4591 style.GetFontStyle(), style.GetFontWeight(), style.GetFontUnderlined(), style.GetFontFaceName()));
4592 }
4593 else if (style.GetFlags() & (wxTEXT_ATTR_FONT))
4594 {
4595 wxFont font = destStyle.GetFont();
4596
4597 if (style.GetFlags() & wxTEXT_ATTR_FONT_FACE)
4598 font.SetFaceName(style.GetFontFaceName());
4599
4600 if (style.GetFlags() & wxTEXT_ATTR_FONT_SIZE)
4601 font.SetPointSize(style.GetFontSize());
4602
4603 if (style.GetFlags() & wxTEXT_ATTR_FONT_ITALIC)
4604 font.SetStyle(style.GetFontStyle());
4605
4606 if (style.GetFlags() & wxTEXT_ATTR_FONT_WEIGHT)
4607 font.SetWeight(style.GetFontWeight());
4608
4609 if (style.GetFlags() & wxTEXT_ATTR_FONT_UNDERLINE)
4610 font.SetUnderlined(style.GetFontUnderlined());
4611
4612 if (font != destStyle.GetFont())
4613 destStyle.SetFont(font);
4614 }
4615
4616 if ( style.GetTextColour().Ok() && style.HasTextColour())
4617 destStyle.SetTextColour(style.GetTextColour());
4618
4619 if ( style.GetBackgroundColour().Ok() && style.HasBackgroundColour())
4620 destStyle.SetBackgroundColour(style.GetBackgroundColour());
4621
4622 if (style.HasAlignment())
4623 destStyle.SetAlignment(style.GetAlignment());
4624
4625 if (style.HasTabs())
4626 destStyle.SetTabs(style.GetTabs());
4627
4628 if (style.HasLeftIndent())
4629 destStyle.SetLeftIndent(style.GetLeftIndent(), style.GetLeftSubIndent());
4630
4631 if (style.HasRightIndent())
4632 destStyle.SetRightIndent(style.GetRightIndent());
4633
4634 if (style.HasParagraphSpacingAfter())
4635 destStyle.SetParagraphSpacingAfter(style.GetParagraphSpacingAfter());
4636
4637 if (style.HasParagraphSpacingBefore())
4638 destStyle.SetParagraphSpacingBefore(style.GetParagraphSpacingBefore());
4639
4640 if (style.HasLineSpacing())
4641 destStyle.SetLineSpacing(style.GetLineSpacing());
4642
4643 if (style.HasCharacterStyleName())
4644 destStyle.SetCharacterStyleName(style.GetCharacterStyleName());
4645
4646 if (style.HasParagraphStyleName())
4647 destStyle.SetParagraphStyleName(style.GetParagraphStyleName());
4648
4649 if (style.HasBulletStyle())
4650 {
4651 destStyle.SetBulletStyle(style.GetBulletStyle());
4652 destStyle.SetBulletSymbol(style.GetBulletSymbol());
4653 }
4654
4655 if (style.HasBulletNumber())
4656 destStyle.SetBulletNumber(style.GetBulletNumber());
4657
4658 return true;
4659 }
4660
4661
4662 /*!
4663 * wxRichTextAttr stores attributes without a wxFont object, so is a much more
4664 * efficient way to query styles.
4665 */
4666
4667 // ctors
4668 wxRichTextAttr::wxRichTextAttr(const wxColour& colText,
4669 const wxColour& colBack,
4670 wxTextAttrAlignment alignment): m_textAlignment(alignment), m_colText(colText), m_colBack(colBack)
4671 {
4672 Init();
4673
4674 if (m_colText.Ok()) m_flags |= wxTEXT_ATTR_TEXT_COLOUR;
4675 if (m_colBack.Ok()) m_flags |= wxTEXT_ATTR_BACKGROUND_COLOUR;
4676 if (alignment != wxTEXT_ALIGNMENT_DEFAULT)
4677 m_flags |= wxTEXT_ATTR_ALIGNMENT;
4678 }
4679
4680 wxRichTextAttr::wxRichTextAttr(const wxTextAttrEx& attr)
4681 {
4682 Init();
4683
4684 (*this) = attr;
4685 }
4686
4687 // operations
4688 void wxRichTextAttr::Init()
4689 {
4690 m_textAlignment = wxTEXT_ALIGNMENT_DEFAULT;
4691 m_flags = 0;
4692 m_leftIndent = 0;
4693 m_leftSubIndent = 0;
4694 m_rightIndent = 0;
4695
4696 m_fontSize = 12;
4697 m_fontStyle = wxNORMAL;
4698 m_fontWeight = wxNORMAL;
4699 m_fontUnderlined = false;
4700
4701 m_paragraphSpacingAfter = 0;
4702 m_paragraphSpacingBefore = 0;
4703 m_lineSpacing = 0;
4704 m_bulletStyle = wxTEXT_ATTR_BULLET_STYLE_NONE;
4705 m_bulletNumber = 0;
4706 m_bulletSymbol = wxT('*');
4707 }
4708
4709 // operators
4710 void wxRichTextAttr::operator= (const wxRichTextAttr& attr)
4711 {
4712 m_colText = attr.m_colText;
4713 m_colBack = attr.m_colBack;
4714 m_textAlignment = attr.m_textAlignment;
4715 m_leftIndent = attr.m_leftIndent;
4716 m_leftSubIndent = attr.m_leftSubIndent;
4717 m_rightIndent = attr.m_rightIndent;
4718 m_tabs = attr.m_tabs;
4719 m_flags = attr.m_flags;
4720
4721 m_fontSize = attr.m_fontSize;
4722 m_fontStyle = attr.m_fontStyle;
4723 m_fontWeight = attr.m_fontWeight;
4724 m_fontUnderlined = attr.m_fontUnderlined;
4725 m_fontFaceName = attr.m_fontFaceName;
4726
4727 m_paragraphSpacingAfter = attr.m_paragraphSpacingAfter;
4728 m_paragraphSpacingBefore = attr.m_paragraphSpacingBefore;
4729 m_lineSpacing = attr.m_lineSpacing;
4730 m_characterStyleName = attr.m_characterStyleName;
4731 m_paragraphStyleName = attr.m_paragraphStyleName;
4732 m_bulletStyle = attr.m_bulletStyle;
4733 m_bulletNumber = attr.m_bulletNumber;
4734 m_bulletSymbol = attr.m_bulletSymbol;
4735 }
4736
4737 // operators
4738 void wxRichTextAttr::operator= (const wxTextAttrEx& attr)
4739 {
4740 m_colText = attr.GetTextColour();
4741 m_colBack = attr.GetBackgroundColour();
4742 m_textAlignment = attr.GetAlignment();
4743 m_leftIndent = attr.GetLeftIndent();
4744 m_leftSubIndent = attr.GetLeftSubIndent();
4745 m_rightIndent = attr.GetRightIndent();
4746 m_tabs = attr.GetTabs();
4747 m_flags = attr.GetFlags();
4748
4749 m_paragraphSpacingAfter = attr.GetParagraphSpacingAfter();
4750 m_paragraphSpacingBefore = attr.GetParagraphSpacingBefore();
4751 m_lineSpacing = attr.GetLineSpacing();
4752 m_characterStyleName = attr.GetCharacterStyleName();
4753 m_paragraphStyleName = attr.GetParagraphStyleName();
4754
4755 if (attr.GetFont().Ok())
4756 GetFontAttributes(attr.GetFont());
4757 }
4758
4759 // Making a wxTextAttrEx object.
4760 wxRichTextAttr::operator wxTextAttrEx () const
4761 {
4762 wxTextAttrEx attr;
4763 CopyTo(attr);
4764 return attr;
4765 }
4766
4767 // Copy to a wxTextAttr
4768 void wxRichTextAttr::CopyTo(wxTextAttrEx& attr) const
4769 {
4770 attr.SetTextColour(GetTextColour());
4771 attr.SetBackgroundColour(GetBackgroundColour());
4772 attr.SetAlignment(GetAlignment());
4773 attr.SetTabs(GetTabs());
4774 attr.SetLeftIndent(GetLeftIndent(), GetLeftSubIndent());
4775 attr.SetRightIndent(GetRightIndent());
4776 attr.SetFont(CreateFont());
4777 attr.SetFlags(GetFlags()); // Important: set after SetFont, since SetFont sets flags
4778
4779 attr.SetParagraphSpacingAfter(m_paragraphSpacingAfter);
4780 attr.SetParagraphSpacingBefore(m_paragraphSpacingBefore);
4781 attr.SetLineSpacing(m_lineSpacing);
4782 attr.SetBulletStyle(m_bulletStyle);
4783 attr.SetBulletNumber(m_bulletNumber);
4784 attr.SetBulletSymbol(m_bulletSymbol);
4785 attr.SetCharacterStyleName(m_characterStyleName);
4786 attr.SetParagraphStyleName(m_paragraphStyleName);
4787
4788 }
4789
4790 // Create font from font attributes.
4791 wxFont wxRichTextAttr::CreateFont() const
4792 {
4793 wxFont font(m_fontSize, wxDEFAULT, m_fontStyle, m_fontWeight, m_fontUnderlined, m_fontFaceName);
4794 #ifdef __WXMAC__
4795 font.SetNoAntiAliasing(true);
4796 #endif
4797 return font;
4798 }
4799
4800 // Get attributes from font.
4801 bool wxRichTextAttr::GetFontAttributes(const wxFont& font)
4802 {
4803 if (!font.Ok())
4804 return false;
4805
4806 m_fontSize = font.GetPointSize();
4807 m_fontStyle = font.GetStyle();
4808 m_fontWeight = font.GetWeight();
4809 m_fontUnderlined = font.GetUnderlined();
4810 m_fontFaceName = font.GetFaceName();
4811
4812 return true;
4813 }
4814
4815 /*!
4816 * wxTextAttrEx is an extended version of wxTextAttr with more paragraph attributes.
4817 */
4818
4819 wxTextAttrEx::wxTextAttrEx(const wxTextAttrEx& attr): wxTextAttr(attr)
4820 {
4821 m_paragraphSpacingAfter = attr.m_paragraphSpacingAfter;
4822 m_paragraphSpacingBefore = attr.m_paragraphSpacingBefore;
4823 m_lineSpacing = attr.m_lineSpacing;
4824 m_paragraphStyleName = attr.m_paragraphStyleName;
4825 m_characterStyleName = attr.m_characterStyleName;
4826 m_bulletStyle = attr.m_bulletStyle;
4827 m_bulletNumber = attr.m_bulletNumber;
4828 m_bulletSymbol = attr.m_bulletSymbol;
4829 }
4830
4831 // Initialise this object.
4832 void wxTextAttrEx::Init()
4833 {
4834 m_paragraphSpacingAfter = 0;
4835 m_paragraphSpacingBefore = 0;
4836 m_lineSpacing = 0;
4837 m_bulletStyle = wxTEXT_ATTR_BULLET_STYLE_NONE;
4838 m_bulletNumber = 0;
4839 m_bulletSymbol = 0;
4840 m_bulletSymbol = wxT('*');
4841 }
4842
4843 // Assignment from a wxTextAttrEx object
4844 void wxTextAttrEx::operator= (const wxTextAttrEx& attr)
4845 {
4846 wxTextAttr::operator= (attr);
4847
4848 m_paragraphSpacingAfter = attr.m_paragraphSpacingAfter;
4849 m_paragraphSpacingBefore = attr.m_paragraphSpacingBefore;
4850 m_lineSpacing = attr.m_lineSpacing;
4851 m_characterStyleName = attr.m_characterStyleName;
4852 m_paragraphStyleName = attr.m_paragraphStyleName;
4853 m_bulletStyle = attr.m_bulletStyle;
4854 m_bulletNumber = attr.m_bulletNumber;
4855 m_bulletSymbol = attr.m_bulletSymbol;
4856 }
4857
4858 // Assignment from a wxTextAttr object.
4859 void wxTextAttrEx::operator= (const wxTextAttr& attr)
4860 {
4861 wxTextAttr::operator= (attr);
4862 }
4863
4864 /*!
4865 * wxRichTextFileHandler
4866 * Base class for file handlers
4867 */
4868
4869 IMPLEMENT_CLASS(wxRichTextFileHandler, wxObject)
4870
4871 #if wxUSE_STREAMS
4872 bool wxRichTextFileHandler::LoadFile(wxRichTextBuffer *buffer, const wxString& filename)
4873 {
4874 wxFFileInputStream stream(filename);
4875 if (stream.Ok())
4876 return LoadFile(buffer, stream);
4877
4878 return false;
4879 }
4880
4881 bool wxRichTextFileHandler::SaveFile(wxRichTextBuffer *buffer, const wxString& filename)
4882 {
4883 wxFFileOutputStream stream(filename);
4884 if (stream.Ok())
4885 return SaveFile(buffer, stream);
4886
4887 return false;
4888 }
4889 #endif // wxUSE_STREAMS
4890
4891 /// Can we handle this filename (if using files)? By default, checks the extension.
4892 bool wxRichTextFileHandler::CanHandle(const wxString& filename) const
4893 {
4894 wxString path, file, ext;
4895 wxSplitPath(filename, & path, & file, & ext);
4896
4897 return (ext.Lower() == GetExtension());
4898 }
4899
4900 /*!
4901 * wxRichTextTextHandler
4902 * Plain text handler
4903 */
4904
4905 IMPLEMENT_CLASS(wxRichTextPlainTextHandler, wxRichTextFileHandler)
4906
4907 #if wxUSE_STREAMS
4908 bool wxRichTextPlainTextHandler::DoLoadFile(wxRichTextBuffer *buffer, wxInputStream& stream)
4909 {
4910 if (!stream.IsOk())
4911 return false;
4912
4913 wxString str;
4914 int lastCh = 0;
4915
4916 while (!stream.Eof())
4917 {
4918 int ch = stream.GetC();
4919
4920 if (!stream.Eof())
4921 {
4922 if (ch == 10 && lastCh != 13)
4923 str += wxT('\n');
4924
4925 if (ch > 0 && ch != 10)
4926 str += wxChar(ch);
4927
4928 lastCh = ch;
4929 }
4930 }
4931
4932 buffer->Clear();
4933 buffer->AddParagraphs(str);
4934 buffer->UpdateRanges();
4935
4936 return true;
4937
4938 }
4939
4940 bool wxRichTextPlainTextHandler::DoSaveFile(wxRichTextBuffer *buffer, wxOutputStream& stream)
4941 {
4942 if (!stream.IsOk())
4943 return false;
4944
4945 wxString text = buffer->GetText();
4946 wxCharBuffer buf = text.ToAscii();
4947
4948 stream.Write((const char*) buf, text.Length());
4949 return true;
4950 }
4951 #endif // wxUSE_STREAMS
4952
4953 /*
4954 * Stores information about an image, in binary in-memory form
4955 */
4956
4957 wxRichTextImageBlock::wxRichTextImageBlock()
4958 {
4959 Init();
4960 }
4961
4962 wxRichTextImageBlock::wxRichTextImageBlock(const wxRichTextImageBlock& block):wxObject()
4963 {
4964 Init();
4965 Copy(block);
4966 }
4967
4968 wxRichTextImageBlock::~wxRichTextImageBlock()
4969 {
4970 if (m_data)
4971 {
4972 delete[] m_data;
4973 m_data = NULL;
4974 }
4975 }
4976
4977 void wxRichTextImageBlock::Init()
4978 {
4979 m_data = NULL;
4980 m_dataSize = 0;
4981 m_imageType = -1;
4982 }
4983
4984 void wxRichTextImageBlock::Clear()
4985 {
4986 delete[] m_data;
4987 m_data = NULL;
4988 m_dataSize = 0;
4989 m_imageType = -1;
4990 }
4991
4992
4993 // Load the original image into a memory block.
4994 // If the image is not a JPEG, we must convert it into a JPEG
4995 // to conserve space.
4996 // If it's not a JPEG we can make use of 'image', already scaled, so we don't have to
4997 // load the image a 2nd time.
4998
4999 bool wxRichTextImageBlock::MakeImageBlock(const wxString& filename, int imageType, wxImage& image, bool convertToJPEG)
5000 {
5001 m_imageType = imageType;
5002
5003 wxString filenameToRead(filename);
5004 bool removeFile = false;
5005
5006 if (imageType == -1)
5007 return false; // Could not determine image type
5008
5009 if ((imageType != wxBITMAP_TYPE_JPEG) && convertToJPEG)
5010 {
5011 wxString tempFile;
5012 bool success = wxGetTempFileName(_("image"), tempFile) ;
5013
5014 wxASSERT(success);
5015
5016 wxUnusedVar(success);
5017
5018 image.SaveFile(tempFile, wxBITMAP_TYPE_JPEG);
5019 filenameToRead = tempFile;
5020 removeFile = true;
5021
5022 m_imageType = wxBITMAP_TYPE_JPEG;
5023 }
5024 wxFile file;
5025 if (!file.Open(filenameToRead))
5026 return false;
5027
5028 m_dataSize = (size_t) file.Length();
5029 file.Close();
5030
5031 if (m_data)
5032 delete[] m_data;
5033 m_data = ReadBlock(filenameToRead, m_dataSize);
5034
5035 if (removeFile)
5036 wxRemoveFile(filenameToRead);
5037
5038 return (m_data != NULL);
5039 }
5040
5041 // Make an image block from the wxImage in the given
5042 // format.
5043 bool wxRichTextImageBlock::MakeImageBlock(wxImage& image, int imageType, int quality)
5044 {
5045 m_imageType = imageType;
5046 image.SetOption(wxT("quality"), quality);
5047
5048 if (imageType == -1)
5049 return false; // Could not determine image type
5050
5051 wxString tempFile;
5052 bool success = wxGetTempFileName(_("image"), tempFile) ;
5053
5054 wxASSERT(success);
5055 wxUnusedVar(success);
5056
5057 if (!image.SaveFile(tempFile, m_imageType))
5058 {
5059 if (wxFileExists(tempFile))
5060 wxRemoveFile(tempFile);
5061 return false;
5062 }
5063
5064 wxFile file;
5065 if (!file.Open(tempFile))
5066 return false;
5067
5068 m_dataSize = (size_t) file.Length();
5069 file.Close();
5070
5071 if (m_data)
5072 delete[] m_data;
5073 m_data = ReadBlock(tempFile, m_dataSize);
5074
5075 wxRemoveFile(tempFile);
5076
5077 return (m_data != NULL);
5078 }
5079
5080
5081 // Write to a file
5082 bool wxRichTextImageBlock::Write(const wxString& filename)
5083 {
5084 return WriteBlock(filename, m_data, m_dataSize);
5085 }
5086
5087 void wxRichTextImageBlock::Copy(const wxRichTextImageBlock& block)
5088 {
5089 m_imageType = block.m_imageType;
5090 if (m_data)
5091 {
5092 delete[] m_data;
5093 m_data = NULL;
5094 }
5095 m_dataSize = block.m_dataSize;
5096 if (m_dataSize == 0)
5097 return;
5098
5099 m_data = new unsigned char[m_dataSize];
5100 unsigned int i;
5101 for (i = 0; i < m_dataSize; i++)
5102 m_data[i] = block.m_data[i];
5103 }
5104
5105 //// Operators
5106 void wxRichTextImageBlock::operator=(const wxRichTextImageBlock& block)
5107 {
5108 Copy(block);
5109 }
5110
5111 // Load a wxImage from the block
5112 bool wxRichTextImageBlock::Load(wxImage& image)
5113 {
5114 if (!m_data)
5115 return false;
5116
5117 // Read in the image.
5118 #if 1
5119 wxMemoryInputStream mstream(m_data, m_dataSize);
5120 bool success = image.LoadFile(mstream, GetImageType());
5121 #else
5122 wxString tempFile;
5123 bool success = wxGetTempFileName(_("image"), tempFile) ;
5124 wxASSERT(success);
5125
5126 if (!WriteBlock(tempFile, m_data, m_dataSize))
5127 {
5128 return false;
5129 }
5130 success = image.LoadFile(tempFile, GetImageType());
5131 wxRemoveFile(tempFile);
5132 #endif
5133
5134 return success;
5135 }
5136
5137 // Write data in hex to a stream
5138 bool wxRichTextImageBlock::WriteHex(wxOutputStream& stream)
5139 {
5140 wxString hex;
5141 int i;
5142 for (i = 0; i < (int) m_dataSize; i++)
5143 {
5144 hex = wxDecToHex(m_data[i]);
5145 wxCharBuffer buf = hex.ToAscii();
5146
5147 stream.Write((const char*) buf, hex.Length());
5148 }
5149
5150 return true;
5151 }
5152
5153 // Read data in hex from a stream
5154 bool wxRichTextImageBlock::ReadHex(wxInputStream& stream, int length, int imageType)
5155 {
5156 int dataSize = length/2;
5157
5158 if (m_data)
5159 delete[] m_data;
5160
5161 wxString str(wxT(" "));
5162 m_data = new unsigned char[dataSize];
5163 int i;
5164 for (i = 0; i < dataSize; i ++)
5165 {
5166 str[0] = stream.GetC();
5167 str[1] = stream.GetC();
5168
5169 m_data[i] = (unsigned char)wxHexToDec(str);
5170 }
5171
5172 m_dataSize = dataSize;
5173 m_imageType = imageType;
5174
5175 return true;
5176 }
5177
5178
5179 // Allocate and read from stream as a block of memory
5180 unsigned char* wxRichTextImageBlock::ReadBlock(wxInputStream& stream, size_t size)
5181 {
5182 unsigned char* block = new unsigned char[size];
5183 if (!block)
5184 return NULL;
5185
5186 stream.Read(block, size);
5187
5188 return block;
5189 }
5190
5191 unsigned char* wxRichTextImageBlock::ReadBlock(const wxString& filename, size_t size)
5192 {
5193 wxFileInputStream stream(filename);
5194 if (!stream.Ok())
5195 return NULL;
5196
5197 return ReadBlock(stream, size);
5198 }
5199
5200 // Write memory block to stream
5201 bool wxRichTextImageBlock::WriteBlock(wxOutputStream& stream, unsigned char* block, size_t size)
5202 {
5203 stream.Write((void*) block, size);
5204 return stream.IsOk();
5205
5206 }
5207
5208 // Write memory block to file
5209 bool wxRichTextImageBlock::WriteBlock(const wxString& filename, unsigned char* block, size_t size)
5210 {
5211 wxFileOutputStream outStream(filename);
5212 if (!outStream.Ok())
5213 return false;
5214
5215 return WriteBlock(outStream, block, size);
5216 }
5217
5218 #endif
5219 // wxUSE_RICHTEXT