]> git.saurik.com Git - wxWidgets.git/blob - src/richtext/richtextbuffer.cpp
typofix
[wxWidgets.git] / src / richtext / richtextbuffer.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/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/dc.h"
25 #include "wx/intl.h"
26 #include "wx/log.h"
27 #include "wx/dataobj.h"
28 #include "wx/module.h"
29 #endif
30
31 #include "wx/settings.h"
32 #include "wx/filename.h"
33 #include "wx/clipbrd.h"
34 #include "wx/wfstream.h"
35 #include "wx/mstream.h"
36 #include "wx/sstream.h"
37 #include "wx/textfile.h"
38 #include "wx/hashmap.h"
39
40 #include "wx/richtext/richtextctrl.h"
41 #include "wx/richtext/richtextstyles.h"
42
43 #include "wx/listimpl.cpp"
44
45 WX_DEFINE_LIST(wxRichTextObjectList)
46 WX_DEFINE_LIST(wxRichTextLineList)
47
48 // Switch off if the platform doesn't like it for some reason
49 #define wxRICHTEXT_USE_OPTIMIZED_DRAWING 1
50
51 const wxChar wxRichTextLineBreakChar = (wxChar) 29;
52
53 // Helpers for efficiency
54
55 inline void wxCheckSetFont(wxDC& dc, const wxFont& font)
56 {
57 const wxFont& font1 = dc.GetFont();
58 if (font1.IsOk() && font.IsOk())
59 {
60 if (font1.GetPointSize() == font.GetPointSize() &&
61 font1.GetFamily() == font.GetFamily() &&
62 font1.GetStyle() == font.GetStyle() &&
63 font1.GetWeight() == font.GetWeight() &&
64 font1.GetUnderlined() == font.GetUnderlined() &&
65 font1.GetFaceName() == font.GetFaceName())
66 return;
67 }
68 dc.SetFont(font);
69 }
70
71 inline void wxCheckSetPen(wxDC& dc, const wxPen& pen)
72 {
73 const wxPen& pen1 = dc.GetPen();
74 if (pen1.IsOk() && pen.IsOk())
75 {
76 if (pen1.GetWidth() == pen.GetWidth() &&
77 pen1.GetStyle() == pen.GetStyle() &&
78 pen1.GetColour() == pen.GetColour())
79 return;
80 }
81 dc.SetPen(pen);
82 }
83
84 inline void wxCheckSetBrush(wxDC& dc, const wxBrush& brush)
85 {
86 const wxBrush& brush1 = dc.GetBrush();
87 if (brush1.IsOk() && brush.IsOk())
88 {
89 if (brush1.GetStyle() == brush.GetStyle() &&
90 brush1.GetColour() == brush.GetColour())
91 return;
92 }
93 dc.SetBrush(brush);
94 }
95
96 /*!
97 * wxRichTextObject
98 * This is the base for drawable objects.
99 */
100
101 IMPLEMENT_CLASS(wxRichTextObject, wxObject)
102
103 wxRichTextObject::wxRichTextObject(wxRichTextObject* parent)
104 {
105 m_dirty = false;
106 m_refCount = 1;
107 m_parent = parent;
108 m_leftMargin = 0;
109 m_rightMargin = 0;
110 m_topMargin = 0;
111 m_bottomMargin = 0;
112 m_descent = 0;
113 }
114
115 wxRichTextObject::~wxRichTextObject()
116 {
117 }
118
119 void wxRichTextObject::Dereference()
120 {
121 m_refCount --;
122 if (m_refCount <= 0)
123 delete this;
124 }
125
126 /// Copy
127 void wxRichTextObject::Copy(const wxRichTextObject& obj)
128 {
129 m_size = obj.m_size;
130 m_pos = obj.m_pos;
131 m_dirty = obj.m_dirty;
132 m_range = obj.m_range;
133 m_attributes = obj.m_attributes;
134 m_descent = obj.m_descent;
135 }
136
137 void wxRichTextObject::SetMargins(int margin)
138 {
139 m_leftMargin = m_rightMargin = m_topMargin = m_bottomMargin = margin;
140 }
141
142 void wxRichTextObject::SetMargins(int leftMargin, int rightMargin, int topMargin, int bottomMargin)
143 {
144 m_leftMargin = leftMargin;
145 m_rightMargin = rightMargin;
146 m_topMargin = topMargin;
147 m_bottomMargin = bottomMargin;
148 }
149
150 // Convert units in tenths of a millimetre to device units
151 int wxRichTextObject::ConvertTenthsMMToPixels(wxDC& dc, int units)
152 {
153 int p = ConvertTenthsMMToPixels(dc.GetPPI().x, units);
154
155 // Unscale
156 wxRichTextBuffer* buffer = GetBuffer();
157 if (buffer)
158 p = (int) ((double)p / buffer->GetScale());
159 return p;
160 }
161
162 // Convert units in tenths of a millimetre to device units
163 int wxRichTextObject::ConvertTenthsMMToPixels(int ppi, int units)
164 {
165 // There are ppi pixels in 254.1 "1/10 mm"
166
167 double pixels = ((double) units * (double)ppi) / 254.1;
168
169 return (int) pixels;
170 }
171
172 /// Dump to output stream for debugging
173 void wxRichTextObject::Dump(wxTextOutputStream& stream)
174 {
175 stream << GetClassInfo()->GetClassName() << wxT("\n");
176 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");
177 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");
178 }
179
180 /// Gets the containing buffer
181 wxRichTextBuffer* wxRichTextObject::GetBuffer() const
182 {
183 const wxRichTextObject* obj = this;
184 while (obj && !obj->IsKindOf(CLASSINFO(wxRichTextBuffer)))
185 obj = obj->GetParent();
186 return wxDynamicCast(obj, wxRichTextBuffer);
187 }
188
189 /*!
190 * wxRichTextCompositeObject
191 * This is the base for drawable objects.
192 */
193
194 IMPLEMENT_CLASS(wxRichTextCompositeObject, wxRichTextObject)
195
196 wxRichTextCompositeObject::wxRichTextCompositeObject(wxRichTextObject* parent):
197 wxRichTextObject(parent)
198 {
199 }
200
201 wxRichTextCompositeObject::~wxRichTextCompositeObject()
202 {
203 DeleteChildren();
204 }
205
206 /// Get the nth child
207 wxRichTextObject* wxRichTextCompositeObject::GetChild(size_t n) const
208 {
209 wxASSERT ( n < m_children.GetCount() );
210
211 return m_children.Item(n)->GetData();
212 }
213
214 /// Append a child, returning the position
215 size_t wxRichTextCompositeObject::AppendChild(wxRichTextObject* child)
216 {
217 m_children.Append(child);
218 child->SetParent(this);
219 return m_children.GetCount() - 1;
220 }
221
222 /// Insert the child in front of the given object, or at the beginning
223 bool wxRichTextCompositeObject::InsertChild(wxRichTextObject* child, wxRichTextObject* inFrontOf)
224 {
225 if (inFrontOf)
226 {
227 wxRichTextObjectList::compatibility_iterator node = m_children.Find(inFrontOf);
228 m_children.Insert(node, child);
229 }
230 else
231 m_children.Insert(child);
232 child->SetParent(this);
233
234 return true;
235 }
236
237 /// Delete the child
238 bool wxRichTextCompositeObject::RemoveChild(wxRichTextObject* child, bool deleteChild)
239 {
240 wxRichTextObjectList::compatibility_iterator node = m_children.Find(child);
241 if (node)
242 {
243 wxRichTextObject* obj = node->GetData();
244 m_children.Erase(node);
245 if (deleteChild)
246 delete obj;
247
248 return true;
249 }
250 return false;
251 }
252
253 /// Delete all children
254 bool wxRichTextCompositeObject::DeleteChildren()
255 {
256 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
257 while (node)
258 {
259 wxRichTextObjectList::compatibility_iterator oldNode = node;
260
261 wxRichTextObject* child = node->GetData();
262 child->Dereference(); // Only delete if reference count is zero
263
264 node = node->GetNext();
265 m_children.Erase(oldNode);
266 }
267
268 return true;
269 }
270
271 /// Get the child count
272 size_t wxRichTextCompositeObject::GetChildCount() const
273 {
274 return m_children.GetCount();
275 }
276
277 /// Copy
278 void wxRichTextCompositeObject::Copy(const wxRichTextCompositeObject& obj)
279 {
280 wxRichTextObject::Copy(obj);
281
282 DeleteChildren();
283
284 wxRichTextObjectList::compatibility_iterator node = obj.m_children.GetFirst();
285 while (node)
286 {
287 wxRichTextObject* child = node->GetData();
288 wxRichTextObject* newChild = child->Clone();
289 newChild->SetParent(this);
290 m_children.Append(newChild);
291
292 node = node->GetNext();
293 }
294 }
295
296 /// Hit-testing: returns a flag indicating hit test details, plus
297 /// information about position
298 int wxRichTextCompositeObject::HitTest(wxDC& dc, const wxPoint& pt, long& textPosition)
299 {
300 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
301 while (node)
302 {
303 wxRichTextObject* child = node->GetData();
304
305 int ret = child->HitTest(dc, pt, textPosition);
306 if (ret != wxRICHTEXT_HITTEST_NONE)
307 return ret;
308
309 node = node->GetNext();
310 }
311
312 textPosition = GetRange().GetEnd()-1;
313 return wxRICHTEXT_HITTEST_AFTER|wxRICHTEXT_HITTEST_OUTSIDE;
314 }
315
316 /// Finds the absolute position and row height for the given character position
317 bool wxRichTextCompositeObject::FindPosition(wxDC& dc, long index, wxPoint& pt, int* height, bool forceLineStart)
318 {
319 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
320 while (node)
321 {
322 wxRichTextObject* child = node->GetData();
323
324 if (child->FindPosition(dc, index, pt, height, forceLineStart))
325 return true;
326
327 node = node->GetNext();
328 }
329
330 return false;
331 }
332
333 /// Calculate range
334 void wxRichTextCompositeObject::CalculateRange(long start, long& end)
335 {
336 long current = start;
337 long lastEnd = current;
338
339 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
340 while (node)
341 {
342 wxRichTextObject* child = node->GetData();
343 long childEnd = 0;
344
345 child->CalculateRange(current, childEnd);
346 lastEnd = childEnd;
347
348 current = childEnd + 1;
349
350 node = node->GetNext();
351 }
352
353 end = lastEnd;
354
355 // An object with no children has zero length
356 if (m_children.GetCount() == 0)
357 end --;
358
359 m_range.SetRange(start, end);
360 }
361
362 /// Delete range from layout.
363 bool wxRichTextCompositeObject::DeleteRange(const wxRichTextRange& range)
364 {
365 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
366
367 while (node)
368 {
369 wxRichTextObject* obj = (wxRichTextObject*) node->GetData();
370 wxRichTextObjectList::compatibility_iterator next = node->GetNext();
371
372 // Delete the range in each paragraph
373
374 // When a chunk has been deleted, internally the content does not
375 // now match the ranges.
376 // However, so long as deletion is not done on the same object twice this is OK.
377 // If you may delete content from the same object twice, recalculate
378 // the ranges inbetween DeleteRange calls by calling CalculateRanges, and
379 // adjust the range you're deleting accordingly.
380
381 if (!obj->GetRange().IsOutside(range))
382 {
383 obj->DeleteRange(range);
384
385 // Delete an empty object, or paragraph within this range.
386 if (obj->IsEmpty() ||
387 (range.GetStart() <= obj->GetRange().GetStart() && range.GetEnd() >= obj->GetRange().GetEnd()))
388 {
389 // An empty paragraph has length 1, so won't be deleted unless the
390 // whole range is deleted.
391 RemoveChild(obj, true);
392 }
393 }
394
395 node = next;
396 }
397
398 return true;
399 }
400
401 /// Get any text in this object for the given range
402 wxString wxRichTextCompositeObject::GetTextForRange(const wxRichTextRange& range) const
403 {
404 wxString text;
405 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
406 while (node)
407 {
408 wxRichTextObject* child = node->GetData();
409 wxRichTextRange childRange = range;
410 if (!child->GetRange().IsOutside(range))
411 {
412 childRange.LimitTo(child->GetRange());
413
414 wxString childText = child->GetTextForRange(childRange);
415
416 text += childText;
417 }
418 node = node->GetNext();
419 }
420
421 return text;
422 }
423
424 /// Recursively merge all pieces that can be merged.
425 bool wxRichTextCompositeObject::Defragment()
426 {
427 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
428 while (node)
429 {
430 wxRichTextObject* child = node->GetData();
431 wxRichTextCompositeObject* composite = wxDynamicCast(child, wxRichTextCompositeObject);
432 if (composite)
433 composite->Defragment();
434
435 if (node->GetNext())
436 {
437 wxRichTextObject* nextChild = node->GetNext()->GetData();
438 if (child->CanMerge(nextChild) && child->Merge(nextChild))
439 {
440 nextChild->Dereference();
441 m_children.Erase(node->GetNext());
442
443 // Don't set node -- we'll see if we can merge again with the next
444 // child.
445 }
446 else
447 node = node->GetNext();
448 }
449 else
450 node = node->GetNext();
451 }
452
453 return true;
454 }
455
456 /// Dump to output stream for debugging
457 void wxRichTextCompositeObject::Dump(wxTextOutputStream& stream)
458 {
459 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
460 while (node)
461 {
462 wxRichTextObject* child = node->GetData();
463 child->Dump(stream);
464 node = node->GetNext();
465 }
466 }
467
468
469 /*!
470 * wxRichTextBox
471 * This defines a 2D space to lay out objects
472 */
473
474 IMPLEMENT_DYNAMIC_CLASS(wxRichTextBox, wxRichTextCompositeObject)
475
476 wxRichTextBox::wxRichTextBox(wxRichTextObject* parent):
477 wxRichTextCompositeObject(parent)
478 {
479 }
480
481 /// Draw the item
482 bool wxRichTextBox::Draw(wxDC& dc, const wxRichTextRange& range, const wxRichTextRange& selectionRange, const wxRect& WXUNUSED(rect), int descent, int style)
483 {
484 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
485 while (node)
486 {
487 wxRichTextObject* child = node->GetData();
488
489 wxRect childRect = wxRect(child->GetPosition(), child->GetCachedSize());
490 child->Draw(dc, range, selectionRange, childRect, descent, style);
491
492 node = node->GetNext();
493 }
494 return true;
495 }
496
497 /// Lay the item out
498 bool wxRichTextBox::Layout(wxDC& dc, const wxRect& rect, int style)
499 {
500 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
501 while (node)
502 {
503 wxRichTextObject* child = node->GetData();
504 child->Layout(dc, rect, style);
505
506 node = node->GetNext();
507 }
508 m_dirty = false;
509 return true;
510 }
511
512 /// Get/set the size for the given range. Assume only has one child.
513 bool wxRichTextBox::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, int flags, wxPoint position) const
514 {
515 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
516 if (node)
517 {
518 wxRichTextObject* child = node->GetData();
519 return child->GetRangeSize(range, size, descent, dc, flags, position);
520 }
521 else
522 return false;
523 }
524
525 /// Copy
526 void wxRichTextBox::Copy(const wxRichTextBox& obj)
527 {
528 wxRichTextCompositeObject::Copy(obj);
529 }
530
531
532 /*!
533 * wxRichTextParagraphLayoutBox
534 * This box knows how to lay out paragraphs.
535 */
536
537 IMPLEMENT_DYNAMIC_CLASS(wxRichTextParagraphLayoutBox, wxRichTextBox)
538
539 wxRichTextParagraphLayoutBox::wxRichTextParagraphLayoutBox(wxRichTextObject* parent):
540 wxRichTextBox(parent)
541 {
542 Init();
543 }
544
545 /// Initialize the object.
546 void wxRichTextParagraphLayoutBox::Init()
547 {
548 m_ctrl = NULL;
549
550 // For now, assume is the only box and has no initial size.
551 m_range = wxRichTextRange(0, -1);
552
553 m_invalidRange.SetRange(-1, -1);
554 m_leftMargin = 4;
555 m_rightMargin = 4;
556 m_topMargin = 4;
557 m_bottomMargin = 4;
558 m_partialParagraph = false;
559 }
560
561 /// Draw the item
562 bool wxRichTextParagraphLayoutBox::Draw(wxDC& dc, const wxRichTextRange& range, const wxRichTextRange& selectionRange, const wxRect& rect, int descent, int style)
563 {
564 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
565 while (node)
566 {
567 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
568 wxASSERT (child != NULL);
569
570 if (child && !child->GetRange().IsOutside(range))
571 {
572 wxRect childRect(child->GetPosition(), child->GetCachedSize());
573
574 if (((style & wxRICHTEXT_DRAW_IGNORE_CACHE) == 0) && childRect.GetTop() > rect.GetBottom())
575 {
576 // Stop drawing
577 break;
578 }
579 else if (((style & wxRICHTEXT_DRAW_IGNORE_CACHE) == 0) && childRect.GetBottom() < rect.GetTop())
580 {
581 // Skip
582 }
583 else
584 child->Draw(dc, range, selectionRange, childRect, descent, style);
585 }
586
587 node = node->GetNext();
588 }
589 return true;
590 }
591
592 /// Lay the item out
593 bool wxRichTextParagraphLayoutBox::Layout(wxDC& dc, const wxRect& rect, int style)
594 {
595 wxRect availableSpace;
596 bool formatRect = (style & wxRICHTEXT_LAYOUT_SPECIFIED_RECT) == wxRICHTEXT_LAYOUT_SPECIFIED_RECT;
597
598 // If only laying out a specific area, the passed rect has a different meaning:
599 // the visible part of the buffer. This is used in wxRichTextCtrl::OnSize,
600 // so that during a size, only the visible part will be relaid out, or
601 // it would take too long causing flicker. As an approximation, we assume that
602 // everything up to the start of the visible area is laid out correctly.
603 if (formatRect)
604 {
605 availableSpace = wxRect(0 + m_leftMargin,
606 0 + m_topMargin,
607 rect.width - m_leftMargin - m_rightMargin,
608 rect.height);
609
610 // Invalidate the part of the buffer from the first visible line
611 // to the end. If other parts of the buffer are currently invalid,
612 // then they too will be taken into account if they are above
613 // the visible point.
614 long startPos = 0;
615 wxRichTextLine* line = GetLineAtYPosition(rect.y);
616 if (line)
617 startPos = line->GetAbsoluteRange().GetStart();
618
619 Invalidate(wxRichTextRange(startPos, GetRange().GetEnd()));
620 }
621 else
622 availableSpace = wxRect(rect.x + m_leftMargin,
623 rect.y + m_topMargin,
624 rect.width - m_leftMargin - m_rightMargin,
625 rect.height - m_topMargin - m_bottomMargin);
626
627 int maxWidth = 0;
628
629 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
630
631 bool layoutAll = true;
632
633 // Get invalid range, rounding to paragraph start/end.
634 wxRichTextRange invalidRange = GetInvalidRange(true);
635
636 if (invalidRange == wxRICHTEXT_NONE && !formatRect)
637 return true;
638
639 if (invalidRange == wxRICHTEXT_ALL)
640 layoutAll = true;
641 else // If we know what range is affected, start laying out from that point on.
642 if (invalidRange.GetStart() >= GetRange().GetStart())
643 {
644 wxRichTextParagraph* firstParagraph = GetParagraphAtPosition(invalidRange.GetStart());
645 if (firstParagraph)
646 {
647 wxRichTextObjectList::compatibility_iterator firstNode = m_children.Find(firstParagraph);
648 wxRichTextObjectList::compatibility_iterator previousNode;
649 if ( firstNode )
650 previousNode = firstNode->GetPrevious();
651 if (firstNode)
652 {
653 if (previousNode)
654 {
655 wxRichTextParagraph* previousParagraph = wxDynamicCast(previousNode->GetData(), wxRichTextParagraph);
656 availableSpace.y = previousParagraph->GetPosition().y + previousParagraph->GetCachedSize().y;
657 }
658
659 // Now we're going to start iterating from the first affected paragraph.
660 node = firstNode;
661
662 layoutAll = false;
663 }
664 }
665 }
666
667 // A way to force speedy rest-of-buffer layout (the 'else' below)
668 bool forceQuickLayout = false;
669
670 while (node)
671 {
672 // Assume this box only contains paragraphs
673
674 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
675 wxCHECK_MSG( child, false, _T("Unknown object in layout") );
676
677 // TODO: what if the child hasn't been laid out (e.g. involved in Undo) but still has 'old' lines
678 if ( !forceQuickLayout &&
679 (layoutAll ||
680 child->GetLines().IsEmpty() ||
681 !child->GetRange().IsOutside(invalidRange)) )
682 {
683 child->Layout(dc, availableSpace, style);
684
685 // Layout must set the cached size
686 availableSpace.y += child->GetCachedSize().y;
687 maxWidth = wxMax(maxWidth, child->GetCachedSize().x);
688
689 // If we're just formatting the visible part of the buffer,
690 // and we're now past the bottom of the window, start quick
691 // layout.
692 if (formatRect && child->GetPosition().y > rect.GetBottom())
693 forceQuickLayout = true;
694 }
695 else
696 {
697 // We're outside the immediately affected range, so now let's just
698 // move everything up or down. This assumes that all the children have previously
699 // been laid out and have wrapped line lists associated with them.
700 // TODO: check all paragraphs before the affected range.
701
702 int inc = availableSpace.y - child->GetPosition().y;
703
704 while (node)
705 {
706 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
707 if (child)
708 {
709 if (child->GetLines().GetCount() == 0)
710 child->Layout(dc, availableSpace, style);
711 else
712 child->SetPosition(wxPoint(child->GetPosition().x, child->GetPosition().y + inc));
713
714 availableSpace.y += child->GetCachedSize().y;
715 maxWidth = wxMax(maxWidth, child->GetCachedSize().x);
716 }
717
718 node = node->GetNext();
719 }
720 break;
721 }
722
723 node = node->GetNext();
724 }
725
726 SetCachedSize(wxSize(maxWidth, availableSpace.y));
727
728 m_dirty = false;
729 m_invalidRange = wxRICHTEXT_NONE;
730
731 return true;
732 }
733
734 /// Copy
735 void wxRichTextParagraphLayoutBox::Copy(const wxRichTextParagraphLayoutBox& obj)
736 {
737 wxRichTextBox::Copy(obj);
738
739 m_partialParagraph = obj.m_partialParagraph;
740 m_defaultAttributes = obj.m_defaultAttributes;
741 }
742
743 /// Get/set the size for the given range.
744 bool wxRichTextParagraphLayoutBox::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, int flags, wxPoint position) const
745 {
746 wxSize sz;
747
748 wxRichTextObjectList::compatibility_iterator startPara = wxRichTextObjectList::compatibility_iterator();
749 wxRichTextObjectList::compatibility_iterator endPara = wxRichTextObjectList::compatibility_iterator();
750
751 // First find the first paragraph whose starting position is within the range.
752 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
753 while (node)
754 {
755 // child is a paragraph
756 wxRichTextObject* child = node->GetData();
757 const wxRichTextRange& r = child->GetRange();
758
759 if (r.GetStart() <= range.GetStart() && r.GetEnd() >= range.GetStart())
760 {
761 startPara = node;
762 break;
763 }
764
765 node = node->GetNext();
766 }
767
768 // Next find the last paragraph containing part of the range
769 node = m_children.GetFirst();
770 while (node)
771 {
772 // child is a paragraph
773 wxRichTextObject* child = node->GetData();
774 const wxRichTextRange& r = child->GetRange();
775
776 if (r.GetStart() <= range.GetEnd() && r.GetEnd() >= range.GetEnd())
777 {
778 endPara = node;
779 break;
780 }
781
782 node = node->GetNext();
783 }
784
785 if (!startPara || !endPara)
786 return false;
787
788 // Now we can add up the sizes
789 for (node = startPara; node ; node = node->GetNext())
790 {
791 // child is a paragraph
792 wxRichTextObject* child = node->GetData();
793 const wxRichTextRange& childRange = child->GetRange();
794 wxRichTextRange rangeToFind = range;
795 rangeToFind.LimitTo(childRange);
796
797 wxSize childSize;
798
799 int childDescent = 0;
800 child->GetRangeSize(rangeToFind, childSize, childDescent, dc, flags, position);
801
802 descent = wxMax(childDescent, descent);
803
804 sz.x = wxMax(sz.x, childSize.x);
805 sz.y += childSize.y;
806
807 if (node == endPara)
808 break;
809 }
810
811 size = sz;
812
813 return true;
814 }
815
816 /// Get the paragraph at the given position
817 wxRichTextParagraph* wxRichTextParagraphLayoutBox::GetParagraphAtPosition(long pos, bool caretPosition) const
818 {
819 if (caretPosition)
820 pos ++;
821
822 // First find the first paragraph whose starting position is within the range.
823 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
824 while (node)
825 {
826 // child is a paragraph
827 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
828 wxASSERT (child != NULL);
829
830 // Return first child in buffer if position is -1
831 // if (pos == -1)
832 // return child;
833
834 if (child->GetRange().Contains(pos))
835 return child;
836
837 node = node->GetNext();
838 }
839 return NULL;
840 }
841
842 /// Get the line at the given position
843 wxRichTextLine* wxRichTextParagraphLayoutBox::GetLineAtPosition(long pos, bool caretPosition) const
844 {
845 if (caretPosition)
846 pos ++;
847
848 // First find the first paragraph whose starting position is within the range.
849 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
850 while (node)
851 {
852 // child is a paragraph
853 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
854 wxASSERT (child != NULL);
855
856 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
857 while (node2)
858 {
859 wxRichTextLine* line = node2->GetData();
860
861 wxRichTextRange range = line->GetAbsoluteRange();
862
863 if (range.Contains(pos) ||
864
865 // If the position is end-of-paragraph, then return the last line of
866 // of the paragraph.
867 (range.GetEnd() == child->GetRange().GetEnd()-1) && (pos == child->GetRange().GetEnd()))
868 return line;
869
870 node2 = node2->GetNext();
871 }
872
873 node = node->GetNext();
874 }
875
876 int lineCount = GetLineCount();
877 if (lineCount > 0)
878 return GetLineForVisibleLineNumber(lineCount-1);
879 else
880 return NULL;
881 }
882
883 /// Get the line at the given y pixel position, or the last line.
884 wxRichTextLine* wxRichTextParagraphLayoutBox::GetLineAtYPosition(int y) const
885 {
886 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
887 while (node)
888 {
889 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
890 wxASSERT (child != NULL);
891
892 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
893 while (node2)
894 {
895 wxRichTextLine* line = node2->GetData();
896
897 wxRect rect(line->GetRect());
898
899 if (y <= rect.GetBottom())
900 return line;
901
902 node2 = node2->GetNext();
903 }
904
905 node = node->GetNext();
906 }
907
908 // Return last line
909 int lineCount = GetLineCount();
910 if (lineCount > 0)
911 return GetLineForVisibleLineNumber(lineCount-1);
912 else
913 return NULL;
914 }
915
916 /// Get the number of visible lines
917 int wxRichTextParagraphLayoutBox::GetLineCount() const
918 {
919 int count = 0;
920
921 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
922 while (node)
923 {
924 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
925 wxASSERT (child != NULL);
926
927 count += child->GetLines().GetCount();
928 node = node->GetNext();
929 }
930 return count;
931 }
932
933
934 /// Get the paragraph for a given line
935 wxRichTextParagraph* wxRichTextParagraphLayoutBox::GetParagraphForLine(wxRichTextLine* line) const
936 {
937 return GetParagraphAtPosition(line->GetAbsoluteRange().GetStart());
938 }
939
940 /// Get the line size at the given position
941 wxSize wxRichTextParagraphLayoutBox::GetLineSizeAtPosition(long pos, bool caretPosition) const
942 {
943 wxRichTextLine* line = GetLineAtPosition(pos, caretPosition);
944 if (line)
945 {
946 return line->GetSize();
947 }
948 else
949 return wxSize(0, 0);
950 }
951
952
953 /// Convenience function to add a paragraph of text
954 wxRichTextRange wxRichTextParagraphLayoutBox::AddParagraph(const wxString& text, wxTextAttr* paraStyle)
955 {
956 // Don't use the base style, just the default style, and the base style will
957 // be combined at display time.
958 // Divide into paragraph and character styles.
959
960 wxTextAttr defaultCharStyle;
961 wxTextAttr defaultParaStyle;
962
963 // If the default style is a named paragraph style, don't apply any character formatting
964 // to the initial text string.
965 if (GetDefaultStyle().HasParagraphStyleName() && GetStyleSheet())
966 {
967 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(GetDefaultStyle().GetParagraphStyleName());
968 if (def)
969 defaultParaStyle = def->GetStyleMergedWithBase(GetStyleSheet());
970 }
971 else
972 wxRichTextSplitParaCharStyles(GetDefaultStyle(), defaultParaStyle, defaultCharStyle);
973
974 wxTextAttr* pStyle = paraStyle ? paraStyle : (wxTextAttr*) & defaultParaStyle;
975 wxTextAttr* cStyle = & defaultCharStyle;
976
977 wxRichTextParagraph* para = new wxRichTextParagraph(text, this, pStyle, cStyle);
978
979 AppendChild(para);
980
981 UpdateRanges();
982 SetDirty(true);
983
984 return para->GetRange();
985 }
986
987 /// Adds multiple paragraphs, based on newlines.
988 wxRichTextRange wxRichTextParagraphLayoutBox::AddParagraphs(const wxString& text, wxTextAttr* paraStyle)
989 {
990 // Don't use the base style, just the default style, and the base style will
991 // be combined at display time.
992 // Divide into paragraph and character styles.
993
994 wxTextAttr defaultCharStyle;
995 wxTextAttr defaultParaStyle;
996
997 // If the default style is a named paragraph style, don't apply any character formatting
998 // to the initial text string.
999 if (GetDefaultStyle().HasParagraphStyleName() && GetStyleSheet())
1000 {
1001 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(GetDefaultStyle().GetParagraphStyleName());
1002 if (def)
1003 defaultParaStyle = def->GetStyleMergedWithBase(GetStyleSheet());
1004 }
1005 else
1006 wxRichTextSplitParaCharStyles(GetDefaultStyle(), defaultParaStyle, defaultCharStyle);
1007
1008 wxTextAttr* pStyle = paraStyle ? paraStyle : (wxTextAttr*) & defaultParaStyle;
1009 wxTextAttr* cStyle = & defaultCharStyle;
1010
1011 wxRichTextParagraph* firstPara = NULL;
1012 wxRichTextParagraph* lastPara = NULL;
1013
1014 wxRichTextRange range(-1, -1);
1015
1016 size_t i = 0;
1017 size_t len = text.length();
1018 wxString line;
1019 wxRichTextParagraph* para = new wxRichTextParagraph(wxEmptyString, this, pStyle, cStyle);
1020
1021 AppendChild(para);
1022
1023 firstPara = para;
1024 lastPara = para;
1025
1026 while (i < len)
1027 {
1028 wxChar ch = text[i];
1029 if (ch == wxT('\n') || ch == wxT('\r'))
1030 {
1031 if (i != (len-1))
1032 {
1033 wxRichTextPlainText* plainText = (wxRichTextPlainText*) para->GetChildren().GetFirst()->GetData();
1034 plainText->SetText(line);
1035
1036 para = new wxRichTextParagraph(wxEmptyString, this, pStyle, cStyle);
1037
1038 AppendChild(para);
1039
1040 lastPara = para;
1041 line = wxEmptyString;
1042 }
1043 }
1044 else
1045 line += ch;
1046
1047 i ++;
1048 }
1049
1050 if (!line.empty())
1051 {
1052 wxRichTextPlainText* plainText = (wxRichTextPlainText*) para->GetChildren().GetFirst()->GetData();
1053 plainText->SetText(line);
1054 }
1055
1056 UpdateRanges();
1057
1058 SetDirty(false);
1059
1060 return wxRichTextRange(firstPara->GetRange().GetStart(), lastPara->GetRange().GetEnd());
1061 }
1062
1063 /// Convenience function to add an image
1064 wxRichTextRange wxRichTextParagraphLayoutBox::AddImage(const wxImage& image, wxTextAttr* paraStyle)
1065 {
1066 // Don't use the base style, just the default style, and the base style will
1067 // be combined at display time.
1068 // Divide into paragraph and character styles.
1069
1070 wxTextAttr defaultCharStyle;
1071 wxTextAttr defaultParaStyle;
1072
1073 // If the default style is a named paragraph style, don't apply any character formatting
1074 // to the initial text string.
1075 if (GetDefaultStyle().HasParagraphStyleName() && GetStyleSheet())
1076 {
1077 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(GetDefaultStyle().GetParagraphStyleName());
1078 if (def)
1079 defaultParaStyle = def->GetStyleMergedWithBase(GetStyleSheet());
1080 }
1081 else
1082 wxRichTextSplitParaCharStyles(GetDefaultStyle(), defaultParaStyle, defaultCharStyle);
1083
1084 wxTextAttr* pStyle = paraStyle ? paraStyle : (wxTextAttr*) & defaultParaStyle;
1085 wxTextAttr* cStyle = & defaultCharStyle;
1086
1087 wxRichTextParagraph* para = new wxRichTextParagraph(this, pStyle);
1088 AppendChild(para);
1089 para->AppendChild(new wxRichTextImage(image, this, cStyle));
1090
1091 UpdateRanges();
1092 SetDirty(true);
1093
1094 return para->GetRange();
1095 }
1096
1097
1098 /// Insert fragment into this box at the given position. If partialParagraph is true,
1099 /// it is assumed that the last (or only) paragraph is just a piece of data with no paragraph
1100 /// marker.
1101
1102 bool wxRichTextParagraphLayoutBox::InsertFragment(long position, wxRichTextParagraphLayoutBox& fragment)
1103 {
1104 SetDirty(true);
1105
1106 // First, find the first paragraph whose starting position is within the range.
1107 wxRichTextParagraph* para = GetParagraphAtPosition(position);
1108 if (para)
1109 {
1110 wxTextAttrEx originalAttr = para->GetAttributes();
1111
1112 wxRichTextObjectList::compatibility_iterator node = m_children.Find(para);
1113
1114 // Now split at this position, returning the object to insert the new
1115 // ones in front of.
1116 wxRichTextObject* nextObject = para->SplitAt(position);
1117
1118 // Special case: partial paragraph, just one paragraph. Might be a small amount of
1119 // text, for example, so let's optimize.
1120
1121 if (fragment.GetPartialParagraph() && fragment.GetChildren().GetCount() == 1)
1122 {
1123 // Add the first para to this para...
1124 wxRichTextObjectList::compatibility_iterator firstParaNode = fragment.GetChildren().GetFirst();
1125 if (!firstParaNode)
1126 return false;
1127
1128 // Iterate through the fragment paragraph inserting the content into this paragraph.
1129 wxRichTextParagraph* firstPara = wxDynamicCast(firstParaNode->GetData(), wxRichTextParagraph);
1130 wxASSERT (firstPara != NULL);
1131
1132 wxRichTextObjectList::compatibility_iterator objectNode = firstPara->GetChildren().GetFirst();
1133 while (objectNode)
1134 {
1135 wxRichTextObject* newObj = objectNode->GetData()->Clone();
1136
1137 if (!nextObject)
1138 {
1139 // Append
1140 para->AppendChild(newObj);
1141 }
1142 else
1143 {
1144 // Insert before nextObject
1145 para->InsertChild(newObj, nextObject);
1146 }
1147
1148 objectNode = objectNode->GetNext();
1149 }
1150
1151 return true;
1152 }
1153 else
1154 {
1155 // Procedure for inserting a fragment consisting of a number of
1156 // paragraphs:
1157 //
1158 // 1. Remove and save the content that's after the insertion point, for adding
1159 // back once we've added the fragment.
1160 // 2. Add the content from the first fragment paragraph to the current
1161 // paragraph.
1162 // 3. Add remaining fragment paragraphs after the current paragraph.
1163 // 4. Add back the saved content from the first paragraph. If partialParagraph
1164 // is true, add it to the last paragraph added and not a new one.
1165
1166 // 1. Remove and save objects after split point.
1167 wxList savedObjects;
1168 if (nextObject)
1169 para->MoveToList(nextObject, savedObjects);
1170
1171 // 2. Add the content from the 1st fragment paragraph.
1172 wxRichTextObjectList::compatibility_iterator firstParaNode = fragment.GetChildren().GetFirst();
1173 if (!firstParaNode)
1174 return false;
1175
1176 wxRichTextParagraph* firstPara = wxDynamicCast(firstParaNode->GetData(), wxRichTextParagraph);
1177 wxASSERT(firstPara != NULL);
1178
1179 if (!(fragment.GetAttributes().GetFlags() & wxTEXT_ATTR_KEEP_FIRST_PARA_STYLE))
1180 para->SetAttributes(firstPara->GetAttributes());
1181
1182 // Save empty paragraph attributes for appending later
1183 // These are character attributes deliberately set for a new paragraph. Without this,
1184 // we couldn't pass default attributes when appending a new paragraph.
1185 wxTextAttrEx emptyParagraphAttributes;
1186
1187 wxRichTextObjectList::compatibility_iterator objectNode = firstPara->GetChildren().GetFirst();
1188
1189 if (objectNode && firstPara->GetChildren().GetCount() == 1 && objectNode->GetData()->IsEmpty())
1190 emptyParagraphAttributes = objectNode->GetData()->GetAttributes();
1191
1192 while (objectNode)
1193 {
1194 wxRichTextObject* newObj = objectNode->GetData()->Clone();
1195
1196 // Append
1197 para->AppendChild(newObj);
1198
1199 objectNode = objectNode->GetNext();
1200 }
1201
1202 // 3. Add remaining fragment paragraphs after the current paragraph.
1203 wxRichTextObjectList::compatibility_iterator nextParagraphNode = node->GetNext();
1204 wxRichTextObject* nextParagraph = NULL;
1205 if (nextParagraphNode)
1206 nextParagraph = nextParagraphNode->GetData();
1207
1208 wxRichTextObjectList::compatibility_iterator i = fragment.GetChildren().GetFirst()->GetNext();
1209 wxRichTextParagraph* finalPara = para;
1210
1211 bool needExtraPara = (!i || !fragment.GetPartialParagraph());
1212
1213 // If there was only one paragraph, we need to insert a new one.
1214 while (i)
1215 {
1216 wxRichTextParagraph* para = wxDynamicCast(i->GetData(), wxRichTextParagraph);
1217 wxASSERT( para != NULL );
1218
1219 finalPara = (wxRichTextParagraph*) para->Clone();
1220
1221 if (nextParagraph)
1222 InsertChild(finalPara, nextParagraph);
1223 else
1224 AppendChild(finalPara);
1225
1226 i = i->GetNext();
1227 }
1228
1229 // If there was only one paragraph, or we have full paragraphs in our fragment,
1230 // we need to insert a new one.
1231 if (needExtraPara)
1232 {
1233 finalPara = new wxRichTextParagraph;
1234
1235 if (nextParagraph)
1236 InsertChild(finalPara, nextParagraph);
1237 else
1238 AppendChild(finalPara);
1239 }
1240
1241 // 4. Add back the remaining content.
1242 if (finalPara)
1243 {
1244 if (nextObject)
1245 finalPara->MoveFromList(savedObjects);
1246
1247 // Ensure there's at least one object
1248 if (finalPara->GetChildCount() == 0)
1249 {
1250 wxRichTextPlainText* text = new wxRichTextPlainText(wxEmptyString);
1251 text->SetAttributes(emptyParagraphAttributes);
1252
1253 finalPara->AppendChild(text);
1254 }
1255 }
1256
1257 if ((fragment.GetAttributes().GetFlags() & wxTEXT_ATTR_KEEP_FIRST_PARA_STYLE) && firstPara)
1258 finalPara->SetAttributes(firstPara->GetAttributes());
1259 else if (finalPara && finalPara != para)
1260 finalPara->SetAttributes(originalAttr);
1261
1262 return true;
1263 }
1264 }
1265 else
1266 {
1267 // Append
1268 wxRichTextObjectList::compatibility_iterator i = fragment.GetChildren().GetFirst();
1269 while (i)
1270 {
1271 wxRichTextParagraph* para = wxDynamicCast(i->GetData(), wxRichTextParagraph);
1272 wxASSERT( para != NULL );
1273
1274 AppendChild(para->Clone());
1275
1276 i = i->GetNext();
1277 }
1278
1279 return true;
1280 }
1281 }
1282
1283 /// Make a copy of the fragment corresponding to the given range, putting it in 'fragment'.
1284 /// If there was an incomplete paragraph at the end, partialParagraph is set to true.
1285 bool wxRichTextParagraphLayoutBox::CopyFragment(const wxRichTextRange& range, wxRichTextParagraphLayoutBox& fragment)
1286 {
1287 wxRichTextObjectList::compatibility_iterator i = GetChildren().GetFirst();
1288 while (i)
1289 {
1290 wxRichTextParagraph* para = wxDynamicCast(i->GetData(), wxRichTextParagraph);
1291 wxASSERT( para != NULL );
1292
1293 if (!para->GetRange().IsOutside(range))
1294 {
1295 fragment.AppendChild(para->Clone());
1296 }
1297 i = i->GetNext();
1298 }
1299
1300 // Now top and tail the first and last paragraphs in our new fragment (which might be the same).
1301 if (!fragment.IsEmpty())
1302 {
1303 wxRichTextRange topTailRange(range);
1304
1305 wxRichTextParagraph* firstPara = wxDynamicCast(fragment.GetChildren().GetFirst()->GetData(), wxRichTextParagraph);
1306 wxASSERT( firstPara != NULL );
1307
1308 // Chop off the start of the paragraph
1309 if (topTailRange.GetStart() > firstPara->GetRange().GetStart())
1310 {
1311 wxRichTextRange r(firstPara->GetRange().GetStart(), topTailRange.GetStart()-1);
1312 firstPara->DeleteRange(r);
1313
1314 // Make sure the numbering is correct
1315 long end;
1316 fragment.CalculateRange(firstPara->GetRange().GetStart(), end);
1317
1318 // Now, we've deleted some positions, so adjust the range
1319 // accordingly.
1320 topTailRange.SetEnd(topTailRange.GetEnd() - r.GetLength());
1321 }
1322
1323 wxRichTextParagraph* lastPara = wxDynamicCast(fragment.GetChildren().GetLast()->GetData(), wxRichTextParagraph);
1324 wxASSERT( lastPara != NULL );
1325
1326 if (topTailRange.GetEnd() < (lastPara->GetRange().GetEnd()-1))
1327 {
1328 wxRichTextRange r(topTailRange.GetEnd()+1, lastPara->GetRange().GetEnd()-1); /* -1 since actual text ends 1 position before end of para marker */
1329 lastPara->DeleteRange(r);
1330
1331 // Make sure the numbering is correct
1332 long end;
1333 fragment.CalculateRange(firstPara->GetRange().GetStart(), end);
1334
1335 // We only have part of a paragraph at the end
1336 fragment.SetPartialParagraph(true);
1337 }
1338 else
1339 {
1340 if (topTailRange.GetEnd() == (lastPara->GetRange().GetEnd() - 1))
1341 // We have a partial paragraph (don't save last new paragraph marker)
1342 fragment.SetPartialParagraph(true);
1343 else
1344 // We have a complete paragraph
1345 fragment.SetPartialParagraph(false);
1346 }
1347 }
1348
1349 return true;
1350 }
1351
1352 /// Given a position, get the number of the visible line (potentially many to a paragraph),
1353 /// starting from zero at the start of the buffer.
1354 long wxRichTextParagraphLayoutBox::GetVisibleLineNumber(long pos, bool caretPosition, bool startOfLine) const
1355 {
1356 if (caretPosition)
1357 pos ++;
1358
1359 int lineCount = 0;
1360
1361 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1362 while (node)
1363 {
1364 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
1365 wxASSERT( child != NULL );
1366
1367 if (child->GetRange().Contains(pos))
1368 {
1369 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
1370 while (node2)
1371 {
1372 wxRichTextLine* line = node2->GetData();
1373 wxRichTextRange lineRange = line->GetAbsoluteRange();
1374
1375 if (lineRange.Contains(pos))
1376 {
1377 // If the caret is displayed at the end of the previous wrapped line,
1378 // we want to return the line it's _displayed_ at (not the actual line
1379 // containing the position).
1380 if (lineRange.GetStart() == pos && !startOfLine && child->GetRange().GetStart() != pos)
1381 return lineCount - 1;
1382 else
1383 return lineCount;
1384 }
1385
1386 lineCount ++;
1387
1388 node2 = node2->GetNext();
1389 }
1390 // If we didn't find it in the lines, it must be
1391 // the last position of the paragraph. So return the last line.
1392 return lineCount-1;
1393 }
1394 else
1395 lineCount += child->GetLines().GetCount();
1396
1397 node = node->GetNext();
1398 }
1399
1400 // Not found
1401 return -1;
1402 }
1403
1404 /// Given a line number, get the corresponding wxRichTextLine object.
1405 wxRichTextLine* wxRichTextParagraphLayoutBox::GetLineForVisibleLineNumber(long lineNumber) const
1406 {
1407 int lineCount = 0;
1408
1409 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1410 while (node)
1411 {
1412 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
1413 wxASSERT(child != NULL);
1414
1415 if (lineNumber < (int) (child->GetLines().GetCount() + lineCount))
1416 {
1417 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
1418 while (node2)
1419 {
1420 wxRichTextLine* line = node2->GetData();
1421
1422 if (lineCount == lineNumber)
1423 return line;
1424
1425 lineCount ++;
1426
1427 node2 = node2->GetNext();
1428 }
1429 }
1430 else
1431 lineCount += child->GetLines().GetCount();
1432
1433 node = node->GetNext();
1434 }
1435
1436 // Didn't find it
1437 return NULL;
1438 }
1439
1440 /// Delete range from layout.
1441 bool wxRichTextParagraphLayoutBox::DeleteRange(const wxRichTextRange& range)
1442 {
1443 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1444
1445 wxRichTextParagraph* firstPara = NULL;
1446 while (node)
1447 {
1448 wxRichTextParagraph* obj = wxDynamicCast(node->GetData(), wxRichTextParagraph);
1449 wxASSERT (obj != NULL);
1450
1451 wxRichTextObjectList::compatibility_iterator next = node->GetNext();
1452
1453 // Delete the range in each paragraph
1454
1455 if (!obj->GetRange().IsOutside(range))
1456 {
1457 // Deletes the content of this object within the given range
1458 obj->DeleteRange(range);
1459
1460 wxRichTextRange thisRange = obj->GetRange();
1461 wxTextAttrEx thisAttr = obj->GetAttributes();
1462
1463 // If the whole paragraph is within the range to delete,
1464 // delete the whole thing.
1465 if (range.GetStart() <= thisRange.GetStart() && range.GetEnd() >= thisRange.GetEnd())
1466 {
1467 // Delete the whole object
1468 RemoveChild(obj, true);
1469 obj = NULL;
1470 }
1471 else if (!firstPara)
1472 firstPara = obj;
1473
1474 // If the range includes the paragraph end, we need to join this
1475 // and the next paragraph.
1476 if (range.GetEnd() <= thisRange.GetEnd())
1477 {
1478 // We need to move the objects from the next paragraph
1479 // to this paragraph
1480
1481 wxRichTextParagraph* nextParagraph = NULL;
1482 if ((range.GetEnd() < thisRange.GetEnd()) && obj)
1483 nextParagraph = obj;
1484 else
1485 {
1486 // We're ending at the end of the paragraph, so merge the _next_ paragraph.
1487 if (next)
1488 nextParagraph = wxDynamicCast(next->GetData(), wxRichTextParagraph);
1489 }
1490
1491 bool applyFinalParagraphStyle = firstPara && nextParagraph && nextParagraph != firstPara;
1492
1493 wxTextAttrEx nextParaAttr;
1494 if (applyFinalParagraphStyle)
1495 {
1496 // Special case when deleting the end of a paragraph - use _this_ paragraph's style,
1497 // not the next one.
1498 if (range.GetStart() == range.GetEnd() && range.GetStart() == thisRange.GetEnd())
1499 nextParaAttr = thisAttr;
1500 else
1501 nextParaAttr = nextParagraph->GetAttributes();
1502 }
1503
1504 if (firstPara && nextParagraph && firstPara != nextParagraph)
1505 {
1506 // Move the objects to the previous para
1507 wxRichTextObjectList::compatibility_iterator node1 = nextParagraph->GetChildren().GetFirst();
1508
1509 while (node1)
1510 {
1511 wxRichTextObject* obj1 = node1->GetData();
1512
1513 firstPara->AppendChild(obj1);
1514
1515 wxRichTextObjectList::compatibility_iterator next1 = node1->GetNext();
1516 nextParagraph->GetChildren().Erase(node1);
1517
1518 node1 = next1;
1519 }
1520
1521 // Delete the paragraph
1522 RemoveChild(nextParagraph, true);
1523 }
1524
1525 // Avoid empty paragraphs
1526 if (firstPara && firstPara->GetChildren().GetCount() == 0)
1527 {
1528 wxRichTextPlainText* text = new wxRichTextPlainText(wxEmptyString);
1529 firstPara->AppendChild(text);
1530 }
1531
1532 if (applyFinalParagraphStyle)
1533 firstPara->SetAttributes(nextParaAttr);
1534
1535 return true;
1536 }
1537 }
1538
1539 node = next;
1540 }
1541
1542 return true;
1543 }
1544
1545 /// Get any text in this object for the given range
1546 wxString wxRichTextParagraphLayoutBox::GetTextForRange(const wxRichTextRange& range) const
1547 {
1548 int lineCount = 0;
1549 wxString text;
1550 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1551 while (node)
1552 {
1553 wxRichTextObject* child = node->GetData();
1554 if (!child->GetRange().IsOutside(range))
1555 {
1556 wxRichTextRange childRange = range;
1557 childRange.LimitTo(child->GetRange());
1558
1559 wxString childText = child->GetTextForRange(childRange);
1560
1561 text += childText;
1562
1563 if ((childRange.GetEnd() == child->GetRange().GetEnd()) && node->GetNext())
1564 text += wxT("\n");
1565
1566 lineCount ++;
1567 }
1568 node = node->GetNext();
1569 }
1570
1571 return text;
1572 }
1573
1574 /// Get all the text
1575 wxString wxRichTextParagraphLayoutBox::GetText() const
1576 {
1577 return GetTextForRange(GetRange());
1578 }
1579
1580 /// Get the paragraph by number
1581 wxRichTextParagraph* wxRichTextParagraphLayoutBox::GetParagraphAtLine(long paragraphNumber) const
1582 {
1583 if ((size_t) paragraphNumber >= GetChildCount())
1584 return NULL;
1585
1586 return (wxRichTextParagraph*) GetChild((size_t) paragraphNumber);
1587 }
1588
1589 /// Get the length of the paragraph
1590 int wxRichTextParagraphLayoutBox::GetParagraphLength(long paragraphNumber) const
1591 {
1592 wxRichTextParagraph* para = GetParagraphAtLine(paragraphNumber);
1593 if (para)
1594 return para->GetRange().GetLength() - 1; // don't include newline
1595 else
1596 return 0;
1597 }
1598
1599 /// Get the text of the paragraph
1600 wxString wxRichTextParagraphLayoutBox::GetParagraphText(long paragraphNumber) const
1601 {
1602 wxRichTextParagraph* para = GetParagraphAtLine(paragraphNumber);
1603 if (para)
1604 return para->GetTextForRange(para->GetRange());
1605 else
1606 return wxEmptyString;
1607 }
1608
1609 /// Convert zero-based line column and paragraph number to a position.
1610 long wxRichTextParagraphLayoutBox::XYToPosition(long x, long y) const
1611 {
1612 wxRichTextParagraph* para = GetParagraphAtLine(y);
1613 if (para)
1614 {
1615 return para->GetRange().GetStart() + x;
1616 }
1617 else
1618 return -1;
1619 }
1620
1621 /// Convert zero-based position to line column and paragraph number
1622 bool wxRichTextParagraphLayoutBox::PositionToXY(long pos, long* x, long* y) const
1623 {
1624 wxRichTextParagraph* para = GetParagraphAtPosition(pos);
1625 if (para)
1626 {
1627 int count = 0;
1628 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1629 while (node)
1630 {
1631 wxRichTextObject* child = node->GetData();
1632 if (child == para)
1633 break;
1634 count ++;
1635 node = node->GetNext();
1636 }
1637
1638 *y = count;
1639 *x = pos - para->GetRange().GetStart();
1640
1641 return true;
1642 }
1643 else
1644 return false;
1645 }
1646
1647 /// Get the leaf object in a paragraph at this position.
1648 /// Given a line number, get the corresponding wxRichTextLine object.
1649 wxRichTextObject* wxRichTextParagraphLayoutBox::GetLeafObjectAtPosition(long position) const
1650 {
1651 wxRichTextParagraph* para = GetParagraphAtPosition(position);
1652 if (para)
1653 {
1654 wxRichTextObjectList::compatibility_iterator node = para->GetChildren().GetFirst();
1655
1656 while (node)
1657 {
1658 wxRichTextObject* child = node->GetData();
1659 if (child->GetRange().Contains(position))
1660 return child;
1661
1662 node = node->GetNext();
1663 }
1664 if (position == para->GetRange().GetEnd() && para->GetChildCount() > 0)
1665 return para->GetChildren().GetLast()->GetData();
1666 }
1667 return NULL;
1668 }
1669
1670 /// Set character or paragraph text attributes: apply character styles only to immediate text nodes
1671 bool wxRichTextParagraphLayoutBox::SetStyle(const wxRichTextRange& range, const wxTextAttr& style, int flags)
1672 {
1673 bool characterStyle = false;
1674 bool paragraphStyle = false;
1675
1676 if (style.IsCharacterStyle())
1677 characterStyle = true;
1678 if (style.IsParagraphStyle())
1679 paragraphStyle = true;
1680
1681 bool withUndo = ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0);
1682 bool applyMinimal = ((flags & wxRICHTEXT_SETSTYLE_OPTIMIZE) != 0);
1683 bool parasOnly = ((flags & wxRICHTEXT_SETSTYLE_PARAGRAPHS_ONLY) != 0);
1684 bool charactersOnly = ((flags & wxRICHTEXT_SETSTYLE_CHARACTERS_ONLY) != 0);
1685 bool resetExistingStyle = ((flags & wxRICHTEXT_SETSTYLE_RESET) != 0);
1686 bool removeStyle = ((flags & wxRICHTEXT_SETSTYLE_REMOVE) != 0);
1687
1688 // Apply paragraph style first, if any
1689 wxTextAttr wholeStyle(style);
1690
1691 if (!removeStyle && wholeStyle.HasParagraphStyleName() && GetStyleSheet())
1692 {
1693 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(wholeStyle.GetParagraphStyleName());
1694 if (def)
1695 wxRichTextApplyStyle(wholeStyle, def->GetStyleMergedWithBase(GetStyleSheet()));
1696 }
1697
1698 // Limit the attributes to be set to the content to only character attributes.
1699 wxTextAttr characterAttributes(wholeStyle);
1700 characterAttributes.SetFlags(characterAttributes.GetFlags() & (wxTEXT_ATTR_CHARACTER));
1701
1702 if (!removeStyle && characterAttributes.HasCharacterStyleName() && GetStyleSheet())
1703 {
1704 wxRichTextCharacterStyleDefinition* def = GetStyleSheet()->FindCharacterStyle(characterAttributes.GetCharacterStyleName());
1705 if (def)
1706 wxRichTextApplyStyle(characterAttributes, def->GetStyleMergedWithBase(GetStyleSheet()));
1707 }
1708
1709 // If we are associated with a control, make undoable; otherwise, apply immediately
1710 // to the data.
1711
1712 bool haveControl = (GetRichTextCtrl() != NULL);
1713
1714 wxRichTextAction* action = NULL;
1715
1716 if (haveControl && withUndo)
1717 {
1718 action = new wxRichTextAction(NULL, _("Change Style"), wxRICHTEXT_CHANGE_STYLE, & GetRichTextCtrl()->GetBuffer(), GetRichTextCtrl());
1719 action->SetRange(range);
1720 action->SetPosition(GetRichTextCtrl()->GetCaretPosition());
1721 }
1722
1723 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1724 while (node)
1725 {
1726 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
1727 wxASSERT (para != NULL);
1728
1729 if (para && para->GetChildCount() > 0)
1730 {
1731 // Stop searching if we're beyond the range of interest
1732 if (para->GetRange().GetStart() > range.GetEnd())
1733 break;
1734
1735 if (!para->GetRange().IsOutside(range))
1736 {
1737 // We'll be using a copy of the paragraph to make style changes,
1738 // not updating the buffer directly.
1739 wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL);
1740
1741 if (haveControl && withUndo)
1742 {
1743 newPara = new wxRichTextParagraph(*para);
1744 action->GetNewParagraphs().AppendChild(newPara);
1745
1746 // Also store the old ones for Undo
1747 action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para));
1748 }
1749 else
1750 newPara = para;
1751
1752 // If we're specifying paragraphs only, then we really mean character formatting
1753 // to be included in the paragraph style
1754 if ((paragraphStyle || parasOnly) && !charactersOnly)
1755 {
1756 if (removeStyle)
1757 {
1758 // Removes the given style from the paragraph
1759 wxRichTextRemoveStyle(newPara->GetAttributes(), style);
1760 }
1761 else if (resetExistingStyle)
1762 newPara->GetAttributes() = wholeStyle;
1763 else
1764 {
1765 if (applyMinimal)
1766 {
1767 // Only apply attributes that will make a difference to the combined
1768 // style as seen on the display
1769 wxTextAttr combinedAttr(para->GetCombinedAttributes());
1770 wxRichTextApplyStyle(newPara->GetAttributes(), wholeStyle, & combinedAttr);
1771 }
1772 else
1773 wxRichTextApplyStyle(newPara->GetAttributes(), wholeStyle);
1774 }
1775 }
1776
1777 // When applying paragraph styles dynamically, don't change the text objects' attributes
1778 // since they will computed as needed. Only apply the character styling if it's _only_
1779 // character styling. This policy is subject to change and might be put under user control.
1780
1781 // Hm. we might well be applying a mix of paragraph and character styles, in which
1782 // case we _do_ want to apply character styles regardless of what para styles are set.
1783 // But if we're applying a paragraph style, which has some character attributes, but
1784 // we only want the paragraphs to hold this character style, then we _don't_ want to
1785 // apply the character style. So we need to be able to choose.
1786
1787 // if (!paragraphStyle && characterStyle && range.GetStart() != newPara->GetRange().GetEnd())
1788 if (!parasOnly && characterStyle && range.GetStart() != newPara->GetRange().GetEnd())
1789 {
1790 wxRichTextRange childRange(range);
1791 childRange.LimitTo(newPara->GetRange());
1792
1793 // Find the starting position and if necessary split it so
1794 // we can start applying a different style.
1795 // TODO: check that the style actually changes or is different
1796 // from style outside of range
1797 wxRichTextObject* firstObject wxDUMMY_INITIALIZE(NULL);
1798 wxRichTextObject* lastObject wxDUMMY_INITIALIZE(NULL);
1799
1800 if (childRange.GetStart() == newPara->GetRange().GetStart())
1801 firstObject = newPara->GetChildren().GetFirst()->GetData();
1802 else
1803 firstObject = newPara->SplitAt(range.GetStart());
1804
1805 // Increment by 1 because we're apply the style one _after_ the split point
1806 long splitPoint = childRange.GetEnd();
1807 if (splitPoint != newPara->GetRange().GetEnd())
1808 splitPoint ++;
1809
1810 // Find last object
1811 if (splitPoint == newPara->GetRange().GetEnd())
1812 lastObject = newPara->GetChildren().GetLast()->GetData();
1813 else
1814 // lastObject is set as a side-effect of splitting. It's
1815 // returned as the object before the new object.
1816 (void) newPara->SplitAt(splitPoint, & lastObject);
1817
1818 wxASSERT(firstObject != NULL);
1819 wxASSERT(lastObject != NULL);
1820
1821 if (!firstObject || !lastObject)
1822 continue;
1823
1824 wxRichTextObjectList::compatibility_iterator firstNode = newPara->GetChildren().Find(firstObject);
1825 wxRichTextObjectList::compatibility_iterator lastNode = newPara->GetChildren().Find(lastObject);
1826
1827 wxASSERT(firstNode);
1828 wxASSERT(lastNode);
1829
1830 wxRichTextObjectList::compatibility_iterator node2 = firstNode;
1831
1832 while (node2)
1833 {
1834 wxRichTextObject* child = node2->GetData();
1835
1836 if (removeStyle)
1837 {
1838 // Removes the given style from the paragraph
1839 wxRichTextRemoveStyle(child->GetAttributes(), style);
1840 }
1841 else if (resetExistingStyle)
1842 child->GetAttributes() = characterAttributes;
1843 else
1844 {
1845 if (applyMinimal)
1846 {
1847 // Only apply attributes that will make a difference to the combined
1848 // style as seen on the display
1849 wxTextAttr combinedAttr(newPara->GetCombinedAttributes(child->GetAttributes()));
1850 wxRichTextApplyStyle(child->GetAttributes(), characterAttributes, & combinedAttr);
1851 }
1852 else
1853 wxRichTextApplyStyle(child->GetAttributes(), characterAttributes);
1854 }
1855
1856 if (node2 == lastNode)
1857 break;
1858
1859 node2 = node2->GetNext();
1860 }
1861 }
1862 }
1863 }
1864
1865 node = node->GetNext();
1866 }
1867
1868 // Do action, or delay it until end of batch.
1869 if (haveControl && withUndo)
1870 GetRichTextCtrl()->GetBuffer().SubmitAction(action);
1871
1872 return true;
1873 }
1874
1875 /// Get the text attributes for this position.
1876 bool wxRichTextParagraphLayoutBox::GetStyle(long position, wxTextAttr& style)
1877 {
1878 return DoGetStyle(position, style, true);
1879 }
1880
1881 bool wxRichTextParagraphLayoutBox::GetUncombinedStyle(long position, wxTextAttr& style)
1882 {
1883 return DoGetStyle(position, style, false);
1884 }
1885
1886 /// Implementation helper for GetStyle. If combineStyles is true, combine base, paragraph and
1887 /// context attributes.
1888 bool wxRichTextParagraphLayoutBox::DoGetStyle(long position, wxTextAttr& style, bool combineStyles)
1889 {
1890 wxRichTextObject* obj wxDUMMY_INITIALIZE(NULL);
1891
1892 if (style.IsParagraphStyle())
1893 {
1894 obj = GetParagraphAtPosition(position);
1895 if (obj)
1896 {
1897 if (combineStyles)
1898 {
1899 // Start with the base style
1900 style = GetAttributes();
1901
1902 // Apply the paragraph style
1903 wxRichTextApplyStyle(style, obj->GetAttributes());
1904 }
1905 else
1906 style = obj->GetAttributes();
1907
1908 return true;
1909 }
1910 }
1911 else
1912 {
1913 obj = GetLeafObjectAtPosition(position);
1914 if (obj)
1915 {
1916 if (combineStyles)
1917 {
1918 wxRichTextParagraph* para = wxDynamicCast(obj->GetParent(), wxRichTextParagraph);
1919 style = para ? para->GetCombinedAttributes(obj->GetAttributes()) : obj->GetAttributes();
1920 }
1921 else
1922 style = obj->GetAttributes();
1923
1924 return true;
1925 }
1926 }
1927 return false;
1928 }
1929
1930 static bool wxHasStyle(long flags, long style)
1931 {
1932 return (flags & style) != 0;
1933 }
1934
1935 /// Combines 'style' with 'currentStyle' for the purpose of summarising the attributes of a range of
1936 /// content.
1937 bool wxRichTextParagraphLayoutBox::CollectStyle(wxTextAttr& currentStyle, const wxTextAttr& style, long& multipleStyleAttributes, int& multipleTextEffectAttributes)
1938 {
1939 if (style.HasFont())
1940 {
1941 if (style.HasFontSize() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_FONT_SIZE))
1942 {
1943 if (currentStyle.HasFontSize())
1944 {
1945 if (currentStyle.GetFontSize() != style.GetFontSize())
1946 {
1947 // Clash of style - mark as such
1948 multipleStyleAttributes |= wxTEXT_ATTR_FONT_SIZE;
1949 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_FONT_SIZE);
1950 }
1951 }
1952 else
1953 {
1954 currentStyle.SetFontSize(style.GetFontSize());
1955 }
1956 }
1957
1958 if (style.HasFontItalic() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_FONT_ITALIC))
1959 {
1960 if (currentStyle.HasFontItalic())
1961 {
1962 if (currentStyle.GetFontStyle() != style.GetFontStyle())
1963 {
1964 // Clash of style - mark as such
1965 multipleStyleAttributes |= wxTEXT_ATTR_FONT_ITALIC;
1966 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_FONT_ITALIC);
1967 }
1968 }
1969 else
1970 {
1971 currentStyle.SetFontStyle(style.GetFontStyle());
1972 }
1973 }
1974
1975 if (style.HasFontWeight() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_FONT_WEIGHT))
1976 {
1977 if (currentStyle.HasFontWeight())
1978 {
1979 if (currentStyle.GetFontWeight() != style.GetFontWeight())
1980 {
1981 // Clash of style - mark as such
1982 multipleStyleAttributes |= wxTEXT_ATTR_FONT_WEIGHT;
1983 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_FONT_WEIGHT);
1984 }
1985 }
1986 else
1987 {
1988 currentStyle.SetFontWeight(style.GetFontWeight());
1989 }
1990 }
1991
1992 if (style.HasFontFaceName() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_FONT_FACE))
1993 {
1994 if (currentStyle.HasFontFaceName())
1995 {
1996 wxString faceName1(currentStyle.GetFontFaceName());
1997 wxString faceName2(style.GetFontFaceName());
1998
1999 if (faceName1 != faceName2)
2000 {
2001 // Clash of style - mark as such
2002 multipleStyleAttributes |= wxTEXT_ATTR_FONT_FACE;
2003 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_FONT_FACE);
2004 }
2005 }
2006 else
2007 {
2008 currentStyle.SetFontFaceName(style.GetFontFaceName());
2009 }
2010 }
2011
2012 if (style.HasFontUnderlined() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_FONT_UNDERLINE))
2013 {
2014 if (currentStyle.HasFontUnderlined())
2015 {
2016 if (currentStyle.GetFontUnderlined() != style.GetFontUnderlined())
2017 {
2018 // Clash of style - mark as such
2019 multipleStyleAttributes |= wxTEXT_ATTR_FONT_UNDERLINE;
2020 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_FONT_UNDERLINE);
2021 }
2022 }
2023 else
2024 {
2025 currentStyle.SetFontUnderlined(style.GetFontUnderlined());
2026 }
2027 }
2028 }
2029
2030 if (style.HasTextColour() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_TEXT_COLOUR))
2031 {
2032 if (currentStyle.HasTextColour())
2033 {
2034 if (currentStyle.GetTextColour() != style.GetTextColour())
2035 {
2036 // Clash of style - mark as such
2037 multipleStyleAttributes |= wxTEXT_ATTR_TEXT_COLOUR;
2038 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_TEXT_COLOUR);
2039 }
2040 }
2041 else
2042 currentStyle.SetTextColour(style.GetTextColour());
2043 }
2044
2045 if (style.HasBackgroundColour() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_BACKGROUND_COLOUR))
2046 {
2047 if (currentStyle.HasBackgroundColour())
2048 {
2049 if (currentStyle.GetBackgroundColour() != style.GetBackgroundColour())
2050 {
2051 // Clash of style - mark as such
2052 multipleStyleAttributes |= wxTEXT_ATTR_BACKGROUND_COLOUR;
2053 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_BACKGROUND_COLOUR);
2054 }
2055 }
2056 else
2057 currentStyle.SetBackgroundColour(style.GetBackgroundColour());
2058 }
2059
2060 if (style.HasAlignment() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_ALIGNMENT))
2061 {
2062 if (currentStyle.HasAlignment())
2063 {
2064 if (currentStyle.GetAlignment() != style.GetAlignment())
2065 {
2066 // Clash of style - mark as such
2067 multipleStyleAttributes |= wxTEXT_ATTR_ALIGNMENT;
2068 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_ALIGNMENT);
2069 }
2070 }
2071 else
2072 currentStyle.SetAlignment(style.GetAlignment());
2073 }
2074
2075 if (style.HasTabs() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_TABS))
2076 {
2077 if (currentStyle.HasTabs())
2078 {
2079 if (!wxRichTextTabsEq(currentStyle.GetTabs(), style.GetTabs()))
2080 {
2081 // Clash of style - mark as such
2082 multipleStyleAttributes |= wxTEXT_ATTR_TABS;
2083 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_TABS);
2084 }
2085 }
2086 else
2087 currentStyle.SetTabs(style.GetTabs());
2088 }
2089
2090 if (style.HasLeftIndent() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_LEFT_INDENT))
2091 {
2092 if (currentStyle.HasLeftIndent())
2093 {
2094 if (currentStyle.GetLeftIndent() != style.GetLeftIndent() || currentStyle.GetLeftSubIndent() != style.GetLeftSubIndent())
2095 {
2096 // Clash of style - mark as such
2097 multipleStyleAttributes |= wxTEXT_ATTR_LEFT_INDENT;
2098 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_LEFT_INDENT);
2099 }
2100 }
2101 else
2102 currentStyle.SetLeftIndent(style.GetLeftIndent(), style.GetLeftSubIndent());
2103 }
2104
2105 if (style.HasRightIndent() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_RIGHT_INDENT))
2106 {
2107 if (currentStyle.HasRightIndent())
2108 {
2109 if (currentStyle.GetRightIndent() != style.GetRightIndent())
2110 {
2111 // Clash of style - mark as such
2112 multipleStyleAttributes |= wxTEXT_ATTR_RIGHT_INDENT;
2113 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_RIGHT_INDENT);
2114 }
2115 }
2116 else
2117 currentStyle.SetRightIndent(style.GetRightIndent());
2118 }
2119
2120 if (style.HasParagraphSpacingAfter() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_PARA_SPACING_AFTER))
2121 {
2122 if (currentStyle.HasParagraphSpacingAfter())
2123 {
2124 if (currentStyle.GetParagraphSpacingAfter() != style.GetParagraphSpacingAfter())
2125 {
2126 // Clash of style - mark as such
2127 multipleStyleAttributes |= wxTEXT_ATTR_PARA_SPACING_AFTER;
2128 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_PARA_SPACING_AFTER);
2129 }
2130 }
2131 else
2132 currentStyle.SetParagraphSpacingAfter(style.GetParagraphSpacingAfter());
2133 }
2134
2135 if (style.HasParagraphSpacingBefore() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_PARA_SPACING_BEFORE))
2136 {
2137 if (currentStyle.HasParagraphSpacingBefore())
2138 {
2139 if (currentStyle.GetParagraphSpacingBefore() != style.GetParagraphSpacingBefore())
2140 {
2141 // Clash of style - mark as such
2142 multipleStyleAttributes |= wxTEXT_ATTR_PARA_SPACING_BEFORE;
2143 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_PARA_SPACING_BEFORE);
2144 }
2145 }
2146 else
2147 currentStyle.SetParagraphSpacingBefore(style.GetParagraphSpacingBefore());
2148 }
2149
2150 if (style.HasLineSpacing() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_LINE_SPACING))
2151 {
2152 if (currentStyle.HasLineSpacing())
2153 {
2154 if (currentStyle.GetLineSpacing() != style.GetLineSpacing())
2155 {
2156 // Clash of style - mark as such
2157 multipleStyleAttributes |= wxTEXT_ATTR_LINE_SPACING;
2158 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_LINE_SPACING);
2159 }
2160 }
2161 else
2162 currentStyle.SetLineSpacing(style.GetLineSpacing());
2163 }
2164
2165 if (style.HasCharacterStyleName() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_CHARACTER_STYLE_NAME))
2166 {
2167 if (currentStyle.HasCharacterStyleName())
2168 {
2169 if (currentStyle.GetCharacterStyleName() != style.GetCharacterStyleName())
2170 {
2171 // Clash of style - mark as such
2172 multipleStyleAttributes |= wxTEXT_ATTR_CHARACTER_STYLE_NAME;
2173 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_CHARACTER_STYLE_NAME);
2174 }
2175 }
2176 else
2177 currentStyle.SetCharacterStyleName(style.GetCharacterStyleName());
2178 }
2179
2180 if (style.HasParagraphStyleName() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_PARAGRAPH_STYLE_NAME))
2181 {
2182 if (currentStyle.HasParagraphStyleName())
2183 {
2184 if (currentStyle.GetParagraphStyleName() != style.GetParagraphStyleName())
2185 {
2186 // Clash of style - mark as such
2187 multipleStyleAttributes |= wxTEXT_ATTR_PARAGRAPH_STYLE_NAME;
2188 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_PARAGRAPH_STYLE_NAME);
2189 }
2190 }
2191 else
2192 currentStyle.SetParagraphStyleName(style.GetParagraphStyleName());
2193 }
2194
2195 if (style.HasListStyleName() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_LIST_STYLE_NAME))
2196 {
2197 if (currentStyle.HasListStyleName())
2198 {
2199 if (currentStyle.GetListStyleName() != style.GetListStyleName())
2200 {
2201 // Clash of style - mark as such
2202 multipleStyleAttributes |= wxTEXT_ATTR_LIST_STYLE_NAME;
2203 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_LIST_STYLE_NAME);
2204 }
2205 }
2206 else
2207 currentStyle.SetListStyleName(style.GetListStyleName());
2208 }
2209
2210 if (style.HasBulletStyle() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_BULLET_STYLE))
2211 {
2212 if (currentStyle.HasBulletStyle())
2213 {
2214 if (currentStyle.GetBulletStyle() != style.GetBulletStyle())
2215 {
2216 // Clash of style - mark as such
2217 multipleStyleAttributes |= wxTEXT_ATTR_BULLET_STYLE;
2218 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_BULLET_STYLE);
2219 }
2220 }
2221 else
2222 currentStyle.SetBulletStyle(style.GetBulletStyle());
2223 }
2224
2225 if (style.HasBulletNumber() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_BULLET_NUMBER))
2226 {
2227 if (currentStyle.HasBulletNumber())
2228 {
2229 if (currentStyle.GetBulletNumber() != style.GetBulletNumber())
2230 {
2231 // Clash of style - mark as such
2232 multipleStyleAttributes |= wxTEXT_ATTR_BULLET_NUMBER;
2233 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_BULLET_NUMBER);
2234 }
2235 }
2236 else
2237 currentStyle.SetBulletNumber(style.GetBulletNumber());
2238 }
2239
2240 if (style.HasBulletText() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_BULLET_TEXT))
2241 {
2242 if (currentStyle.HasBulletText())
2243 {
2244 if (currentStyle.GetBulletText() != style.GetBulletText())
2245 {
2246 // Clash of style - mark as such
2247 multipleStyleAttributes |= wxTEXT_ATTR_BULLET_TEXT;
2248 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_BULLET_TEXT);
2249 }
2250 }
2251 else
2252 {
2253 currentStyle.SetBulletText(style.GetBulletText());
2254 currentStyle.SetBulletFont(style.GetBulletFont());
2255 }
2256 }
2257
2258 if (style.HasBulletName() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_BULLET_NAME))
2259 {
2260 if (currentStyle.HasBulletName())
2261 {
2262 if (currentStyle.GetBulletName() != style.GetBulletName())
2263 {
2264 // Clash of style - mark as such
2265 multipleStyleAttributes |= wxTEXT_ATTR_BULLET_NAME;
2266 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_BULLET_NAME);
2267 }
2268 }
2269 else
2270 {
2271 currentStyle.SetBulletName(style.GetBulletName());
2272 }
2273 }
2274
2275 if (style.HasURL() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_URL))
2276 {
2277 if (currentStyle.HasURL())
2278 {
2279 if (currentStyle.GetURL() != style.GetURL())
2280 {
2281 // Clash of style - mark as such
2282 multipleStyleAttributes |= wxTEXT_ATTR_URL;
2283 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_URL);
2284 }
2285 }
2286 else
2287 {
2288 currentStyle.SetURL(style.GetURL());
2289 }
2290 }
2291
2292 if (style.HasTextEffects() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_EFFECTS))
2293 {
2294 if (currentStyle.HasTextEffects())
2295 {
2296 // We need to find the bits in the new style that are different:
2297 // just look at those bits that are specified by the new style.
2298
2299 int currentRelevantTextEffects = currentStyle.GetTextEffects() & style.GetTextEffectFlags();
2300 int newRelevantTextEffects = style.GetTextEffects() & style.GetTextEffectFlags();
2301
2302 if (currentRelevantTextEffects != newRelevantTextEffects)
2303 {
2304 // Find the text effects that were different, using XOR
2305 int differentEffects = currentRelevantTextEffects ^ newRelevantTextEffects;
2306
2307 // Clash of style - mark as such
2308 multipleTextEffectAttributes |= differentEffects;
2309 currentStyle.SetTextEffectFlags(currentStyle.GetTextEffectFlags() & ~differentEffects);
2310 }
2311 }
2312 else
2313 {
2314 currentStyle.SetTextEffects(style.GetTextEffects());
2315 currentStyle.SetTextEffectFlags(style.GetTextEffectFlags());
2316 }
2317 }
2318
2319 if (style.HasOutlineLevel() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_OUTLINE_LEVEL))
2320 {
2321 if (currentStyle.HasOutlineLevel())
2322 {
2323 if (currentStyle.GetOutlineLevel() != style.GetOutlineLevel())
2324 {
2325 // Clash of style - mark as such
2326 multipleStyleAttributes |= wxTEXT_ATTR_OUTLINE_LEVEL;
2327 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_OUTLINE_LEVEL);
2328 }
2329 }
2330 else
2331 currentStyle.SetOutlineLevel(style.GetOutlineLevel());
2332 }
2333
2334 return true;
2335 }
2336
2337 /// Get the combined style for a range - if any attribute is different within the range,
2338 /// that attribute is not present within the flags.
2339 /// *** Note that this is not recursive, and so assumes that content inside a paragraph is not itself
2340 /// nested.
2341 bool wxRichTextParagraphLayoutBox::GetStyleForRange(const wxRichTextRange& range, wxTextAttr& style)
2342 {
2343 style = wxTextAttr();
2344
2345 // The attributes that aren't valid because of multiple styles within the range
2346 long multipleStyleAttributes = 0;
2347 int multipleTextEffectAttributes = 0;
2348
2349 wxRichTextObjectList::compatibility_iterator node = GetChildren().GetFirst();
2350 while (node)
2351 {
2352 wxRichTextParagraph* para = (wxRichTextParagraph*) node->GetData();
2353 if (!(para->GetRange().GetStart() > range.GetEnd() || para->GetRange().GetEnd() < range.GetStart()))
2354 {
2355 if (para->GetChildren().GetCount() == 0)
2356 {
2357 wxTextAttr paraStyle = para->GetCombinedAttributes();
2358
2359 CollectStyle(style, paraStyle, multipleStyleAttributes, multipleTextEffectAttributes);
2360 }
2361 else
2362 {
2363 wxRichTextRange paraRange(para->GetRange());
2364 paraRange.LimitTo(range);
2365
2366 // First collect paragraph attributes only
2367 wxTextAttr paraStyle = para->GetCombinedAttributes();
2368 paraStyle.SetFlags(paraStyle.GetFlags() & wxTEXT_ATTR_PARAGRAPH);
2369 CollectStyle(style, paraStyle, multipleStyleAttributes, multipleTextEffectAttributes);
2370
2371 wxRichTextObjectList::compatibility_iterator childNode = para->GetChildren().GetFirst();
2372
2373 while (childNode)
2374 {
2375 wxRichTextObject* child = childNode->GetData();
2376 if (!(child->GetRange().GetStart() > range.GetEnd() || child->GetRange().GetEnd() < range.GetStart()))
2377 {
2378 wxTextAttr childStyle = para->GetCombinedAttributes(child->GetAttributes());
2379
2380 // Now collect character attributes only
2381 childStyle.SetFlags(childStyle.GetFlags() & wxTEXT_ATTR_CHARACTER);
2382
2383 CollectStyle(style, childStyle, multipleStyleAttributes, multipleTextEffectAttributes);
2384 }
2385
2386 childNode = childNode->GetNext();
2387 }
2388 }
2389 }
2390 node = node->GetNext();
2391 }
2392 return true;
2393 }
2394
2395 /// Set default style
2396 bool wxRichTextParagraphLayoutBox::SetDefaultStyle(const wxTextAttr& style)
2397 {
2398 m_defaultAttributes = style;
2399 return true;
2400 }
2401
2402 /// Test if this whole range has character attributes of the specified kind. If any
2403 /// of the attributes are different within the range, the test fails. You
2404 /// can use this to implement, for example, bold button updating. style must have
2405 /// flags indicating which attributes are of interest.
2406 bool wxRichTextParagraphLayoutBox::HasCharacterAttributes(const wxRichTextRange& range, const wxTextAttr& style) const
2407 {
2408 int foundCount = 0;
2409 int matchingCount = 0;
2410
2411 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2412 while (node)
2413 {
2414 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2415 wxASSERT (para != NULL);
2416
2417 if (para)
2418 {
2419 // Stop searching if we're beyond the range of interest
2420 if (para->GetRange().GetStart() > range.GetEnd())
2421 return foundCount == matchingCount;
2422
2423 if (!para->GetRange().IsOutside(range))
2424 {
2425 wxRichTextObjectList::compatibility_iterator node2 = para->GetChildren().GetFirst();
2426
2427 while (node2)
2428 {
2429 wxRichTextObject* child = node2->GetData();
2430 if (!child->GetRange().IsOutside(range) && child->IsKindOf(CLASSINFO(wxRichTextPlainText)))
2431 {
2432 foundCount ++;
2433 wxTextAttr textAttr = para->GetCombinedAttributes(child->GetAttributes());
2434
2435 if (wxTextAttrEqPartial(textAttr, style, style.GetFlags()))
2436 matchingCount ++;
2437 }
2438
2439 node2 = node2->GetNext();
2440 }
2441 }
2442 }
2443
2444 node = node->GetNext();
2445 }
2446
2447 return foundCount == matchingCount;
2448 }
2449
2450 /// Test if this whole range has paragraph attributes of the specified kind. If any
2451 /// of the attributes are different within the range, the test fails. You
2452 /// can use this to implement, for example, centering button updating. style must have
2453 /// flags indicating which attributes are of interest.
2454 bool wxRichTextParagraphLayoutBox::HasParagraphAttributes(const wxRichTextRange& range, const wxTextAttr& style) const
2455 {
2456 int foundCount = 0;
2457 int matchingCount = 0;
2458
2459 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2460 while (node)
2461 {
2462 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2463 wxASSERT (para != NULL);
2464
2465 if (para)
2466 {
2467 // Stop searching if we're beyond the range of interest
2468 if (para->GetRange().GetStart() > range.GetEnd())
2469 return foundCount == matchingCount;
2470
2471 if (!para->GetRange().IsOutside(range))
2472 {
2473 wxTextAttr textAttr = GetAttributes();
2474 // Apply the paragraph style
2475 wxRichTextApplyStyle(textAttr, para->GetAttributes());
2476
2477 foundCount ++;
2478 if (wxTextAttrEqPartial(textAttr, style, style.GetFlags()))
2479 matchingCount ++;
2480 }
2481 }
2482
2483 node = node->GetNext();
2484 }
2485 return foundCount == matchingCount;
2486 }
2487
2488 void wxRichTextParagraphLayoutBox::Clear()
2489 {
2490 DeleteChildren();
2491 }
2492
2493 void wxRichTextParagraphLayoutBox::Reset()
2494 {
2495 Clear();
2496
2497 wxRichTextBuffer* buffer = wxDynamicCast(this, wxRichTextBuffer);
2498 if (buffer && GetRichTextCtrl())
2499 {
2500 wxRichTextEvent event(wxEVT_COMMAND_RICHTEXT_BUFFER_RESET, GetRichTextCtrl()->GetId());
2501 event.SetEventObject(GetRichTextCtrl());
2502
2503 buffer->SendEvent(event, true);
2504 }
2505
2506 AddParagraph(wxEmptyString);
2507
2508 Invalidate(wxRICHTEXT_ALL);
2509 }
2510
2511 /// Invalidate the buffer. With no argument, invalidates whole buffer.
2512 void wxRichTextParagraphLayoutBox::Invalidate(const wxRichTextRange& invalidRange)
2513 {
2514 SetDirty(true);
2515
2516 if (invalidRange == wxRICHTEXT_ALL)
2517 {
2518 m_invalidRange = wxRICHTEXT_ALL;
2519 return;
2520 }
2521
2522 // Already invalidating everything
2523 if (m_invalidRange == wxRICHTEXT_ALL)
2524 return;
2525
2526 if ((invalidRange.GetStart() < m_invalidRange.GetStart()) || m_invalidRange.GetStart() == -1)
2527 m_invalidRange.SetStart(invalidRange.GetStart());
2528 if (invalidRange.GetEnd() > m_invalidRange.GetEnd())
2529 m_invalidRange.SetEnd(invalidRange.GetEnd());
2530 }
2531
2532 /// Get invalid range, rounding to entire paragraphs if argument is true.
2533 wxRichTextRange wxRichTextParagraphLayoutBox::GetInvalidRange(bool wholeParagraphs) const
2534 {
2535 if (m_invalidRange == wxRICHTEXT_ALL || m_invalidRange == wxRICHTEXT_NONE)
2536 return m_invalidRange;
2537
2538 wxRichTextRange range = m_invalidRange;
2539
2540 if (wholeParagraphs)
2541 {
2542 wxRichTextParagraph* para1 = GetParagraphAtPosition(range.GetStart());
2543 wxRichTextParagraph* para2 = GetParagraphAtPosition(range.GetEnd());
2544 if (para1)
2545 range.SetStart(para1->GetRange().GetStart());
2546 if (para2)
2547 range.SetEnd(para2->GetRange().GetEnd());
2548 }
2549 return range;
2550 }
2551
2552 /// Apply the style sheet to the buffer, for example if the styles have changed.
2553 bool wxRichTextParagraphLayoutBox::ApplyStyleSheet(wxRichTextStyleSheet* styleSheet)
2554 {
2555 wxASSERT(styleSheet != NULL);
2556 if (!styleSheet)
2557 return false;
2558
2559 int foundCount = 0;
2560
2561 wxRichTextAttr attr(GetBasicStyle());
2562 if (GetBasicStyle().HasParagraphStyleName())
2563 {
2564 wxRichTextParagraphStyleDefinition* paraDef = styleSheet->FindParagraphStyle(GetBasicStyle().GetParagraphStyleName());
2565 if (paraDef)
2566 {
2567 attr.Apply(paraDef->GetStyleMergedWithBase(styleSheet));
2568 SetBasicStyle(attr);
2569 foundCount ++;
2570 }
2571 }
2572
2573 if (GetBasicStyle().HasCharacterStyleName())
2574 {
2575 wxRichTextCharacterStyleDefinition* charDef = styleSheet->FindCharacterStyle(GetBasicStyle().GetCharacterStyleName());
2576 if (charDef)
2577 {
2578 attr.Apply(charDef->GetStyleMergedWithBase(styleSheet));
2579 SetBasicStyle(attr);
2580 foundCount ++;
2581 }
2582 }
2583
2584 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2585 while (node)
2586 {
2587 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2588 wxASSERT (para != NULL);
2589
2590 if (para)
2591 {
2592 // Combine paragraph and list styles. If there is a list style in the original attributes,
2593 // the current indentation overrides anything else and is used to find the item indentation.
2594 // Also, for applying paragraph styles, consider having 2 modes: (1) we merge with what we have,
2595 // thereby taking into account all user changes, (2) reset the style completely (except for indentation/list
2596 // exception as above).
2597 // Problem: when changing from one list style to another, there's a danger that the level info will get lost.
2598 // So when changing a list style interactively, could retrieve level based on current style, then
2599 // set appropriate indent and apply new style.
2600
2601 if (!para->GetAttributes().GetParagraphStyleName().IsEmpty() && !para->GetAttributes().GetListStyleName().IsEmpty())
2602 {
2603 int currentIndent = para->GetAttributes().GetLeftIndent();
2604
2605 wxRichTextParagraphStyleDefinition* paraDef = styleSheet->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName());
2606 wxRichTextListStyleDefinition* listDef = styleSheet->FindListStyle(para->GetAttributes().GetListStyleName());
2607 if (paraDef && !listDef)
2608 {
2609 para->GetAttributes() = paraDef->GetStyleMergedWithBase(styleSheet);
2610 foundCount ++;
2611 }
2612 else if (listDef && !paraDef)
2613 {
2614 // Set overall style defined for the list style definition
2615 para->GetAttributes() = listDef->GetStyleMergedWithBase(styleSheet);
2616
2617 // Apply the style for this level
2618 wxRichTextApplyStyle(para->GetAttributes(), * listDef->GetLevelAttributes(listDef->FindLevelForIndent(currentIndent)));
2619 foundCount ++;
2620 }
2621 else if (listDef && paraDef)
2622 {
2623 // Combines overall list style, style for level, and paragraph style
2624 para->GetAttributes() = listDef->CombineWithParagraphStyle(currentIndent, paraDef->GetStyleMergedWithBase(styleSheet));
2625 foundCount ++;
2626 }
2627 }
2628 else if (para->GetAttributes().GetParagraphStyleName().IsEmpty() && !para->GetAttributes().GetListStyleName().IsEmpty())
2629 {
2630 int currentIndent = para->GetAttributes().GetLeftIndent();
2631
2632 wxRichTextListStyleDefinition* listDef = styleSheet->FindListStyle(para->GetAttributes().GetListStyleName());
2633
2634 // Overall list definition style
2635 para->GetAttributes() = listDef->GetStyleMergedWithBase(styleSheet);
2636
2637 // Style for this level
2638 wxRichTextApplyStyle(para->GetAttributes(), * listDef->GetLevelAttributes(listDef->FindLevelForIndent(currentIndent)));
2639
2640 foundCount ++;
2641 }
2642 else if (!para->GetAttributes().GetParagraphStyleName().IsEmpty() && para->GetAttributes().GetListStyleName().IsEmpty())
2643 {
2644 wxRichTextParagraphStyleDefinition* def = styleSheet->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName());
2645 if (def)
2646 {
2647 para->GetAttributes() = def->GetStyleMergedWithBase(styleSheet);
2648 foundCount ++;
2649 }
2650 }
2651 }
2652
2653 node = node->GetNext();
2654 }
2655 return foundCount != 0;
2656 }
2657
2658 /// Set list style
2659 bool wxRichTextParagraphLayoutBox::SetListStyle(const wxRichTextRange& range, wxRichTextListStyleDefinition* def, int flags, int startFrom, int specifiedLevel)
2660 {
2661 wxRichTextStyleSheet* styleSheet = GetStyleSheet();
2662
2663 bool withUndo = ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0);
2664 // bool applyMinimal = ((flags & wxRICHTEXT_SETSTYLE_OPTIMIZE) != 0);
2665 bool specifyLevel = ((flags & wxRICHTEXT_SETSTYLE_SPECIFY_LEVEL) != 0);
2666 bool renumber = ((flags & wxRICHTEXT_SETSTYLE_RENUMBER) != 0);
2667
2668 // Current number, if numbering
2669 int n = startFrom;
2670
2671 wxASSERT (!specifyLevel || (specifyLevel && (specifiedLevel >= 0)));
2672
2673 // If we are associated with a control, make undoable; otherwise, apply immediately
2674 // to the data.
2675
2676 bool haveControl = (GetRichTextCtrl() != NULL);
2677
2678 wxRichTextAction* action = NULL;
2679
2680 if (haveControl && withUndo)
2681 {
2682 action = new wxRichTextAction(NULL, _("Change List Style"), wxRICHTEXT_CHANGE_STYLE, & GetRichTextCtrl()->GetBuffer(), GetRichTextCtrl());
2683 action->SetRange(range);
2684 action->SetPosition(GetRichTextCtrl()->GetCaretPosition());
2685 }
2686
2687 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2688 while (node)
2689 {
2690 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2691 wxASSERT (para != NULL);
2692
2693 if (para && para->GetChildCount() > 0)
2694 {
2695 // Stop searching if we're beyond the range of interest
2696 if (para->GetRange().GetStart() > range.GetEnd())
2697 break;
2698
2699 if (!para->GetRange().IsOutside(range))
2700 {
2701 // We'll be using a copy of the paragraph to make style changes,
2702 // not updating the buffer directly.
2703 wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL);
2704
2705 if (haveControl && withUndo)
2706 {
2707 newPara = new wxRichTextParagraph(*para);
2708 action->GetNewParagraphs().AppendChild(newPara);
2709
2710 // Also store the old ones for Undo
2711 action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para));
2712 }
2713 else
2714 newPara = para;
2715
2716 if (def)
2717 {
2718 int thisIndent = newPara->GetAttributes().GetLeftIndent();
2719 int thisLevel = specifyLevel ? specifiedLevel : def->FindLevelForIndent(thisIndent);
2720
2721 // How is numbering going to work?
2722 // If we are renumbering, or numbering for the first time, we need to keep
2723 // track of the number for each level. But we might be simply applying a different
2724 // list style.
2725 // In Word, applying a style to several paragraphs, even if at different levels,
2726 // reverts the level back to the same one. So we could do the same here.
2727 // Renumbering will need to be done when we promote/demote a paragraph.
2728
2729 // Apply the overall list style, and item style for this level
2730 wxTextAttr listStyle(def->GetCombinedStyleForLevel(thisLevel, styleSheet));
2731 wxRichTextApplyStyle(newPara->GetAttributes(), listStyle);
2732
2733 // Now we need to do numbering
2734 if (renumber)
2735 {
2736 newPara->GetAttributes().SetBulletNumber(n);
2737 }
2738
2739 n ++;
2740 }
2741 else if (!newPara->GetAttributes().GetListStyleName().IsEmpty())
2742 {
2743 // if def is NULL, remove list style, applying any associated paragraph style
2744 // to restore the attributes
2745
2746 newPara->GetAttributes().SetListStyleName(wxEmptyString);
2747 newPara->GetAttributes().SetLeftIndent(0, 0);
2748 newPara->GetAttributes().SetBulletText(wxEmptyString);
2749
2750 // Eliminate the main list-related attributes
2751 newPara->GetAttributes().SetFlags(newPara->GetAttributes().GetFlags() & ~wxTEXT_ATTR_LEFT_INDENT & ~wxTEXT_ATTR_BULLET_STYLE & ~wxTEXT_ATTR_BULLET_NUMBER & ~wxTEXT_ATTR_BULLET_TEXT & wxTEXT_ATTR_LIST_STYLE_NAME);
2752
2753 if (styleSheet && !newPara->GetAttributes().GetParagraphStyleName().IsEmpty())
2754 {
2755 wxRichTextParagraphStyleDefinition* def = styleSheet->FindParagraphStyle(newPara->GetAttributes().GetParagraphStyleName());
2756 if (def)
2757 {
2758 newPara->GetAttributes() = def->GetStyleMergedWithBase(styleSheet);
2759 }
2760 }
2761 }
2762 }
2763 }
2764
2765 node = node->GetNext();
2766 }
2767
2768 // Do action, or delay it until end of batch.
2769 if (haveControl && withUndo)
2770 GetRichTextCtrl()->GetBuffer().SubmitAction(action);
2771
2772 return true;
2773 }
2774
2775 bool wxRichTextParagraphLayoutBox::SetListStyle(const wxRichTextRange& range, const wxString& defName, int flags, int startFrom, int specifiedLevel)
2776 {
2777 if (GetStyleSheet())
2778 {
2779 wxRichTextListStyleDefinition* def = GetStyleSheet()->FindListStyle(defName);
2780 if (def)
2781 return SetListStyle(range, def, flags, startFrom, specifiedLevel);
2782 }
2783 return false;
2784 }
2785
2786 /// Clear list for given range
2787 bool wxRichTextParagraphLayoutBox::ClearListStyle(const wxRichTextRange& range, int flags)
2788 {
2789 return SetListStyle(range, NULL, flags);
2790 }
2791
2792 /// Number/renumber any list elements in the given range
2793 bool wxRichTextParagraphLayoutBox::NumberList(const wxRichTextRange& range, wxRichTextListStyleDefinition* def, int flags, int startFrom, int specifiedLevel)
2794 {
2795 return DoNumberList(range, range, 0, def, flags, startFrom, specifiedLevel);
2796 }
2797
2798 /// Number/renumber any list elements in the given range. Also do promotion or demotion of items, if specified
2799 bool wxRichTextParagraphLayoutBox::DoNumberList(const wxRichTextRange& range, const wxRichTextRange& promotionRange, int promoteBy,
2800 wxRichTextListStyleDefinition* def, int flags, int startFrom, int specifiedLevel)
2801 {
2802 wxRichTextStyleSheet* styleSheet = GetStyleSheet();
2803
2804 bool withUndo = ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0);
2805 // bool applyMinimal = ((flags & wxRICHTEXT_SETSTYLE_OPTIMIZE) != 0);
2806 #ifdef __WXDEBUG__
2807 bool specifyLevel = ((flags & wxRICHTEXT_SETSTYLE_SPECIFY_LEVEL) != 0);
2808 #endif
2809
2810 bool renumber = ((flags & wxRICHTEXT_SETSTYLE_RENUMBER) != 0);
2811
2812 // Max number of levels
2813 const int maxLevels = 10;
2814
2815 // The level we're looking at now
2816 int currentLevel = -1;
2817
2818 // The item number for each level
2819 int levels[maxLevels];
2820 int i;
2821
2822 // Reset all numbering
2823 for (i = 0; i < maxLevels; i++)
2824 {
2825 if (startFrom != -1)
2826 levels[i] = startFrom-1;
2827 else if (renumber) // start again
2828 levels[i] = 0;
2829 else
2830 levels[i] = -1; // start from the number we found, if any
2831 }
2832
2833 wxASSERT(!specifyLevel || (specifyLevel && (specifiedLevel >= 0)));
2834
2835 // If we are associated with a control, make undoable; otherwise, apply immediately
2836 // to the data.
2837
2838 bool haveControl = (GetRichTextCtrl() != NULL);
2839
2840 wxRichTextAction* action = NULL;
2841
2842 if (haveControl && withUndo)
2843 {
2844 action = new wxRichTextAction(NULL, _("Renumber List"), wxRICHTEXT_CHANGE_STYLE, & GetRichTextCtrl()->GetBuffer(), GetRichTextCtrl());
2845 action->SetRange(range);
2846 action->SetPosition(GetRichTextCtrl()->GetCaretPosition());
2847 }
2848
2849 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2850 while (node)
2851 {
2852 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2853 wxASSERT (para != NULL);
2854
2855 if (para && para->GetChildCount() > 0)
2856 {
2857 // Stop searching if we're beyond the range of interest
2858 if (para->GetRange().GetStart() > range.GetEnd())
2859 break;
2860
2861 if (!para->GetRange().IsOutside(range))
2862 {
2863 // We'll be using a copy of the paragraph to make style changes,
2864 // not updating the buffer directly.
2865 wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL);
2866
2867 if (haveControl && withUndo)
2868 {
2869 newPara = new wxRichTextParagraph(*para);
2870 action->GetNewParagraphs().AppendChild(newPara);
2871
2872 // Also store the old ones for Undo
2873 action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para));
2874 }
2875 else
2876 newPara = para;
2877
2878 wxRichTextListStyleDefinition* defToUse = def;
2879 if (!defToUse)
2880 {
2881 if (styleSheet && !newPara->GetAttributes().GetListStyleName().IsEmpty())
2882 defToUse = styleSheet->FindListStyle(newPara->GetAttributes().GetListStyleName());
2883 }
2884
2885 if (defToUse)
2886 {
2887 int thisIndent = newPara->GetAttributes().GetLeftIndent();
2888 int thisLevel = defToUse->FindLevelForIndent(thisIndent);
2889
2890 // If we've specified a level to apply to all, change the level.
2891 if (specifiedLevel != -1)
2892 thisLevel = specifiedLevel;
2893
2894 // Do promotion if specified
2895 if ((promoteBy != 0) && !para->GetRange().IsOutside(promotionRange))
2896 {
2897 thisLevel = thisLevel - promoteBy;
2898 if (thisLevel < 0)
2899 thisLevel = 0;
2900 if (thisLevel > 9)
2901 thisLevel = 9;
2902 }
2903
2904 // Apply the overall list style, and item style for this level
2905 wxTextAttr listStyle(defToUse->GetCombinedStyleForLevel(thisLevel, styleSheet));
2906 wxRichTextApplyStyle(newPara->GetAttributes(), listStyle);
2907
2908 // OK, we've (re)applied the style, now let's get the numbering right.
2909
2910 if (currentLevel == -1)
2911 currentLevel = thisLevel;
2912
2913 // Same level as before, do nothing except increment level's number afterwards
2914 if (currentLevel == thisLevel)
2915 {
2916 }
2917 // A deeper level: start renumbering all levels after current level
2918 else if (thisLevel > currentLevel)
2919 {
2920 for (i = currentLevel+1; i <= thisLevel; i++)
2921 {
2922 levels[i] = 0;
2923 }
2924 currentLevel = thisLevel;
2925 }
2926 else if (thisLevel < currentLevel)
2927 {
2928 currentLevel = thisLevel;
2929 }
2930
2931 // Use the current numbering if -1 and we have a bullet number already
2932 if (levels[currentLevel] == -1)
2933 {
2934 if (newPara->GetAttributes().HasBulletNumber())
2935 levels[currentLevel] = newPara->GetAttributes().GetBulletNumber();
2936 else
2937 levels[currentLevel] = 1;
2938 }
2939 else
2940 {
2941 levels[currentLevel] ++;
2942 }
2943
2944 newPara->GetAttributes().SetBulletNumber(levels[currentLevel]);
2945
2946 // Create the bullet text if an outline list
2947 if (listStyle.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE)
2948 {
2949 wxString text;
2950 for (i = 0; i <= currentLevel; i++)
2951 {
2952 if (!text.IsEmpty())
2953 text += wxT(".");
2954 text += wxString::Format(wxT("%d"), levels[i]);
2955 }
2956 newPara->GetAttributes().SetBulletText(text);
2957 }
2958 }
2959 }
2960 }
2961
2962 node = node->GetNext();
2963 }
2964
2965 // Do action, or delay it until end of batch.
2966 if (haveControl && withUndo)
2967 GetRichTextCtrl()->GetBuffer().SubmitAction(action);
2968
2969 return true;
2970 }
2971
2972 bool wxRichTextParagraphLayoutBox::NumberList(const wxRichTextRange& range, const wxString& defName, int flags, int startFrom, int specifiedLevel)
2973 {
2974 if (GetStyleSheet())
2975 {
2976 wxRichTextListStyleDefinition* def = NULL;
2977 if (!defName.IsEmpty())
2978 def = GetStyleSheet()->FindListStyle(defName);
2979 return NumberList(range, def, flags, startFrom, specifiedLevel);
2980 }
2981 return false;
2982 }
2983
2984 /// Promote the list items within the given range. promoteBy can be a positive or negative number, e.g. 1 or -1
2985 bool wxRichTextParagraphLayoutBox::PromoteList(int promoteBy, const wxRichTextRange& range, wxRichTextListStyleDefinition* def, int flags, int specifiedLevel)
2986 {
2987 // TODO
2988 // One strategy is to first work out the range within which renumbering must occur. Then could pass these two ranges
2989 // to NumberList with a flag indicating promotion is required within one of the ranges.
2990 // Find first and last paragraphs in range. Then for first, calculate new indentation and look back until we find
2991 // a paragraph that either has no list style, or has one that is different or whose indentation is less.
2992 // We start renumbering from the para after that different para we found. We specify that the numbering of that
2993 // list position will start from 1.
2994 // Similarly, we look after the last para in the promote range for an indentation that is less (or no list style).
2995 // We can end the renumbering at this point.
2996
2997 // For now, only renumber within the promotion range.
2998
2999 return DoNumberList(range, range, promoteBy, def, flags, 1, specifiedLevel);
3000 }
3001
3002 bool wxRichTextParagraphLayoutBox::PromoteList(int promoteBy, const wxRichTextRange& range, const wxString& defName, int flags, int specifiedLevel)
3003 {
3004 if (GetStyleSheet())
3005 {
3006 wxRichTextListStyleDefinition* def = NULL;
3007 if (!defName.IsEmpty())
3008 def = GetStyleSheet()->FindListStyle(defName);
3009 return PromoteList(promoteBy, range, def, flags, specifiedLevel);
3010 }
3011 return false;
3012 }
3013
3014 /// Fills in the attributes for numbering a paragraph after previousParagraph. It also finds the
3015 /// position of the paragraph that it had to start looking from.
3016 bool wxRichTextParagraphLayoutBox::FindNextParagraphNumber(wxRichTextParagraph* previousParagraph, wxTextAttr& attr) const
3017 {
3018 if (!previousParagraph->GetAttributes().HasFlag(wxTEXT_ATTR_BULLET_STYLE) || previousParagraph->GetAttributes().GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_NONE)
3019 return false;
3020
3021 wxRichTextStyleSheet* styleSheet = GetStyleSheet();
3022 if (styleSheet && !previousParagraph->GetAttributes().GetListStyleName().IsEmpty())
3023 {
3024 wxRichTextListStyleDefinition* def = styleSheet->FindListStyle(previousParagraph->GetAttributes().GetListStyleName());
3025 if (def)
3026 {
3027 // int thisIndent = previousParagraph->GetAttributes().GetLeftIndent();
3028 // int thisLevel = def->FindLevelForIndent(thisIndent);
3029
3030 bool isOutline = (previousParagraph->GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE) != 0;
3031
3032 attr.SetFlags(previousParagraph->GetAttributes().GetFlags() & (wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_BULLET_NUMBER|wxTEXT_ATTR_BULLET_TEXT|wxTEXT_ATTR_BULLET_NAME));
3033 if (previousParagraph->GetAttributes().HasBulletName())
3034 attr.SetBulletName(previousParagraph->GetAttributes().GetBulletName());
3035 attr.SetBulletStyle(previousParagraph->GetAttributes().GetBulletStyle());
3036 attr.SetListStyleName(previousParagraph->GetAttributes().GetListStyleName());
3037
3038 int nextNumber = previousParagraph->GetAttributes().GetBulletNumber() + 1;
3039 attr.SetBulletNumber(nextNumber);
3040
3041 if (isOutline)
3042 {
3043 wxString text = previousParagraph->GetAttributes().GetBulletText();
3044 if (!text.IsEmpty())
3045 {
3046 int pos = text.Find(wxT('.'), true);
3047 if (pos != wxNOT_FOUND)
3048 {
3049 text = text.Mid(0, text.Length() - pos - 1);
3050 }
3051 else
3052 text = wxEmptyString;
3053 if (!text.IsEmpty())
3054 text += wxT(".");
3055 text += wxString::Format(wxT("%d"), nextNumber);
3056 attr.SetBulletText(text);
3057 }
3058 }
3059
3060 return true;
3061 }
3062 else
3063 return false;
3064 }
3065 else
3066 return false;
3067 }
3068
3069 /*!
3070 * wxRichTextParagraph
3071 * This object represents a single paragraph (or in a straight text editor, a line).
3072 */
3073
3074 IMPLEMENT_DYNAMIC_CLASS(wxRichTextParagraph, wxRichTextBox)
3075
3076 wxArrayInt wxRichTextParagraph::sm_defaultTabs;
3077
3078 wxRichTextParagraph::wxRichTextParagraph(wxRichTextObject* parent, wxTextAttr* style):
3079 wxRichTextBox(parent)
3080 {
3081 if (style)
3082 SetAttributes(*style);
3083 }
3084
3085 wxRichTextParagraph::wxRichTextParagraph(const wxString& text, wxRichTextObject* parent, wxTextAttr* paraStyle, wxTextAttr* charStyle):
3086 wxRichTextBox(parent)
3087 {
3088 if (paraStyle)
3089 SetAttributes(*paraStyle);
3090
3091 AppendChild(new wxRichTextPlainText(text, this, charStyle));
3092 }
3093
3094 wxRichTextParagraph::~wxRichTextParagraph()
3095 {
3096 ClearLines();
3097 }
3098
3099 /// Draw the item
3100 bool wxRichTextParagraph::Draw(wxDC& dc, const wxRichTextRange& range, const wxRichTextRange& selectionRange, const wxRect& WXUNUSED(rect), int WXUNUSED(descent), int style)
3101 {
3102 wxTextAttr attr = GetCombinedAttributes();
3103
3104 // Draw the bullet, if any
3105 if (attr.GetBulletStyle() != wxTEXT_ATTR_BULLET_STYLE_NONE)
3106 {
3107 if (attr.GetLeftSubIndent() != 0)
3108 {
3109 int spaceBeforePara = ConvertTenthsMMToPixels(dc, attr.GetParagraphSpacingBefore());
3110 int leftIndent = ConvertTenthsMMToPixels(dc, attr.GetLeftIndent());
3111
3112 wxTextAttr bulletAttr(GetCombinedAttributes());
3113
3114 // Combine with the font of the first piece of content, if one is specified
3115 if (GetChildren().GetCount() > 0)
3116 {
3117 wxRichTextObject* firstObj = (wxRichTextObject*) GetChildren().GetFirst()->GetData();
3118 if (firstObj->GetAttributes().HasFont())
3119 {
3120 wxRichTextApplyStyle(bulletAttr, firstObj->GetAttributes());
3121 }
3122 }
3123
3124 // Get line height from first line, if any
3125 wxRichTextLine* line = m_cachedLines.GetFirst() ? (wxRichTextLine* ) m_cachedLines.GetFirst()->GetData() : (wxRichTextLine*) NULL;
3126
3127 wxPoint linePos;
3128 int lineHeight wxDUMMY_INITIALIZE(0);
3129 if (line)
3130 {
3131 lineHeight = line->GetSize().y;
3132 linePos = line->GetPosition() + GetPosition();
3133 }
3134 else
3135 {
3136 wxFont font;
3137 if (bulletAttr.HasFont() && GetBuffer())
3138 font = GetBuffer()->GetFontTable().FindFont(bulletAttr);
3139 else
3140 font = (*wxNORMAL_FONT);
3141
3142 wxCheckSetFont(dc, font);
3143
3144 lineHeight = dc.GetCharHeight();
3145 linePos = GetPosition();
3146 linePos.y += spaceBeforePara;
3147 }
3148
3149 wxRect bulletRect(GetPosition().x + leftIndent, linePos.y, linePos.x - (GetPosition().x + leftIndent), lineHeight);
3150
3151 if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_BITMAP)
3152 {
3153 if (wxRichTextBuffer::GetRenderer())
3154 wxRichTextBuffer::GetRenderer()->DrawBitmapBullet(this, dc, bulletAttr, bulletRect);
3155 }
3156 else if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_STANDARD)
3157 {
3158 if (wxRichTextBuffer::GetRenderer())
3159 wxRichTextBuffer::GetRenderer()->DrawStandardBullet(this, dc, bulletAttr, bulletRect);
3160 }
3161 else
3162 {
3163 wxString bulletText = GetBulletText();
3164
3165 if (!bulletText.empty() && wxRichTextBuffer::GetRenderer())
3166 wxRichTextBuffer::GetRenderer()->DrawTextBullet(this, dc, bulletAttr, bulletRect, bulletText);
3167 }
3168 }
3169 }
3170
3171 // Draw the range for each line, one object at a time.
3172
3173 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
3174 while (node)
3175 {
3176 wxRichTextLine* line = node->GetData();
3177 wxRichTextRange lineRange = line->GetAbsoluteRange();
3178
3179 int maxDescent = line->GetDescent();
3180
3181 // Lines are specified relative to the paragraph
3182
3183 wxPoint linePosition = line->GetPosition() + GetPosition();
3184 wxPoint objectPosition = linePosition;
3185
3186 // Loop through objects until we get to the one within range
3187 wxRichTextObjectList::compatibility_iterator node2 = m_children.GetFirst();
3188 while (node2)
3189 {
3190 wxRichTextObject* child = node2->GetData();
3191
3192 if (!child->GetRange().IsOutside(lineRange) && !lineRange.IsOutside(range))
3193 {
3194 // Draw this part of the line at the correct position
3195 wxRichTextRange objectRange(child->GetRange());
3196 objectRange.LimitTo(lineRange);
3197
3198 wxSize objectSize;
3199 int descent = 0;
3200 child->GetRangeSize(objectRange, objectSize, descent, dc, wxRICHTEXT_UNFORMATTED, objectPosition);
3201
3202 // Use the child object's width, but the whole line's height
3203 wxRect childRect(objectPosition, wxSize(objectSize.x, line->GetSize().y));
3204 child->Draw(dc, objectRange, selectionRange, childRect, maxDescent, style);
3205
3206 objectPosition.x += objectSize.x;
3207 }
3208 else if (child->GetRange().GetStart() > lineRange.GetEnd())
3209 // Can break out of inner loop now since we've passed this line's range
3210 break;
3211
3212 node2 = node2->GetNext();
3213 }
3214
3215 node = node->GetNext();
3216 }
3217
3218 return true;
3219 }
3220
3221 /// Lay the item out
3222 bool wxRichTextParagraph::Layout(wxDC& dc, const wxRect& rect, int style)
3223 {
3224 wxTextAttr attr = GetCombinedAttributes();
3225
3226 // ClearLines();
3227
3228 // Increase the size of the paragraph due to spacing
3229 int spaceBeforePara = ConvertTenthsMMToPixels(dc, attr.GetParagraphSpacingBefore());
3230 int spaceAfterPara = ConvertTenthsMMToPixels(dc, attr.GetParagraphSpacingAfter());
3231 int leftIndent = ConvertTenthsMMToPixels(dc, attr.GetLeftIndent());
3232 int leftSubIndent = ConvertTenthsMMToPixels(dc, attr.GetLeftSubIndent());
3233 int rightIndent = ConvertTenthsMMToPixels(dc, attr.GetRightIndent());
3234
3235 int lineSpacing = 0;
3236
3237 // Let's assume line spacing of 10 is normal, 15 is 1.5, 20 is 2, etc.
3238 if (attr.GetLineSpacing() != 10 && GetBuffer())
3239 {
3240 wxFont font(GetBuffer()->GetFontTable().FindFont(attr));
3241 wxCheckSetFont(dc, font);
3242 lineSpacing = (ConvertTenthsMMToPixels(dc, dc.GetCharHeight()) * attr.GetLineSpacing())/10;
3243 }
3244
3245 // Available space for text on each line differs.
3246 int availableTextSpaceFirstLine = rect.GetWidth() - leftIndent - rightIndent;
3247
3248 // Bullets start the text at the same position as subsequent lines
3249 if (attr.GetBulletStyle() != wxTEXT_ATTR_BULLET_STYLE_NONE)
3250 availableTextSpaceFirstLine -= leftSubIndent;
3251
3252 int availableTextSpaceSubsequentLines = rect.GetWidth() - leftIndent - rightIndent - leftSubIndent;
3253
3254 // Start position for each line relative to the paragraph
3255 int startPositionFirstLine = leftIndent;
3256 int startPositionSubsequentLines = leftIndent + leftSubIndent;
3257
3258 // If we have a bullet in this paragraph, the start position for the first line's text
3259 // is actually leftIndent + leftSubIndent.
3260 if (attr.GetBulletStyle() != wxTEXT_ATTR_BULLET_STYLE_NONE)
3261 startPositionFirstLine = startPositionSubsequentLines;
3262
3263 long lastEndPos = GetRange().GetStart()-1;
3264 long lastCompletedEndPos = lastEndPos;
3265
3266 int currentWidth = 0;
3267 SetPosition(rect.GetPosition());
3268
3269 wxPoint currentPosition(0, spaceBeforePara); // We will calculate lines relative to paragraph
3270 int lineHeight = 0;
3271 int maxWidth = 0;
3272 int maxDescent = 0;
3273
3274 int lineCount = 0;
3275
3276 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3277 while (node)
3278 {
3279 wxRichTextObject* child = node->GetData();
3280
3281 child->SetCachedSize(wxDefaultSize);
3282 child->Layout(dc, rect, style);
3283
3284 node = node->GetNext();
3285 }
3286
3287 // Split up lines
3288
3289 // We may need to go back to a previous child, in which case create the new line,
3290 // find the child corresponding to the start position of the string, and
3291 // continue.
3292
3293 node = m_children.GetFirst();
3294 while (node)
3295 {
3296 wxRichTextObject* child = node->GetData();
3297
3298 // If this is e.g. a composite text box, it will need to be laid out itself.
3299 // But if just a text fragment or image, for example, this will
3300 // do nothing. NB: won't we need to set the position after layout?
3301 // since for example if position is dependent on vertical line size, we
3302 // can't tell the position until the size is determined. So possibly introduce
3303 // another layout phase.
3304
3305 // Available width depends on whether we're on the first or subsequent lines
3306 int availableSpaceForText = (lineCount == 0 ? availableTextSpaceFirstLine : availableTextSpaceSubsequentLines);
3307
3308 currentPosition.x = (lineCount == 0 ? startPositionFirstLine : startPositionSubsequentLines);
3309
3310 // We may only be looking at part of a child, if we searched back for wrapping
3311 // and found a suitable point some way into the child. So get the size for the fragment
3312 // if necessary.
3313
3314 long nextBreakPos = GetFirstLineBreakPosition(lastEndPos+1);
3315 long lastPosToUse = child->GetRange().GetEnd();
3316 bool lineBreakInThisObject = (nextBreakPos > -1 && nextBreakPos <= child->GetRange().GetEnd());
3317
3318 if (lineBreakInThisObject)
3319 lastPosToUse = nextBreakPos;
3320
3321 wxSize childSize;
3322 int childDescent = 0;
3323
3324 if ((nextBreakPos == -1) && (lastEndPos == child->GetRange().GetStart() - 1)) // i.e. we want to get the whole thing
3325 {
3326 childSize = child->GetCachedSize();
3327 childDescent = child->GetDescent();
3328 }
3329 else
3330 GetRangeSize(wxRichTextRange(lastEndPos+1, lastPosToUse), childSize, childDescent, dc, wxRICHTEXT_UNFORMATTED, rect.GetPosition());
3331
3332 // Cases:
3333 // 1) There was a line break BEFORE the natural break
3334 // 2) There was a line break AFTER the natural break
3335 // 3) The child still fits (carry on)
3336
3337 if ((lineBreakInThisObject && (childSize.x + currentWidth <= availableSpaceForText)) ||
3338 (childSize.x + currentWidth > availableSpaceForText))
3339 {
3340 long wrapPosition = 0;
3341
3342 // Find a place to wrap. This may walk back to previous children,
3343 // for example if a word spans several objects.
3344 if (!FindWrapPosition(wxRichTextRange(lastCompletedEndPos+1, child->GetRange().GetEnd()), dc, availableSpaceForText, wrapPosition))
3345 {
3346 // If the function failed, just cut it off at the end of this child.
3347 wrapPosition = child->GetRange().GetEnd();
3348 }
3349
3350 // FindWrapPosition can still return a value that will put us in an endless wrapping loop
3351 if (wrapPosition <= lastCompletedEndPos)
3352 wrapPosition = wxMax(lastCompletedEndPos+1,child->GetRange().GetEnd());
3353
3354 // wxLogDebug(wxT("Split at %ld"), wrapPosition);
3355
3356 // Let's find the actual size of the current line now
3357 wxSize actualSize;
3358 wxRichTextRange actualRange(lastCompletedEndPos+1, wrapPosition);
3359 GetRangeSize(actualRange, actualSize, childDescent, dc, wxRICHTEXT_UNFORMATTED);
3360 currentWidth = actualSize.x;
3361 lineHeight = wxMax(lineHeight, actualSize.y);
3362 maxDescent = wxMax(childDescent, maxDescent);
3363
3364 // Add a new line
3365 wxRichTextLine* line = AllocateLine(lineCount);
3366
3367 // Set relative range so we won't have to change line ranges when paragraphs are moved
3368 line->SetRange(wxRichTextRange(actualRange.GetStart() - GetRange().GetStart(), actualRange.GetEnd() - GetRange().GetStart()));
3369 line->SetPosition(currentPosition);
3370 line->SetSize(wxSize(currentWidth, lineHeight));
3371 line->SetDescent(maxDescent);
3372
3373 // Now move down a line. TODO: add margins, spacing
3374 currentPosition.y += lineHeight;
3375 currentPosition.y += lineSpacing;
3376 currentWidth = 0;
3377 maxDescent = 0;
3378 maxWidth = wxMax(maxWidth, currentWidth);
3379
3380 lineCount ++;
3381
3382 // TODO: account for zero-length objects, such as fields
3383 wxASSERT(wrapPosition > lastCompletedEndPos);
3384
3385 lastEndPos = wrapPosition;
3386 lastCompletedEndPos = lastEndPos;
3387
3388 lineHeight = 0;
3389
3390 // May need to set the node back to a previous one, due to searching back in wrapping
3391 wxRichTextObject* childAfterWrapPosition = FindObjectAtPosition(wrapPosition+1);
3392 if (childAfterWrapPosition)
3393 node = m_children.Find(childAfterWrapPosition);
3394 else
3395 node = node->GetNext();
3396 }
3397 else
3398 {
3399 // We still fit, so don't add a line, and keep going
3400 currentWidth += childSize.x;
3401 lineHeight = wxMax(lineHeight, childSize.y);
3402 maxDescent = wxMax(childDescent, maxDescent);
3403
3404 maxWidth = wxMax(maxWidth, currentWidth);
3405 lastEndPos = child->GetRange().GetEnd();
3406
3407 node = node->GetNext();
3408 }
3409 }
3410
3411 // Add the last line - it's the current pos -> last para pos
3412 // Substract -1 because the last position is always the end-paragraph position.
3413 if (lastCompletedEndPos <= GetRange().GetEnd()-1)
3414 {
3415 currentPosition.x = (lineCount == 0 ? startPositionFirstLine : startPositionSubsequentLines);
3416
3417 wxRichTextLine* line = AllocateLine(lineCount);
3418
3419 wxRichTextRange actualRange(lastCompletedEndPos+1, GetRange().GetEnd()-1);
3420
3421 // Set relative range so we won't have to change line ranges when paragraphs are moved
3422 line->SetRange(wxRichTextRange(actualRange.GetStart() - GetRange().GetStart(), actualRange.GetEnd() - GetRange().GetStart()));
3423
3424 line->SetPosition(currentPosition);
3425
3426 if (lineHeight == 0 && GetBuffer())
3427 {
3428 wxFont font(GetBuffer()->GetFontTable().FindFont(attr));
3429 wxCheckSetFont(dc, font);
3430 lineHeight = dc.GetCharHeight();
3431 }
3432 if (maxDescent == 0)
3433 {
3434 int w, h;
3435 dc.GetTextExtent(wxT("X"), & w, &h, & maxDescent);
3436 }
3437
3438 line->SetSize(wxSize(currentWidth, lineHeight));
3439 line->SetDescent(maxDescent);
3440 currentPosition.y += lineHeight;
3441 currentPosition.y += lineSpacing;
3442 lineCount ++;
3443 }
3444
3445 // Remove remaining unused line objects, if any
3446 ClearUnusedLines(lineCount);
3447
3448 // Apply styles to wrapped lines
3449 ApplyParagraphStyle(attr, rect);
3450
3451 SetCachedSize(wxSize(maxWidth, currentPosition.y + spaceBeforePara + spaceAfterPara));
3452
3453 m_dirty = false;
3454
3455 return true;
3456 }
3457
3458 /// Apply paragraph styles, such as centering, to wrapped lines
3459 void wxRichTextParagraph::ApplyParagraphStyle(const wxTextAttr& attr, const wxRect& rect)
3460 {
3461 if (!attr.HasAlignment())
3462 return;
3463
3464 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
3465 while (node)
3466 {
3467 wxRichTextLine* line = node->GetData();
3468
3469 wxPoint pos = line->GetPosition();
3470 wxSize size = line->GetSize();
3471
3472 // centering, right-justification
3473 if (attr.HasAlignment() && GetAttributes().GetAlignment() == wxTEXT_ALIGNMENT_CENTRE)
3474 {
3475 pos.x = (rect.GetWidth() - size.x)/2 + pos.x;
3476 line->SetPosition(pos);
3477 }
3478 else if (attr.HasAlignment() && GetAttributes().GetAlignment() == wxTEXT_ALIGNMENT_RIGHT)
3479 {
3480 pos.x = pos.x + rect.GetWidth() - size.x;
3481 line->SetPosition(pos);
3482 }
3483
3484 node = node->GetNext();
3485 }
3486 }
3487
3488 /// Insert text at the given position
3489 bool wxRichTextParagraph::InsertText(long pos, const wxString& text)
3490 {
3491 wxRichTextObject* childToUse = NULL;
3492 wxRichTextObjectList::compatibility_iterator nodeToUse = wxRichTextObjectList::compatibility_iterator();
3493
3494 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3495 while (node)
3496 {
3497 wxRichTextObject* child = node->GetData();
3498 if (child->GetRange().Contains(pos) && child->GetRange().GetLength() > 0)
3499 {
3500 childToUse = child;
3501 nodeToUse = node;
3502 break;
3503 }
3504
3505 node = node->GetNext();
3506 }
3507
3508 if (childToUse)
3509 {
3510 wxRichTextPlainText* textObject = wxDynamicCast(childToUse, wxRichTextPlainText);
3511 if (textObject)
3512 {
3513 int posInString = pos - textObject->GetRange().GetStart();
3514
3515 wxString newText = textObject->GetText().Mid(0, posInString) +
3516 text + textObject->GetText().Mid(posInString);
3517 textObject->SetText(newText);
3518
3519 int textLength = text.length();
3520
3521 textObject->SetRange(wxRichTextRange(textObject->GetRange().GetStart(),
3522 textObject->GetRange().GetEnd() + textLength));
3523
3524 // Increment the end range of subsequent fragments in this paragraph.
3525 // We'll set the paragraph range itself at a higher level.
3526
3527 wxRichTextObjectList::compatibility_iterator node = nodeToUse->GetNext();
3528 while (node)
3529 {
3530 wxRichTextObject* child = node->GetData();
3531 child->SetRange(wxRichTextRange(textObject->GetRange().GetStart() + textLength,
3532 textObject->GetRange().GetEnd() + textLength));
3533
3534 node = node->GetNext();
3535 }
3536
3537 return true;
3538 }
3539 else
3540 {
3541 // TODO: if not a text object, insert at closest position, e.g. in front of it
3542 }
3543 }
3544 else
3545 {
3546 // Add at end.
3547 // Don't pass parent initially to suppress auto-setting of parent range.
3548 // We'll do that at a higher level.
3549 wxRichTextPlainText* textObject = new wxRichTextPlainText(text, this);
3550
3551 AppendChild(textObject);
3552 return true;
3553 }
3554
3555 return false;
3556 }
3557
3558 void wxRichTextParagraph::Copy(const wxRichTextParagraph& obj)
3559 {
3560 wxRichTextBox::Copy(obj);
3561 }
3562
3563 /// Clear the cached lines
3564 void wxRichTextParagraph::ClearLines()
3565 {
3566 WX_CLEAR_LIST(wxRichTextLineList, m_cachedLines);
3567 }
3568
3569 /// Get/set the object size for the given range. Returns false if the range
3570 /// is invalid for this object.
3571 bool wxRichTextParagraph::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, int flags, wxPoint position) const
3572 {
3573 if (!range.IsWithin(GetRange()))
3574 return false;
3575
3576 if (flags & wxRICHTEXT_UNFORMATTED)
3577 {
3578 // Just use unformatted data, assume no line breaks
3579 // TODO: take into account line breaks
3580
3581 wxSize sz;
3582
3583 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3584 while (node)
3585 {
3586 wxRichTextObject* child = node->GetData();
3587 if (!child->GetRange().IsOutside(range))
3588 {
3589 wxSize childSize;
3590
3591 wxRichTextRange rangeToUse = range;
3592 rangeToUse.LimitTo(child->GetRange());
3593 int childDescent = 0;
3594
3595 if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, flags, wxPoint(position.x + sz.x, position.y)))
3596 {
3597 sz.y = wxMax(sz.y, childSize.y);
3598 sz.x += childSize.x;
3599 descent = wxMax(descent, childDescent);
3600 }
3601 }
3602
3603 node = node->GetNext();
3604 }
3605 size = sz;
3606 }
3607 else
3608 {
3609 // Use formatted data, with line breaks
3610 wxSize sz;
3611
3612 // We're going to loop through each line, and then for each line,
3613 // call GetRangeSize for the fragment that comprises that line.
3614 // Only we have to do that multiple times within the line, because
3615 // the line may be broken into pieces. For now ignore line break commands
3616 // (so we can assume that getting the unformatted size for a fragment
3617 // within a line is the actual size)
3618
3619 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
3620 while (node)
3621 {
3622 wxRichTextLine* line = node->GetData();
3623 wxRichTextRange lineRange = line->GetAbsoluteRange();
3624 if (!lineRange.IsOutside(range))
3625 {
3626 wxSize lineSize;
3627
3628 wxRichTextObjectList::compatibility_iterator node2 = m_children.GetFirst();
3629 while (node2)
3630 {
3631 wxRichTextObject* child = node2->GetData();
3632
3633 if (!child->GetRange().IsOutside(lineRange))
3634 {
3635 wxRichTextRange rangeToUse = lineRange;
3636 rangeToUse.LimitTo(child->GetRange());
3637
3638 wxSize childSize;
3639 int childDescent = 0;
3640 if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, flags, wxPoint(position.x + sz.x, position.y)))
3641 {
3642 lineSize.y = wxMax(lineSize.y, childSize.y);
3643 lineSize.x += childSize.x;
3644 }
3645 descent = wxMax(descent, childDescent);
3646 }
3647
3648 node2 = node2->GetNext();
3649 }
3650
3651 // Increase size by a line (TODO: paragraph spacing)
3652 sz.y += lineSize.y;
3653 sz.x = wxMax(sz.x, lineSize.x);
3654 }
3655 node = node->GetNext();
3656 }
3657 size = sz;
3658 }
3659 return true;
3660 }
3661
3662 /// Finds the absolute position and row height for the given character position
3663 bool wxRichTextParagraph::FindPosition(wxDC& dc, long index, wxPoint& pt, int* height, bool forceLineStart)
3664 {
3665 if (index == -1)
3666 {
3667 wxRichTextLine* line = ((wxRichTextParagraphLayoutBox*)GetParent())->GetLineAtPosition(0);
3668 if (line)
3669 *height = line->GetSize().y;
3670 else
3671 *height = dc.GetCharHeight();
3672
3673 // -1 means 'the start of the buffer'.
3674 pt = GetPosition();
3675 if (line)
3676 pt = pt + line->GetPosition();
3677
3678 return true;
3679 }
3680
3681 // The final position in a paragraph is taken to mean the position
3682 // at the start of the next paragraph.
3683 if (index == GetRange().GetEnd())
3684 {
3685 wxRichTextParagraphLayoutBox* parent = wxDynamicCast(GetParent(), wxRichTextParagraphLayoutBox);
3686 wxASSERT( parent != NULL );
3687
3688 // Find the height at the next paragraph, if any
3689 wxRichTextLine* line = parent->GetLineAtPosition(index + 1);
3690 if (line)
3691 {
3692 *height = line->GetSize().y;
3693 pt = line->GetAbsolutePosition();
3694 }
3695 else
3696 {
3697 *height = dc.GetCharHeight();
3698 int indent = ConvertTenthsMMToPixels(dc, m_attributes.GetLeftIndent());
3699 pt = wxPoint(indent, GetCachedSize().y);
3700 }
3701
3702 return true;
3703 }
3704
3705 if (index < GetRange().GetStart() || index > GetRange().GetEnd())
3706 return false;
3707
3708 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
3709 while (node)
3710 {
3711 wxRichTextLine* line = node->GetData();
3712 wxRichTextRange lineRange = line->GetAbsoluteRange();
3713 if (index >= lineRange.GetStart() && index <= lineRange.GetEnd())
3714 {
3715 // If this is the last point in the line, and we're forcing the
3716 // returned value to be the start of the next line, do the required
3717 // thing.
3718 if (index == lineRange.GetEnd() && forceLineStart)
3719 {
3720 if (node->GetNext())
3721 {
3722 wxRichTextLine* nextLine = node->GetNext()->GetData();
3723 *height = nextLine->GetSize().y;
3724 pt = nextLine->GetAbsolutePosition();
3725 return true;
3726 }
3727 }
3728
3729 pt.y = line->GetPosition().y + GetPosition().y;
3730
3731 wxRichTextRange r(lineRange.GetStart(), index);
3732 wxSize rangeSize;
3733 int descent = 0;
3734
3735 // We find the size of the line up to this point,
3736 // then we can add this size to the line start position and
3737 // paragraph start position to find the actual position.
3738
3739 if (GetRangeSize(r, rangeSize, descent, dc, wxRICHTEXT_UNFORMATTED, line->GetPosition()+ GetPosition()))
3740 {
3741 pt.x = line->GetPosition().x + GetPosition().x + rangeSize.x;
3742 *height = line->GetSize().y;
3743
3744 return true;
3745 }
3746
3747 }
3748
3749 node = node->GetNext();
3750 }
3751
3752 return false;
3753 }
3754
3755 /// Hit-testing: returns a flag indicating hit test details, plus
3756 /// information about position
3757 int wxRichTextParagraph::HitTest(wxDC& dc, const wxPoint& pt, long& textPosition)
3758 {
3759 wxPoint paraPos = GetPosition();
3760
3761 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
3762 while (node)
3763 {
3764 wxRichTextLine* line = node->GetData();
3765 wxPoint linePos = paraPos + line->GetPosition();
3766 wxSize lineSize = line->GetSize();
3767 wxRichTextRange lineRange = line->GetAbsoluteRange();
3768
3769 if (pt.y <= linePos.y + lineSize.y)
3770 {
3771 if (pt.x < linePos.x)
3772 {
3773 textPosition = lineRange.GetStart();
3774 return wxRICHTEXT_HITTEST_BEFORE|wxRICHTEXT_HITTEST_OUTSIDE;
3775 }
3776 else if (pt.x >= (linePos.x + lineSize.x))
3777 {
3778 textPosition = lineRange.GetEnd();
3779 return wxRICHTEXT_HITTEST_AFTER|wxRICHTEXT_HITTEST_OUTSIDE;
3780 }
3781 else
3782 {
3783 long i;
3784 int lastX = linePos.x;
3785 for (i = lineRange.GetStart(); i <= lineRange.GetEnd(); i++)
3786 {
3787 wxSize childSize;
3788 int descent = 0;
3789
3790 wxRichTextRange rangeToUse(lineRange.GetStart(), i);
3791
3792 GetRangeSize(rangeToUse, childSize, descent, dc, wxRICHTEXT_UNFORMATTED, linePos);
3793
3794 int nextX = childSize.x + linePos.x;
3795
3796 if (pt.x >= lastX && pt.x <= nextX)
3797 {
3798 textPosition = i;
3799
3800 // So now we know it's between i-1 and i.
3801 // Let's see if we can be more precise about
3802 // which side of the position it's on.
3803
3804 int midPoint = (nextX - lastX)/2 + lastX;
3805 if (pt.x >= midPoint)
3806 return wxRICHTEXT_HITTEST_AFTER;
3807 else
3808 return wxRICHTEXT_HITTEST_BEFORE;
3809 }
3810 else
3811 {
3812 lastX = nextX;
3813 }
3814 }
3815 }
3816 }
3817
3818 node = node->GetNext();
3819 }
3820
3821 return wxRICHTEXT_HITTEST_NONE;
3822 }
3823
3824 /// Split an object at this position if necessary, and return
3825 /// the previous object, or NULL if inserting at beginning.
3826 wxRichTextObject* wxRichTextParagraph::SplitAt(long pos, wxRichTextObject** previousObject)
3827 {
3828 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3829 while (node)
3830 {
3831 wxRichTextObject* child = node->GetData();
3832
3833 if (pos == child->GetRange().GetStart())
3834 {
3835 if (previousObject)
3836 {
3837 if (node->GetPrevious())
3838 *previousObject = node->GetPrevious()->GetData();
3839 else
3840 *previousObject = NULL;
3841 }
3842
3843 return child;
3844 }
3845
3846 if (child->GetRange().Contains(pos))
3847 {
3848 // This should create a new object, transferring part of
3849 // the content to the old object and the rest to the new object.
3850 wxRichTextObject* newObject = child->DoSplit(pos);
3851
3852 // If we couldn't split this object, just insert in front of it.
3853 if (!newObject)
3854 {
3855 // Maybe this is an empty string, try the next one
3856 // return child;
3857 }
3858 else
3859 {
3860 // Insert the new object after 'child'
3861 if (node->GetNext())
3862 m_children.Insert(node->GetNext(), newObject);
3863 else
3864 m_children.Append(newObject);
3865 newObject->SetParent(this);
3866
3867 if (previousObject)
3868 *previousObject = child;
3869
3870 return newObject;
3871 }
3872 }
3873
3874 node = node->GetNext();
3875 }
3876 if (previousObject)
3877 *previousObject = NULL;
3878 return NULL;
3879 }
3880
3881 /// Move content to a list from obj on
3882 void wxRichTextParagraph::MoveToList(wxRichTextObject* obj, wxList& list)
3883 {
3884 wxRichTextObjectList::compatibility_iterator node = m_children.Find(obj);
3885 while (node)
3886 {
3887 wxRichTextObject* child = node->GetData();
3888 list.Append(child);
3889
3890 wxRichTextObjectList::compatibility_iterator oldNode = node;
3891
3892 node = node->GetNext();
3893
3894 m_children.DeleteNode(oldNode);
3895 }
3896 }
3897
3898 /// Add content back from list
3899 void wxRichTextParagraph::MoveFromList(wxList& list)
3900 {
3901 for (wxList::compatibility_iterator node = list.GetFirst(); node; node = node->GetNext())
3902 {
3903 AppendChild((wxRichTextObject*) node->GetData());
3904 }
3905 }
3906
3907 /// Calculate range
3908 void wxRichTextParagraph::CalculateRange(long start, long& end)
3909 {
3910 wxRichTextCompositeObject::CalculateRange(start, end);
3911
3912 // Add one for end of paragraph
3913 end ++;
3914
3915 m_range.SetRange(start, end);
3916 }
3917
3918 /// Find the object at the given position
3919 wxRichTextObject* wxRichTextParagraph::FindObjectAtPosition(long position)
3920 {
3921 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3922 while (node)
3923 {
3924 wxRichTextObject* obj = node->GetData();
3925 if (obj->GetRange().Contains(position))
3926 return obj;
3927
3928 node = node->GetNext();
3929 }
3930 return NULL;
3931 }
3932
3933 /// Get the plain text searching from the start or end of the range.
3934 /// The resulting string may be shorter than the range given.
3935 bool wxRichTextParagraph::GetContiguousPlainText(wxString& text, const wxRichTextRange& range, bool fromStart)
3936 {
3937 text = wxEmptyString;
3938
3939 if (fromStart)
3940 {
3941 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3942 while (node)
3943 {
3944 wxRichTextObject* obj = node->GetData();
3945 if (!obj->GetRange().IsOutside(range))
3946 {
3947 wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText);
3948 if (textObj)
3949 {
3950 text += textObj->GetTextForRange(range);
3951 }
3952 else
3953 return true;
3954 }
3955
3956 node = node->GetNext();
3957 }
3958 }
3959 else
3960 {
3961 wxRichTextObjectList::compatibility_iterator node = m_children.GetLast();
3962 while (node)
3963 {
3964 wxRichTextObject* obj = node->GetData();
3965 if (!obj->GetRange().IsOutside(range))
3966 {
3967 wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText);
3968 if (textObj)
3969 {
3970 text = textObj->GetTextForRange(range) + text;
3971 }
3972 else
3973 return true;
3974 }
3975
3976 node = node->GetPrevious();
3977 }
3978 }
3979
3980 return true;
3981 }
3982
3983 /// Find a suitable wrap position.
3984 bool wxRichTextParagraph::FindWrapPosition(const wxRichTextRange& range, wxDC& dc, int availableSpace, long& wrapPosition)
3985 {
3986 // Find the first position where the line exceeds the available space.
3987 wxSize sz;
3988 long breakPosition = range.GetEnd();
3989
3990 // Binary chop for speed
3991 long minPos = range.GetStart();
3992 long maxPos = range.GetEnd();
3993 while (true)
3994 {
3995 if (minPos == maxPos)
3996 {
3997 int descent = 0;
3998 GetRangeSize(wxRichTextRange(range.GetStart(), minPos), sz, descent, dc, wxRICHTEXT_UNFORMATTED);
3999
4000 if (sz.x > availableSpace)
4001 breakPosition = minPos - 1;
4002 break;
4003 }
4004 else if ((maxPos - minPos) == 1)
4005 {
4006 int descent = 0;
4007 GetRangeSize(wxRichTextRange(range.GetStart(), minPos), sz, descent, dc, wxRICHTEXT_UNFORMATTED);
4008
4009 if (sz.x > availableSpace)
4010 breakPosition = minPos - 1;
4011 else
4012 {
4013 GetRangeSize(wxRichTextRange(range.GetStart(), maxPos), sz, descent, dc, wxRICHTEXT_UNFORMATTED);
4014 if (sz.x > availableSpace)
4015 breakPosition = maxPos-1;
4016 }
4017 break;
4018 }
4019 else
4020 {
4021 long nextPos = minPos + ((maxPos - minPos) / 2);
4022
4023 int descent = 0;
4024 GetRangeSize(wxRichTextRange(range.GetStart(), nextPos), sz, descent, dc, wxRICHTEXT_UNFORMATTED);
4025
4026 if (sz.x > availableSpace)
4027 {
4028 maxPos = nextPos;
4029 }
4030 else
4031 {
4032 minPos = nextPos;
4033 }
4034 }
4035 }
4036
4037 // Now we know the last position on the line.
4038 // Let's try to find a word break.
4039
4040 wxString plainText;
4041 if (GetContiguousPlainText(plainText, wxRichTextRange(range.GetStart(), breakPosition), false))
4042 {
4043 int newLinePos = plainText.Find(wxRichTextLineBreakChar);
4044 if (newLinePos != wxNOT_FOUND)
4045 {
4046 breakPosition = wxMax(0, range.GetStart() + newLinePos);
4047 }
4048 else
4049 {
4050 int spacePos = plainText.Find(wxT(' '), true);
4051 int tabPos = plainText.Find(wxT('\t'), true);
4052 int pos = wxMax(spacePos, tabPos);
4053 if (pos != wxNOT_FOUND)
4054 {
4055 int positionsFromEndOfString = plainText.length() - pos - 1;
4056 breakPosition = breakPosition - positionsFromEndOfString;
4057 }
4058 }
4059 }
4060
4061 wrapPosition = breakPosition;
4062
4063 return true;
4064 }
4065
4066 /// Get the bullet text for this paragraph.
4067 wxString wxRichTextParagraph::GetBulletText()
4068 {
4069 if (GetAttributes().GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_NONE ||
4070 (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_BITMAP))
4071 return wxEmptyString;
4072
4073 int number = GetAttributes().GetBulletNumber();
4074
4075 wxString text;
4076 if ((GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ARABIC) || (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE))
4077 {
4078 text.Printf(wxT("%d"), number);
4079 }
4080 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_LETTERS_UPPER)
4081 {
4082 // TODO: Unicode, and also check if number > 26
4083 text.Printf(wxT("%c"), (wxChar) (number+64));
4084 }
4085 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_LETTERS_LOWER)
4086 {
4087 // TODO: Unicode, and also check if number > 26
4088 text.Printf(wxT("%c"), (wxChar) (number+96));
4089 }
4090 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ROMAN_UPPER)
4091 {
4092 text = wxRichTextDecimalToRoman(number);
4093 }
4094 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ROMAN_LOWER)
4095 {
4096 text = wxRichTextDecimalToRoman(number);
4097 text.MakeLower();
4098 }
4099 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_SYMBOL)
4100 {
4101 text = GetAttributes().GetBulletText();
4102 }
4103
4104 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE)
4105 {
4106 // The outline style relies on the text being computed statically,
4107 // since it depends on other levels points (e.g. 1.2.1.1). So normally the bullet text
4108 // should be stored in the attributes; if not, just use the number for this
4109 // level, as previously computed.
4110 if (!GetAttributes().GetBulletText().IsEmpty())
4111 text = GetAttributes().GetBulletText();
4112 }
4113
4114 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_PARENTHESES)
4115 {
4116 text = wxT("(") + text + wxT(")");
4117 }
4118 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_RIGHT_PARENTHESIS)
4119 {
4120 text = text + wxT(")");
4121 }
4122
4123 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_PERIOD)
4124 {
4125 text += wxT(".");
4126 }
4127
4128 return text;
4129 }
4130
4131 /// Allocate or reuse a line object
4132 wxRichTextLine* wxRichTextParagraph::AllocateLine(int pos)
4133 {
4134 if (pos < (int) m_cachedLines.GetCount())
4135 {
4136 wxRichTextLine* line = m_cachedLines.Item(pos)->GetData();
4137 line->Init(this);
4138 return line;
4139 }
4140 else
4141 {
4142 wxRichTextLine* line = new wxRichTextLine(this);
4143 m_cachedLines.Append(line);
4144 return line;
4145 }
4146 }
4147
4148 /// Clear remaining unused line objects, if any
4149 bool wxRichTextParagraph::ClearUnusedLines(int lineCount)
4150 {
4151 int cachedLineCount = m_cachedLines.GetCount();
4152 if ((int) cachedLineCount > lineCount)
4153 {
4154 for (int i = 0; i < (int) (cachedLineCount - lineCount); i ++)
4155 {
4156 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetLast();
4157 wxRichTextLine* line = node->GetData();
4158 m_cachedLines.Erase(node);
4159 delete line;
4160 }
4161 }
4162 return true;
4163 }
4164
4165 /// Get combined attributes of the base style, paragraph style and character style. We use this to dynamically
4166 /// retrieve the actual style.
4167 wxTextAttr wxRichTextParagraph::GetCombinedAttributes(const wxTextAttr& contentStyle) const
4168 {
4169 wxTextAttr attr;
4170 wxRichTextBuffer* buf = wxDynamicCast(GetParent(), wxRichTextBuffer);
4171 if (buf)
4172 {
4173 attr = buf->GetBasicStyle();
4174 wxRichTextApplyStyle(attr, GetAttributes());
4175 }
4176 else
4177 attr = GetAttributes();
4178
4179 wxRichTextApplyStyle(attr, contentStyle);
4180 return attr;
4181 }
4182
4183 /// Get combined attributes of the base style and paragraph style.
4184 wxTextAttr wxRichTextParagraph::GetCombinedAttributes() const
4185 {
4186 wxTextAttr attr;
4187 wxRichTextBuffer* buf = wxDynamicCast(GetParent(), wxRichTextBuffer);
4188 if (buf)
4189 {
4190 attr = buf->GetBasicStyle();
4191 wxRichTextApplyStyle(attr, GetAttributes());
4192 }
4193 else
4194 attr = GetAttributes();
4195
4196 return attr;
4197 }
4198
4199 /// Create default tabstop array
4200 void wxRichTextParagraph::InitDefaultTabs()
4201 {
4202 // create a default tab list at 10 mm each.
4203 for (int i = 0; i < 20; ++i)
4204 {
4205 sm_defaultTabs.Add(i*100);
4206 }
4207 }
4208
4209 /// Clear default tabstop array
4210 void wxRichTextParagraph::ClearDefaultTabs()
4211 {
4212 sm_defaultTabs.Clear();
4213 }
4214
4215 /// Get the first position from pos that has a line break character.
4216 long wxRichTextParagraph::GetFirstLineBreakPosition(long pos)
4217 {
4218 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
4219 while (node)
4220 {
4221 wxRichTextObject* obj = node->GetData();
4222 if (pos >= obj->GetRange().GetStart() && pos <= obj->GetRange().GetEnd())
4223 {
4224 wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText);
4225 if (textObj)
4226 {
4227 long breakPos = textObj->GetFirstLineBreakPosition(pos);
4228 if (breakPos > -1)
4229 return breakPos;
4230 }
4231 }
4232 node = node->GetNext();
4233 }
4234 return -1;
4235 }
4236
4237 /*!
4238 * wxRichTextLine
4239 * This object represents a line in a paragraph, and stores
4240 * offsets from the start of the paragraph representing the
4241 * start and end positions of the line.
4242 */
4243
4244 wxRichTextLine::wxRichTextLine(wxRichTextParagraph* parent)
4245 {
4246 Init(parent);
4247 }
4248
4249 /// Initialisation
4250 void wxRichTextLine::Init(wxRichTextParagraph* parent)
4251 {
4252 m_parent = parent;
4253 m_range.SetRange(-1, -1);
4254 m_pos = wxPoint(0, 0);
4255 m_size = wxSize(0, 0);
4256 m_descent = 0;
4257 }
4258
4259 /// Copy
4260 void wxRichTextLine::Copy(const wxRichTextLine& obj)
4261 {
4262 m_range = obj.m_range;
4263 }
4264
4265 /// Get the absolute object position
4266 wxPoint wxRichTextLine::GetAbsolutePosition() const
4267 {
4268 return m_parent->GetPosition() + m_pos;
4269 }
4270
4271 /// Get the absolute range
4272 wxRichTextRange wxRichTextLine::GetAbsoluteRange() const
4273 {
4274 wxRichTextRange range(m_range.GetStart() + m_parent->GetRange().GetStart(), 0);
4275 range.SetEnd(range.GetStart() + m_range.GetLength()-1);
4276 return range;
4277 }
4278
4279 /*!
4280 * wxRichTextPlainText
4281 * This object represents a single piece of text.
4282 */
4283
4284 IMPLEMENT_DYNAMIC_CLASS(wxRichTextPlainText, wxRichTextObject)
4285
4286 wxRichTextPlainText::wxRichTextPlainText(const wxString& text, wxRichTextObject* parent, wxTextAttr* style):
4287 wxRichTextObject(parent)
4288 {
4289 if (style)
4290 SetAttributes(*style);
4291
4292 m_text = text;
4293 }
4294
4295 #define USE_KERNING_FIX 1
4296
4297 // If insufficient tabs are defined, this is the tab width used
4298 #define WIDTH_FOR_DEFAULT_TABS 50
4299
4300 /// Draw the item
4301 bool wxRichTextPlainText::Draw(wxDC& dc, const wxRichTextRange& range, const wxRichTextRange& selectionRange, const wxRect& rect, int descent, int WXUNUSED(style))
4302 {
4303 wxRichTextParagraph* para = wxDynamicCast(GetParent(), wxRichTextParagraph);
4304 wxASSERT (para != NULL);
4305
4306 wxTextAttr textAttr(para ? para->GetCombinedAttributes(GetAttributes()) : GetAttributes());
4307
4308 int offset = GetRange().GetStart();
4309
4310 // Replace line break characters with spaces
4311 wxString str = m_text;
4312 wxString toRemove = wxRichTextLineBreakChar;
4313 str.Replace(toRemove, wxT(" "));
4314 if (textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_CAPITALS))
4315 str.MakeUpper();
4316
4317 long len = range.GetLength();
4318 wxString stringChunk = str.Mid(range.GetStart() - offset, (size_t) len);
4319
4320 int charHeight = dc.GetCharHeight();
4321
4322 int x = rect.x;
4323 int y = rect.y + (rect.height - charHeight - (descent - m_descent));
4324
4325 // Test for the optimized situations where all is selected, or none
4326 // is selected.
4327
4328 wxFont font(GetBuffer()->GetFontTable().FindFont(textAttr));
4329 wxCheckSetFont(dc, font);
4330
4331 // (a) All selected.
4332 if (selectionRange.GetStart() <= range.GetStart() && selectionRange.GetEnd() >= range.GetEnd())
4333 {
4334 DrawTabbedString(dc, textAttr, rect, stringChunk, x, y, true);
4335 }
4336 // (b) None selected.
4337 else if (selectionRange.GetEnd() < range.GetStart() || selectionRange.GetStart() > range.GetEnd())
4338 {
4339 // Draw all unselected
4340 DrawTabbedString(dc, textAttr, rect, stringChunk, x, y, false);
4341 }
4342 else
4343 {
4344 // (c) Part selected, part not
4345 // Let's draw unselected chunk, selected chunk, then unselected chunk.
4346
4347 dc.SetBackgroundMode(wxTRANSPARENT);
4348
4349 // 1. Initial unselected chunk, if any, up until start of selection.
4350 if (selectionRange.GetStart() > range.GetStart() && selectionRange.GetStart() <= range.GetEnd())
4351 {
4352 int r1 = range.GetStart();
4353 int s1 = selectionRange.GetStart()-1;
4354 int fragmentLen = s1 - r1 + 1;
4355 if (fragmentLen < 0)
4356 wxLogDebug(wxT("Mid(%d, %d"), (int)(r1 - offset), (int)fragmentLen);
4357 wxString stringFragment = str.Mid(r1 - offset, fragmentLen);
4358
4359 DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, false);
4360
4361 #if USE_KERNING_FIX
4362 if (stringChunk.Find(wxT("\t")) == wxNOT_FOUND)
4363 {
4364 // Compensate for kerning difference
4365 wxString stringFragment2(str.Mid(r1 - offset, fragmentLen+1));
4366 wxString stringFragment3(str.Mid(r1 - offset + fragmentLen, 1));
4367
4368 wxCoord w1, h1, w2, h2, w3, h3;
4369 dc.GetTextExtent(stringFragment, & w1, & h1);
4370 dc.GetTextExtent(stringFragment2, & w2, & h2);
4371 dc.GetTextExtent(stringFragment3, & w3, & h3);
4372
4373 int kerningDiff = (w1 + w3) - w2;
4374 x = x - kerningDiff;
4375 }
4376 #endif
4377 }
4378
4379 // 2. Selected chunk, if any.
4380 if (selectionRange.GetEnd() >= range.GetStart())
4381 {
4382 int s1 = wxMax(selectionRange.GetStart(), range.GetStart());
4383 int s2 = wxMin(selectionRange.GetEnd(), range.GetEnd());
4384
4385 int fragmentLen = s2 - s1 + 1;
4386 if (fragmentLen < 0)
4387 wxLogDebug(wxT("Mid(%d, %d"), (int)(s1 - offset), (int)fragmentLen);
4388 wxString stringFragment = str.Mid(s1 - offset, fragmentLen);
4389
4390 DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, true);
4391
4392 #if USE_KERNING_FIX
4393 if (stringChunk.Find(wxT("\t")) == wxNOT_FOUND)
4394 {
4395 // Compensate for kerning difference
4396 wxString stringFragment2(str.Mid(s1 - offset, fragmentLen+1));
4397 wxString stringFragment3(str.Mid(s1 - offset + fragmentLen, 1));
4398
4399 wxCoord w1, h1, w2, h2, w3, h3;
4400 dc.GetTextExtent(stringFragment, & w1, & h1);
4401 dc.GetTextExtent(stringFragment2, & w2, & h2);
4402 dc.GetTextExtent(stringFragment3, & w3, & h3);
4403
4404 int kerningDiff = (w1 + w3) - w2;
4405 x = x - kerningDiff;
4406 }
4407 #endif
4408 }
4409
4410 // 3. Remaining unselected chunk, if any
4411 if (selectionRange.GetEnd() < range.GetEnd())
4412 {
4413 int s2 = wxMin(selectionRange.GetEnd()+1, range.GetEnd());
4414 int r2 = range.GetEnd();
4415
4416 int fragmentLen = r2 - s2 + 1;
4417 if (fragmentLen < 0)
4418 wxLogDebug(wxT("Mid(%d, %d"), (int)(s2 - offset), (int)fragmentLen);
4419 wxString stringFragment = str.Mid(s2 - offset, fragmentLen);
4420
4421 DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, false);
4422 }
4423 }
4424
4425 return true;
4426 }
4427
4428 bool wxRichTextPlainText::DrawTabbedString(wxDC& dc, const wxTextAttr& attr, const wxRect& rect,wxString& str, wxCoord& x, wxCoord& y, bool selected)
4429 {
4430 bool hasTabs = (str.Find(wxT('\t')) != wxNOT_FOUND);
4431
4432 wxArrayInt tabArray;
4433 int tabCount;
4434 if (hasTabs)
4435 {
4436 if (attr.GetTabs().IsEmpty())
4437 tabArray = wxRichTextParagraph::GetDefaultTabs();
4438 else
4439 tabArray = attr.GetTabs();
4440 tabCount = tabArray.GetCount();
4441
4442 for (int i = 0; i < tabCount; ++i)
4443 {
4444 int pos = tabArray[i];
4445 pos = ConvertTenthsMMToPixels(dc, pos);
4446 tabArray[i] = pos;
4447 }
4448 }
4449 else
4450 tabCount = 0;
4451
4452 int nextTabPos = -1;
4453 int tabPos = -1;
4454 wxCoord w, h;
4455
4456 if (selected)
4457 {
4458 wxColour highlightColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT));
4459 wxColour highlightTextColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT));
4460
4461 wxCheckSetBrush(dc, wxBrush(highlightColour));
4462 wxCheckSetPen(dc, wxPen(highlightColour));
4463 dc.SetTextForeground(highlightTextColour);
4464 dc.SetBackgroundMode(wxTRANSPARENT);
4465 }
4466 else
4467 {
4468 dc.SetTextForeground(attr.GetTextColour());
4469
4470 if (attr.HasFlag(wxTEXT_ATTR_BACKGROUND_COLOUR) && attr.GetBackgroundColour().IsOk())
4471 {
4472 dc.SetBackgroundMode(wxSOLID);
4473 dc.SetTextBackground(attr.GetBackgroundColour());
4474 }
4475 else
4476 dc.SetBackgroundMode(wxTRANSPARENT);
4477 }
4478
4479 while (hasTabs)
4480 {
4481 // the string has a tab
4482 // break up the string at the Tab
4483 wxString stringChunk = str.BeforeFirst(wxT('\t'));
4484 str = str.AfterFirst(wxT('\t'));
4485 dc.GetTextExtent(stringChunk, & w, & h);
4486 tabPos = x + w;
4487 bool not_found = true;
4488 for (int i = 0; i < tabCount && not_found; ++i)
4489 {
4490 nextTabPos = tabArray.Item(i);
4491
4492 // Find the next tab position.
4493 // Even if we're at the end of the tab array, we must still draw the chunk.
4494
4495 if (nextTabPos > tabPos || (i == (tabCount - 1)))
4496 {
4497 if (nextTabPos <= tabPos)
4498 {
4499 int defaultTabWidth = ConvertTenthsMMToPixels(dc, WIDTH_FOR_DEFAULT_TABS);
4500 nextTabPos = tabPos + defaultTabWidth;
4501 }
4502
4503 not_found = false;
4504 if (selected)
4505 {
4506 w = nextTabPos - x;
4507 wxRect selRect(x, rect.y, w, rect.GetHeight());
4508 dc.DrawRectangle(selRect);
4509 }
4510 dc.DrawText(stringChunk, x, y);
4511
4512 if (attr.HasTextEffects() && (attr.GetTextEffects() & wxTEXT_ATTR_EFFECT_STRIKETHROUGH))
4513 {
4514 wxPen oldPen = dc.GetPen();
4515 wxCheckSetPen(dc, wxPen(attr.GetTextColour(), 1));
4516 dc.DrawLine(x, (int) (y+(h/2)+0.5), x+w, (int) (y+(h/2)+0.5));
4517 wxCheckSetPen(dc, oldPen);
4518 }
4519
4520 x = nextTabPos;
4521 }
4522 }
4523 hasTabs = (str.Find(wxT('\t')) != wxNOT_FOUND);
4524 }
4525
4526 if (!str.IsEmpty())
4527 {
4528 dc.GetTextExtent(str, & w, & h);
4529 if (selected)
4530 {
4531 wxRect selRect(x, rect.y, w, rect.GetHeight());
4532 dc.DrawRectangle(selRect);
4533 }
4534 dc.DrawText(str, x, y);
4535
4536 if (attr.HasTextEffects() && (attr.GetTextEffects() & wxTEXT_ATTR_EFFECT_STRIKETHROUGH))
4537 {
4538 wxPen oldPen = dc.GetPen();
4539 wxCheckSetPen(dc, wxPen(attr.GetTextColour(), 1));
4540 dc.DrawLine(x, (int) (y+(h/2)+0.5), x+w, (int) (y+(h/2)+0.5));
4541 wxCheckSetPen(dc, oldPen);
4542 }
4543
4544 x += w;
4545 }
4546 return true;
4547
4548 }
4549
4550 /// Lay the item out
4551 bool wxRichTextPlainText::Layout(wxDC& dc, const wxRect& WXUNUSED(rect), int WXUNUSED(style))
4552 {
4553 // Only lay out if we haven't already cached the size
4554 if (m_size.x == -1)
4555 GetRangeSize(GetRange(), m_size, m_descent, dc, 0, wxPoint(0, 0));
4556
4557 return true;
4558 }
4559
4560 /// Copy
4561 void wxRichTextPlainText::Copy(const wxRichTextPlainText& obj)
4562 {
4563 wxRichTextObject::Copy(obj);
4564
4565 m_text = obj.m_text;
4566 }
4567
4568 /// Get/set the object size for the given range. Returns false if the range
4569 /// is invalid for this object.
4570 bool wxRichTextPlainText::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, int WXUNUSED(flags), wxPoint position) const
4571 {
4572 if (!range.IsWithin(GetRange()))
4573 return false;
4574
4575 wxRichTextParagraph* para = wxDynamicCast(GetParent(), wxRichTextParagraph);
4576 wxASSERT (para != NULL);
4577
4578 wxTextAttr textAttr(para ? para->GetCombinedAttributes(GetAttributes()) : GetAttributes());
4579
4580 // Always assume unformatted text, since at this level we have no knowledge
4581 // of line breaks - and we don't need it, since we'll calculate size within
4582 // formatted text by doing it in chunks according to the line ranges
4583
4584 wxFont font(GetBuffer()->GetFontTable().FindFont(textAttr));
4585 wxCheckSetFont(dc, font);
4586
4587 int startPos = range.GetStart() - GetRange().GetStart();
4588 long len = range.GetLength();
4589
4590 wxString str(m_text);
4591 wxString toReplace = wxRichTextLineBreakChar;
4592 str.Replace(toReplace, wxT(" "));
4593
4594 wxString stringChunk = str.Mid(startPos, (size_t) len);
4595
4596 if (textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_CAPITALS))
4597 stringChunk.MakeUpper();
4598
4599 wxCoord w, h;
4600 int width = 0;
4601 if (stringChunk.Find(wxT('\t')) != wxNOT_FOUND)
4602 {
4603 // the string has a tab
4604 wxArrayInt tabArray;
4605 if (textAttr.GetTabs().IsEmpty())
4606 tabArray = wxRichTextParagraph::GetDefaultTabs();
4607 else
4608 tabArray = textAttr.GetTabs();
4609
4610 int tabCount = tabArray.GetCount();
4611
4612 for (int i = 0; i < tabCount; ++i)
4613 {
4614 int pos = tabArray[i];
4615 pos = ((wxRichTextPlainText*) this)->ConvertTenthsMMToPixels(dc, pos);
4616 tabArray[i] = pos;
4617 }
4618
4619 int nextTabPos = -1;
4620
4621 while (stringChunk.Find(wxT('\t')) >= 0)
4622 {
4623 // the string has a tab
4624 // break up the string at the Tab
4625 wxString stringFragment = stringChunk.BeforeFirst(wxT('\t'));
4626 stringChunk = stringChunk.AfterFirst(wxT('\t'));
4627 dc.GetTextExtent(stringFragment, & w, & h);
4628 width += w;
4629 int absoluteWidth = width + position.x;
4630
4631 bool notFound = true;
4632 for (int i = 0; i < tabCount && notFound; ++i)
4633 {
4634 nextTabPos = tabArray.Item(i);
4635
4636 // Find the next tab position.
4637 // Even if we're at the end of the tab array, we must still process the chunk.
4638
4639 if (nextTabPos > absoluteWidth || (i == (tabCount - 1)))
4640 {
4641 if (nextTabPos <= absoluteWidth)
4642 {
4643 int defaultTabWidth = ((wxRichTextPlainText*) this)->ConvertTenthsMMToPixels(dc, WIDTH_FOR_DEFAULT_TABS);
4644 nextTabPos = absoluteWidth + defaultTabWidth;
4645 }
4646
4647 notFound = false;
4648 width = nextTabPos - position.x;
4649 }
4650 }
4651 }
4652 }
4653 dc.GetTextExtent(stringChunk, & w, & h, & descent);
4654 width += w;
4655 size = wxSize(width, dc.GetCharHeight());
4656
4657 return true;
4658 }
4659
4660 /// Do a split, returning an object containing the second part, and setting
4661 /// the first part in 'this'.
4662 wxRichTextObject* wxRichTextPlainText::DoSplit(long pos)
4663 {
4664 long index = pos - GetRange().GetStart();
4665
4666 if (index < 0 || index >= (int) m_text.length())
4667 return NULL;
4668
4669 wxString firstPart = m_text.Mid(0, index);
4670 wxString secondPart = m_text.Mid(index);
4671
4672 m_text = firstPart;
4673
4674 wxRichTextPlainText* newObject = new wxRichTextPlainText(secondPart);
4675 newObject->SetAttributes(GetAttributes());
4676
4677 newObject->SetRange(wxRichTextRange(pos, GetRange().GetEnd()));
4678 GetRange().SetEnd(pos-1);
4679
4680 return newObject;
4681 }
4682
4683 /// Calculate range
4684 void wxRichTextPlainText::CalculateRange(long start, long& end)
4685 {
4686 end = start + m_text.length() - 1;
4687 m_range.SetRange(start, end);
4688 }
4689
4690 /// Delete range
4691 bool wxRichTextPlainText::DeleteRange(const wxRichTextRange& range)
4692 {
4693 wxRichTextRange r = range;
4694
4695 r.LimitTo(GetRange());
4696
4697 if (r.GetStart() == GetRange().GetStart() && r.GetEnd() == GetRange().GetEnd())
4698 {
4699 m_text.Empty();
4700 return true;
4701 }
4702
4703 long startIndex = r.GetStart() - GetRange().GetStart();
4704 long len = r.GetLength();
4705
4706 m_text = m_text.Mid(0, startIndex) + m_text.Mid(startIndex+len);
4707 return true;
4708 }
4709
4710 /// Get text for the given range.
4711 wxString wxRichTextPlainText::GetTextForRange(const wxRichTextRange& range) const
4712 {
4713 wxRichTextRange r = range;
4714
4715 r.LimitTo(GetRange());
4716
4717 long startIndex = r.GetStart() - GetRange().GetStart();
4718 long len = r.GetLength();
4719
4720 return m_text.Mid(startIndex, len);
4721 }
4722
4723 /// Returns true if this object can merge itself with the given one.
4724 bool wxRichTextPlainText::CanMerge(wxRichTextObject* object) const
4725 {
4726 return object->GetClassInfo() == CLASSINFO(wxRichTextPlainText) &&
4727 (m_text.empty() || wxTextAttrEq(GetAttributes(), object->GetAttributes()));
4728 }
4729
4730 /// Returns true if this object merged itself with the given one.
4731 /// The calling code will then delete the given object.
4732 bool wxRichTextPlainText::Merge(wxRichTextObject* object)
4733 {
4734 wxRichTextPlainText* textObject = wxDynamicCast(object, wxRichTextPlainText);
4735 wxASSERT( textObject != NULL );
4736
4737 if (textObject)
4738 {
4739 m_text += textObject->GetText();
4740 wxRichTextApplyStyle(m_attributes, textObject->GetAttributes());
4741 return true;
4742 }
4743 else
4744 return false;
4745 }
4746
4747 /// Dump to output stream for debugging
4748 void wxRichTextPlainText::Dump(wxTextOutputStream& stream)
4749 {
4750 wxRichTextObject::Dump(stream);
4751 stream << m_text << wxT("\n");
4752 }
4753
4754 /// Get the first position from pos that has a line break character.
4755 long wxRichTextPlainText::GetFirstLineBreakPosition(long pos)
4756 {
4757 int i;
4758 int len = m_text.length();
4759 int startPos = pos - m_range.GetStart();
4760 for (i = startPos; i < len; i++)
4761 {
4762 wxChar ch = m_text[i];
4763 if (ch == wxRichTextLineBreakChar)
4764 {
4765 return i + m_range.GetStart();
4766 }
4767 }
4768 return -1;
4769 }
4770
4771 /*!
4772 * wxRichTextBuffer
4773 * This is a kind of box, used to represent the whole buffer
4774 */
4775
4776 IMPLEMENT_DYNAMIC_CLASS(wxRichTextBuffer, wxRichTextParagraphLayoutBox)
4777
4778 wxList wxRichTextBuffer::sm_handlers;
4779 wxRichTextRenderer* wxRichTextBuffer::sm_renderer = NULL;
4780 int wxRichTextBuffer::sm_bulletRightMargin = 20;
4781 float wxRichTextBuffer::sm_bulletProportion = (float) 0.3;
4782
4783 /// Initialisation
4784 void wxRichTextBuffer::Init()
4785 {
4786 m_commandProcessor = new wxCommandProcessor;
4787 m_styleSheet = NULL;
4788 m_modified = false;
4789 m_batchedCommandDepth = 0;
4790 m_batchedCommand = NULL;
4791 m_suppressUndo = 0;
4792 m_handlerFlags = 0;
4793 m_scale = 1.0;
4794 }
4795
4796 /// Initialisation
4797 wxRichTextBuffer::~wxRichTextBuffer()
4798 {
4799 delete m_commandProcessor;
4800 delete m_batchedCommand;
4801
4802 ClearStyleStack();
4803 ClearEventHandlers();
4804 }
4805
4806 void wxRichTextBuffer::ResetAndClearCommands()
4807 {
4808 Reset();
4809
4810 GetCommandProcessor()->ClearCommands();
4811
4812 Modify(false);
4813 Invalidate(wxRICHTEXT_ALL);
4814 }
4815
4816 void wxRichTextBuffer::Copy(const wxRichTextBuffer& obj)
4817 {
4818 wxRichTextParagraphLayoutBox::Copy(obj);
4819
4820 m_styleSheet = obj.m_styleSheet;
4821 m_modified = obj.m_modified;
4822 m_batchedCommandDepth = obj.m_batchedCommandDepth;
4823 m_batchedCommand = obj.m_batchedCommand;
4824 m_suppressUndo = obj.m_suppressUndo;
4825 }
4826
4827 /// Push style sheet to top of stack
4828 bool wxRichTextBuffer::PushStyleSheet(wxRichTextStyleSheet* styleSheet)
4829 {
4830 if (m_styleSheet)
4831 styleSheet->InsertSheet(m_styleSheet);
4832
4833 SetStyleSheet(styleSheet);
4834
4835 return true;
4836 }
4837
4838 /// Pop style sheet from top of stack
4839 wxRichTextStyleSheet* wxRichTextBuffer::PopStyleSheet()
4840 {
4841 if (m_styleSheet)
4842 {
4843 wxRichTextStyleSheet* oldSheet = m_styleSheet;
4844 m_styleSheet = oldSheet->GetNextSheet();
4845 oldSheet->Unlink();
4846
4847 return oldSheet;
4848 }
4849 else
4850 return NULL;
4851 }
4852
4853 /// Submit command to insert paragraphs
4854 bool wxRichTextBuffer::InsertParagraphsWithUndo(long pos, const wxRichTextParagraphLayoutBox& paragraphs, wxRichTextCtrl* ctrl, int flags)
4855 {
4856 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, this, ctrl, false);
4857
4858 wxTextAttr attr(GetDefaultStyle());
4859
4860 wxTextAttr* p = NULL;
4861 wxTextAttr paraAttr;
4862 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
4863 {
4864 paraAttr = GetStyleForNewParagraph(pos);
4865 if (!paraAttr.IsDefault())
4866 p = & paraAttr;
4867 }
4868 else
4869 p = & attr;
4870
4871 action->GetNewParagraphs() = paragraphs;
4872
4873 action->SetPosition(pos);
4874
4875 wxRichTextRange range = wxRichTextRange(pos, pos + paragraphs.GetRange().GetEnd() - 1);
4876 if (!paragraphs.GetPartialParagraph())
4877 range.SetEnd(range.GetEnd()+1);
4878
4879 // Set the range we'll need to delete in Undo
4880 action->SetRange(range);
4881
4882 SubmitAction(action);
4883
4884 return true;
4885 }
4886
4887 /// Submit command to insert the given text
4888 bool wxRichTextBuffer::InsertTextWithUndo(long pos, const wxString& text, wxRichTextCtrl* ctrl, int flags)
4889 {
4890 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, this, ctrl, false);
4891
4892 wxTextAttr* p = NULL;
4893 wxTextAttr paraAttr;
4894 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
4895 {
4896 // Get appropriate paragraph style
4897 paraAttr = GetStyleForNewParagraph(pos, false, false);
4898 if (!paraAttr.IsDefault())
4899 p = & paraAttr;
4900 }
4901
4902 action->GetNewParagraphs().AddParagraphs(text, p);
4903
4904 int length = action->GetNewParagraphs().GetRange().GetLength();
4905
4906 if (text.length() > 0 && text.Last() != wxT('\n'))
4907 {
4908 // Don't count the newline when undoing
4909 length --;
4910 action->GetNewParagraphs().SetPartialParagraph(true);
4911 }
4912 else if (text.length() > 0 && text.Last() == wxT('\n'))
4913 length --;
4914
4915 action->SetPosition(pos);
4916
4917 // Set the range we'll need to delete in Undo
4918 action->SetRange(wxRichTextRange(pos, pos + length - 1));
4919
4920 SubmitAction(action);
4921
4922 return true;
4923 }
4924
4925 /// Submit command to insert the given text
4926 bool wxRichTextBuffer::InsertNewlineWithUndo(long pos, wxRichTextCtrl* ctrl, int flags)
4927 {
4928 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, this, ctrl, false);
4929
4930 wxTextAttr* p = NULL;
4931 wxTextAttr paraAttr;
4932 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
4933 {
4934 paraAttr = GetStyleForNewParagraph(pos, false, true /* look for next paragraph style */);
4935 if (!paraAttr.IsDefault())
4936 p = & paraAttr;
4937 }
4938
4939 wxTextAttr attr(GetDefaultStyle());
4940
4941 wxRichTextParagraph* newPara = new wxRichTextParagraph(wxEmptyString, this, & attr);
4942 action->GetNewParagraphs().AppendChild(newPara);
4943 action->GetNewParagraphs().UpdateRanges();
4944 action->GetNewParagraphs().SetPartialParagraph(false);
4945 wxRichTextParagraph* para = GetParagraphAtPosition(pos, false);
4946 long pos1 = pos;
4947
4948 if (p)
4949 newPara->SetAttributes(*p);
4950
4951 if (flags & wxRICHTEXT_INSERT_INTERACTIVE)
4952 {
4953 if (para && para->GetRange().GetEnd() == pos)
4954 pos1 ++;
4955 if (newPara->GetAttributes().HasBulletNumber())
4956 newPara->GetAttributes().SetBulletNumber(newPara->GetAttributes().GetBulletNumber()+1);
4957 }
4958
4959 action->SetPosition(pos);
4960
4961 // Use the default character style
4962 // Use the default character style
4963 if (!GetDefaultStyle().IsDefault() && newPara->GetChildren().GetFirst())
4964 {
4965 // Check whether the default style merely reflects the paragraph/basic style,
4966 // in which case don't apply it.
4967 wxTextAttrEx defaultStyle(GetDefaultStyle());
4968 wxTextAttrEx toApply;
4969 if (para)
4970 {
4971 wxRichTextAttr combinedAttr = para->GetCombinedAttributes();
4972 wxTextAttrEx newAttr;
4973 // This filters out attributes that are accounted for by the current
4974 // paragraph/basic style
4975 wxRichTextApplyStyle(toApply, defaultStyle, & combinedAttr);
4976 }
4977 else
4978 toApply = defaultStyle;
4979
4980 if (!toApply.IsDefault())
4981 newPara->GetChildren().GetFirst()->GetData()->SetAttributes(toApply);
4982 }
4983
4984 // Set the range we'll need to delete in Undo
4985 action->SetRange(wxRichTextRange(pos1, pos1));
4986
4987 SubmitAction(action);
4988
4989 return true;
4990 }
4991
4992 /// Submit command to insert the given image
4993 bool wxRichTextBuffer::InsertImageWithUndo(long pos, const wxRichTextImageBlock& imageBlock, wxRichTextCtrl* ctrl, int flags)
4994 {
4995 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Image"), wxRICHTEXT_INSERT, this, ctrl, false);
4996
4997 wxTextAttr* p = NULL;
4998 wxTextAttr paraAttr;
4999 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
5000 {
5001 paraAttr = GetStyleForNewParagraph(pos);
5002 if (!paraAttr.IsDefault())
5003 p = & paraAttr;
5004 }
5005
5006 wxTextAttr attr(GetDefaultStyle());
5007
5008 wxRichTextParagraph* newPara = new wxRichTextParagraph(this, & attr);
5009 if (p)
5010 newPara->SetAttributes(*p);
5011
5012 wxRichTextImage* imageObject = new wxRichTextImage(imageBlock, newPara);
5013 newPara->AppendChild(imageObject);
5014 action->GetNewParagraphs().AppendChild(newPara);
5015 action->GetNewParagraphs().UpdateRanges();
5016
5017 action->GetNewParagraphs().SetPartialParagraph(true);
5018
5019 action->SetPosition(pos);
5020
5021 // Set the range we'll need to delete in Undo
5022 action->SetRange(wxRichTextRange(pos, pos));
5023
5024 SubmitAction(action);
5025
5026 return true;
5027 }
5028
5029 /// Get the style that is appropriate for a new paragraph at this position.
5030 /// If the previous paragraph has a paragraph style name, look up the next-paragraph
5031 /// style.
5032 wxTextAttr wxRichTextBuffer::GetStyleForNewParagraph(long pos, bool caretPosition, bool lookUpNewParaStyle) const
5033 {
5034 wxRichTextParagraph* para = GetParagraphAtPosition(pos, caretPosition);
5035 if (para)
5036 {
5037 wxTextAttr attr;
5038 bool foundAttributes = false;
5039
5040 // Look for a matching paragraph style
5041 if (lookUpNewParaStyle && !para->GetAttributes().GetParagraphStyleName().IsEmpty() && GetStyleSheet())
5042 {
5043 wxRichTextParagraphStyleDefinition* paraDef = GetStyleSheet()->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName());
5044 if (paraDef)
5045 {
5046 // If we're not at the end of the paragraph, then we apply THIS style, and not the designated next style.
5047 if (para->GetRange().GetEnd() == pos && !paraDef->GetNextStyle().IsEmpty())
5048 {
5049 wxRichTextParagraphStyleDefinition* nextParaDef = GetStyleSheet()->FindParagraphStyle(paraDef->GetNextStyle());
5050 if (nextParaDef)
5051 {
5052 foundAttributes = true;
5053 attr = nextParaDef->GetStyleMergedWithBase(GetStyleSheet());
5054 }
5055 }
5056
5057 // If we didn't find the 'next style', use this style instead.
5058 if (!foundAttributes)
5059 {
5060 foundAttributes = true;
5061 attr = paraDef->GetStyleMergedWithBase(GetStyleSheet());
5062 }
5063 }
5064 }
5065 if (!foundAttributes)
5066 {
5067 attr = para->GetAttributes();
5068 int flags = attr.GetFlags();
5069
5070 // Eliminate character styles
5071 flags &= ( (~ wxTEXT_ATTR_FONT) |
5072 (~ wxTEXT_ATTR_TEXT_COLOUR) |
5073 (~ wxTEXT_ATTR_BACKGROUND_COLOUR) );
5074 attr.SetFlags(flags);
5075 }
5076
5077 // Now see if we need to number the paragraph.
5078 if (attr.HasBulletStyle())
5079 {
5080 wxTextAttr numberingAttr;
5081 if (FindNextParagraphNumber(para, numberingAttr))
5082 wxRichTextApplyStyle(attr, (const wxTextAttr&) numberingAttr);
5083 }
5084
5085 return attr;
5086 }
5087 else
5088 return wxTextAttr();
5089 }
5090
5091 /// Submit command to delete this range
5092 bool wxRichTextBuffer::DeleteRangeWithUndo(const wxRichTextRange& range, wxRichTextCtrl* ctrl)
5093 {
5094 wxRichTextAction* action = new wxRichTextAction(NULL, _("Delete"), wxRICHTEXT_DELETE, this, ctrl);
5095
5096 action->SetPosition(ctrl->GetCaretPosition());
5097
5098 // Set the range to delete
5099 action->SetRange(range);
5100
5101 // Copy the fragment that we'll need to restore in Undo
5102 CopyFragment(range, action->GetOldParagraphs());
5103
5104 // See if we're deleting a paragraph marker, in which case we need to
5105 // make a note not to copy the attributes from the 2nd paragraph to the 1st.
5106 if (range.GetStart() == range.GetEnd())
5107 {
5108 wxRichTextParagraph* para = GetParagraphAtPosition(range.GetStart());
5109 if (para && para->GetRange().GetEnd() == range.GetEnd())
5110 {
5111 wxRichTextParagraph* nextPara = GetParagraphAtPosition(range.GetStart()+1);
5112 if (nextPara && nextPara != para)
5113 {
5114 action->GetOldParagraphs().GetChildren().GetFirst()->GetData()->SetAttributes(nextPara->GetAttributes());
5115 action->GetOldParagraphs().GetAttributes().SetFlags(action->GetOldParagraphs().GetAttributes().GetFlags() | wxTEXT_ATTR_KEEP_FIRST_PARA_STYLE);
5116 }
5117 }
5118 }
5119
5120 SubmitAction(action);
5121
5122 return true;
5123 }
5124
5125 /// Collapse undo/redo commands
5126 bool wxRichTextBuffer::BeginBatchUndo(const wxString& cmdName)
5127 {
5128 if (m_batchedCommandDepth == 0)
5129 {
5130 wxASSERT(m_batchedCommand == NULL);
5131 if (m_batchedCommand)
5132 {
5133 GetCommandProcessor()->Store(m_batchedCommand);
5134 }
5135 m_batchedCommand = new wxRichTextCommand(cmdName);
5136 }
5137
5138 m_batchedCommandDepth ++;
5139
5140 return true;
5141 }
5142
5143 /// Collapse undo/redo commands
5144 bool wxRichTextBuffer::EndBatchUndo()
5145 {
5146 m_batchedCommandDepth --;
5147
5148 wxASSERT(m_batchedCommandDepth >= 0);
5149 wxASSERT(m_batchedCommand != NULL);
5150
5151 if (m_batchedCommandDepth == 0)
5152 {
5153 GetCommandProcessor()->Store(m_batchedCommand);
5154 m_batchedCommand = NULL;
5155 }
5156
5157 return true;
5158 }
5159
5160 /// Submit immediately, or delay according to whether collapsing is on
5161 bool wxRichTextBuffer::SubmitAction(wxRichTextAction* action)
5162 {
5163 if (BatchingUndo() && m_batchedCommand && !SuppressingUndo())
5164 {
5165 wxRichTextCommand* cmd = new wxRichTextCommand(action->GetName());
5166 cmd->AddAction(action);
5167 cmd->Do();
5168 cmd->GetActions().Clear();
5169 delete cmd;
5170
5171 m_batchedCommand->AddAction(action);
5172 }
5173 else
5174 {
5175 wxRichTextCommand* cmd = new wxRichTextCommand(action->GetName());
5176 cmd->AddAction(action);
5177
5178 // Only store it if we're not suppressing undo.
5179 return GetCommandProcessor()->Submit(cmd, !SuppressingUndo());
5180 }
5181
5182 return true;
5183 }
5184
5185 /// Begin suppressing undo/redo commands.
5186 bool wxRichTextBuffer::BeginSuppressUndo()
5187 {
5188 m_suppressUndo ++;
5189
5190 return true;
5191 }
5192
5193 /// End suppressing undo/redo commands.
5194 bool wxRichTextBuffer::EndSuppressUndo()
5195 {
5196 m_suppressUndo --;
5197
5198 return true;
5199 }
5200
5201 /// Begin using a style
5202 bool wxRichTextBuffer::BeginStyle(const wxTextAttr& style)
5203 {
5204 wxTextAttr newStyle(GetDefaultStyle());
5205
5206 // Save the old default style
5207 m_attributeStack.Append((wxObject*) new wxTextAttr(GetDefaultStyle()));
5208
5209 wxRichTextApplyStyle(newStyle, style);
5210 newStyle.SetFlags(style.GetFlags()|newStyle.GetFlags());
5211
5212 SetDefaultStyle(newStyle);
5213
5214 // wxLogDebug("Default style size = %d", GetDefaultStyle().GetFont().GetPointSize());
5215
5216 return true;
5217 }
5218
5219 /// End the style
5220 bool wxRichTextBuffer::EndStyle()
5221 {
5222 if (!m_attributeStack.GetFirst())
5223 {
5224 wxLogDebug(_("Too many EndStyle calls!"));
5225 return false;
5226 }
5227
5228 wxList::compatibility_iterator node = m_attributeStack.GetLast();
5229 wxTextAttr* attr = (wxTextAttr*)node->GetData();
5230 m_attributeStack.Erase(node);
5231
5232 SetDefaultStyle(*attr);
5233
5234 delete attr;
5235 return true;
5236 }
5237
5238 /// End all styles
5239 bool wxRichTextBuffer::EndAllStyles()
5240 {
5241 while (m_attributeStack.GetCount() != 0)
5242 EndStyle();
5243 return true;
5244 }
5245
5246 /// Clear the style stack
5247 void wxRichTextBuffer::ClearStyleStack()
5248 {
5249 for (wxList::compatibility_iterator node = m_attributeStack.GetFirst(); node; node = node->GetNext())
5250 delete (wxTextAttr*) node->GetData();
5251 m_attributeStack.Clear();
5252 }
5253
5254 /// Begin using bold
5255 bool wxRichTextBuffer::BeginBold()
5256 {
5257 wxTextAttr attr;
5258 attr.SetFontWeight(wxBOLD);
5259
5260 return BeginStyle(attr);
5261 }
5262
5263 /// Begin using italic
5264 bool wxRichTextBuffer::BeginItalic()
5265 {
5266 wxTextAttr attr;
5267 attr.SetFontStyle(wxITALIC);
5268
5269 return BeginStyle(attr);
5270 }
5271
5272 /// Begin using underline
5273 bool wxRichTextBuffer::BeginUnderline()
5274 {
5275 wxTextAttr attr;
5276 attr.SetFontUnderlined(true);
5277
5278 return BeginStyle(attr);
5279 }
5280
5281 /// Begin using point size
5282 bool wxRichTextBuffer::BeginFontSize(int pointSize)
5283 {
5284 wxTextAttr attr;
5285 attr.SetFontSize(pointSize);
5286
5287 return BeginStyle(attr);
5288 }
5289
5290 /// Begin using this font
5291 bool wxRichTextBuffer::BeginFont(const wxFont& font)
5292 {
5293 wxTextAttr attr;
5294 attr.SetFont(font);
5295
5296 return BeginStyle(attr);
5297 }
5298
5299 /// Begin using this colour
5300 bool wxRichTextBuffer::BeginTextColour(const wxColour& colour)
5301 {
5302 wxTextAttr attr;
5303 attr.SetFlags(wxTEXT_ATTR_TEXT_COLOUR);
5304 attr.SetTextColour(colour);
5305
5306 return BeginStyle(attr);
5307 }
5308
5309 /// Begin using alignment
5310 bool wxRichTextBuffer::BeginAlignment(wxTextAttrAlignment alignment)
5311 {
5312 wxTextAttr attr;
5313 attr.SetFlags(wxTEXT_ATTR_ALIGNMENT);
5314 attr.SetAlignment(alignment);
5315
5316 return BeginStyle(attr);
5317 }
5318
5319 /// Begin left indent
5320 bool wxRichTextBuffer::BeginLeftIndent(int leftIndent, int leftSubIndent)
5321 {
5322 wxTextAttr attr;
5323 attr.SetFlags(wxTEXT_ATTR_LEFT_INDENT);
5324 attr.SetLeftIndent(leftIndent, leftSubIndent);
5325
5326 return BeginStyle(attr);
5327 }
5328
5329 /// Begin right indent
5330 bool wxRichTextBuffer::BeginRightIndent(int rightIndent)
5331 {
5332 wxTextAttr attr;
5333 attr.SetFlags(wxTEXT_ATTR_RIGHT_INDENT);
5334 attr.SetRightIndent(rightIndent);
5335
5336 return BeginStyle(attr);
5337 }
5338
5339 /// Begin paragraph spacing
5340 bool wxRichTextBuffer::BeginParagraphSpacing(int before, int after)
5341 {
5342 long flags = 0;
5343 if (before != 0)
5344 flags |= wxTEXT_ATTR_PARA_SPACING_BEFORE;
5345 if (after != 0)
5346 flags |= wxTEXT_ATTR_PARA_SPACING_AFTER;
5347
5348 wxTextAttr attr;
5349 attr.SetFlags(flags);
5350 attr.SetParagraphSpacingBefore(before);
5351 attr.SetParagraphSpacingAfter(after);
5352
5353 return BeginStyle(attr);
5354 }
5355
5356 /// Begin line spacing
5357 bool wxRichTextBuffer::BeginLineSpacing(int lineSpacing)
5358 {
5359 wxTextAttr attr;
5360 attr.SetFlags(wxTEXT_ATTR_LINE_SPACING);
5361 attr.SetLineSpacing(lineSpacing);
5362
5363 return BeginStyle(attr);
5364 }
5365
5366 /// Begin numbered bullet
5367 bool wxRichTextBuffer::BeginNumberedBullet(int bulletNumber, int leftIndent, int leftSubIndent, int bulletStyle)
5368 {
5369 wxTextAttr attr;
5370 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT);
5371 attr.SetBulletStyle(bulletStyle);
5372 attr.SetBulletNumber(bulletNumber);
5373 attr.SetLeftIndent(leftIndent, leftSubIndent);
5374
5375 return BeginStyle(attr);
5376 }
5377
5378 /// Begin symbol bullet
5379 bool wxRichTextBuffer::BeginSymbolBullet(const wxString& symbol, int leftIndent, int leftSubIndent, int bulletStyle)
5380 {
5381 wxTextAttr attr;
5382 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT);
5383 attr.SetBulletStyle(bulletStyle);
5384 attr.SetLeftIndent(leftIndent, leftSubIndent);
5385 attr.SetBulletText(symbol);
5386
5387 return BeginStyle(attr);
5388 }
5389
5390 /// Begin standard bullet
5391 bool wxRichTextBuffer::BeginStandardBullet(const wxString& bulletName, int leftIndent, int leftSubIndent, int bulletStyle)
5392 {
5393 wxTextAttr attr;
5394 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT);
5395 attr.SetBulletStyle(bulletStyle);
5396 attr.SetLeftIndent(leftIndent, leftSubIndent);
5397 attr.SetBulletName(bulletName);
5398
5399 return BeginStyle(attr);
5400 }
5401
5402 /// Begin named character style
5403 bool wxRichTextBuffer::BeginCharacterStyle(const wxString& characterStyle)
5404 {
5405 if (GetStyleSheet())
5406 {
5407 wxRichTextCharacterStyleDefinition* def = GetStyleSheet()->FindCharacterStyle(characterStyle);
5408 if (def)
5409 {
5410 wxTextAttr attr = def->GetStyleMergedWithBase(GetStyleSheet());
5411 return BeginStyle(attr);
5412 }
5413 }
5414 return false;
5415 }
5416
5417 /// Begin named paragraph style
5418 bool wxRichTextBuffer::BeginParagraphStyle(const wxString& paragraphStyle)
5419 {
5420 if (GetStyleSheet())
5421 {
5422 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(paragraphStyle);
5423 if (def)
5424 {
5425 wxTextAttr attr = def->GetStyleMergedWithBase(GetStyleSheet());
5426 return BeginStyle(attr);
5427 }
5428 }
5429 return false;
5430 }
5431
5432 /// Begin named list style
5433 bool wxRichTextBuffer::BeginListStyle(const wxString& listStyle, int level, int number)
5434 {
5435 if (GetStyleSheet())
5436 {
5437 wxRichTextListStyleDefinition* def = GetStyleSheet()->FindListStyle(listStyle);
5438 if (def)
5439 {
5440 wxTextAttr attr(def->GetCombinedStyleForLevel(level));
5441
5442 attr.SetBulletNumber(number);
5443
5444 return BeginStyle(attr);
5445 }
5446 }
5447 return false;
5448 }
5449
5450 /// Begin URL
5451 bool wxRichTextBuffer::BeginURL(const wxString& url, const wxString& characterStyle)
5452 {
5453 wxTextAttr attr;
5454
5455 if (!characterStyle.IsEmpty() && GetStyleSheet())
5456 {
5457 wxRichTextCharacterStyleDefinition* def = GetStyleSheet()->FindCharacterStyle(characterStyle);
5458 if (def)
5459 {
5460 attr = def->GetStyleMergedWithBase(GetStyleSheet());
5461 }
5462 }
5463 attr.SetURL(url);
5464
5465 return BeginStyle(attr);
5466 }
5467
5468 /// Adds a handler to the end
5469 void wxRichTextBuffer::AddHandler(wxRichTextFileHandler *handler)
5470 {
5471 sm_handlers.Append(handler);
5472 }
5473
5474 /// Inserts a handler at the front
5475 void wxRichTextBuffer::InsertHandler(wxRichTextFileHandler *handler)
5476 {
5477 sm_handlers.Insert( handler );
5478 }
5479
5480 /// Removes a handler
5481 bool wxRichTextBuffer::RemoveHandler(const wxString& name)
5482 {
5483 wxRichTextFileHandler *handler = FindHandler(name);
5484 if (handler)
5485 {
5486 sm_handlers.DeleteObject(handler);
5487 delete handler;
5488 return true;
5489 }
5490 else
5491 return false;
5492 }
5493
5494 /// Finds a handler by filename or, if supplied, type
5495 wxRichTextFileHandler *wxRichTextBuffer::FindHandlerFilenameOrType(const wxString& filename, int imageType)
5496 {
5497 if (imageType != wxRICHTEXT_TYPE_ANY)
5498 return FindHandler(imageType);
5499 else if (!filename.IsEmpty())
5500 {
5501 wxString path, file, ext;
5502 wxSplitPath(filename, & path, & file, & ext);
5503 return FindHandler(ext, imageType);
5504 }
5505 else
5506 return NULL;
5507 }
5508
5509
5510 /// Finds a handler by name
5511 wxRichTextFileHandler* wxRichTextBuffer::FindHandler(const wxString& name)
5512 {
5513 wxList::compatibility_iterator node = sm_handlers.GetFirst();
5514 while (node)
5515 {
5516 wxRichTextFileHandler *handler = (wxRichTextFileHandler*)node->GetData();
5517 if (handler->GetName().Lower() == name.Lower()) return handler;
5518
5519 node = node->GetNext();
5520 }
5521 return NULL;
5522 }
5523
5524 /// Finds a handler by extension and type
5525 wxRichTextFileHandler* wxRichTextBuffer::FindHandler(const wxString& extension, int type)
5526 {
5527 wxList::compatibility_iterator node = sm_handlers.GetFirst();
5528 while (node)
5529 {
5530 wxRichTextFileHandler *handler = (wxRichTextFileHandler*)node->GetData();
5531 if ( handler->GetExtension().Lower() == extension.Lower() &&
5532 (type == wxRICHTEXT_TYPE_ANY || handler->GetType() == type) )
5533 return handler;
5534 node = node->GetNext();
5535 }
5536 return 0;
5537 }
5538
5539 /// Finds a handler by type
5540 wxRichTextFileHandler* wxRichTextBuffer::FindHandler(int type)
5541 {
5542 wxList::compatibility_iterator node = sm_handlers.GetFirst();
5543 while (node)
5544 {
5545 wxRichTextFileHandler *handler = (wxRichTextFileHandler *)node->GetData();
5546 if (handler->GetType() == type) return handler;
5547 node = node->GetNext();
5548 }
5549 return NULL;
5550 }
5551
5552 void wxRichTextBuffer::InitStandardHandlers()
5553 {
5554 if (!FindHandler(wxRICHTEXT_TYPE_TEXT))
5555 AddHandler(new wxRichTextPlainTextHandler);
5556 }
5557
5558 void wxRichTextBuffer::CleanUpHandlers()
5559 {
5560 wxList::compatibility_iterator node = sm_handlers.GetFirst();
5561 while (node)
5562 {
5563 wxRichTextFileHandler* handler = (wxRichTextFileHandler*)node->GetData();
5564 wxList::compatibility_iterator next = node->GetNext();
5565 delete handler;
5566 node = next;
5567 }
5568
5569 sm_handlers.Clear();
5570 }
5571
5572 wxString wxRichTextBuffer::GetExtWildcard(bool combine, bool save, wxArrayInt* types)
5573 {
5574 if (types)
5575 types->Clear();
5576
5577 wxString wildcard;
5578
5579 wxList::compatibility_iterator node = GetHandlers().GetFirst();
5580 int count = 0;
5581 while (node)
5582 {
5583 wxRichTextFileHandler* handler = (wxRichTextFileHandler*) node->GetData();
5584 if (handler->IsVisible() && ((save && handler->CanSave()) || !save && handler->CanLoad()))
5585 {
5586 if (combine)
5587 {
5588 if (count > 0)
5589 wildcard += wxT(";");
5590 wildcard += wxT("*.") + handler->GetExtension();
5591 }
5592 else
5593 {
5594 if (count > 0)
5595 wildcard += wxT("|");
5596 wildcard += handler->GetName();
5597 wildcard += wxT(" ");
5598 wildcard += _("files");
5599 wildcard += wxT(" (*.");
5600 wildcard += handler->GetExtension();
5601 wildcard += wxT(")|*.");
5602 wildcard += handler->GetExtension();
5603 if (types)
5604 types->Add(handler->GetType());
5605 }
5606 count ++;
5607 }
5608
5609 node = node->GetNext();
5610 }
5611
5612 if (combine)
5613 wildcard = wxT("(") + wildcard + wxT(")|") + wildcard;
5614 return wildcard;
5615 }
5616
5617 /// Load a file
5618 bool wxRichTextBuffer::LoadFile(const wxString& filename, int type)
5619 {
5620 wxRichTextFileHandler* handler = FindHandlerFilenameOrType(filename, type);
5621 if (handler)
5622 {
5623 SetDefaultStyle(wxTextAttr());
5624 handler->SetFlags(GetHandlerFlags());
5625 bool success = handler->LoadFile(this, filename);
5626 Invalidate(wxRICHTEXT_ALL);
5627 return success;
5628 }
5629 else
5630 return false;
5631 }
5632
5633 /// Save a file
5634 bool wxRichTextBuffer::SaveFile(const wxString& filename, int type)
5635 {
5636 wxRichTextFileHandler* handler = FindHandlerFilenameOrType(filename, type);
5637 if (handler)
5638 {
5639 handler->SetFlags(GetHandlerFlags());
5640 return handler->SaveFile(this, filename);
5641 }
5642 else
5643 return false;
5644 }
5645
5646 /// Load from a stream
5647 bool wxRichTextBuffer::LoadFile(wxInputStream& stream, int type)
5648 {
5649 wxRichTextFileHandler* handler = FindHandler(type);
5650 if (handler)
5651 {
5652 SetDefaultStyle(wxTextAttr());
5653 handler->SetFlags(GetHandlerFlags());
5654 bool success = handler->LoadFile(this, stream);
5655 Invalidate(wxRICHTEXT_ALL);
5656 return success;
5657 }
5658 else
5659 return false;
5660 }
5661
5662 /// Save to a stream
5663 bool wxRichTextBuffer::SaveFile(wxOutputStream& stream, int type)
5664 {
5665 wxRichTextFileHandler* handler = FindHandler(type);
5666 if (handler)
5667 {
5668 handler->SetFlags(GetHandlerFlags());
5669 return handler->SaveFile(this, stream);
5670 }
5671 else
5672 return false;
5673 }
5674
5675 /// Copy the range to the clipboard
5676 bool wxRichTextBuffer::CopyToClipboard(const wxRichTextRange& range)
5677 {
5678 bool success = false;
5679 #if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
5680
5681 if (!wxTheClipboard->IsOpened() && wxTheClipboard->Open())
5682 {
5683 wxTheClipboard->Clear();
5684
5685 // Add composite object
5686
5687 wxDataObjectComposite* compositeObject = new wxDataObjectComposite();
5688
5689 {
5690 wxString text = GetTextForRange(range);
5691
5692 #ifdef __WXMSW__
5693 text = wxTextFile::Translate(text, wxTextFileType_Dos);
5694 #endif
5695
5696 compositeObject->Add(new wxTextDataObject(text), false /* not preferred */);
5697 }
5698
5699 // Add rich text buffer data object. This needs the XML handler to be present.
5700
5701 if (FindHandler(wxRICHTEXT_TYPE_XML))
5702 {
5703 wxRichTextBuffer* richTextBuf = new wxRichTextBuffer;
5704 CopyFragment(range, *richTextBuf);
5705
5706 compositeObject->Add(new wxRichTextBufferDataObject(richTextBuf), true /* preferred */);
5707 }
5708
5709 if (wxTheClipboard->SetData(compositeObject))
5710 success = true;
5711
5712 wxTheClipboard->Close();
5713 }
5714
5715 #else
5716 wxUnusedVar(range);
5717 #endif
5718 return success;
5719 }
5720
5721 /// Paste the clipboard content to the buffer
5722 bool wxRichTextBuffer::PasteFromClipboard(long position)
5723 {
5724 bool success = false;
5725 #if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
5726 if (CanPasteFromClipboard())
5727 {
5728 if (wxTheClipboard->Open())
5729 {
5730 if (wxTheClipboard->IsSupported(wxDataFormat(wxRichTextBufferDataObject::GetRichTextBufferFormatId())))
5731 {
5732 wxRichTextBufferDataObject data;
5733 wxTheClipboard->GetData(data);
5734 wxRichTextBuffer* richTextBuffer = data.GetRichTextBuffer();
5735 if (richTextBuffer)
5736 {
5737 InsertParagraphsWithUndo(position+1, *richTextBuffer, GetRichTextCtrl(), wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE);
5738 if (GetRichTextCtrl())
5739 GetRichTextCtrl()->ShowPosition(position + richTextBuffer->GetRange().GetEnd());
5740 delete richTextBuffer;
5741 }
5742 }
5743 else if (wxTheClipboard->IsSupported(wxDF_TEXT) || wxTheClipboard->IsSupported(wxDF_UNICODETEXT))
5744 {
5745 wxTextDataObject data;
5746 wxTheClipboard->GetData(data);
5747 wxString text(data.GetText());
5748 #ifdef __WXMSW__
5749 wxString text2;
5750 text2.Alloc(text.Length()+1);
5751 size_t i;
5752 for (i = 0; i < text.Length(); i++)
5753 {
5754 wxChar ch = text[i];
5755 if (ch != wxT('\r'))
5756 text2 += ch;
5757 }
5758 #else
5759 wxString text2 = text;
5760 #endif
5761 InsertTextWithUndo(position+1, text2, GetRichTextCtrl());
5762
5763 if (GetRichTextCtrl())
5764 GetRichTextCtrl()->ShowPosition(position + text2.Length());
5765
5766 success = true;
5767 }
5768 else if (wxTheClipboard->IsSupported(wxDF_BITMAP))
5769 {
5770 wxBitmapDataObject data;
5771 wxTheClipboard->GetData(data);
5772 wxBitmap bitmap(data.GetBitmap());
5773 wxImage image(bitmap.ConvertToImage());
5774
5775 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Image"), wxRICHTEXT_INSERT, this, GetRichTextCtrl(), false);
5776
5777 action->GetNewParagraphs().AddImage(image);
5778
5779 if (action->GetNewParagraphs().GetChildCount() == 1)
5780 action->GetNewParagraphs().SetPartialParagraph(true);
5781
5782 action->SetPosition(position);
5783
5784 // Set the range we'll need to delete in Undo
5785 action->SetRange(wxRichTextRange(position, position));
5786
5787 SubmitAction(action);
5788
5789 success = true;
5790 }
5791 wxTheClipboard->Close();
5792 }
5793 }
5794 #else
5795 wxUnusedVar(position);
5796 #endif
5797 return success;
5798 }
5799
5800 /// Can we paste from the clipboard?
5801 bool wxRichTextBuffer::CanPasteFromClipboard() const
5802 {
5803 bool canPaste = false;
5804 #if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
5805 if (!wxTheClipboard->IsOpened() && wxTheClipboard->Open())
5806 {
5807 if (wxTheClipboard->IsSupported(wxDF_TEXT) || wxTheClipboard->IsSupported(wxDF_UNICODETEXT) ||
5808 wxTheClipboard->IsSupported(wxDataFormat(wxRichTextBufferDataObject::GetRichTextBufferFormatId())) ||
5809 wxTheClipboard->IsSupported(wxDF_BITMAP))
5810 {
5811 canPaste = true;
5812 }
5813 wxTheClipboard->Close();
5814 }
5815 #endif
5816 return canPaste;
5817 }
5818
5819 /// Dumps contents of buffer for debugging purposes
5820 void wxRichTextBuffer::Dump()
5821 {
5822 wxString text;
5823 {
5824 wxStringOutputStream stream(& text);
5825 wxTextOutputStream textStream(stream);
5826 Dump(textStream);
5827 }
5828
5829 wxLogDebug(text);
5830 }
5831
5832 /// Add an event handler
5833 bool wxRichTextBuffer::AddEventHandler(wxEvtHandler* handler)
5834 {
5835 m_eventHandlers.Append(handler);
5836 return true;
5837 }
5838
5839 /// Remove an event handler
5840 bool wxRichTextBuffer::RemoveEventHandler(wxEvtHandler* handler, bool deleteHandler)
5841 {
5842 wxList::compatibility_iterator node = m_eventHandlers.Find(handler);
5843 if (node)
5844 {
5845 m_eventHandlers.Erase(node);
5846 if (deleteHandler)
5847 delete handler;
5848
5849 return true;
5850 }
5851 else
5852 return false;
5853 }
5854
5855 /// Clear event handlers
5856 void wxRichTextBuffer::ClearEventHandlers()
5857 {
5858 m_eventHandlers.Clear();
5859 }
5860
5861 /// Send event to event handlers. If sendToAll is true, will send to all event handlers,
5862 /// otherwise will stop at the first successful one.
5863 bool wxRichTextBuffer::SendEvent(wxEvent& event, bool sendToAll)
5864 {
5865 bool success = false;
5866 for (wxList::compatibility_iterator node = m_eventHandlers.GetFirst(); node; node = node->GetNext())
5867 {
5868 wxEvtHandler* handler = (wxEvtHandler*) node->GetData();
5869 if (handler->ProcessEvent(event))
5870 {
5871 success = true;
5872 if (!sendToAll)
5873 return true;
5874 }
5875 }
5876 return success;
5877 }
5878
5879 /// Set style sheet and notify of the change
5880 bool wxRichTextBuffer::SetStyleSheetAndNotify(wxRichTextStyleSheet* sheet)
5881 {
5882 wxRichTextStyleSheet* oldSheet = GetStyleSheet();
5883
5884 wxWindowID id = wxID_ANY;
5885 if (GetRichTextCtrl())
5886 id = GetRichTextCtrl()->GetId();
5887
5888 wxRichTextEvent event(wxEVT_COMMAND_RICHTEXT_STYLESHEET_REPLACING, id);
5889 event.SetEventObject(GetRichTextCtrl());
5890 event.SetOldStyleSheet(oldSheet);
5891 event.SetNewStyleSheet(sheet);
5892 event.Allow();
5893
5894 if (SendEvent(event) && !event.IsAllowed())
5895 {
5896 if (sheet != oldSheet)
5897 delete sheet;
5898
5899 return false;
5900 }
5901
5902 if (oldSheet && oldSheet != sheet)
5903 delete oldSheet;
5904
5905 SetStyleSheet(sheet);
5906
5907 event.SetEventType(wxEVT_COMMAND_RICHTEXT_STYLESHEET_REPLACED);
5908 event.SetOldStyleSheet(NULL);
5909 event.Allow();
5910
5911 return SendEvent(event);
5912 }
5913
5914 /// Set renderer, deleting old one
5915 void wxRichTextBuffer::SetRenderer(wxRichTextRenderer* renderer)
5916 {
5917 if (sm_renderer)
5918 delete sm_renderer;
5919 sm_renderer = renderer;
5920 }
5921
5922 bool wxRichTextStdRenderer::DrawStandardBullet(wxRichTextParagraph* paragraph, wxDC& dc, const wxTextAttr& bulletAttr, const wxRect& rect)
5923 {
5924 if (bulletAttr.GetTextColour().Ok())
5925 {
5926 wxCheckSetPen(dc, wxPen(bulletAttr.GetTextColour()));
5927 wxCheckSetBrush(dc, wxBrush(bulletAttr.GetTextColour()));
5928 }
5929 else
5930 {
5931 wxCheckSetPen(dc, *wxBLACK_PEN);
5932 wxCheckSetBrush(dc, *wxBLACK_BRUSH);
5933 }
5934
5935 wxFont font;
5936 if (bulletAttr.HasFont())
5937 {
5938 font = paragraph->GetBuffer()->GetFontTable().FindFont(bulletAttr);
5939 }
5940 else
5941 font = (*wxNORMAL_FONT);
5942
5943 wxCheckSetFont(dc, font);
5944
5945 int charHeight = dc.GetCharHeight();
5946
5947 int bulletWidth = (int) (((float) charHeight) * wxRichTextBuffer::GetBulletProportion());
5948 int bulletHeight = bulletWidth;
5949
5950 int x = rect.x;
5951
5952 // Calculate the top position of the character (as opposed to the whole line height)
5953 int y = rect.y + (rect.height - charHeight);
5954
5955 // Calculate where the bullet should be positioned
5956 y = y + (charHeight+1)/2 - (bulletHeight+1)/2;
5957
5958 // The margin between a bullet and text.
5959 int margin = paragraph->ConvertTenthsMMToPixels(dc, wxRichTextBuffer::GetBulletRightMargin());
5960
5961 if (bulletAttr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_RIGHT)
5962 x = rect.x + rect.width - bulletWidth - margin;
5963 else if (bulletAttr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_CENTRE)
5964 x = x + (rect.width)/2 - bulletWidth/2;
5965
5966 if (bulletAttr.GetBulletName() == wxT("standard/square"))
5967 {
5968 dc.DrawRectangle(x, y, bulletWidth, bulletHeight);
5969 }
5970 else if (bulletAttr.GetBulletName() == wxT("standard/diamond"))
5971 {
5972 wxPoint pts[5];
5973 pts[0].x = x; pts[0].y = y + bulletHeight/2;
5974 pts[1].x = x + bulletWidth/2; pts[1].y = y;
5975 pts[2].x = x + bulletWidth; pts[2].y = y + bulletHeight/2;
5976 pts[3].x = x + bulletWidth/2; pts[3].y = y + bulletHeight;
5977
5978 dc.DrawPolygon(4, pts);
5979 }
5980 else if (bulletAttr.GetBulletName() == wxT("standard/triangle"))
5981 {
5982 wxPoint pts[3];
5983 pts[0].x = x; pts[0].y = y;
5984 pts[1].x = x + bulletWidth; pts[1].y = y + bulletHeight/2;
5985 pts[2].x = x; pts[2].y = y + bulletHeight;
5986
5987 dc.DrawPolygon(3, pts);
5988 }
5989 else // "standard/circle", and catch-all
5990 {
5991 dc.DrawEllipse(x, y, bulletWidth, bulletHeight);
5992 }
5993
5994 return true;
5995 }
5996
5997 bool wxRichTextStdRenderer::DrawTextBullet(wxRichTextParagraph* paragraph, wxDC& dc, const wxTextAttr& attr, const wxRect& rect, const wxString& text)
5998 {
5999 if (!text.empty())
6000 {
6001 wxFont font;
6002 if ((attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_SYMBOL) && !attr.GetBulletFont().IsEmpty() && attr.HasFont())
6003 {
6004 wxTextAttr fontAttr;
6005 fontAttr.SetFontSize(attr.GetFontSize());
6006 fontAttr.SetFontStyle(attr.GetFontStyle());
6007 fontAttr.SetFontWeight(attr.GetFontWeight());
6008 fontAttr.SetFontUnderlined(attr.GetFontUnderlined());
6009 fontAttr.SetFontFaceName(attr.GetBulletFont());
6010 font = paragraph->GetBuffer()->GetFontTable().FindFont(fontAttr);
6011 }
6012 else if (attr.HasFont())
6013 font = paragraph->GetBuffer()->GetFontTable().FindFont(attr);
6014 else
6015 font = (*wxNORMAL_FONT);
6016
6017 wxCheckSetFont(dc, font);
6018
6019 if (attr.GetTextColour().Ok())
6020 dc.SetTextForeground(attr.GetTextColour());
6021
6022 dc.SetBackgroundMode(wxTRANSPARENT);
6023
6024 int charHeight = dc.GetCharHeight();
6025 wxCoord tw, th;
6026 dc.GetTextExtent(text, & tw, & th);
6027
6028 int x = rect.x;
6029
6030 // Calculate the top position of the character (as opposed to the whole line height)
6031 int y = rect.y + (rect.height - charHeight);
6032
6033 // The margin between a bullet and text.
6034 int margin = paragraph->ConvertTenthsMMToPixels(dc, wxRichTextBuffer::GetBulletRightMargin());
6035
6036 if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_RIGHT)
6037 x = (rect.x + rect.width) - tw - margin;
6038 else if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_CENTRE)
6039 x = x + (rect.width)/2 - tw/2;
6040
6041 dc.DrawText(text, x, y);
6042
6043 return true;
6044 }
6045 else
6046 return false;
6047 }
6048
6049 bool wxRichTextStdRenderer::DrawBitmapBullet(wxRichTextParagraph* WXUNUSED(paragraph), wxDC& WXUNUSED(dc), const wxTextAttr& WXUNUSED(attr), const wxRect& WXUNUSED(rect))
6050 {
6051 // Currently unimplemented. The intention is to store bitmaps by name in a media store associated
6052 // with the buffer. The store will allow retrieval from memory, disk or other means.
6053 return false;
6054 }
6055
6056 /// Enumerate the standard bullet names currently supported
6057 bool wxRichTextStdRenderer::EnumerateStandardBulletNames(wxArrayString& bulletNames)
6058 {
6059 bulletNames.Add(wxT("standard/circle"));
6060 bulletNames.Add(wxT("standard/square"));
6061 bulletNames.Add(wxT("standard/diamond"));
6062 bulletNames.Add(wxT("standard/triangle"));
6063
6064 return true;
6065 }
6066
6067 /*
6068 * Module to initialise and clean up handlers
6069 */
6070
6071 class wxRichTextModule: public wxModule
6072 {
6073 DECLARE_DYNAMIC_CLASS(wxRichTextModule)
6074 public:
6075 wxRichTextModule() {}
6076 bool OnInit()
6077 {
6078 wxRichTextBuffer::SetRenderer(new wxRichTextStdRenderer);
6079 wxRichTextBuffer::InitStandardHandlers();
6080 wxRichTextParagraph::InitDefaultTabs();
6081 return true;
6082 }
6083 void OnExit()
6084 {
6085 wxRichTextBuffer::CleanUpHandlers();
6086 wxRichTextDecimalToRoman(-1);
6087 wxRichTextParagraph::ClearDefaultTabs();
6088 wxRichTextCtrl::ClearAvailableFontNames();
6089 wxRichTextBuffer::SetRenderer(NULL);
6090 }
6091 };
6092
6093 IMPLEMENT_DYNAMIC_CLASS(wxRichTextModule, wxModule)
6094
6095
6096 // If the richtext lib is dynamically loaded after the app has already started
6097 // (such as from wxPython) then the built-in module system will not init this
6098 // module. Provide this function to do it manually.
6099 void wxRichTextModuleInit()
6100 {
6101 wxModule* module = new wxRichTextModule;
6102 module->Init();
6103 wxModule::RegisterModule(module);
6104 }
6105
6106
6107 /*!
6108 * Commands for undo/redo
6109 *
6110 */
6111
6112 wxRichTextCommand::wxRichTextCommand(const wxString& name, wxRichTextCommandId id, wxRichTextBuffer* buffer,
6113 wxRichTextCtrl* ctrl, bool ignoreFirstTime): wxCommand(true, name)
6114 {
6115 /* wxRichTextAction* action = */ new wxRichTextAction(this, name, id, buffer, ctrl, ignoreFirstTime);
6116 }
6117
6118 wxRichTextCommand::wxRichTextCommand(const wxString& name): wxCommand(true, name)
6119 {
6120 }
6121
6122 wxRichTextCommand::~wxRichTextCommand()
6123 {
6124 ClearActions();
6125 }
6126
6127 void wxRichTextCommand::AddAction(wxRichTextAction* action)
6128 {
6129 if (!m_actions.Member(action))
6130 m_actions.Append(action);
6131 }
6132
6133 bool wxRichTextCommand::Do()
6134 {
6135 for (wxList::compatibility_iterator node = m_actions.GetFirst(); node; node = node->GetNext())
6136 {
6137 wxRichTextAction* action = (wxRichTextAction*) node->GetData();
6138 action->Do();
6139 }
6140
6141 return true;
6142 }
6143
6144 bool wxRichTextCommand::Undo()
6145 {
6146 for (wxList::compatibility_iterator node = m_actions.GetLast(); node; node = node->GetPrevious())
6147 {
6148 wxRichTextAction* action = (wxRichTextAction*) node->GetData();
6149 action->Undo();
6150 }
6151
6152 return true;
6153 }
6154
6155 void wxRichTextCommand::ClearActions()
6156 {
6157 WX_CLEAR_LIST(wxList, m_actions);
6158 }
6159
6160 /*!
6161 * Individual action
6162 *
6163 */
6164
6165 wxRichTextAction::wxRichTextAction(wxRichTextCommand* cmd, const wxString& name, wxRichTextCommandId id, wxRichTextBuffer* buffer,
6166 wxRichTextCtrl* ctrl, bool ignoreFirstTime)
6167 {
6168 m_buffer = buffer;
6169 m_ignoreThis = ignoreFirstTime;
6170 m_cmdId = id;
6171 m_position = -1;
6172 m_ctrl = ctrl;
6173 m_name = name;
6174 m_newParagraphs.SetDefaultStyle(buffer->GetDefaultStyle());
6175 m_newParagraphs.SetBasicStyle(buffer->GetBasicStyle());
6176 if (cmd)
6177 cmd->AddAction(this);
6178 }
6179
6180 wxRichTextAction::~wxRichTextAction()
6181 {
6182 }
6183
6184 bool wxRichTextAction::Do()
6185 {
6186 m_buffer->Modify(true);
6187
6188 switch (m_cmdId)
6189 {
6190 case wxRICHTEXT_INSERT:
6191 {
6192 // Store a list of line start character and y positions so we can figure out which area
6193 // we need to refresh
6194 wxArrayInt optimizationLineCharPositions;
6195 wxArrayInt optimizationLineYPositions;
6196
6197 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
6198 // NOTE: we're assuming that the buffer is laid out correctly at this point.
6199 // If we had several actions, which only invalidate and leave layout until the
6200 // paint handler is called, then this might not be true. So we may need to switch
6201 // optimisation on only when we're simply adding text and not simultaneously
6202 // deleting a selection, for example. Or, we make sure the buffer is laid out correctly
6203 // first, but of course this means we'll be doing it twice.
6204 if (!m_buffer->GetDirty() && m_ctrl) // can only do optimisation if the buffer is already laid out correctly
6205 {
6206 wxSize clientSize = m_ctrl->GetClientSize();
6207 wxPoint firstVisiblePt = m_ctrl->GetFirstVisiblePoint();
6208 int lastY = firstVisiblePt.y + clientSize.y;
6209
6210 wxRichTextParagraph* para = m_buffer->GetParagraphAtPosition(GetRange().GetStart());
6211 wxRichTextObjectList::compatibility_iterator node = m_buffer->GetChildren().Find(para);
6212 while (node)
6213 {
6214 wxRichTextParagraph* child = (wxRichTextParagraph*) node->GetData();
6215 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
6216 while (node2)
6217 {
6218 wxRichTextLine* line = node2->GetData();
6219 wxPoint pt = line->GetAbsolutePosition();
6220 wxRichTextRange range = line->GetAbsoluteRange();
6221
6222 if (pt.y > lastY)
6223 {
6224 node2 = wxRichTextLineList::compatibility_iterator();
6225 node = wxRichTextObjectList::compatibility_iterator();
6226 }
6227 else if (range.GetStart() > GetPosition() && pt.y >= firstVisiblePt.y)
6228 {
6229 optimizationLineCharPositions.Add(range.GetStart());
6230 optimizationLineYPositions.Add(pt.y);
6231 }
6232
6233 if (node2)
6234 node2 = node2->GetNext();
6235 }
6236
6237 if (node)
6238 node = node->GetNext();
6239 }
6240 }
6241 #endif
6242
6243 m_buffer->InsertFragment(GetRange().GetStart(), m_newParagraphs);
6244 m_buffer->UpdateRanges();
6245 m_buffer->Invalidate(wxRichTextRange(GetRange().GetStart()-1, GetRange().GetEnd()));
6246
6247 long newCaretPosition = GetPosition() + m_newParagraphs.GetRange().GetLength();
6248
6249 // Character position to caret position
6250 newCaretPosition --;
6251
6252 // Don't take into account the last newline
6253 if (m_newParagraphs.GetPartialParagraph())
6254 newCaretPosition --;
6255 else
6256 if (m_newParagraphs.GetChildren().GetCount() > 1)
6257 {
6258 wxRichTextObject* p = (wxRichTextObject*) m_newParagraphs.GetChildren().GetLast()->GetData();
6259 if (p->GetRange().GetLength() == 1)
6260 newCaretPosition --;
6261 }
6262
6263 newCaretPosition = wxMin(newCaretPosition, (m_buffer->GetRange().GetEnd()-1));
6264
6265 if (optimizationLineCharPositions.GetCount() > 0)
6266 UpdateAppearance(newCaretPosition, true /* send update event */, & optimizationLineCharPositions, & optimizationLineYPositions);
6267 else
6268 UpdateAppearance(newCaretPosition, true /* send update event */);
6269
6270 wxRichTextEvent cmdEvent(
6271 wxEVT_COMMAND_RICHTEXT_CONTENT_INSERTED,
6272 m_ctrl ? m_ctrl->GetId() : -1);
6273 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
6274 cmdEvent.SetRange(GetRange());
6275 cmdEvent.SetPosition(GetRange().GetStart());
6276
6277 m_buffer->SendEvent(cmdEvent);
6278
6279 break;
6280 }
6281 case wxRICHTEXT_DELETE:
6282 {
6283 m_buffer->DeleteRange(GetRange());
6284 m_buffer->UpdateRanges();
6285 m_buffer->Invalidate(wxRichTextRange(GetRange().GetStart(), GetRange().GetStart()));
6286
6287 long caretPos = GetRange().GetStart()-1;
6288 if (caretPos >= m_buffer->GetRange().GetEnd())
6289 caretPos --;
6290
6291 UpdateAppearance(caretPos, true /* send update event */);
6292
6293 wxRichTextEvent cmdEvent(
6294 wxEVT_COMMAND_RICHTEXT_CONTENT_DELETED,
6295 m_ctrl ? m_ctrl->GetId() : -1);
6296 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
6297 cmdEvent.SetRange(GetRange());
6298 cmdEvent.SetPosition(GetRange().GetStart());
6299
6300 m_buffer->SendEvent(cmdEvent);
6301
6302 break;
6303 }
6304 case wxRICHTEXT_CHANGE_STYLE:
6305 {
6306 ApplyParagraphs(GetNewParagraphs());
6307 m_buffer->Invalidate(GetRange());
6308
6309 UpdateAppearance(GetPosition());
6310
6311 wxRichTextEvent cmdEvent(
6312 wxEVT_COMMAND_RICHTEXT_STYLE_CHANGED,
6313 m_ctrl ? m_ctrl->GetId() : -1);
6314 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
6315 cmdEvent.SetRange(GetRange());
6316 cmdEvent.SetPosition(GetRange().GetStart());
6317
6318 m_buffer->SendEvent(cmdEvent);
6319
6320 break;
6321 }
6322 default:
6323 break;
6324 }
6325
6326 return true;
6327 }
6328
6329 bool wxRichTextAction::Undo()
6330 {
6331 m_buffer->Modify(true);
6332
6333 switch (m_cmdId)
6334 {
6335 case wxRICHTEXT_INSERT:
6336 {
6337 m_buffer->DeleteRange(GetRange());
6338 m_buffer->UpdateRanges();
6339 m_buffer->Invalidate(wxRichTextRange(GetRange().GetStart(), GetRange().GetStart()));
6340
6341 long newCaretPosition = GetPosition() - 1;
6342
6343 UpdateAppearance(newCaretPosition, true /* send update event */);
6344
6345 wxRichTextEvent cmdEvent(
6346 wxEVT_COMMAND_RICHTEXT_CONTENT_DELETED,
6347 m_ctrl ? m_ctrl->GetId() : -1);
6348 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
6349 cmdEvent.SetRange(GetRange());
6350 cmdEvent.SetPosition(GetRange().GetStart());
6351
6352 m_buffer->SendEvent(cmdEvent);
6353
6354 break;
6355 }
6356 case wxRICHTEXT_DELETE:
6357 {
6358 m_buffer->InsertFragment(GetRange().GetStart(), m_oldParagraphs);
6359 m_buffer->UpdateRanges();
6360 m_buffer->Invalidate(GetRange());
6361
6362 UpdateAppearance(GetPosition(), true /* send update event */);
6363
6364 wxRichTextEvent cmdEvent(
6365 wxEVT_COMMAND_RICHTEXT_CONTENT_INSERTED,
6366 m_ctrl ? m_ctrl->GetId() : -1);
6367 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
6368 cmdEvent.SetRange(GetRange());
6369 cmdEvent.SetPosition(GetRange().GetStart());
6370
6371 m_buffer->SendEvent(cmdEvent);
6372
6373 break;
6374 }
6375 case wxRICHTEXT_CHANGE_STYLE:
6376 {
6377 ApplyParagraphs(GetOldParagraphs());
6378 m_buffer->Invalidate(GetRange());
6379
6380 UpdateAppearance(GetPosition());
6381
6382 wxRichTextEvent cmdEvent(
6383 wxEVT_COMMAND_RICHTEXT_STYLE_CHANGED,
6384 m_ctrl ? m_ctrl->GetId() : -1);
6385 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
6386 cmdEvent.SetRange(GetRange());
6387 cmdEvent.SetPosition(GetRange().GetStart());
6388
6389 m_buffer->SendEvent(cmdEvent);
6390
6391 break;
6392 }
6393 default:
6394 break;
6395 }
6396
6397 return true;
6398 }
6399
6400 /// Update the control appearance
6401 void wxRichTextAction::UpdateAppearance(long caretPosition, bool sendUpdateEvent, wxArrayInt* optimizationLineCharPositions, wxArrayInt* optimizationLineYPositions)
6402 {
6403 if (m_ctrl)
6404 {
6405 m_ctrl->SetCaretPosition(caretPosition);
6406 if (!m_ctrl->IsFrozen())
6407 {
6408 m_ctrl->LayoutContent();
6409 m_ctrl->PositionCaret();
6410
6411 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
6412 // Find refresh rectangle if we are in a position to optimise refresh
6413 if (m_cmdId == wxRICHTEXT_INSERT && optimizationLineCharPositions && optimizationLineCharPositions->GetCount() > 0)
6414 {
6415 size_t i;
6416
6417 wxSize clientSize = m_ctrl->GetClientSize();
6418 wxPoint firstVisiblePt = m_ctrl->GetFirstVisiblePoint();
6419
6420 // Start/end positions
6421 int firstY = 0;
6422 int lastY = firstVisiblePt.y + clientSize.y;
6423
6424 bool foundStart = false;
6425 bool foundEnd = false;
6426
6427 // position offset - how many characters were inserted
6428 int positionOffset = GetRange().GetLength();
6429
6430 // find the first line which is being drawn at the same position as it was
6431 // before. Since we're talking about a simple insertion, we can assume
6432 // that the rest of the window does not need to be redrawn.
6433
6434 wxRichTextParagraph* para = m_buffer->GetParagraphAtPosition(GetPosition());
6435 wxRichTextObjectList::compatibility_iterator node = m_buffer->GetChildren().Find(para);
6436 while (node)
6437 {
6438 wxRichTextParagraph* child = (wxRichTextParagraph*) node->GetData();
6439 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
6440 while (node2)
6441 {
6442 wxRichTextLine* line = node2->GetData();
6443 wxPoint pt = line->GetAbsolutePosition();
6444 wxRichTextRange range = line->GetAbsoluteRange();
6445
6446 // we want to find the first line that is in the same position
6447 // as before. This will mean we're at the end of the changed text.
6448
6449 if (pt.y > lastY) // going past the end of the window, no more info
6450 {
6451 node2 = wxRichTextLineList::compatibility_iterator();
6452 node = wxRichTextObjectList::compatibility_iterator();
6453 }
6454 else
6455 {
6456 if (!foundStart)
6457 {
6458 firstY = pt.y - firstVisiblePt.y;
6459 foundStart = true;
6460 }
6461
6462 // search for this line being at the same position as before
6463 for (i = 0; i < optimizationLineCharPositions->GetCount(); i++)
6464 {
6465 if (((*optimizationLineCharPositions)[i] + positionOffset == range.GetStart()) &&
6466 ((*optimizationLineYPositions)[i] == pt.y))
6467 {
6468 // Stop, we're now the same as we were
6469 foundEnd = true;
6470 lastY = pt.y - firstVisiblePt.y;
6471
6472 node2 = wxRichTextLineList::compatibility_iterator();
6473 node = wxRichTextObjectList::compatibility_iterator();
6474
6475 break;
6476 }
6477 }
6478 }
6479
6480 if (node2)
6481 node2 = node2->GetNext();
6482 }
6483
6484 if (node)
6485 node = node->GetNext();
6486 }
6487
6488 if (!foundStart)
6489 firstY = firstVisiblePt.y;
6490 if (!foundEnd)
6491 lastY = firstVisiblePt.y + clientSize.y;
6492
6493 wxRect rect(firstVisiblePt.x, firstY, firstVisiblePt.x + clientSize.x, lastY - firstY);
6494 m_ctrl->RefreshRect(rect);
6495
6496 // TODO: we need to make sure that lines are only drawn if in the update region. The rect
6497 // passed to Draw is currently used in different ways (to pass the position the content should
6498 // be drawn at as well as the relevant region).
6499 }
6500 else
6501 #endif
6502 m_ctrl->Refresh(false);
6503
6504 if (sendUpdateEvent)
6505 wxTextCtrl::SendTextUpdatedEvent(m_ctrl);
6506 }
6507 }
6508 }
6509
6510 /// Replace the buffer paragraphs with the new ones.
6511 void wxRichTextAction::ApplyParagraphs(const wxRichTextParagraphLayoutBox& fragment)
6512 {
6513 wxRichTextObjectList::compatibility_iterator node = fragment.GetChildren().GetFirst();
6514 while (node)
6515 {
6516 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
6517 wxASSERT (para != NULL);
6518
6519 // We'll replace the existing paragraph by finding the paragraph at this position,
6520 // delete its node data, and setting a copy as the new node data.
6521 // TODO: make more efficient by simply swapping old and new paragraph objects.
6522
6523 wxRichTextParagraph* existingPara = m_buffer->GetParagraphAtPosition(para->GetRange().GetStart());
6524 if (existingPara)
6525 {
6526 wxRichTextObjectList::compatibility_iterator bufferParaNode = m_buffer->GetChildren().Find(existingPara);
6527 if (bufferParaNode)
6528 {
6529 wxRichTextParagraph* newPara = new wxRichTextParagraph(*para);
6530 newPara->SetParent(m_buffer);
6531
6532 bufferParaNode->SetData(newPara);
6533
6534 delete existingPara;
6535 }
6536 }
6537
6538 node = node->GetNext();
6539 }
6540 }
6541
6542
6543 /*!
6544 * wxRichTextRange
6545 * This stores beginning and end positions for a range of data.
6546 */
6547
6548 /// Limit this range to be within 'range'
6549 bool wxRichTextRange::LimitTo(const wxRichTextRange& range)
6550 {
6551 if (m_start < range.m_start)
6552 m_start = range.m_start;
6553
6554 if (m_end > range.m_end)
6555 m_end = range.m_end;
6556
6557 return true;
6558 }
6559
6560 /*!
6561 * wxRichTextImage implementation
6562 * This object represents an image.
6563 */
6564
6565 IMPLEMENT_DYNAMIC_CLASS(wxRichTextImage, wxRichTextObject)
6566
6567 wxRichTextImage::wxRichTextImage(const wxImage& image, wxRichTextObject* parent, wxTextAttr* charStyle):
6568 wxRichTextObject(parent)
6569 {
6570 m_image = image;
6571 if (charStyle)
6572 SetAttributes(*charStyle);
6573 }
6574
6575 wxRichTextImage::wxRichTextImage(const wxRichTextImageBlock& imageBlock, wxRichTextObject* parent, wxTextAttr* charStyle):
6576 wxRichTextObject(parent)
6577 {
6578 m_imageBlock = imageBlock;
6579 m_imageBlock.Load(m_image);
6580 if (charStyle)
6581 SetAttributes(*charStyle);
6582 }
6583
6584 /// Load wxImage from the block
6585 bool wxRichTextImage::LoadFromBlock()
6586 {
6587 m_imageBlock.Load(m_image);
6588 return m_imageBlock.Ok();
6589 }
6590
6591 /// Make block from the wxImage
6592 bool wxRichTextImage::MakeBlock()
6593 {
6594 if (m_imageBlock.GetImageType() == wxBITMAP_TYPE_ANY || m_imageBlock.GetImageType() == -1)
6595 m_imageBlock.SetImageType(wxBITMAP_TYPE_PNG);
6596
6597 m_imageBlock.MakeImageBlock(m_image, m_imageBlock.GetImageType());
6598 return m_imageBlock.Ok();
6599 }
6600
6601
6602 /// Draw the item
6603 bool wxRichTextImage::Draw(wxDC& dc, const wxRichTextRange& range, const wxRichTextRange& selectionRange, const wxRect& rect, int WXUNUSED(descent), int WXUNUSED(style))
6604 {
6605 if (!m_image.Ok() && m_imageBlock.Ok())
6606 LoadFromBlock();
6607
6608 if (!m_image.Ok())
6609 return false;
6610
6611 if (m_image.Ok() && !m_bitmap.Ok())
6612 m_bitmap = wxBitmap(m_image);
6613
6614 int y = rect.y + (rect.height - m_image.GetHeight());
6615
6616 if (m_bitmap.Ok())
6617 dc.DrawBitmap(m_bitmap, rect.x, y, true);
6618
6619 if (selectionRange.Contains(range.GetStart()))
6620 {
6621 wxCheckSetBrush(dc, *wxBLACK_BRUSH);
6622 wxCheckSetPen(dc, *wxBLACK_PEN);
6623 dc.SetLogicalFunction(wxINVERT);
6624 dc.DrawRectangle(rect);
6625 dc.SetLogicalFunction(wxCOPY);
6626 }
6627
6628 return true;
6629 }
6630
6631 /// Lay the item out
6632 bool wxRichTextImage::Layout(wxDC& WXUNUSED(dc), const wxRect& rect, int WXUNUSED(style))
6633 {
6634 if (!m_image.Ok())
6635 LoadFromBlock();
6636
6637 if (m_image.Ok())
6638 {
6639 SetCachedSize(wxSize(m_image.GetWidth(), m_image.GetHeight()));
6640 SetPosition(rect.GetPosition());
6641 }
6642
6643 return true;
6644 }
6645
6646 /// Get/set the object size for the given range. Returns false if the range
6647 /// is invalid for this object.
6648 bool wxRichTextImage::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& WXUNUSED(descent), wxDC& WXUNUSED(dc), int WXUNUSED(flags), wxPoint WXUNUSED(position)) const
6649 {
6650 if (!range.IsWithin(GetRange()))
6651 return false;
6652
6653 if (!m_image.Ok())
6654 return false;
6655
6656 size.x = m_image.GetWidth();
6657 size.y = m_image.GetHeight();
6658
6659 return true;
6660 }
6661
6662 /// Copy
6663 void wxRichTextImage::Copy(const wxRichTextImage& obj)
6664 {
6665 wxRichTextObject::Copy(obj);
6666
6667 m_image = obj.m_image;
6668 m_imageBlock = obj.m_imageBlock;
6669 }
6670
6671 /*!
6672 * Utilities
6673 *
6674 */
6675
6676 /// Compare two attribute objects
6677 bool wxTextAttrEq(const wxTextAttr& attr1, const wxTextAttr& attr2)
6678 {
6679 return (attr1 == attr2);
6680 }
6681
6682 // Partial equality test taking flags into account
6683 bool wxTextAttrEqPartial(const wxTextAttr& attr1, const wxTextAttr& attr2, int flags)
6684 {
6685 return attr1.EqPartial(attr2, flags);
6686 }
6687
6688 /// Compare tabs
6689 bool wxRichTextTabsEq(const wxArrayInt& tabs1, const wxArrayInt& tabs2)
6690 {
6691 if (tabs1.GetCount() != tabs2.GetCount())
6692 return false;
6693
6694 size_t i;
6695 for (i = 0; i < tabs1.GetCount(); i++)
6696 {
6697 if (tabs1[i] != tabs2[i])
6698 return false;
6699 }
6700 return true;
6701 }
6702
6703 bool wxRichTextApplyStyle(wxTextAttr& destStyle, const wxTextAttr& style, wxTextAttr* compareWith)
6704 {
6705 return destStyle.Apply(style, compareWith);
6706 }
6707
6708 // Remove attributes
6709 bool wxRichTextRemoveStyle(wxTextAttr& destStyle, const wxTextAttr& style)
6710 {
6711 return wxTextAttr::RemoveStyle(destStyle, style);
6712 }
6713
6714 /// Combine two bitlists, specifying the bits of interest with separate flags.
6715 bool wxRichTextCombineBitlists(int& valueA, int valueB, int& flagsA, int flagsB)
6716 {
6717 return wxTextAttr::CombineBitlists(valueA, valueB, flagsA, flagsB);
6718 }
6719
6720 /// Compare two bitlists
6721 bool wxRichTextBitlistsEqPartial(int valueA, int valueB, int flags)
6722 {
6723 return wxTextAttr::BitlistsEqPartial(valueA, valueB, flags);
6724 }
6725
6726 /// Split into paragraph and character styles
6727 bool wxRichTextSplitParaCharStyles(const wxTextAttr& style, wxTextAttr& parStyle, wxTextAttr& charStyle)
6728 {
6729 return wxTextAttr::SplitParaCharStyles(style, parStyle, charStyle);
6730 }
6731
6732 /// Convert a decimal to Roman numerals
6733 wxString wxRichTextDecimalToRoman(long n)
6734 {
6735 static wxArrayInt decimalNumbers;
6736 static wxArrayString romanNumbers;
6737
6738 // Clean up arrays
6739 if (n == -1)
6740 {
6741 decimalNumbers.Clear();
6742 romanNumbers.Clear();
6743 return wxEmptyString;
6744 }
6745
6746 if (decimalNumbers.GetCount() == 0)
6747 {
6748 #define wxRichTextAddDecRom(n, r) decimalNumbers.Add(n); romanNumbers.Add(r);
6749
6750 wxRichTextAddDecRom(1000, wxT("M"));
6751 wxRichTextAddDecRom(900, wxT("CM"));
6752 wxRichTextAddDecRom(500, wxT("D"));
6753 wxRichTextAddDecRom(400, wxT("CD"));
6754 wxRichTextAddDecRom(100, wxT("C"));
6755 wxRichTextAddDecRom(90, wxT("XC"));
6756 wxRichTextAddDecRom(50, wxT("L"));
6757 wxRichTextAddDecRom(40, wxT("XL"));
6758 wxRichTextAddDecRom(10, wxT("X"));
6759 wxRichTextAddDecRom(9, wxT("IX"));
6760 wxRichTextAddDecRom(5, wxT("V"));
6761 wxRichTextAddDecRom(4, wxT("IV"));
6762 wxRichTextAddDecRom(1, wxT("I"));
6763 }
6764
6765 int i = 0;
6766 wxString roman;
6767
6768 while (n > 0 && i < 13)
6769 {
6770 if (n >= decimalNumbers[i])
6771 {
6772 n -= decimalNumbers[i];
6773 roman += romanNumbers[i];
6774 }
6775 else
6776 {
6777 i ++;
6778 }
6779 }
6780 if (roman.IsEmpty())
6781 roman = wxT("0");
6782 return roman;
6783 }
6784
6785 /*!
6786 * wxRichTextFileHandler
6787 * Base class for file handlers
6788 */
6789
6790 IMPLEMENT_CLASS(wxRichTextFileHandler, wxObject)
6791
6792 #if wxUSE_FFILE && wxUSE_STREAMS
6793 bool wxRichTextFileHandler::LoadFile(wxRichTextBuffer *buffer, const wxString& filename)
6794 {
6795 wxFFileInputStream stream(filename);
6796 if (stream.Ok())
6797 return LoadFile(buffer, stream);
6798
6799 return false;
6800 }
6801
6802 bool wxRichTextFileHandler::SaveFile(wxRichTextBuffer *buffer, const wxString& filename)
6803 {
6804 wxFFileOutputStream stream(filename);
6805 if (stream.Ok())
6806 return SaveFile(buffer, stream);
6807
6808 return false;
6809 }
6810 #endif // wxUSE_FFILE && wxUSE_STREAMS
6811
6812 /// Can we handle this filename (if using files)? By default, checks the extension.
6813 bool wxRichTextFileHandler::CanHandle(const wxString& filename) const
6814 {
6815 wxString path, file, ext;
6816 wxSplitPath(filename, & path, & file, & ext);
6817
6818 return (ext.Lower() == GetExtension());
6819 }
6820
6821 /*!
6822 * wxRichTextTextHandler
6823 * Plain text handler
6824 */
6825
6826 IMPLEMENT_CLASS(wxRichTextPlainTextHandler, wxRichTextFileHandler)
6827
6828 #if wxUSE_STREAMS
6829 bool wxRichTextPlainTextHandler::DoLoadFile(wxRichTextBuffer *buffer, wxInputStream& stream)
6830 {
6831 if (!stream.IsOk())
6832 return false;
6833
6834 wxString str;
6835 int lastCh = 0;
6836
6837 while (!stream.Eof())
6838 {
6839 int ch = stream.GetC();
6840
6841 if (!stream.Eof())
6842 {
6843 if (ch == 10 && lastCh != 13)
6844 str += wxT('\n');
6845
6846 if (ch > 0 && ch != 10)
6847 str += wxChar(ch);
6848
6849 lastCh = ch;
6850 }
6851 }
6852
6853 buffer->ResetAndClearCommands();
6854 buffer->Clear();
6855 buffer->AddParagraphs(str);
6856 buffer->UpdateRanges();
6857
6858 return true;
6859 }
6860
6861 bool wxRichTextPlainTextHandler::DoSaveFile(wxRichTextBuffer *buffer, wxOutputStream& stream)
6862 {
6863 if (!stream.IsOk())
6864 return false;
6865
6866 wxString text = buffer->GetText();
6867
6868 wxString newLine = wxRichTextLineBreakChar;
6869 text.Replace(newLine, wxT("\n"));
6870
6871 wxCharBuffer buf = text.ToAscii();
6872
6873 stream.Write((const char*) buf, text.length());
6874 return true;
6875 }
6876 #endif // wxUSE_STREAMS
6877
6878 /*
6879 * Stores information about an image, in binary in-memory form
6880 */
6881
6882 wxRichTextImageBlock::wxRichTextImageBlock()
6883 {
6884 Init();
6885 }
6886
6887 wxRichTextImageBlock::wxRichTextImageBlock(const wxRichTextImageBlock& block):wxObject()
6888 {
6889 Init();
6890 Copy(block);
6891 }
6892
6893 wxRichTextImageBlock::~wxRichTextImageBlock()
6894 {
6895 if (m_data)
6896 {
6897 delete[] m_data;
6898 m_data = NULL;
6899 }
6900 }
6901
6902 void wxRichTextImageBlock::Init()
6903 {
6904 m_data = NULL;
6905 m_dataSize = 0;
6906 m_imageType = -1;
6907 }
6908
6909 void wxRichTextImageBlock::Clear()
6910 {
6911 delete[] m_data;
6912 m_data = NULL;
6913 m_dataSize = 0;
6914 m_imageType = -1;
6915 }
6916
6917
6918 // Load the original image into a memory block.
6919 // If the image is not a JPEG, we must convert it into a JPEG
6920 // to conserve space.
6921 // If it's not a JPEG we can make use of 'image', already scaled, so we don't have to
6922 // load the image a 2nd time.
6923
6924 bool wxRichTextImageBlock::MakeImageBlock(const wxString& filename, int imageType, wxImage& image, bool convertToJPEG)
6925 {
6926 m_imageType = imageType;
6927
6928 wxString filenameToRead(filename);
6929 bool removeFile = false;
6930
6931 if (imageType == -1)
6932 return false; // Could not determine image type
6933
6934 if ((imageType != wxBITMAP_TYPE_JPEG) && convertToJPEG)
6935 {
6936 wxString tempFile;
6937 bool success = wxGetTempFileName(_("image"), tempFile) ;
6938
6939 wxASSERT(success);
6940
6941 wxUnusedVar(success);
6942
6943 image.SaveFile(tempFile, wxBITMAP_TYPE_JPEG);
6944 filenameToRead = tempFile;
6945 removeFile = true;
6946
6947 m_imageType = wxBITMAP_TYPE_JPEG;
6948 }
6949 wxFile file;
6950 if (!file.Open(filenameToRead))
6951 return false;
6952
6953 m_dataSize = (size_t) file.Length();
6954 file.Close();
6955
6956 if (m_data)
6957 delete[] m_data;
6958 m_data = ReadBlock(filenameToRead, m_dataSize);
6959
6960 if (removeFile)
6961 wxRemoveFile(filenameToRead);
6962
6963 return (m_data != NULL);
6964 }
6965
6966 // Make an image block from the wxImage in the given
6967 // format.
6968 bool wxRichTextImageBlock::MakeImageBlock(wxImage& image, int imageType, int quality)
6969 {
6970 m_imageType = imageType;
6971 image.SetOption(wxT("quality"), quality);
6972
6973 if (imageType == -1)
6974 return false; // Could not determine image type
6975
6976 wxString tempFile;
6977 bool success = wxGetTempFileName(_("image"), tempFile) ;
6978
6979 wxASSERT(success);
6980 wxUnusedVar(success);
6981
6982 if (!image.SaveFile(tempFile, m_imageType))
6983 {
6984 if (wxFileExists(tempFile))
6985 wxRemoveFile(tempFile);
6986 return false;
6987 }
6988
6989 wxFile file;
6990 if (!file.Open(tempFile))
6991 return false;
6992
6993 m_dataSize = (size_t) file.Length();
6994 file.Close();
6995
6996 if (m_data)
6997 delete[] m_data;
6998 m_data = ReadBlock(tempFile, m_dataSize);
6999
7000 wxRemoveFile(tempFile);
7001
7002 return (m_data != NULL);
7003 }
7004
7005
7006 // Write to a file
7007 bool wxRichTextImageBlock::Write(const wxString& filename)
7008 {
7009 return WriteBlock(filename, m_data, m_dataSize);
7010 }
7011
7012 void wxRichTextImageBlock::Copy(const wxRichTextImageBlock& block)
7013 {
7014 m_imageType = block.m_imageType;
7015 if (m_data)
7016 {
7017 delete[] m_data;
7018 m_data = NULL;
7019 }
7020 m_dataSize = block.m_dataSize;
7021 if (m_dataSize == 0)
7022 return;
7023
7024 m_data = new unsigned char[m_dataSize];
7025 unsigned int i;
7026 for (i = 0; i < m_dataSize; i++)
7027 m_data[i] = block.m_data[i];
7028 }
7029
7030 //// Operators
7031 void wxRichTextImageBlock::operator=(const wxRichTextImageBlock& block)
7032 {
7033 Copy(block);
7034 }
7035
7036 // Load a wxImage from the block
7037 bool wxRichTextImageBlock::Load(wxImage& image)
7038 {
7039 if (!m_data)
7040 return false;
7041
7042 // Read in the image.
7043 #if wxUSE_STREAMS
7044 wxMemoryInputStream mstream(m_data, m_dataSize);
7045 bool success = image.LoadFile(mstream, GetImageType());
7046 #else
7047 wxString tempFile;
7048 bool success = wxGetTempFileName(_("image"), tempFile) ;
7049 wxASSERT(success);
7050
7051 if (!WriteBlock(tempFile, m_data, m_dataSize))
7052 {
7053 return false;
7054 }
7055 success = image.LoadFile(tempFile, GetImageType());
7056 wxRemoveFile(tempFile);
7057 #endif
7058
7059 return success;
7060 }
7061
7062 // Write data in hex to a stream
7063 bool wxRichTextImageBlock::WriteHex(wxOutputStream& stream)
7064 {
7065 const int bufSize = 512;
7066 char buf[bufSize+1];
7067
7068 int left = m_dataSize;
7069 int n, i, j;
7070 j = 0;
7071 while (left > 0)
7072 {
7073 if (left*2 > bufSize)
7074 {
7075 n = bufSize; left -= (bufSize/2);
7076 }
7077 else
7078 {
7079 n = left*2; left = 0;
7080 }
7081
7082 char* b = buf;
7083 for (i = 0; i < (n/2); i++)
7084 {
7085 wxDecToHex(m_data[j], b, b+1);
7086 b += 2; j ++;
7087 }
7088
7089 buf[n] = 0;
7090 stream.Write((const char*) buf, n);
7091 }
7092 return true;
7093 }
7094
7095 // Read data in hex from a stream
7096 bool wxRichTextImageBlock::ReadHex(wxInputStream& stream, int length, int imageType)
7097 {
7098 int dataSize = length/2;
7099
7100 if (m_data)
7101 delete[] m_data;
7102
7103 wxChar str[2];
7104 m_data = new unsigned char[dataSize];
7105 int i;
7106 for (i = 0; i < dataSize; i ++)
7107 {
7108 str[0] = (char)stream.GetC();
7109 str[1] = (char)stream.GetC();
7110
7111 m_data[i] = (unsigned char)wxHexToDec(str);
7112 }
7113
7114 m_dataSize = dataSize;
7115 m_imageType = imageType;
7116
7117 return true;
7118 }
7119
7120 // Allocate and read from stream as a block of memory
7121 unsigned char* wxRichTextImageBlock::ReadBlock(wxInputStream& stream, size_t size)
7122 {
7123 unsigned char* block = new unsigned char[size];
7124 if (!block)
7125 return NULL;
7126
7127 stream.Read(block, size);
7128
7129 return block;
7130 }
7131
7132 unsigned char* wxRichTextImageBlock::ReadBlock(const wxString& filename, size_t size)
7133 {
7134 wxFileInputStream stream(filename);
7135 if (!stream.Ok())
7136 return NULL;
7137
7138 return ReadBlock(stream, size);
7139 }
7140
7141 // Write memory block to stream
7142 bool wxRichTextImageBlock::WriteBlock(wxOutputStream& stream, unsigned char* block, size_t size)
7143 {
7144 stream.Write((void*) block, size);
7145 return stream.IsOk();
7146
7147 }
7148
7149 // Write memory block to file
7150 bool wxRichTextImageBlock::WriteBlock(const wxString& filename, unsigned char* block, size_t size)
7151 {
7152 wxFileOutputStream outStream(filename);
7153 if (!outStream.Ok())
7154 return false;
7155
7156 return WriteBlock(outStream, block, size);
7157 }
7158
7159 // Gets the extension for the block's type
7160 wxString wxRichTextImageBlock::GetExtension() const
7161 {
7162 wxImageHandler* handler = wxImage::FindHandler(GetImageType());
7163 if (handler)
7164 return handler->GetExtension();
7165 else
7166 return wxEmptyString;
7167 }
7168
7169 #if wxUSE_DATAOBJ
7170
7171 /*!
7172 * The data object for a wxRichTextBuffer
7173 */
7174
7175 const wxChar *wxRichTextBufferDataObject::ms_richTextBufferFormatId = wxT("wxShape");
7176
7177 wxRichTextBufferDataObject::wxRichTextBufferDataObject(wxRichTextBuffer* richTextBuffer)
7178 {
7179 m_richTextBuffer = richTextBuffer;
7180
7181 // this string should uniquely identify our format, but is otherwise
7182 // arbitrary
7183 m_formatRichTextBuffer.SetId(GetRichTextBufferFormatId());
7184
7185 SetFormat(m_formatRichTextBuffer);
7186 }
7187
7188 wxRichTextBufferDataObject::~wxRichTextBufferDataObject()
7189 {
7190 delete m_richTextBuffer;
7191 }
7192
7193 // after a call to this function, the richTextBuffer is owned by the caller and it
7194 // is responsible for deleting it!
7195 wxRichTextBuffer* wxRichTextBufferDataObject::GetRichTextBuffer()
7196 {
7197 wxRichTextBuffer* richTextBuffer = m_richTextBuffer;
7198 m_richTextBuffer = NULL;
7199
7200 return richTextBuffer;
7201 }
7202
7203 wxDataFormat wxRichTextBufferDataObject::GetPreferredFormat(Direction WXUNUSED(dir)) const
7204 {
7205 return m_formatRichTextBuffer;
7206 }
7207
7208 size_t wxRichTextBufferDataObject::GetDataSize() const
7209 {
7210 if (!m_richTextBuffer)
7211 return 0;
7212
7213 wxString bufXML;
7214
7215 {
7216 wxStringOutputStream stream(& bufXML);
7217 if (!m_richTextBuffer->SaveFile(stream, wxRICHTEXT_TYPE_XML))
7218 {
7219 wxLogError(wxT("Could not write the buffer to an XML stream.\nYou may have forgotten to add the XML file handler."));
7220 return 0;
7221 }
7222 }
7223
7224 #if wxUSE_UNICODE
7225 wxCharBuffer buffer = bufXML.mb_str(wxConvUTF8);
7226 return strlen(buffer) + 1;
7227 #else
7228 return bufXML.Length()+1;
7229 #endif
7230 }
7231
7232 bool wxRichTextBufferDataObject::GetDataHere(void *pBuf) const
7233 {
7234 if (!pBuf || !m_richTextBuffer)
7235 return false;
7236
7237 wxString bufXML;
7238
7239 {
7240 wxStringOutputStream stream(& bufXML);
7241 if (!m_richTextBuffer->SaveFile(stream, wxRICHTEXT_TYPE_XML))
7242 {
7243 wxLogError(wxT("Could not write the buffer to an XML stream.\nYou may have forgotten to add the XML file handler."));
7244 return 0;
7245 }
7246 }
7247
7248 #if wxUSE_UNICODE
7249 wxCharBuffer buffer = bufXML.mb_str(wxConvUTF8);
7250 size_t len = strlen(buffer);
7251 memcpy((char*) pBuf, (const char*) buffer, len);
7252 ((char*) pBuf)[len] = 0;
7253 #else
7254 size_t len = bufXML.Length();
7255 memcpy((char*) pBuf, (const char*) bufXML.c_str(), len);
7256 ((char*) pBuf)[len] = 0;
7257 #endif
7258
7259 return true;
7260 }
7261
7262 bool wxRichTextBufferDataObject::SetData(size_t WXUNUSED(len), const void *buf)
7263 {
7264 delete m_richTextBuffer;
7265 m_richTextBuffer = NULL;
7266
7267 wxString bufXML((const char*) buf, wxConvUTF8);
7268
7269 m_richTextBuffer = new wxRichTextBuffer;
7270
7271 wxStringInputStream stream(bufXML);
7272 if (!m_richTextBuffer->LoadFile(stream, wxRICHTEXT_TYPE_XML))
7273 {
7274 wxLogError(wxT("Could not read the buffer from an XML stream.\nYou may have forgotten to add the XML file handler."));
7275
7276 delete m_richTextBuffer;
7277 m_richTextBuffer = NULL;
7278
7279 return false;
7280 }
7281 return true;
7282 }
7283
7284 #endif
7285 // wxUSE_DATAOBJ
7286
7287
7288 /*
7289 * wxRichTextFontTable
7290 * Manages quick access to a pool of fonts for rendering rich text
7291 */
7292
7293 WX_DECLARE_STRING_HASH_MAP_WITH_DECL(wxFont, wxRichTextFontTableHashMap, class WXDLLIMPEXP_RICHTEXT);
7294
7295 class wxRichTextFontTableData: public wxObjectRefData
7296 {
7297 public:
7298 wxRichTextFontTableData() {}
7299
7300 wxFont FindFont(const wxTextAttr& fontSpec);
7301
7302 wxRichTextFontTableHashMap m_hashMap;
7303 };
7304
7305 wxFont wxRichTextFontTableData::FindFont(const wxTextAttr& fontSpec)
7306 {
7307 wxString facename(fontSpec.GetFontFaceName());
7308 wxString spec(wxString::Format(wxT("%d-%d-%d-%d-%s-%d"), fontSpec.GetFontSize(), fontSpec.GetFontStyle(), fontSpec.GetFontWeight(), (int) fontSpec.GetFontUnderlined(), facename.c_str(), (int) fontSpec.GetFontEncoding()));
7309 wxRichTextFontTableHashMap::iterator entry = m_hashMap.find(spec);
7310
7311 if ( entry == m_hashMap.end() )
7312 {
7313 wxFont font(fontSpec.GetFontSize(), wxDEFAULT, fontSpec.GetFontStyle(), fontSpec.GetFontWeight(), fontSpec.GetFontUnderlined(), facename.c_str());
7314 m_hashMap[spec] = font;
7315 return font;
7316 }
7317 else
7318 {
7319 return entry->second;
7320 }
7321 }
7322
7323 IMPLEMENT_DYNAMIC_CLASS(wxRichTextFontTable, wxObject)
7324
7325 wxRichTextFontTable::wxRichTextFontTable()
7326 {
7327 m_refData = new wxRichTextFontTableData;
7328 }
7329
7330 wxRichTextFontTable::wxRichTextFontTable(const wxRichTextFontTable& table)
7331 {
7332 (*this) = table;
7333 }
7334
7335 wxRichTextFontTable::~wxRichTextFontTable()
7336 {
7337 UnRef();
7338 }
7339
7340 bool wxRichTextFontTable::operator == (const wxRichTextFontTable& table) const
7341 {
7342 return (m_refData == table.m_refData);
7343 }
7344
7345 void wxRichTextFontTable::operator= (const wxRichTextFontTable& table)
7346 {
7347 Ref(table);
7348 }
7349
7350 wxFont wxRichTextFontTable::FindFont(const wxTextAttr& fontSpec)
7351 {
7352 wxRichTextFontTableData* data = (wxRichTextFontTableData*) m_refData;
7353 if (data)
7354 return data->FindFont(fontSpec);
7355 else
7356 return wxFont();
7357 }
7358
7359 void wxRichTextFontTable::Clear()
7360 {
7361 wxRichTextFontTableData* data = (wxRichTextFontTableData*) m_refData;
7362 if (data)
7363 data->m_hashMap.clear();
7364 }
7365
7366 #endif
7367 // wxUSE_RICHTEXT