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