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