]> git.saurik.com Git - wxWidgets.git/blob - src/richtext/richtextbuffer.cpp
fix child window redraw glitches during scrolling (bug 1944002)
[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 // Test for the optimized situations where all is selected, or none
4321 // is selected.
4322
4323 wxFont textFont(GetBuffer()->GetFontTable().FindFont(textAttr));
4324 wxCheckSetFont(dc, textFont);
4325 int charHeight = dc.GetCharHeight();
4326
4327 int x, y;
4328 if ( textFont.Ok() )
4329 {
4330 if ( textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUPERSCRIPT) )
4331 {
4332 double size = static_cast<double>(textFont.GetPointSize()) / wxSCRIPT_MUL_FACTOR;
4333 textFont.SetPointSize( static_cast<int>(size) );
4334 x = rect.x;
4335 y = rect.y;
4336 wxCheckSetFont(dc, textFont);
4337 }
4338 else if ( textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUBSCRIPT) )
4339 {
4340 double size = static_cast<double>(textFont.GetPointSize()) / wxSCRIPT_MUL_FACTOR;
4341 textFont.SetPointSize( static_cast<int>(size) );
4342 x = rect.x;
4343 int sub_height = static_cast<int>( static_cast<double>(charHeight) / wxSCRIPT_MUL_FACTOR);
4344 y = rect.y + (rect.height - sub_height + (descent - m_descent));
4345 wxCheckSetFont(dc, textFont);
4346 }
4347 else
4348 {
4349 x = rect.x;
4350 y = rect.y + (rect.height - charHeight - (descent - m_descent));
4351 }
4352 }
4353 else
4354 {
4355 x = rect.x;
4356 y = rect.y + (rect.height - charHeight - (descent - m_descent));
4357 }
4358
4359 // (a) All selected.
4360 if (selectionRange.GetStart() <= range.GetStart() && selectionRange.GetEnd() >= range.GetEnd())
4361 {
4362 DrawTabbedString(dc, textAttr, rect, stringChunk, x, y, true);
4363 }
4364 // (b) None selected.
4365 else if (selectionRange.GetEnd() < range.GetStart() || selectionRange.GetStart() > range.GetEnd())
4366 {
4367 // Draw all unselected
4368 DrawTabbedString(dc, textAttr, rect, stringChunk, x, y, false);
4369 }
4370 else
4371 {
4372 // (c) Part selected, part not
4373 // Let's draw unselected chunk, selected chunk, then unselected chunk.
4374
4375 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
4376
4377 // 1. Initial unselected chunk, if any, up until start of selection.
4378 if (selectionRange.GetStart() > range.GetStart() && selectionRange.GetStart() <= range.GetEnd())
4379 {
4380 int r1 = range.GetStart();
4381 int s1 = selectionRange.GetStart()-1;
4382 int fragmentLen = s1 - r1 + 1;
4383 if (fragmentLen < 0)
4384 wxLogDebug(wxT("Mid(%d, %d"), (int)(r1 - offset), (int)fragmentLen);
4385 wxString stringFragment = str.Mid(r1 - offset, fragmentLen);
4386
4387 DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, false);
4388
4389 #if USE_KERNING_FIX
4390 if (stringChunk.Find(wxT("\t")) == wxNOT_FOUND)
4391 {
4392 // Compensate for kerning difference
4393 wxString stringFragment2(str.Mid(r1 - offset, fragmentLen+1));
4394 wxString stringFragment3(str.Mid(r1 - offset + fragmentLen, 1));
4395
4396 wxCoord w1, h1, w2, h2, w3, h3;
4397 dc.GetTextExtent(stringFragment, & w1, & h1);
4398 dc.GetTextExtent(stringFragment2, & w2, & h2);
4399 dc.GetTextExtent(stringFragment3, & w3, & h3);
4400
4401 int kerningDiff = (w1 + w3) - w2;
4402 x = x - kerningDiff;
4403 }
4404 #endif
4405 }
4406
4407 // 2. Selected chunk, if any.
4408 if (selectionRange.GetEnd() >= range.GetStart())
4409 {
4410 int s1 = wxMax(selectionRange.GetStart(), range.GetStart());
4411 int s2 = wxMin(selectionRange.GetEnd(), range.GetEnd());
4412
4413 int fragmentLen = s2 - s1 + 1;
4414 if (fragmentLen < 0)
4415 wxLogDebug(wxT("Mid(%d, %d"), (int)(s1 - offset), (int)fragmentLen);
4416 wxString stringFragment = str.Mid(s1 - offset, fragmentLen);
4417
4418 DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, true);
4419
4420 #if USE_KERNING_FIX
4421 if (stringChunk.Find(wxT("\t")) == wxNOT_FOUND)
4422 {
4423 // Compensate for kerning difference
4424 wxString stringFragment2(str.Mid(s1 - offset, fragmentLen+1));
4425 wxString stringFragment3(str.Mid(s1 - offset + fragmentLen, 1));
4426
4427 wxCoord w1, h1, w2, h2, w3, h3;
4428 dc.GetTextExtent(stringFragment, & w1, & h1);
4429 dc.GetTextExtent(stringFragment2, & w2, & h2);
4430 dc.GetTextExtent(stringFragment3, & w3, & h3);
4431
4432 int kerningDiff = (w1 + w3) - w2;
4433 x = x - kerningDiff;
4434 }
4435 #endif
4436 }
4437
4438 // 3. Remaining unselected chunk, if any
4439 if (selectionRange.GetEnd() < range.GetEnd())
4440 {
4441 int s2 = wxMin(selectionRange.GetEnd()+1, range.GetEnd());
4442 int r2 = range.GetEnd();
4443
4444 int fragmentLen = r2 - s2 + 1;
4445 if (fragmentLen < 0)
4446 wxLogDebug(wxT("Mid(%d, %d"), (int)(s2 - offset), (int)fragmentLen);
4447 wxString stringFragment = str.Mid(s2 - offset, fragmentLen);
4448
4449 DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, false);
4450 }
4451 }
4452
4453 return true;
4454 }
4455
4456 bool wxRichTextPlainText::DrawTabbedString(wxDC& dc, const wxTextAttr& attr, const wxRect& rect,wxString& str, wxCoord& x, wxCoord& y, bool selected)
4457 {
4458 bool hasTabs = (str.Find(wxT('\t')) != wxNOT_FOUND);
4459
4460 wxArrayInt tabArray;
4461 int tabCount;
4462 if (hasTabs)
4463 {
4464 if (attr.GetTabs().IsEmpty())
4465 tabArray = wxRichTextParagraph::GetDefaultTabs();
4466 else
4467 tabArray = attr.GetTabs();
4468 tabCount = tabArray.GetCount();
4469
4470 for (int i = 0; i < tabCount; ++i)
4471 {
4472 int pos = tabArray[i];
4473 pos = ConvertTenthsMMToPixels(dc, pos);
4474 tabArray[i] = pos;
4475 }
4476 }
4477 else
4478 tabCount = 0;
4479
4480 int nextTabPos = -1;
4481 int tabPos = -1;
4482 wxCoord w, h;
4483
4484 if (selected)
4485 {
4486 wxColour highlightColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT));
4487 wxColour highlightTextColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT));
4488
4489 wxCheckSetBrush(dc, wxBrush(highlightColour));
4490 wxCheckSetPen(dc, wxPen(highlightColour));
4491 dc.SetTextForeground(highlightTextColour);
4492 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
4493 }
4494 else
4495 {
4496 dc.SetTextForeground(attr.GetTextColour());
4497
4498 if (attr.HasFlag(wxTEXT_ATTR_BACKGROUND_COLOUR) && attr.GetBackgroundColour().IsOk())
4499 {
4500 dc.SetBackgroundMode(wxBRUSHSTYLE_SOLID);
4501 dc.SetTextBackground(attr.GetBackgroundColour());
4502 }
4503 else
4504 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
4505 }
4506
4507 while (hasTabs)
4508 {
4509 // the string has a tab
4510 // break up the string at the Tab
4511 wxString stringChunk = str.BeforeFirst(wxT('\t'));
4512 str = str.AfterFirst(wxT('\t'));
4513 dc.GetTextExtent(stringChunk, & w, & h);
4514 tabPos = x + w;
4515 bool not_found = true;
4516 for (int i = 0; i < tabCount && not_found; ++i)
4517 {
4518 nextTabPos = tabArray.Item(i);
4519
4520 // Find the next tab position.
4521 // Even if we're at the end of the tab array, we must still draw the chunk.
4522
4523 if (nextTabPos > tabPos || (i == (tabCount - 1)))
4524 {
4525 if (nextTabPos <= tabPos)
4526 {
4527 int defaultTabWidth = ConvertTenthsMMToPixels(dc, WIDTH_FOR_DEFAULT_TABS);
4528 nextTabPos = tabPos + defaultTabWidth;
4529 }
4530
4531 not_found = false;
4532 if (selected)
4533 {
4534 w = nextTabPos - x;
4535 wxRect selRect(x, rect.y, w, rect.GetHeight());
4536 dc.DrawRectangle(selRect);
4537 }
4538 dc.DrawText(stringChunk, x, y);
4539
4540 if (attr.HasTextEffects() && (attr.GetTextEffects() & wxTEXT_ATTR_EFFECT_STRIKETHROUGH))
4541 {
4542 wxPen oldPen = dc.GetPen();
4543 wxCheckSetPen(dc, wxPen(attr.GetTextColour(), 1));
4544 dc.DrawLine(x, (int) (y+(h/2)+0.5), x+w, (int) (y+(h/2)+0.5));
4545 wxCheckSetPen(dc, oldPen);
4546 }
4547
4548 x = nextTabPos;
4549 }
4550 }
4551 hasTabs = (str.Find(wxT('\t')) != wxNOT_FOUND);
4552 }
4553
4554 if (!str.IsEmpty())
4555 {
4556 dc.GetTextExtent(str, & w, & h);
4557 if (selected)
4558 {
4559 wxRect selRect(x, rect.y, w, rect.GetHeight());
4560 dc.DrawRectangle(selRect);
4561 }
4562 dc.DrawText(str, x, y);
4563
4564 if (attr.HasTextEffects() && (attr.GetTextEffects() & wxTEXT_ATTR_EFFECT_STRIKETHROUGH))
4565 {
4566 wxPen oldPen = dc.GetPen();
4567 wxCheckSetPen(dc, wxPen(attr.GetTextColour(), 1));
4568 dc.DrawLine(x, (int) (y+(h/2)+0.5), x+w, (int) (y+(h/2)+0.5));
4569 wxCheckSetPen(dc, oldPen);
4570 }
4571
4572 x += w;
4573 }
4574 return true;
4575
4576 }
4577
4578 /// Lay the item out
4579 bool wxRichTextPlainText::Layout(wxDC& dc, const wxRect& WXUNUSED(rect), int WXUNUSED(style))
4580 {
4581 // Only lay out if we haven't already cached the size
4582 if (m_size.x == -1)
4583 GetRangeSize(GetRange(), m_size, m_descent, dc, 0, wxPoint(0, 0));
4584
4585 return true;
4586 }
4587
4588 /// Copy
4589 void wxRichTextPlainText::Copy(const wxRichTextPlainText& obj)
4590 {
4591 wxRichTextObject::Copy(obj);
4592
4593 m_text = obj.m_text;
4594 }
4595
4596 /// Get/set the object size for the given range. Returns false if the range
4597 /// is invalid for this object.
4598 bool wxRichTextPlainText::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, int WXUNUSED(flags), wxPoint position) const
4599 {
4600 if (!range.IsWithin(GetRange()))
4601 return false;
4602
4603 wxRichTextParagraph* para = wxDynamicCast(GetParent(), wxRichTextParagraph);
4604 wxASSERT (para != NULL);
4605
4606 wxTextAttr textAttr(para ? para->GetCombinedAttributes(GetAttributes()) : GetAttributes());
4607
4608 // Always assume unformatted text, since at this level we have no knowledge
4609 // of line breaks - and we don't need it, since we'll calculate size within
4610 // formatted text by doing it in chunks according to the line ranges
4611
4612 bool bScript(false);
4613 wxFont font(GetBuffer()->GetFontTable().FindFont(textAttr));
4614 if (font.Ok())
4615 {
4616 if ( textAttr.HasTextEffects() && ( (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUPERSCRIPT)
4617 || (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUBSCRIPT) ) )
4618 {
4619 wxFont textFont = font;
4620 double size = static_cast<double>(textFont.GetPointSize()) / wxSCRIPT_MUL_FACTOR;
4621 textFont.SetPointSize( static_cast<int>(size) );
4622 wxCheckSetFont(dc, textFont);
4623 bScript = true;
4624 }
4625 else
4626 {
4627 wxCheckSetFont(dc, font);
4628 }
4629 }
4630
4631 int startPos = range.GetStart() - GetRange().GetStart();
4632 long len = range.GetLength();
4633
4634 wxString str(m_text);
4635 wxString toReplace = wxRichTextLineBreakChar;
4636 str.Replace(toReplace, wxT(" "));
4637
4638 wxString stringChunk = str.Mid(startPos, (size_t) len);
4639
4640 if (textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_CAPITALS))
4641 stringChunk.MakeUpper();
4642
4643 wxCoord w, h;
4644 int width = 0;
4645 if (stringChunk.Find(wxT('\t')) != wxNOT_FOUND)
4646 {
4647 // the string has a tab
4648 wxArrayInt tabArray;
4649 if (textAttr.GetTabs().IsEmpty())
4650 tabArray = wxRichTextParagraph::GetDefaultTabs();
4651 else
4652 tabArray = textAttr.GetTabs();
4653
4654 int tabCount = tabArray.GetCount();
4655
4656 for (int i = 0; i < tabCount; ++i)
4657 {
4658 int pos = tabArray[i];
4659 pos = ((wxRichTextPlainText*) this)->ConvertTenthsMMToPixels(dc, pos);
4660 tabArray[i] = pos;
4661 }
4662
4663 int nextTabPos = -1;
4664
4665 while (stringChunk.Find(wxT('\t')) >= 0)
4666 {
4667 // the string has a tab
4668 // break up the string at the Tab
4669 wxString stringFragment = stringChunk.BeforeFirst(wxT('\t'));
4670 stringChunk = stringChunk.AfterFirst(wxT('\t'));
4671 dc.GetTextExtent(stringFragment, & w, & h);
4672 width += w;
4673 int absoluteWidth = width + position.x;
4674
4675 bool notFound = true;
4676 for (int i = 0; i < tabCount && notFound; ++i)
4677 {
4678 nextTabPos = tabArray.Item(i);
4679
4680 // Find the next tab position.
4681 // Even if we're at the end of the tab array, we must still process the chunk.
4682
4683 if (nextTabPos > absoluteWidth || (i == (tabCount - 1)))
4684 {
4685 if (nextTabPos <= absoluteWidth)
4686 {
4687 int defaultTabWidth = ((wxRichTextPlainText*) this)->ConvertTenthsMMToPixels(dc, WIDTH_FOR_DEFAULT_TABS);
4688 nextTabPos = absoluteWidth + defaultTabWidth;
4689 }
4690
4691 notFound = false;
4692 width = nextTabPos - position.x;
4693 }
4694 }
4695 }
4696 }
4697
4698 dc.GetTextExtent(stringChunk, & w, & h, & descent);
4699 width += w;
4700
4701 if ( bScript )
4702 dc.SetFont(font);
4703
4704 size = wxSize(width, dc.GetCharHeight());
4705
4706 return true;
4707 }
4708
4709 /// Do a split, returning an object containing the second part, and setting
4710 /// the first part in 'this'.
4711 wxRichTextObject* wxRichTextPlainText::DoSplit(long pos)
4712 {
4713 long index = pos - GetRange().GetStart();
4714
4715 if (index < 0 || index >= (int) m_text.length())
4716 return NULL;
4717
4718 wxString firstPart = m_text.Mid(0, index);
4719 wxString secondPart = m_text.Mid(index);
4720
4721 m_text = firstPart;
4722
4723 wxRichTextPlainText* newObject = new wxRichTextPlainText(secondPart);
4724 newObject->SetAttributes(GetAttributes());
4725
4726 newObject->SetRange(wxRichTextRange(pos, GetRange().GetEnd()));
4727 GetRange().SetEnd(pos-1);
4728
4729 return newObject;
4730 }
4731
4732 /// Calculate range
4733 void wxRichTextPlainText::CalculateRange(long start, long& end)
4734 {
4735 end = start + m_text.length() - 1;
4736 m_range.SetRange(start, end);
4737 }
4738
4739 /// Delete range
4740 bool wxRichTextPlainText::DeleteRange(const wxRichTextRange& range)
4741 {
4742 wxRichTextRange r = range;
4743
4744 r.LimitTo(GetRange());
4745
4746 if (r.GetStart() == GetRange().GetStart() && r.GetEnd() == GetRange().GetEnd())
4747 {
4748 m_text.Empty();
4749 return true;
4750 }
4751
4752 long startIndex = r.GetStart() - GetRange().GetStart();
4753 long len = r.GetLength();
4754
4755 m_text = m_text.Mid(0, startIndex) + m_text.Mid(startIndex+len);
4756 return true;
4757 }
4758
4759 /// Get text for the given range.
4760 wxString wxRichTextPlainText::GetTextForRange(const wxRichTextRange& range) const
4761 {
4762 wxRichTextRange r = range;
4763
4764 r.LimitTo(GetRange());
4765
4766 long startIndex = r.GetStart() - GetRange().GetStart();
4767 long len = r.GetLength();
4768
4769 return m_text.Mid(startIndex, len);
4770 }
4771
4772 /// Returns true if this object can merge itself with the given one.
4773 bool wxRichTextPlainText::CanMerge(wxRichTextObject* object) const
4774 {
4775 return object->GetClassInfo() == CLASSINFO(wxRichTextPlainText) &&
4776 (m_text.empty() || wxTextAttrEq(GetAttributes(), object->GetAttributes()));
4777 }
4778
4779 /// Returns true if this object merged itself with the given one.
4780 /// The calling code will then delete the given object.
4781 bool wxRichTextPlainText::Merge(wxRichTextObject* object)
4782 {
4783 wxRichTextPlainText* textObject = wxDynamicCast(object, wxRichTextPlainText);
4784 wxASSERT( textObject != NULL );
4785
4786 if (textObject)
4787 {
4788 m_text += textObject->GetText();
4789 wxRichTextApplyStyle(m_attributes, textObject->GetAttributes());
4790 return true;
4791 }
4792 else
4793 return false;
4794 }
4795
4796 /// Dump to output stream for debugging
4797 void wxRichTextPlainText::Dump(wxTextOutputStream& stream)
4798 {
4799 wxRichTextObject::Dump(stream);
4800 stream << m_text << wxT("\n");
4801 }
4802
4803 /// Get the first position from pos that has a line break character.
4804 long wxRichTextPlainText::GetFirstLineBreakPosition(long pos)
4805 {
4806 int i;
4807 int len = m_text.length();
4808 int startPos = pos - m_range.GetStart();
4809 for (i = startPos; i < len; i++)
4810 {
4811 wxChar ch = m_text[i];
4812 if (ch == wxRichTextLineBreakChar)
4813 {
4814 return i + m_range.GetStart();
4815 }
4816 }
4817 return -1;
4818 }
4819
4820 /*!
4821 * wxRichTextBuffer
4822 * This is a kind of box, used to represent the whole buffer
4823 */
4824
4825 IMPLEMENT_DYNAMIC_CLASS(wxRichTextBuffer, wxRichTextParagraphLayoutBox)
4826
4827 wxList wxRichTextBuffer::sm_handlers;
4828 wxRichTextRenderer* wxRichTextBuffer::sm_renderer = NULL;
4829 int wxRichTextBuffer::sm_bulletRightMargin = 20;
4830 float wxRichTextBuffer::sm_bulletProportion = (float) 0.3;
4831
4832 /// Initialisation
4833 void wxRichTextBuffer::Init()
4834 {
4835 m_commandProcessor = new wxCommandProcessor;
4836 m_styleSheet = NULL;
4837 m_modified = false;
4838 m_batchedCommandDepth = 0;
4839 m_batchedCommand = NULL;
4840 m_suppressUndo = 0;
4841 m_handlerFlags = 0;
4842 m_scale = 1.0;
4843 }
4844
4845 /// Initialisation
4846 wxRichTextBuffer::~wxRichTextBuffer()
4847 {
4848 delete m_commandProcessor;
4849 delete m_batchedCommand;
4850
4851 ClearStyleStack();
4852 ClearEventHandlers();
4853 }
4854
4855 void wxRichTextBuffer::ResetAndClearCommands()
4856 {
4857 Reset();
4858
4859 GetCommandProcessor()->ClearCommands();
4860
4861 Modify(false);
4862 Invalidate(wxRICHTEXT_ALL);
4863 }
4864
4865 void wxRichTextBuffer::Copy(const wxRichTextBuffer& obj)
4866 {
4867 wxRichTextParagraphLayoutBox::Copy(obj);
4868
4869 m_styleSheet = obj.m_styleSheet;
4870 m_modified = obj.m_modified;
4871 m_batchedCommandDepth = obj.m_batchedCommandDepth;
4872 m_batchedCommand = obj.m_batchedCommand;
4873 m_suppressUndo = obj.m_suppressUndo;
4874 }
4875
4876 /// Push style sheet to top of stack
4877 bool wxRichTextBuffer::PushStyleSheet(wxRichTextStyleSheet* styleSheet)
4878 {
4879 if (m_styleSheet)
4880 styleSheet->InsertSheet(m_styleSheet);
4881
4882 SetStyleSheet(styleSheet);
4883
4884 return true;
4885 }
4886
4887 /// Pop style sheet from top of stack
4888 wxRichTextStyleSheet* wxRichTextBuffer::PopStyleSheet()
4889 {
4890 if (m_styleSheet)
4891 {
4892 wxRichTextStyleSheet* oldSheet = m_styleSheet;
4893 m_styleSheet = oldSheet->GetNextSheet();
4894 oldSheet->Unlink();
4895
4896 return oldSheet;
4897 }
4898 else
4899 return NULL;
4900 }
4901
4902 /// Submit command to insert paragraphs
4903 bool wxRichTextBuffer::InsertParagraphsWithUndo(long pos, const wxRichTextParagraphLayoutBox& paragraphs, wxRichTextCtrl* ctrl, int flags)
4904 {
4905 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, this, ctrl, false);
4906
4907 wxTextAttr attr(GetDefaultStyle());
4908
4909 wxTextAttr* p = NULL;
4910 wxTextAttr paraAttr;
4911 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
4912 {
4913 paraAttr = GetStyleForNewParagraph(pos);
4914 if (!paraAttr.IsDefault())
4915 p = & paraAttr;
4916 }
4917 else
4918 p = & attr;
4919
4920 action->GetNewParagraphs() = paragraphs;
4921
4922 action->SetPosition(pos);
4923
4924 wxRichTextRange range = wxRichTextRange(pos, pos + paragraphs.GetRange().GetEnd() - 1);
4925 if (!paragraphs.GetPartialParagraph())
4926 range.SetEnd(range.GetEnd()+1);
4927
4928 // Set the range we'll need to delete in Undo
4929 action->SetRange(range);
4930
4931 SubmitAction(action);
4932
4933 return true;
4934 }
4935
4936 /// Submit command to insert the given text
4937 bool wxRichTextBuffer::InsertTextWithUndo(long pos, const wxString& text, wxRichTextCtrl* ctrl, int flags)
4938 {
4939 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, this, ctrl, false);
4940
4941 wxTextAttr* p = NULL;
4942 wxTextAttr paraAttr;
4943 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
4944 {
4945 // Get appropriate paragraph style
4946 paraAttr = GetStyleForNewParagraph(pos, false, false);
4947 if (!paraAttr.IsDefault())
4948 p = & paraAttr;
4949 }
4950
4951 action->GetNewParagraphs().AddParagraphs(text, p);
4952
4953 int length = action->GetNewParagraphs().GetRange().GetLength();
4954
4955 if (text.length() > 0 && text.Last() != wxT('\n'))
4956 {
4957 // Don't count the newline when undoing
4958 length --;
4959 action->GetNewParagraphs().SetPartialParagraph(true);
4960 }
4961 else if (text.length() > 0 && text.Last() == wxT('\n'))
4962 length --;
4963
4964 action->SetPosition(pos);
4965
4966 // Set the range we'll need to delete in Undo
4967 action->SetRange(wxRichTextRange(pos, pos + length - 1));
4968
4969 SubmitAction(action);
4970
4971 return true;
4972 }
4973
4974 /// Submit command to insert the given text
4975 bool wxRichTextBuffer::InsertNewlineWithUndo(long pos, wxRichTextCtrl* ctrl, int flags)
4976 {
4977 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, this, ctrl, false);
4978
4979 wxTextAttr* p = NULL;
4980 wxTextAttr paraAttr;
4981 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
4982 {
4983 paraAttr = GetStyleForNewParagraph(pos, false, true /* look for next paragraph style */);
4984 if (!paraAttr.IsDefault())
4985 p = & paraAttr;
4986 }
4987
4988 wxTextAttr attr(GetDefaultStyle());
4989
4990 wxRichTextParagraph* newPara = new wxRichTextParagraph(wxEmptyString, this, & attr);
4991 action->GetNewParagraphs().AppendChild(newPara);
4992 action->GetNewParagraphs().UpdateRanges();
4993 action->GetNewParagraphs().SetPartialParagraph(false);
4994 wxRichTextParagraph* para = GetParagraphAtPosition(pos, false);
4995 long pos1 = pos;
4996
4997 if (p)
4998 newPara->SetAttributes(*p);
4999
5000 if (flags & wxRICHTEXT_INSERT_INTERACTIVE)
5001 {
5002 if (para && para->GetRange().GetEnd() == pos)
5003 pos1 ++;
5004 if (newPara->GetAttributes().HasBulletNumber())
5005 newPara->GetAttributes().SetBulletNumber(newPara->GetAttributes().GetBulletNumber()+1);
5006 }
5007
5008 action->SetPosition(pos);
5009
5010 // Use the default character style
5011 // Use the default character style
5012 if (!GetDefaultStyle().IsDefault() && newPara->GetChildren().GetFirst())
5013 {
5014 // Check whether the default style merely reflects the paragraph/basic style,
5015 // in which case don't apply it.
5016 wxTextAttrEx defaultStyle(GetDefaultStyle());
5017 wxTextAttrEx toApply;
5018 if (para)
5019 {
5020 wxRichTextAttr combinedAttr = para->GetCombinedAttributes();
5021 wxTextAttrEx newAttr;
5022 // This filters out attributes that are accounted for by the current
5023 // paragraph/basic style
5024 wxRichTextApplyStyle(toApply, defaultStyle, & combinedAttr);
5025 }
5026 else
5027 toApply = defaultStyle;
5028
5029 if (!toApply.IsDefault())
5030 newPara->GetChildren().GetFirst()->GetData()->SetAttributes(toApply);
5031 }
5032
5033 // Set the range we'll need to delete in Undo
5034 action->SetRange(wxRichTextRange(pos1, pos1));
5035
5036 SubmitAction(action);
5037
5038 return true;
5039 }
5040
5041 /// Submit command to insert the given image
5042 bool wxRichTextBuffer::InsertImageWithUndo(long pos, const wxRichTextImageBlock& imageBlock, wxRichTextCtrl* ctrl, int flags)
5043 {
5044 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Image"), wxRICHTEXT_INSERT, this, ctrl, false);
5045
5046 wxTextAttr* p = NULL;
5047 wxTextAttr paraAttr;
5048 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
5049 {
5050 paraAttr = GetStyleForNewParagraph(pos);
5051 if (!paraAttr.IsDefault())
5052 p = & paraAttr;
5053 }
5054
5055 wxTextAttr attr(GetDefaultStyle());
5056
5057 wxRichTextParagraph* newPara = new wxRichTextParagraph(this, & attr);
5058 if (p)
5059 newPara->SetAttributes(*p);
5060
5061 wxRichTextImage* imageObject = new wxRichTextImage(imageBlock, newPara);
5062 newPara->AppendChild(imageObject);
5063 action->GetNewParagraphs().AppendChild(newPara);
5064 action->GetNewParagraphs().UpdateRanges();
5065
5066 action->GetNewParagraphs().SetPartialParagraph(true);
5067
5068 action->SetPosition(pos);
5069
5070 // Set the range we'll need to delete in Undo
5071 action->SetRange(wxRichTextRange(pos, pos));
5072
5073 SubmitAction(action);
5074
5075 return true;
5076 }
5077
5078 /// Get the style that is appropriate for a new paragraph at this position.
5079 /// If the previous paragraph has a paragraph style name, look up the next-paragraph
5080 /// style.
5081 wxTextAttr wxRichTextBuffer::GetStyleForNewParagraph(long pos, bool caretPosition, bool lookUpNewParaStyle) const
5082 {
5083 wxRichTextParagraph* para = GetParagraphAtPosition(pos, caretPosition);
5084 if (para)
5085 {
5086 wxTextAttr attr;
5087 bool foundAttributes = false;
5088
5089 // Look for a matching paragraph style
5090 if (lookUpNewParaStyle && !para->GetAttributes().GetParagraphStyleName().IsEmpty() && GetStyleSheet())
5091 {
5092 wxRichTextParagraphStyleDefinition* paraDef = GetStyleSheet()->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName());
5093 if (paraDef)
5094 {
5095 // If we're not at the end of the paragraph, then we apply THIS style, and not the designated next style.
5096 if (para->GetRange().GetEnd() == pos && !paraDef->GetNextStyle().IsEmpty())
5097 {
5098 wxRichTextParagraphStyleDefinition* nextParaDef = GetStyleSheet()->FindParagraphStyle(paraDef->GetNextStyle());
5099 if (nextParaDef)
5100 {
5101 foundAttributes = true;
5102 attr = nextParaDef->GetStyleMergedWithBase(GetStyleSheet());
5103 }
5104 }
5105
5106 // If we didn't find the 'next style', use this style instead.
5107 if (!foundAttributes)
5108 {
5109 foundAttributes = true;
5110 attr = paraDef->GetStyleMergedWithBase(GetStyleSheet());
5111 }
5112 }
5113 }
5114 if (!foundAttributes)
5115 {
5116 attr = para->GetAttributes();
5117 int flags = attr.GetFlags();
5118
5119 // Eliminate character styles
5120 flags &= ( (~ wxTEXT_ATTR_FONT) |
5121 (~ wxTEXT_ATTR_TEXT_COLOUR) |
5122 (~ wxTEXT_ATTR_BACKGROUND_COLOUR) );
5123 attr.SetFlags(flags);
5124 }
5125
5126 // Now see if we need to number the paragraph.
5127 if (attr.HasBulletStyle())
5128 {
5129 wxTextAttr numberingAttr;
5130 if (FindNextParagraphNumber(para, numberingAttr))
5131 wxRichTextApplyStyle(attr, (const wxTextAttr&) numberingAttr);
5132 }
5133
5134 return attr;
5135 }
5136 else
5137 return wxTextAttr();
5138 }
5139
5140 /// Submit command to delete this range
5141 bool wxRichTextBuffer::DeleteRangeWithUndo(const wxRichTextRange& range, wxRichTextCtrl* ctrl)
5142 {
5143 wxRichTextAction* action = new wxRichTextAction(NULL, _("Delete"), wxRICHTEXT_DELETE, this, ctrl);
5144
5145 action->SetPosition(ctrl->GetCaretPosition());
5146
5147 // Set the range to delete
5148 action->SetRange(range);
5149
5150 // Copy the fragment that we'll need to restore in Undo
5151 CopyFragment(range, action->GetOldParagraphs());
5152
5153 // See if we're deleting a paragraph marker, in which case we need to
5154 // make a note not to copy the attributes from the 2nd paragraph to the 1st.
5155 if (range.GetStart() == range.GetEnd())
5156 {
5157 wxRichTextParagraph* para = GetParagraphAtPosition(range.GetStart());
5158 if (para && para->GetRange().GetEnd() == range.GetEnd())
5159 {
5160 wxRichTextParagraph* nextPara = GetParagraphAtPosition(range.GetStart()+1);
5161 if (nextPara && nextPara != para)
5162 {
5163 action->GetOldParagraphs().GetChildren().GetFirst()->GetData()->SetAttributes(nextPara->GetAttributes());
5164 action->GetOldParagraphs().GetAttributes().SetFlags(action->GetOldParagraphs().GetAttributes().GetFlags() | wxTEXT_ATTR_KEEP_FIRST_PARA_STYLE);
5165 }
5166 }
5167 }
5168
5169 SubmitAction(action);
5170
5171 return true;
5172 }
5173
5174 /// Collapse undo/redo commands
5175 bool wxRichTextBuffer::BeginBatchUndo(const wxString& cmdName)
5176 {
5177 if (m_batchedCommandDepth == 0)
5178 {
5179 wxASSERT(m_batchedCommand == NULL);
5180 if (m_batchedCommand)
5181 {
5182 GetCommandProcessor()->Store(m_batchedCommand);
5183 }
5184 m_batchedCommand = new wxRichTextCommand(cmdName);
5185 }
5186
5187 m_batchedCommandDepth ++;
5188
5189 return true;
5190 }
5191
5192 /// Collapse undo/redo commands
5193 bool wxRichTextBuffer::EndBatchUndo()
5194 {
5195 m_batchedCommandDepth --;
5196
5197 wxASSERT(m_batchedCommandDepth >= 0);
5198 wxASSERT(m_batchedCommand != NULL);
5199
5200 if (m_batchedCommandDepth == 0)
5201 {
5202 GetCommandProcessor()->Store(m_batchedCommand);
5203 m_batchedCommand = NULL;
5204 }
5205
5206 return true;
5207 }
5208
5209 /// Submit immediately, or delay according to whether collapsing is on
5210 bool wxRichTextBuffer::SubmitAction(wxRichTextAction* action)
5211 {
5212 if (BatchingUndo() && m_batchedCommand && !SuppressingUndo())
5213 {
5214 wxRichTextCommand* cmd = new wxRichTextCommand(action->GetName());
5215 cmd->AddAction(action);
5216 cmd->Do();
5217 cmd->GetActions().Clear();
5218 delete cmd;
5219
5220 m_batchedCommand->AddAction(action);
5221 }
5222 else
5223 {
5224 wxRichTextCommand* cmd = new wxRichTextCommand(action->GetName());
5225 cmd->AddAction(action);
5226
5227 // Only store it if we're not suppressing undo.
5228 return GetCommandProcessor()->Submit(cmd, !SuppressingUndo());
5229 }
5230
5231 return true;
5232 }
5233
5234 /// Begin suppressing undo/redo commands.
5235 bool wxRichTextBuffer::BeginSuppressUndo()
5236 {
5237 m_suppressUndo ++;
5238
5239 return true;
5240 }
5241
5242 /// End suppressing undo/redo commands.
5243 bool wxRichTextBuffer::EndSuppressUndo()
5244 {
5245 m_suppressUndo --;
5246
5247 return true;
5248 }
5249
5250 /// Begin using a style
5251 bool wxRichTextBuffer::BeginStyle(const wxTextAttr& style)
5252 {
5253 wxTextAttr newStyle(GetDefaultStyle());
5254
5255 // Save the old default style
5256 m_attributeStack.Append((wxObject*) new wxTextAttr(GetDefaultStyle()));
5257
5258 wxRichTextApplyStyle(newStyle, style);
5259 newStyle.SetFlags(style.GetFlags()|newStyle.GetFlags());
5260
5261 SetDefaultStyle(newStyle);
5262
5263 // wxLogDebug("Default style size = %d", GetDefaultStyle().GetFont().GetPointSize());
5264
5265 return true;
5266 }
5267
5268 /// End the style
5269 bool wxRichTextBuffer::EndStyle()
5270 {
5271 if (!m_attributeStack.GetFirst())
5272 {
5273 wxLogDebug(_("Too many EndStyle calls!"));
5274 return false;
5275 }
5276
5277 wxList::compatibility_iterator node = m_attributeStack.GetLast();
5278 wxTextAttr* attr = (wxTextAttr*)node->GetData();
5279 m_attributeStack.Erase(node);
5280
5281 SetDefaultStyle(*attr);
5282
5283 delete attr;
5284 return true;
5285 }
5286
5287 /// End all styles
5288 bool wxRichTextBuffer::EndAllStyles()
5289 {
5290 while (m_attributeStack.GetCount() != 0)
5291 EndStyle();
5292 return true;
5293 }
5294
5295 /// Clear the style stack
5296 void wxRichTextBuffer::ClearStyleStack()
5297 {
5298 for (wxList::compatibility_iterator node = m_attributeStack.GetFirst(); node; node = node->GetNext())
5299 delete (wxTextAttr*) node->GetData();
5300 m_attributeStack.Clear();
5301 }
5302
5303 /// Begin using bold
5304 bool wxRichTextBuffer::BeginBold()
5305 {
5306 wxTextAttr attr;
5307 attr.SetFontWeight(wxBOLD);
5308
5309 return BeginStyle(attr);
5310 }
5311
5312 /// Begin using italic
5313 bool wxRichTextBuffer::BeginItalic()
5314 {
5315 wxTextAttr attr;
5316 attr.SetFontStyle(wxITALIC);
5317
5318 return BeginStyle(attr);
5319 }
5320
5321 /// Begin using underline
5322 bool wxRichTextBuffer::BeginUnderline()
5323 {
5324 wxTextAttr attr;
5325 attr.SetFontUnderlined(true);
5326
5327 return BeginStyle(attr);
5328 }
5329
5330 /// Begin using point size
5331 bool wxRichTextBuffer::BeginFontSize(int pointSize)
5332 {
5333 wxTextAttr attr;
5334 attr.SetFontSize(pointSize);
5335
5336 return BeginStyle(attr);
5337 }
5338
5339 /// Begin using this font
5340 bool wxRichTextBuffer::BeginFont(const wxFont& font)
5341 {
5342 wxTextAttr attr;
5343 attr.SetFont(font);
5344
5345 return BeginStyle(attr);
5346 }
5347
5348 /// Begin using this colour
5349 bool wxRichTextBuffer::BeginTextColour(const wxColour& colour)
5350 {
5351 wxTextAttr attr;
5352 attr.SetFlags(wxTEXT_ATTR_TEXT_COLOUR);
5353 attr.SetTextColour(colour);
5354
5355 return BeginStyle(attr);
5356 }
5357
5358 /// Begin using alignment
5359 bool wxRichTextBuffer::BeginAlignment(wxTextAttrAlignment alignment)
5360 {
5361 wxTextAttr attr;
5362 attr.SetFlags(wxTEXT_ATTR_ALIGNMENT);
5363 attr.SetAlignment(alignment);
5364
5365 return BeginStyle(attr);
5366 }
5367
5368 /// Begin left indent
5369 bool wxRichTextBuffer::BeginLeftIndent(int leftIndent, int leftSubIndent)
5370 {
5371 wxTextAttr attr;
5372 attr.SetFlags(wxTEXT_ATTR_LEFT_INDENT);
5373 attr.SetLeftIndent(leftIndent, leftSubIndent);
5374
5375 return BeginStyle(attr);
5376 }
5377
5378 /// Begin right indent
5379 bool wxRichTextBuffer::BeginRightIndent(int rightIndent)
5380 {
5381 wxTextAttr attr;
5382 attr.SetFlags(wxTEXT_ATTR_RIGHT_INDENT);
5383 attr.SetRightIndent(rightIndent);
5384
5385 return BeginStyle(attr);
5386 }
5387
5388 /// Begin paragraph spacing
5389 bool wxRichTextBuffer::BeginParagraphSpacing(int before, int after)
5390 {
5391 long flags = 0;
5392 if (before != 0)
5393 flags |= wxTEXT_ATTR_PARA_SPACING_BEFORE;
5394 if (after != 0)
5395 flags |= wxTEXT_ATTR_PARA_SPACING_AFTER;
5396
5397 wxTextAttr attr;
5398 attr.SetFlags(flags);
5399 attr.SetParagraphSpacingBefore(before);
5400 attr.SetParagraphSpacingAfter(after);
5401
5402 return BeginStyle(attr);
5403 }
5404
5405 /// Begin line spacing
5406 bool wxRichTextBuffer::BeginLineSpacing(int lineSpacing)
5407 {
5408 wxTextAttr attr;
5409 attr.SetFlags(wxTEXT_ATTR_LINE_SPACING);
5410 attr.SetLineSpacing(lineSpacing);
5411
5412 return BeginStyle(attr);
5413 }
5414
5415 /// Begin numbered bullet
5416 bool wxRichTextBuffer::BeginNumberedBullet(int bulletNumber, int leftIndent, int leftSubIndent, int bulletStyle)
5417 {
5418 wxTextAttr attr;
5419 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT);
5420 attr.SetBulletStyle(bulletStyle);
5421 attr.SetBulletNumber(bulletNumber);
5422 attr.SetLeftIndent(leftIndent, leftSubIndent);
5423
5424 return BeginStyle(attr);
5425 }
5426
5427 /// Begin symbol bullet
5428 bool wxRichTextBuffer::BeginSymbolBullet(const wxString& symbol, int leftIndent, int leftSubIndent, int bulletStyle)
5429 {
5430 wxTextAttr attr;
5431 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT);
5432 attr.SetBulletStyle(bulletStyle);
5433 attr.SetLeftIndent(leftIndent, leftSubIndent);
5434 attr.SetBulletText(symbol);
5435
5436 return BeginStyle(attr);
5437 }
5438
5439 /// Begin standard bullet
5440 bool wxRichTextBuffer::BeginStandardBullet(const wxString& bulletName, int leftIndent, int leftSubIndent, int bulletStyle)
5441 {
5442 wxTextAttr attr;
5443 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT);
5444 attr.SetBulletStyle(bulletStyle);
5445 attr.SetLeftIndent(leftIndent, leftSubIndent);
5446 attr.SetBulletName(bulletName);
5447
5448 return BeginStyle(attr);
5449 }
5450
5451 /// Begin named character style
5452 bool wxRichTextBuffer::BeginCharacterStyle(const wxString& characterStyle)
5453 {
5454 if (GetStyleSheet())
5455 {
5456 wxRichTextCharacterStyleDefinition* def = GetStyleSheet()->FindCharacterStyle(characterStyle);
5457 if (def)
5458 {
5459 wxTextAttr attr = def->GetStyleMergedWithBase(GetStyleSheet());
5460 return BeginStyle(attr);
5461 }
5462 }
5463 return false;
5464 }
5465
5466 /// Begin named paragraph style
5467 bool wxRichTextBuffer::BeginParagraphStyle(const wxString& paragraphStyle)
5468 {
5469 if (GetStyleSheet())
5470 {
5471 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(paragraphStyle);
5472 if (def)
5473 {
5474 wxTextAttr attr = def->GetStyleMergedWithBase(GetStyleSheet());
5475 return BeginStyle(attr);
5476 }
5477 }
5478 return false;
5479 }
5480
5481 /// Begin named list style
5482 bool wxRichTextBuffer::BeginListStyle(const wxString& listStyle, int level, int number)
5483 {
5484 if (GetStyleSheet())
5485 {
5486 wxRichTextListStyleDefinition* def = GetStyleSheet()->FindListStyle(listStyle);
5487 if (def)
5488 {
5489 wxTextAttr attr(def->GetCombinedStyleForLevel(level));
5490
5491 attr.SetBulletNumber(number);
5492
5493 return BeginStyle(attr);
5494 }
5495 }
5496 return false;
5497 }
5498
5499 /// Begin URL
5500 bool wxRichTextBuffer::BeginURL(const wxString& url, const wxString& characterStyle)
5501 {
5502 wxTextAttr attr;
5503
5504 if (!characterStyle.IsEmpty() && GetStyleSheet())
5505 {
5506 wxRichTextCharacterStyleDefinition* def = GetStyleSheet()->FindCharacterStyle(characterStyle);
5507 if (def)
5508 {
5509 attr = def->GetStyleMergedWithBase(GetStyleSheet());
5510 }
5511 }
5512 attr.SetURL(url);
5513
5514 return BeginStyle(attr);
5515 }
5516
5517 /// Adds a handler to the end
5518 void wxRichTextBuffer::AddHandler(wxRichTextFileHandler *handler)
5519 {
5520 sm_handlers.Append(handler);
5521 }
5522
5523 /// Inserts a handler at the front
5524 void wxRichTextBuffer::InsertHandler(wxRichTextFileHandler *handler)
5525 {
5526 sm_handlers.Insert( handler );
5527 }
5528
5529 /// Removes a handler
5530 bool wxRichTextBuffer::RemoveHandler(const wxString& name)
5531 {
5532 wxRichTextFileHandler *handler = FindHandler(name);
5533 if (handler)
5534 {
5535 sm_handlers.DeleteObject(handler);
5536 delete handler;
5537 return true;
5538 }
5539 else
5540 return false;
5541 }
5542
5543 /// Finds a handler by filename or, if supplied, type
5544 wxRichTextFileHandler *wxRichTextBuffer::FindHandlerFilenameOrType(const wxString& filename, int imageType)
5545 {
5546 if (imageType != wxRICHTEXT_TYPE_ANY)
5547 return FindHandler(imageType);
5548 else if (!filename.IsEmpty())
5549 {
5550 wxString path, file, ext;
5551 wxSplitPath(filename, & path, & file, & ext);
5552 return FindHandler(ext, imageType);
5553 }
5554 else
5555 return NULL;
5556 }
5557
5558
5559 /// Finds a handler by name
5560 wxRichTextFileHandler* wxRichTextBuffer::FindHandler(const wxString& name)
5561 {
5562 wxList::compatibility_iterator node = sm_handlers.GetFirst();
5563 while (node)
5564 {
5565 wxRichTextFileHandler *handler = (wxRichTextFileHandler*)node->GetData();
5566 if (handler->GetName().Lower() == name.Lower()) return handler;
5567
5568 node = node->GetNext();
5569 }
5570 return NULL;
5571 }
5572
5573 /// Finds a handler by extension and type
5574 wxRichTextFileHandler* wxRichTextBuffer::FindHandler(const wxString& extension, int type)
5575 {
5576 wxList::compatibility_iterator node = sm_handlers.GetFirst();
5577 while (node)
5578 {
5579 wxRichTextFileHandler *handler = (wxRichTextFileHandler*)node->GetData();
5580 if ( handler->GetExtension().Lower() == extension.Lower() &&
5581 (type == wxRICHTEXT_TYPE_ANY || handler->GetType() == type) )
5582 return handler;
5583 node = node->GetNext();
5584 }
5585 return 0;
5586 }
5587
5588 /// Finds a handler by type
5589 wxRichTextFileHandler* wxRichTextBuffer::FindHandler(int type)
5590 {
5591 wxList::compatibility_iterator node = sm_handlers.GetFirst();
5592 while (node)
5593 {
5594 wxRichTextFileHandler *handler = (wxRichTextFileHandler *)node->GetData();
5595 if (handler->GetType() == type) return handler;
5596 node = node->GetNext();
5597 }
5598 return NULL;
5599 }
5600
5601 void wxRichTextBuffer::InitStandardHandlers()
5602 {
5603 if (!FindHandler(wxRICHTEXT_TYPE_TEXT))
5604 AddHandler(new wxRichTextPlainTextHandler);
5605 }
5606
5607 void wxRichTextBuffer::CleanUpHandlers()
5608 {
5609 wxList::compatibility_iterator node = sm_handlers.GetFirst();
5610 while (node)
5611 {
5612 wxRichTextFileHandler* handler = (wxRichTextFileHandler*)node->GetData();
5613 wxList::compatibility_iterator next = node->GetNext();
5614 delete handler;
5615 node = next;
5616 }
5617
5618 sm_handlers.Clear();
5619 }
5620
5621 wxString wxRichTextBuffer::GetExtWildcard(bool combine, bool save, wxArrayInt* types)
5622 {
5623 if (types)
5624 types->Clear();
5625
5626 wxString wildcard;
5627
5628 wxList::compatibility_iterator node = GetHandlers().GetFirst();
5629 int count = 0;
5630 while (node)
5631 {
5632 wxRichTextFileHandler* handler = (wxRichTextFileHandler*) node->GetData();
5633 if (handler->IsVisible() && ((save && handler->CanSave()) || !save && handler->CanLoad()))
5634 {
5635 if (combine)
5636 {
5637 if (count > 0)
5638 wildcard += wxT(";");
5639 wildcard += wxT("*.") + handler->GetExtension();
5640 }
5641 else
5642 {
5643 if (count > 0)
5644 wildcard += wxT("|");
5645 wildcard += handler->GetName();
5646 wildcard += wxT(" ");
5647 wildcard += _("files");
5648 wildcard += wxT(" (*.");
5649 wildcard += handler->GetExtension();
5650 wildcard += wxT(")|*.");
5651 wildcard += handler->GetExtension();
5652 if (types)
5653 types->Add(handler->GetType());
5654 }
5655 count ++;
5656 }
5657
5658 node = node->GetNext();
5659 }
5660
5661 if (combine)
5662 wildcard = wxT("(") + wildcard + wxT(")|") + wildcard;
5663 return wildcard;
5664 }
5665
5666 /// Load a file
5667 bool wxRichTextBuffer::LoadFile(const wxString& filename, int type)
5668 {
5669 wxRichTextFileHandler* handler = FindHandlerFilenameOrType(filename, type);
5670 if (handler)
5671 {
5672 SetDefaultStyle(wxTextAttr());
5673 handler->SetFlags(GetHandlerFlags());
5674 bool success = handler->LoadFile(this, filename);
5675 Invalidate(wxRICHTEXT_ALL);
5676 return success;
5677 }
5678 else
5679 return false;
5680 }
5681
5682 /// Save a file
5683 bool wxRichTextBuffer::SaveFile(const wxString& filename, int type)
5684 {
5685 wxRichTextFileHandler* handler = FindHandlerFilenameOrType(filename, type);
5686 if (handler)
5687 {
5688 handler->SetFlags(GetHandlerFlags());
5689 return handler->SaveFile(this, filename);
5690 }
5691 else
5692 return false;
5693 }
5694
5695 /// Load from a stream
5696 bool wxRichTextBuffer::LoadFile(wxInputStream& stream, int type)
5697 {
5698 wxRichTextFileHandler* handler = FindHandler(type);
5699 if (handler)
5700 {
5701 SetDefaultStyle(wxTextAttr());
5702 handler->SetFlags(GetHandlerFlags());
5703 bool success = handler->LoadFile(this, stream);
5704 Invalidate(wxRICHTEXT_ALL);
5705 return success;
5706 }
5707 else
5708 return false;
5709 }
5710
5711 /// Save to a stream
5712 bool wxRichTextBuffer::SaveFile(wxOutputStream& stream, int type)
5713 {
5714 wxRichTextFileHandler* handler = FindHandler(type);
5715 if (handler)
5716 {
5717 handler->SetFlags(GetHandlerFlags());
5718 return handler->SaveFile(this, stream);
5719 }
5720 else
5721 return false;
5722 }
5723
5724 /// Copy the range to the clipboard
5725 bool wxRichTextBuffer::CopyToClipboard(const wxRichTextRange& range)
5726 {
5727 bool success = false;
5728 #if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
5729
5730 if (!wxTheClipboard->IsOpened() && wxTheClipboard->Open())
5731 {
5732 wxTheClipboard->Clear();
5733
5734 // Add composite object
5735
5736 wxDataObjectComposite* compositeObject = new wxDataObjectComposite();
5737
5738 {
5739 wxString text = GetTextForRange(range);
5740
5741 #ifdef __WXMSW__
5742 text = wxTextFile::Translate(text, wxTextFileType_Dos);
5743 #endif
5744
5745 compositeObject->Add(new wxTextDataObject(text), false /* not preferred */);
5746 }
5747
5748 // Add rich text buffer data object. This needs the XML handler to be present.
5749
5750 if (FindHandler(wxRICHTEXT_TYPE_XML))
5751 {
5752 wxRichTextBuffer* richTextBuf = new wxRichTextBuffer;
5753 CopyFragment(range, *richTextBuf);
5754
5755 compositeObject->Add(new wxRichTextBufferDataObject(richTextBuf), true /* preferred */);
5756 }
5757
5758 if (wxTheClipboard->SetData(compositeObject))
5759 success = true;
5760
5761 wxTheClipboard->Close();
5762 }
5763
5764 #else
5765 wxUnusedVar(range);
5766 #endif
5767 return success;
5768 }
5769
5770 /// Paste the clipboard content to the buffer
5771 bool wxRichTextBuffer::PasteFromClipboard(long position)
5772 {
5773 bool success = false;
5774 #if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
5775 if (CanPasteFromClipboard())
5776 {
5777 if (wxTheClipboard->Open())
5778 {
5779 if (wxTheClipboard->IsSupported(wxDataFormat(wxRichTextBufferDataObject::GetRichTextBufferFormatId())))
5780 {
5781 wxRichTextBufferDataObject data;
5782 wxTheClipboard->GetData(data);
5783 wxRichTextBuffer* richTextBuffer = data.GetRichTextBuffer();
5784 if (richTextBuffer)
5785 {
5786 InsertParagraphsWithUndo(position+1, *richTextBuffer, GetRichTextCtrl(), wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE);
5787 if (GetRichTextCtrl())
5788 GetRichTextCtrl()->ShowPosition(position + richTextBuffer->GetRange().GetEnd());
5789 delete richTextBuffer;
5790 }
5791 }
5792 else if (wxTheClipboard->IsSupported(wxDF_TEXT) || wxTheClipboard->IsSupported(wxDF_UNICODETEXT))
5793 {
5794 wxTextDataObject data;
5795 wxTheClipboard->GetData(data);
5796 wxString text(data.GetText());
5797 #ifdef __WXMSW__
5798 wxString text2;
5799 text2.Alloc(text.Length()+1);
5800 size_t i;
5801 for (i = 0; i < text.Length(); i++)
5802 {
5803 wxChar ch = text[i];
5804 if (ch != wxT('\r'))
5805 text2 += ch;
5806 }
5807 #else
5808 wxString text2 = text;
5809 #endif
5810 InsertTextWithUndo(position+1, text2, GetRichTextCtrl());
5811
5812 if (GetRichTextCtrl())
5813 GetRichTextCtrl()->ShowPosition(position + text2.Length());
5814
5815 success = true;
5816 }
5817 else if (wxTheClipboard->IsSupported(wxDF_BITMAP))
5818 {
5819 wxBitmapDataObject data;
5820 wxTheClipboard->GetData(data);
5821 wxBitmap bitmap(data.GetBitmap());
5822 wxImage image(bitmap.ConvertToImage());
5823
5824 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Image"), wxRICHTEXT_INSERT, this, GetRichTextCtrl(), false);
5825
5826 action->GetNewParagraphs().AddImage(image);
5827
5828 if (action->GetNewParagraphs().GetChildCount() == 1)
5829 action->GetNewParagraphs().SetPartialParagraph(true);
5830
5831 action->SetPosition(position);
5832
5833 // Set the range we'll need to delete in Undo
5834 action->SetRange(wxRichTextRange(position, position));
5835
5836 SubmitAction(action);
5837
5838 success = true;
5839 }
5840 wxTheClipboard->Close();
5841 }
5842 }
5843 #else
5844 wxUnusedVar(position);
5845 #endif
5846 return success;
5847 }
5848
5849 /// Can we paste from the clipboard?
5850 bool wxRichTextBuffer::CanPasteFromClipboard() const
5851 {
5852 bool canPaste = false;
5853 #if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
5854 if (!wxTheClipboard->IsOpened() && wxTheClipboard->Open())
5855 {
5856 if (wxTheClipboard->IsSupported(wxDF_TEXT) || wxTheClipboard->IsSupported(wxDF_UNICODETEXT) ||
5857 wxTheClipboard->IsSupported(wxDataFormat(wxRichTextBufferDataObject::GetRichTextBufferFormatId())) ||
5858 wxTheClipboard->IsSupported(wxDF_BITMAP))
5859 {
5860 canPaste = true;
5861 }
5862 wxTheClipboard->Close();
5863 }
5864 #endif
5865 return canPaste;
5866 }
5867
5868 /// Dumps contents of buffer for debugging purposes
5869 void wxRichTextBuffer::Dump()
5870 {
5871 wxString text;
5872 {
5873 wxStringOutputStream stream(& text);
5874 wxTextOutputStream textStream(stream);
5875 Dump(textStream);
5876 }
5877
5878 wxLogDebug(text);
5879 }
5880
5881 /// Add an event handler
5882 bool wxRichTextBuffer::AddEventHandler(wxEvtHandler* handler)
5883 {
5884 m_eventHandlers.Append(handler);
5885 return true;
5886 }
5887
5888 /// Remove an event handler
5889 bool wxRichTextBuffer::RemoveEventHandler(wxEvtHandler* handler, bool deleteHandler)
5890 {
5891 wxList::compatibility_iterator node = m_eventHandlers.Find(handler);
5892 if (node)
5893 {
5894 m_eventHandlers.Erase(node);
5895 if (deleteHandler)
5896 delete handler;
5897
5898 return true;
5899 }
5900 else
5901 return false;
5902 }
5903
5904 /// Clear event handlers
5905 void wxRichTextBuffer::ClearEventHandlers()
5906 {
5907 m_eventHandlers.Clear();
5908 }
5909
5910 /// Send event to event handlers. If sendToAll is true, will send to all event handlers,
5911 /// otherwise will stop at the first successful one.
5912 bool wxRichTextBuffer::SendEvent(wxEvent& event, bool sendToAll)
5913 {
5914 bool success = false;
5915 for (wxList::compatibility_iterator node = m_eventHandlers.GetFirst(); node; node = node->GetNext())
5916 {
5917 wxEvtHandler* handler = (wxEvtHandler*) node->GetData();
5918 if (handler->ProcessEvent(event))
5919 {
5920 success = true;
5921 if (!sendToAll)
5922 return true;
5923 }
5924 }
5925 return success;
5926 }
5927
5928 /// Set style sheet and notify of the change
5929 bool wxRichTextBuffer::SetStyleSheetAndNotify(wxRichTextStyleSheet* sheet)
5930 {
5931 wxRichTextStyleSheet* oldSheet = GetStyleSheet();
5932
5933 wxWindowID id = wxID_ANY;
5934 if (GetRichTextCtrl())
5935 id = GetRichTextCtrl()->GetId();
5936
5937 wxRichTextEvent event(wxEVT_COMMAND_RICHTEXT_STYLESHEET_REPLACING, id);
5938 event.SetEventObject(GetRichTextCtrl());
5939 event.SetOldStyleSheet(oldSheet);
5940 event.SetNewStyleSheet(sheet);
5941 event.Allow();
5942
5943 if (SendEvent(event) && !event.IsAllowed())
5944 {
5945 if (sheet != oldSheet)
5946 delete sheet;
5947
5948 return false;
5949 }
5950
5951 if (oldSheet && oldSheet != sheet)
5952 delete oldSheet;
5953
5954 SetStyleSheet(sheet);
5955
5956 event.SetEventType(wxEVT_COMMAND_RICHTEXT_STYLESHEET_REPLACED);
5957 event.SetOldStyleSheet(NULL);
5958 event.Allow();
5959
5960 return SendEvent(event);
5961 }
5962
5963 /// Set renderer, deleting old one
5964 void wxRichTextBuffer::SetRenderer(wxRichTextRenderer* renderer)
5965 {
5966 if (sm_renderer)
5967 delete sm_renderer;
5968 sm_renderer = renderer;
5969 }
5970
5971 bool wxRichTextStdRenderer::DrawStandardBullet(wxRichTextParagraph* paragraph, wxDC& dc, const wxTextAttr& bulletAttr, const wxRect& rect)
5972 {
5973 if (bulletAttr.GetTextColour().Ok())
5974 {
5975 wxCheckSetPen(dc, wxPen(bulletAttr.GetTextColour()));
5976 wxCheckSetBrush(dc, wxBrush(bulletAttr.GetTextColour()));
5977 }
5978 else
5979 {
5980 wxCheckSetPen(dc, *wxBLACK_PEN);
5981 wxCheckSetBrush(dc, *wxBLACK_BRUSH);
5982 }
5983
5984 wxFont font;
5985 if (bulletAttr.HasFont())
5986 {
5987 font = paragraph->GetBuffer()->GetFontTable().FindFont(bulletAttr);
5988 }
5989 else
5990 font = (*wxNORMAL_FONT);
5991
5992 wxCheckSetFont(dc, font);
5993
5994 int charHeight = dc.GetCharHeight();
5995
5996 int bulletWidth = (int) (((float) charHeight) * wxRichTextBuffer::GetBulletProportion());
5997 int bulletHeight = bulletWidth;
5998
5999 int x = rect.x;
6000
6001 // Calculate the top position of the character (as opposed to the whole line height)
6002 int y = rect.y + (rect.height - charHeight);
6003
6004 // Calculate where the bullet should be positioned
6005 y = y + (charHeight+1)/2 - (bulletHeight+1)/2;
6006
6007 // The margin between a bullet and text.
6008 int margin = paragraph->ConvertTenthsMMToPixels(dc, wxRichTextBuffer::GetBulletRightMargin());
6009
6010 if (bulletAttr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_RIGHT)
6011 x = rect.x + rect.width - bulletWidth - margin;
6012 else if (bulletAttr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_CENTRE)
6013 x = x + (rect.width)/2 - bulletWidth/2;
6014
6015 if (bulletAttr.GetBulletName() == wxT("standard/square"))
6016 {
6017 dc.DrawRectangle(x, y, bulletWidth, bulletHeight);
6018 }
6019 else if (bulletAttr.GetBulletName() == wxT("standard/diamond"))
6020 {
6021 wxPoint pts[5];
6022 pts[0].x = x; pts[0].y = y + bulletHeight/2;
6023 pts[1].x = x + bulletWidth/2; pts[1].y = y;
6024 pts[2].x = x + bulletWidth; pts[2].y = y + bulletHeight/2;
6025 pts[3].x = x + bulletWidth/2; pts[3].y = y + bulletHeight;
6026
6027 dc.DrawPolygon(4, pts);
6028 }
6029 else if (bulletAttr.GetBulletName() == wxT("standard/triangle"))
6030 {
6031 wxPoint pts[3];
6032 pts[0].x = x; pts[0].y = y;
6033 pts[1].x = x + bulletWidth; pts[1].y = y + bulletHeight/2;
6034 pts[2].x = x; pts[2].y = y + bulletHeight;
6035
6036 dc.DrawPolygon(3, pts);
6037 }
6038 else // "standard/circle", and catch-all
6039 {
6040 dc.DrawEllipse(x, y, bulletWidth, bulletHeight);
6041 }
6042
6043 return true;
6044 }
6045
6046 bool wxRichTextStdRenderer::DrawTextBullet(wxRichTextParagraph* paragraph, wxDC& dc, const wxTextAttr& attr, const wxRect& rect, const wxString& text)
6047 {
6048 if (!text.empty())
6049 {
6050 wxFont font;
6051 if ((attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_SYMBOL) && !attr.GetBulletFont().IsEmpty() && attr.HasFont())
6052 {
6053 wxTextAttr fontAttr;
6054 fontAttr.SetFontSize(attr.GetFontSize());
6055 fontAttr.SetFontStyle(attr.GetFontStyle());
6056 fontAttr.SetFontWeight(attr.GetFontWeight());
6057 fontAttr.SetFontUnderlined(attr.GetFontUnderlined());
6058 fontAttr.SetFontFaceName(attr.GetBulletFont());
6059 font = paragraph->GetBuffer()->GetFontTable().FindFont(fontAttr);
6060 }
6061 else if (attr.HasFont())
6062 font = paragraph->GetBuffer()->GetFontTable().FindFont(attr);
6063 else
6064 font = (*wxNORMAL_FONT);
6065
6066 wxCheckSetFont(dc, font);
6067
6068 if (attr.GetTextColour().Ok())
6069 dc.SetTextForeground(attr.GetTextColour());
6070
6071 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
6072
6073 int charHeight = dc.GetCharHeight();
6074 wxCoord tw, th;
6075 dc.GetTextExtent(text, & tw, & th);
6076
6077 int x = rect.x;
6078
6079 // Calculate the top position of the character (as opposed to the whole line height)
6080 int y = rect.y + (rect.height - charHeight);
6081
6082 // The margin between a bullet and text.
6083 int margin = paragraph->ConvertTenthsMMToPixels(dc, wxRichTextBuffer::GetBulletRightMargin());
6084
6085 if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_RIGHT)
6086 x = (rect.x + rect.width) - tw - margin;
6087 else if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_CENTRE)
6088 x = x + (rect.width)/2 - tw/2;
6089
6090 dc.DrawText(text, x, y);
6091
6092 return true;
6093 }
6094 else
6095 return false;
6096 }
6097
6098 bool wxRichTextStdRenderer::DrawBitmapBullet(wxRichTextParagraph* WXUNUSED(paragraph), wxDC& WXUNUSED(dc), const wxTextAttr& WXUNUSED(attr), const wxRect& WXUNUSED(rect))
6099 {
6100 // Currently unimplemented. The intention is to store bitmaps by name in a media store associated
6101 // with the buffer. The store will allow retrieval from memory, disk or other means.
6102 return false;
6103 }
6104
6105 /// Enumerate the standard bullet names currently supported
6106 bool wxRichTextStdRenderer::EnumerateStandardBulletNames(wxArrayString& bulletNames)
6107 {
6108 bulletNames.Add(wxT("standard/circle"));
6109 bulletNames.Add(wxT("standard/square"));
6110 bulletNames.Add(wxT("standard/diamond"));
6111 bulletNames.Add(wxT("standard/triangle"));
6112
6113 return true;
6114 }
6115
6116 /*
6117 * Module to initialise and clean up handlers
6118 */
6119
6120 class wxRichTextModule: public wxModule
6121 {
6122 DECLARE_DYNAMIC_CLASS(wxRichTextModule)
6123 public:
6124 wxRichTextModule() {}
6125 bool OnInit()
6126 {
6127 wxRichTextBuffer::SetRenderer(new wxRichTextStdRenderer);
6128 wxRichTextBuffer::InitStandardHandlers();
6129 wxRichTextParagraph::InitDefaultTabs();
6130 return true;
6131 }
6132 void OnExit()
6133 {
6134 wxRichTextBuffer::CleanUpHandlers();
6135 wxRichTextDecimalToRoman(-1);
6136 wxRichTextParagraph::ClearDefaultTabs();
6137 wxRichTextCtrl::ClearAvailableFontNames();
6138 wxRichTextBuffer::SetRenderer(NULL);
6139 }
6140 };
6141
6142 IMPLEMENT_DYNAMIC_CLASS(wxRichTextModule, wxModule)
6143
6144
6145 // If the richtext lib is dynamically loaded after the app has already started
6146 // (such as from wxPython) then the built-in module system will not init this
6147 // module. Provide this function to do it manually.
6148 void wxRichTextModuleInit()
6149 {
6150 wxModule* module = new wxRichTextModule;
6151 module->Init();
6152 wxModule::RegisterModule(module);
6153 }
6154
6155
6156 /*!
6157 * Commands for undo/redo
6158 *
6159 */
6160
6161 wxRichTextCommand::wxRichTextCommand(const wxString& name, wxRichTextCommandId id, wxRichTextBuffer* buffer,
6162 wxRichTextCtrl* ctrl, bool ignoreFirstTime): wxCommand(true, name)
6163 {
6164 /* wxRichTextAction* action = */ new wxRichTextAction(this, name, id, buffer, ctrl, ignoreFirstTime);
6165 }
6166
6167 wxRichTextCommand::wxRichTextCommand(const wxString& name): wxCommand(true, name)
6168 {
6169 }
6170
6171 wxRichTextCommand::~wxRichTextCommand()
6172 {
6173 ClearActions();
6174 }
6175
6176 void wxRichTextCommand::AddAction(wxRichTextAction* action)
6177 {
6178 if (!m_actions.Member(action))
6179 m_actions.Append(action);
6180 }
6181
6182 bool wxRichTextCommand::Do()
6183 {
6184 for (wxList::compatibility_iterator node = m_actions.GetFirst(); node; node = node->GetNext())
6185 {
6186 wxRichTextAction* action = (wxRichTextAction*) node->GetData();
6187 action->Do();
6188 }
6189
6190 return true;
6191 }
6192
6193 bool wxRichTextCommand::Undo()
6194 {
6195 for (wxList::compatibility_iterator node = m_actions.GetLast(); node; node = node->GetPrevious())
6196 {
6197 wxRichTextAction* action = (wxRichTextAction*) node->GetData();
6198 action->Undo();
6199 }
6200
6201 return true;
6202 }
6203
6204 void wxRichTextCommand::ClearActions()
6205 {
6206 WX_CLEAR_LIST(wxList, m_actions);
6207 }
6208
6209 /*!
6210 * Individual action
6211 *
6212 */
6213
6214 wxRichTextAction::wxRichTextAction(wxRichTextCommand* cmd, const wxString& name, wxRichTextCommandId id, wxRichTextBuffer* buffer,
6215 wxRichTextCtrl* ctrl, bool ignoreFirstTime)
6216 {
6217 m_buffer = buffer;
6218 m_ignoreThis = ignoreFirstTime;
6219 m_cmdId = id;
6220 m_position = -1;
6221 m_ctrl = ctrl;
6222 m_name = name;
6223 m_newParagraphs.SetDefaultStyle(buffer->GetDefaultStyle());
6224 m_newParagraphs.SetBasicStyle(buffer->GetBasicStyle());
6225 if (cmd)
6226 cmd->AddAction(this);
6227 }
6228
6229 wxRichTextAction::~wxRichTextAction()
6230 {
6231 }
6232
6233 bool wxRichTextAction::Do()
6234 {
6235 m_buffer->Modify(true);
6236
6237 switch (m_cmdId)
6238 {
6239 case wxRICHTEXT_INSERT:
6240 {
6241 // Store a list of line start character and y positions so we can figure out which area
6242 // we need to refresh
6243 wxArrayInt optimizationLineCharPositions;
6244 wxArrayInt optimizationLineYPositions;
6245
6246 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
6247 // NOTE: we're assuming that the buffer is laid out correctly at this point.
6248 // If we had several actions, which only invalidate and leave layout until the
6249 // paint handler is called, then this might not be true. So we may need to switch
6250 // optimisation on only when we're simply adding text and not simultaneously
6251 // deleting a selection, for example. Or, we make sure the buffer is laid out correctly
6252 // first, but of course this means we'll be doing it twice.
6253 if (!m_buffer->GetDirty() && m_ctrl) // can only do optimisation if the buffer is already laid out correctly
6254 {
6255 wxSize clientSize = m_ctrl->GetClientSize();
6256 wxPoint firstVisiblePt = m_ctrl->GetFirstVisiblePoint();
6257 int lastY = firstVisiblePt.y + clientSize.y;
6258
6259 wxRichTextParagraph* para = m_buffer->GetParagraphAtPosition(GetRange().GetStart());
6260 wxRichTextObjectList::compatibility_iterator node = m_buffer->GetChildren().Find(para);
6261 while (node)
6262 {
6263 wxRichTextParagraph* child = (wxRichTextParagraph*) node->GetData();
6264 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
6265 while (node2)
6266 {
6267 wxRichTextLine* line = node2->GetData();
6268 wxPoint pt = line->GetAbsolutePosition();
6269 wxRichTextRange range = line->GetAbsoluteRange();
6270
6271 if (pt.y > lastY)
6272 {
6273 node2 = wxRichTextLineList::compatibility_iterator();
6274 node = wxRichTextObjectList::compatibility_iterator();
6275 }
6276 else if (range.GetStart() > GetPosition() && pt.y >= firstVisiblePt.y)
6277 {
6278 optimizationLineCharPositions.Add(range.GetStart());
6279 optimizationLineYPositions.Add(pt.y);
6280 }
6281
6282 if (node2)
6283 node2 = node2->GetNext();
6284 }
6285
6286 if (node)
6287 node = node->GetNext();
6288 }
6289 }
6290 #endif
6291
6292 m_buffer->InsertFragment(GetRange().GetStart(), m_newParagraphs);
6293 m_buffer->UpdateRanges();
6294 m_buffer->Invalidate(wxRichTextRange(wxMax(0, GetRange().GetStart()-1), GetRange().GetEnd()));
6295
6296 long newCaretPosition = GetPosition() + m_newParagraphs.GetRange().GetLength();
6297
6298 // Character position to caret position
6299 newCaretPosition --;
6300
6301 // Don't take into account the last newline
6302 if (m_newParagraphs.GetPartialParagraph())
6303 newCaretPosition --;
6304 else
6305 if (m_newParagraphs.GetChildren().GetCount() > 1)
6306 {
6307 wxRichTextObject* p = (wxRichTextObject*) m_newParagraphs.GetChildren().GetLast()->GetData();
6308 if (p->GetRange().GetLength() == 1)
6309 newCaretPosition --;
6310 }
6311
6312 newCaretPosition = wxMin(newCaretPosition, (m_buffer->GetRange().GetEnd()-1));
6313
6314 if (optimizationLineCharPositions.GetCount() > 0)
6315 UpdateAppearance(newCaretPosition, true /* send update event */, & optimizationLineCharPositions, & optimizationLineYPositions);
6316 else
6317 UpdateAppearance(newCaretPosition, true /* send update event */);
6318
6319 wxRichTextEvent cmdEvent(
6320 wxEVT_COMMAND_RICHTEXT_CONTENT_INSERTED,
6321 m_ctrl ? m_ctrl->GetId() : -1);
6322 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
6323 cmdEvent.SetRange(GetRange());
6324 cmdEvent.SetPosition(GetRange().GetStart());
6325
6326 m_buffer->SendEvent(cmdEvent);
6327
6328 break;
6329 }
6330 case wxRICHTEXT_DELETE:
6331 {
6332 m_buffer->DeleteRange(GetRange());
6333 m_buffer->UpdateRanges();
6334 m_buffer->Invalidate(wxRichTextRange(GetRange().GetStart(), GetRange().GetStart()));
6335
6336 long caretPos = GetRange().GetStart()-1;
6337 if (caretPos >= m_buffer->GetRange().GetEnd())
6338 caretPos --;
6339
6340 UpdateAppearance(caretPos, true /* send update event */);
6341
6342 wxRichTextEvent cmdEvent(
6343 wxEVT_COMMAND_RICHTEXT_CONTENT_DELETED,
6344 m_ctrl ? m_ctrl->GetId() : -1);
6345 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
6346 cmdEvent.SetRange(GetRange());
6347 cmdEvent.SetPosition(GetRange().GetStart());
6348
6349 m_buffer->SendEvent(cmdEvent);
6350
6351 break;
6352 }
6353 case wxRICHTEXT_CHANGE_STYLE:
6354 {
6355 ApplyParagraphs(GetNewParagraphs());
6356 m_buffer->Invalidate(GetRange());
6357
6358 UpdateAppearance(GetPosition());
6359
6360 wxRichTextEvent cmdEvent(
6361 wxEVT_COMMAND_RICHTEXT_STYLE_CHANGED,
6362 m_ctrl ? m_ctrl->GetId() : -1);
6363 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
6364 cmdEvent.SetRange(GetRange());
6365 cmdEvent.SetPosition(GetRange().GetStart());
6366
6367 m_buffer->SendEvent(cmdEvent);
6368
6369 break;
6370 }
6371 default:
6372 break;
6373 }
6374
6375 return true;
6376 }
6377
6378 bool wxRichTextAction::Undo()
6379 {
6380 m_buffer->Modify(true);
6381
6382 switch (m_cmdId)
6383 {
6384 case wxRICHTEXT_INSERT:
6385 {
6386 m_buffer->DeleteRange(GetRange());
6387 m_buffer->UpdateRanges();
6388 m_buffer->Invalidate(wxRichTextRange(GetRange().GetStart(), GetRange().GetStart()));
6389
6390 long newCaretPosition = GetPosition() - 1;
6391
6392 UpdateAppearance(newCaretPosition, true /* send update event */);
6393
6394 wxRichTextEvent cmdEvent(
6395 wxEVT_COMMAND_RICHTEXT_CONTENT_DELETED,
6396 m_ctrl ? m_ctrl->GetId() : -1);
6397 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
6398 cmdEvent.SetRange(GetRange());
6399 cmdEvent.SetPosition(GetRange().GetStart());
6400
6401 m_buffer->SendEvent(cmdEvent);
6402
6403 break;
6404 }
6405 case wxRICHTEXT_DELETE:
6406 {
6407 m_buffer->InsertFragment(GetRange().GetStart(), m_oldParagraphs);
6408 m_buffer->UpdateRanges();
6409 m_buffer->Invalidate(GetRange());
6410
6411 UpdateAppearance(GetPosition(), true /* send update event */);
6412
6413 wxRichTextEvent cmdEvent(
6414 wxEVT_COMMAND_RICHTEXT_CONTENT_INSERTED,
6415 m_ctrl ? m_ctrl->GetId() : -1);
6416 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
6417 cmdEvent.SetRange(GetRange());
6418 cmdEvent.SetPosition(GetRange().GetStart());
6419
6420 m_buffer->SendEvent(cmdEvent);
6421
6422 break;
6423 }
6424 case wxRICHTEXT_CHANGE_STYLE:
6425 {
6426 ApplyParagraphs(GetOldParagraphs());
6427 m_buffer->Invalidate(GetRange());
6428
6429 UpdateAppearance(GetPosition());
6430
6431 wxRichTextEvent cmdEvent(
6432 wxEVT_COMMAND_RICHTEXT_STYLE_CHANGED,
6433 m_ctrl ? m_ctrl->GetId() : -1);
6434 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
6435 cmdEvent.SetRange(GetRange());
6436 cmdEvent.SetPosition(GetRange().GetStart());
6437
6438 m_buffer->SendEvent(cmdEvent);
6439
6440 break;
6441 }
6442 default:
6443 break;
6444 }
6445
6446 return true;
6447 }
6448
6449 /// Update the control appearance
6450 void wxRichTextAction::UpdateAppearance(long caretPosition, bool sendUpdateEvent, wxArrayInt* optimizationLineCharPositions, wxArrayInt* optimizationLineYPositions)
6451 {
6452 if (m_ctrl)
6453 {
6454 m_ctrl->SetCaretPosition(caretPosition);
6455 if (!m_ctrl->IsFrozen())
6456 {
6457 m_ctrl->LayoutContent();
6458 m_ctrl->PositionCaret();
6459
6460 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
6461 // Find refresh rectangle if we are in a position to optimise refresh
6462 if (m_cmdId == wxRICHTEXT_INSERT && optimizationLineCharPositions && optimizationLineCharPositions->GetCount() > 0)
6463 {
6464 size_t i;
6465
6466 wxSize clientSize = m_ctrl->GetClientSize();
6467 wxPoint firstVisiblePt = m_ctrl->GetFirstVisiblePoint();
6468
6469 // Start/end positions
6470 int firstY = 0;
6471 int lastY = firstVisiblePt.y + clientSize.y;
6472
6473 bool foundStart = false;
6474 bool foundEnd = false;
6475
6476 // position offset - how many characters were inserted
6477 int positionOffset = GetRange().GetLength();
6478
6479 // find the first line which is being drawn at the same position as it was
6480 // before. Since we're talking about a simple insertion, we can assume
6481 // that the rest of the window does not need to be redrawn.
6482
6483 wxRichTextParagraph* para = m_buffer->GetParagraphAtPosition(GetPosition());
6484 wxRichTextObjectList::compatibility_iterator node = m_buffer->GetChildren().Find(para);
6485 while (node)
6486 {
6487 wxRichTextParagraph* child = (wxRichTextParagraph*) node->GetData();
6488 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
6489 while (node2)
6490 {
6491 wxRichTextLine* line = node2->GetData();
6492 wxPoint pt = line->GetAbsolutePosition();
6493 wxRichTextRange range = line->GetAbsoluteRange();
6494
6495 // we want to find the first line that is in the same position
6496 // as before. This will mean we're at the end of the changed text.
6497
6498 if (pt.y > lastY) // going past the end of the window, no more info
6499 {
6500 node2 = wxRichTextLineList::compatibility_iterator();
6501 node = wxRichTextObjectList::compatibility_iterator();
6502 }
6503 else
6504 {
6505 if (!foundStart)
6506 {
6507 firstY = pt.y - firstVisiblePt.y;
6508 foundStart = true;
6509 }
6510
6511 // search for this line being at the same position as before
6512 for (i = 0; i < optimizationLineCharPositions->GetCount(); i++)
6513 {
6514 if (((*optimizationLineCharPositions)[i] + positionOffset == range.GetStart()) &&
6515 ((*optimizationLineYPositions)[i] == pt.y))
6516 {
6517 // Stop, we're now the same as we were
6518 foundEnd = true;
6519 lastY = pt.y - firstVisiblePt.y;
6520
6521 node2 = wxRichTextLineList::compatibility_iterator();
6522 node = wxRichTextObjectList::compatibility_iterator();
6523
6524 break;
6525 }
6526 }
6527 }
6528
6529 if (node2)
6530 node2 = node2->GetNext();
6531 }
6532
6533 if (node)
6534 node = node->GetNext();
6535 }
6536
6537 if (!foundStart)
6538 firstY = firstVisiblePt.y;
6539 if (!foundEnd)
6540 lastY = firstVisiblePt.y + clientSize.y;
6541
6542 wxRect rect(firstVisiblePt.x, firstY, firstVisiblePt.x + clientSize.x, lastY - firstY);
6543 m_ctrl->RefreshRect(rect);
6544
6545 // TODO: we need to make sure that lines are only drawn if in the update region. The rect
6546 // passed to Draw is currently used in different ways (to pass the position the content should
6547 // be drawn at as well as the relevant region).
6548 }
6549 else
6550 #endif
6551 m_ctrl->Refresh(false);
6552
6553 if (sendUpdateEvent)
6554 wxTextCtrl::SendTextUpdatedEvent(m_ctrl);
6555 }
6556 }
6557 }
6558
6559 /// Replace the buffer paragraphs with the new ones.
6560 void wxRichTextAction::ApplyParagraphs(const wxRichTextParagraphLayoutBox& fragment)
6561 {
6562 wxRichTextObjectList::compatibility_iterator node = fragment.GetChildren().GetFirst();
6563 while (node)
6564 {
6565 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
6566 wxASSERT (para != NULL);
6567
6568 // We'll replace the existing paragraph by finding the paragraph at this position,
6569 // delete its node data, and setting a copy as the new node data.
6570 // TODO: make more efficient by simply swapping old and new paragraph objects.
6571
6572 wxRichTextParagraph* existingPara = m_buffer->GetParagraphAtPosition(para->GetRange().GetStart());
6573 if (existingPara)
6574 {
6575 wxRichTextObjectList::compatibility_iterator bufferParaNode = m_buffer->GetChildren().Find(existingPara);
6576 if (bufferParaNode)
6577 {
6578 wxRichTextParagraph* newPara = new wxRichTextParagraph(*para);
6579 newPara->SetParent(m_buffer);
6580
6581 bufferParaNode->SetData(newPara);
6582
6583 delete existingPara;
6584 }
6585 }
6586
6587 node = node->GetNext();
6588 }
6589 }
6590
6591
6592 /*!
6593 * wxRichTextRange
6594 * This stores beginning and end positions for a range of data.
6595 */
6596
6597 /// Limit this range to be within 'range'
6598 bool wxRichTextRange::LimitTo(const wxRichTextRange& range)
6599 {
6600 if (m_start < range.m_start)
6601 m_start = range.m_start;
6602
6603 if (m_end > range.m_end)
6604 m_end = range.m_end;
6605
6606 return true;
6607 }
6608
6609 /*!
6610 * wxRichTextImage implementation
6611 * This object represents an image.
6612 */
6613
6614 IMPLEMENT_DYNAMIC_CLASS(wxRichTextImage, wxRichTextObject)
6615
6616 wxRichTextImage::wxRichTextImage(const wxImage& image, wxRichTextObject* parent, wxTextAttr* charStyle):
6617 wxRichTextObject(parent)
6618 {
6619 m_image = image;
6620 if (charStyle)
6621 SetAttributes(*charStyle);
6622 }
6623
6624 wxRichTextImage::wxRichTextImage(const wxRichTextImageBlock& imageBlock, wxRichTextObject* parent, wxTextAttr* charStyle):
6625 wxRichTextObject(parent)
6626 {
6627 m_imageBlock = imageBlock;
6628 m_imageBlock.Load(m_image);
6629 if (charStyle)
6630 SetAttributes(*charStyle);
6631 }
6632
6633 /// Load wxImage from the block
6634 bool wxRichTextImage::LoadFromBlock()
6635 {
6636 m_imageBlock.Load(m_image);
6637 return m_imageBlock.Ok();
6638 }
6639
6640 /// Make block from the wxImage
6641 bool wxRichTextImage::MakeBlock()
6642 {
6643 if (m_imageBlock.GetImageType() == wxBITMAP_TYPE_ANY || m_imageBlock.GetImageType() == -1)
6644 m_imageBlock.SetImageType(wxBITMAP_TYPE_PNG);
6645
6646 m_imageBlock.MakeImageBlock(m_image, m_imageBlock.GetImageType());
6647 return m_imageBlock.Ok();
6648 }
6649
6650
6651 /// Draw the item
6652 bool wxRichTextImage::Draw(wxDC& dc, const wxRichTextRange& range, const wxRichTextRange& selectionRange, const wxRect& rect, int WXUNUSED(descent), int WXUNUSED(style))
6653 {
6654 if (!m_image.Ok() && m_imageBlock.Ok())
6655 LoadFromBlock();
6656
6657 if (!m_image.Ok())
6658 return false;
6659
6660 if (m_image.Ok() && !m_bitmap.Ok())
6661 m_bitmap = wxBitmap(m_image);
6662
6663 int y = rect.y + (rect.height - m_image.GetHeight());
6664
6665 if (m_bitmap.Ok())
6666 dc.DrawBitmap(m_bitmap, rect.x, y, true);
6667
6668 if (selectionRange.Contains(range.GetStart()))
6669 {
6670 wxCheckSetBrush(dc, *wxBLACK_BRUSH);
6671 wxCheckSetPen(dc, *wxBLACK_PEN);
6672 dc.SetLogicalFunction(wxINVERT);
6673 dc.DrawRectangle(rect);
6674 dc.SetLogicalFunction(wxCOPY);
6675 }
6676
6677 return true;
6678 }
6679
6680 /// Lay the item out
6681 bool wxRichTextImage::Layout(wxDC& WXUNUSED(dc), const wxRect& rect, int WXUNUSED(style))
6682 {
6683 if (!m_image.Ok())
6684 LoadFromBlock();
6685
6686 if (m_image.Ok())
6687 {
6688 SetCachedSize(wxSize(m_image.GetWidth(), m_image.GetHeight()));
6689 SetPosition(rect.GetPosition());
6690 }
6691
6692 return true;
6693 }
6694
6695 /// Get/set the object size for the given range. Returns false if the range
6696 /// is invalid for this object.
6697 bool wxRichTextImage::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& WXUNUSED(descent), wxDC& WXUNUSED(dc), int WXUNUSED(flags), wxPoint WXUNUSED(position)) const
6698 {
6699 if (!range.IsWithin(GetRange()))
6700 return false;
6701
6702 if (!m_image.Ok())
6703 return false;
6704
6705 size.x = m_image.GetWidth();
6706 size.y = m_image.GetHeight();
6707
6708 return true;
6709 }
6710
6711 /// Copy
6712 void wxRichTextImage::Copy(const wxRichTextImage& obj)
6713 {
6714 wxRichTextObject::Copy(obj);
6715
6716 m_image = obj.m_image;
6717 m_imageBlock = obj.m_imageBlock;
6718 }
6719
6720 /*!
6721 * Utilities
6722 *
6723 */
6724
6725 /// Compare two attribute objects
6726 bool wxTextAttrEq(const wxTextAttr& attr1, const wxTextAttr& attr2)
6727 {
6728 return (attr1 == attr2);
6729 }
6730
6731 // Partial equality test taking flags into account
6732 bool wxTextAttrEqPartial(const wxTextAttr& attr1, const wxTextAttr& attr2, int flags)
6733 {
6734 return attr1.EqPartial(attr2, flags);
6735 }
6736
6737 /// Compare tabs
6738 bool wxRichTextTabsEq(const wxArrayInt& tabs1, const wxArrayInt& tabs2)
6739 {
6740 if (tabs1.GetCount() != tabs2.GetCount())
6741 return false;
6742
6743 size_t i;
6744 for (i = 0; i < tabs1.GetCount(); i++)
6745 {
6746 if (tabs1[i] != tabs2[i])
6747 return false;
6748 }
6749 return true;
6750 }
6751
6752 bool wxRichTextApplyStyle(wxTextAttr& destStyle, const wxTextAttr& style, wxTextAttr* compareWith)
6753 {
6754 return destStyle.Apply(style, compareWith);
6755 }
6756
6757 // Remove attributes
6758 bool wxRichTextRemoveStyle(wxTextAttr& destStyle, const wxTextAttr& style)
6759 {
6760 return wxTextAttr::RemoveStyle(destStyle, style);
6761 }
6762
6763 /// Combine two bitlists, specifying the bits of interest with separate flags.
6764 bool wxRichTextCombineBitlists(int& valueA, int valueB, int& flagsA, int flagsB)
6765 {
6766 return wxTextAttr::CombineBitlists(valueA, valueB, flagsA, flagsB);
6767 }
6768
6769 /// Compare two bitlists
6770 bool wxRichTextBitlistsEqPartial(int valueA, int valueB, int flags)
6771 {
6772 return wxTextAttr::BitlistsEqPartial(valueA, valueB, flags);
6773 }
6774
6775 /// Split into paragraph and character styles
6776 bool wxRichTextSplitParaCharStyles(const wxTextAttr& style, wxTextAttr& parStyle, wxTextAttr& charStyle)
6777 {
6778 return wxTextAttr::SplitParaCharStyles(style, parStyle, charStyle);
6779 }
6780
6781 /// Convert a decimal to Roman numerals
6782 wxString wxRichTextDecimalToRoman(long n)
6783 {
6784 static wxArrayInt decimalNumbers;
6785 static wxArrayString romanNumbers;
6786
6787 // Clean up arrays
6788 if (n == -1)
6789 {
6790 decimalNumbers.Clear();
6791 romanNumbers.Clear();
6792 return wxEmptyString;
6793 }
6794
6795 if (decimalNumbers.GetCount() == 0)
6796 {
6797 #define wxRichTextAddDecRom(n, r) decimalNumbers.Add(n); romanNumbers.Add(r);
6798
6799 wxRichTextAddDecRom(1000, wxT("M"));
6800 wxRichTextAddDecRom(900, wxT("CM"));
6801 wxRichTextAddDecRom(500, wxT("D"));
6802 wxRichTextAddDecRom(400, wxT("CD"));
6803 wxRichTextAddDecRom(100, wxT("C"));
6804 wxRichTextAddDecRom(90, wxT("XC"));
6805 wxRichTextAddDecRom(50, wxT("L"));
6806 wxRichTextAddDecRom(40, wxT("XL"));
6807 wxRichTextAddDecRom(10, wxT("X"));
6808 wxRichTextAddDecRom(9, wxT("IX"));
6809 wxRichTextAddDecRom(5, wxT("V"));
6810 wxRichTextAddDecRom(4, wxT("IV"));
6811 wxRichTextAddDecRom(1, wxT("I"));
6812 }
6813
6814 int i = 0;
6815 wxString roman;
6816
6817 while (n > 0 && i < 13)
6818 {
6819 if (n >= decimalNumbers[i])
6820 {
6821 n -= decimalNumbers[i];
6822 roman += romanNumbers[i];
6823 }
6824 else
6825 {
6826 i ++;
6827 }
6828 }
6829 if (roman.IsEmpty())
6830 roman = wxT("0");
6831 return roman;
6832 }
6833
6834 /*!
6835 * wxRichTextFileHandler
6836 * Base class for file handlers
6837 */
6838
6839 IMPLEMENT_CLASS(wxRichTextFileHandler, wxObject)
6840
6841 #if wxUSE_FFILE && wxUSE_STREAMS
6842 bool wxRichTextFileHandler::LoadFile(wxRichTextBuffer *buffer, const wxString& filename)
6843 {
6844 wxFFileInputStream stream(filename);
6845 if (stream.Ok())
6846 return LoadFile(buffer, stream);
6847
6848 return false;
6849 }
6850
6851 bool wxRichTextFileHandler::SaveFile(wxRichTextBuffer *buffer, const wxString& filename)
6852 {
6853 wxFFileOutputStream stream(filename);
6854 if (stream.Ok())
6855 return SaveFile(buffer, stream);
6856
6857 return false;
6858 }
6859 #endif // wxUSE_FFILE && wxUSE_STREAMS
6860
6861 /// Can we handle this filename (if using files)? By default, checks the extension.
6862 bool wxRichTextFileHandler::CanHandle(const wxString& filename) const
6863 {
6864 wxString path, file, ext;
6865 wxSplitPath(filename, & path, & file, & ext);
6866
6867 return (ext.Lower() == GetExtension());
6868 }
6869
6870 /*!
6871 * wxRichTextTextHandler
6872 * Plain text handler
6873 */
6874
6875 IMPLEMENT_CLASS(wxRichTextPlainTextHandler, wxRichTextFileHandler)
6876
6877 #if wxUSE_STREAMS
6878 bool wxRichTextPlainTextHandler::DoLoadFile(wxRichTextBuffer *buffer, wxInputStream& stream)
6879 {
6880 if (!stream.IsOk())
6881 return false;
6882
6883 wxString str;
6884 int lastCh = 0;
6885
6886 while (!stream.Eof())
6887 {
6888 int ch = stream.GetC();
6889
6890 if (!stream.Eof())
6891 {
6892 if (ch == 10 && lastCh != 13)
6893 str += wxT('\n');
6894
6895 if (ch > 0 && ch != 10)
6896 str += wxChar(ch);
6897
6898 lastCh = ch;
6899 }
6900 }
6901
6902 buffer->ResetAndClearCommands();
6903 buffer->Clear();
6904 buffer->AddParagraphs(str);
6905 buffer->UpdateRanges();
6906
6907 return true;
6908 }
6909
6910 bool wxRichTextPlainTextHandler::DoSaveFile(wxRichTextBuffer *buffer, wxOutputStream& stream)
6911 {
6912 if (!stream.IsOk())
6913 return false;
6914
6915 wxString text = buffer->GetText();
6916
6917 wxString newLine = wxRichTextLineBreakChar;
6918 text.Replace(newLine, wxT("\n"));
6919
6920 wxCharBuffer buf = text.ToAscii();
6921
6922 stream.Write((const char*) buf, text.length());
6923 return true;
6924 }
6925 #endif // wxUSE_STREAMS
6926
6927 /*
6928 * Stores information about an image, in binary in-memory form
6929 */
6930
6931 wxRichTextImageBlock::wxRichTextImageBlock()
6932 {
6933 Init();
6934 }
6935
6936 wxRichTextImageBlock::wxRichTextImageBlock(const wxRichTextImageBlock& block):wxObject()
6937 {
6938 Init();
6939 Copy(block);
6940 }
6941
6942 wxRichTextImageBlock::~wxRichTextImageBlock()
6943 {
6944 if (m_data)
6945 {
6946 delete[] m_data;
6947 m_data = NULL;
6948 }
6949 }
6950
6951 void wxRichTextImageBlock::Init()
6952 {
6953 m_data = NULL;
6954 m_dataSize = 0;
6955 m_imageType = -1;
6956 }
6957
6958 void wxRichTextImageBlock::Clear()
6959 {
6960 delete[] m_data;
6961 m_data = NULL;
6962 m_dataSize = 0;
6963 m_imageType = -1;
6964 }
6965
6966
6967 // Load the original image into a memory block.
6968 // If the image is not a JPEG, we must convert it into a JPEG
6969 // to conserve space.
6970 // If it's not a JPEG we can make use of 'image', already scaled, so we don't have to
6971 // load the image a 2nd time.
6972
6973 bool wxRichTextImageBlock::MakeImageBlock(const wxString& filename, int imageType, wxImage& image, bool convertToJPEG)
6974 {
6975 m_imageType = imageType;
6976
6977 wxString filenameToRead(filename);
6978 bool removeFile = false;
6979
6980 if (imageType == -1)
6981 return false; // Could not determine image type
6982
6983 if ((imageType != wxBITMAP_TYPE_JPEG) && convertToJPEG)
6984 {
6985 wxString tempFile;
6986 bool success = wxGetTempFileName(_("image"), tempFile) ;
6987
6988 wxASSERT(success);
6989
6990 wxUnusedVar(success);
6991
6992 image.SaveFile(tempFile, wxBITMAP_TYPE_JPEG);
6993 filenameToRead = tempFile;
6994 removeFile = true;
6995
6996 m_imageType = wxBITMAP_TYPE_JPEG;
6997 }
6998 wxFile file;
6999 if (!file.Open(filenameToRead))
7000 return false;
7001
7002 m_dataSize = (size_t) file.Length();
7003 file.Close();
7004
7005 if (m_data)
7006 delete[] m_data;
7007 m_data = ReadBlock(filenameToRead, m_dataSize);
7008
7009 if (removeFile)
7010 wxRemoveFile(filenameToRead);
7011
7012 return (m_data != NULL);
7013 }
7014
7015 // Make an image block from the wxImage in the given
7016 // format.
7017 bool wxRichTextImageBlock::MakeImageBlock(wxImage& image, int imageType, int quality)
7018 {
7019 m_imageType = imageType;
7020 image.SetOption(wxT("quality"), quality);
7021
7022 if (imageType == -1)
7023 return false; // Could not determine image type
7024
7025 wxString tempFile;
7026 bool success = wxGetTempFileName(_("image"), tempFile) ;
7027
7028 wxASSERT(success);
7029 wxUnusedVar(success);
7030
7031 if (!image.SaveFile(tempFile, m_imageType))
7032 {
7033 if (wxFileExists(tempFile))
7034 wxRemoveFile(tempFile);
7035 return false;
7036 }
7037
7038 wxFile file;
7039 if (!file.Open(tempFile))
7040 return false;
7041
7042 m_dataSize = (size_t) file.Length();
7043 file.Close();
7044
7045 if (m_data)
7046 delete[] m_data;
7047 m_data = ReadBlock(tempFile, m_dataSize);
7048
7049 wxRemoveFile(tempFile);
7050
7051 return (m_data != NULL);
7052 }
7053
7054
7055 // Write to a file
7056 bool wxRichTextImageBlock::Write(const wxString& filename)
7057 {
7058 return WriteBlock(filename, m_data, m_dataSize);
7059 }
7060
7061 void wxRichTextImageBlock::Copy(const wxRichTextImageBlock& block)
7062 {
7063 m_imageType = block.m_imageType;
7064 if (m_data)
7065 {
7066 delete[] m_data;
7067 m_data = NULL;
7068 }
7069 m_dataSize = block.m_dataSize;
7070 if (m_dataSize == 0)
7071 return;
7072
7073 m_data = new unsigned char[m_dataSize];
7074 unsigned int i;
7075 for (i = 0; i < m_dataSize; i++)
7076 m_data[i] = block.m_data[i];
7077 }
7078
7079 //// Operators
7080 void wxRichTextImageBlock::operator=(const wxRichTextImageBlock& block)
7081 {
7082 Copy(block);
7083 }
7084
7085 // Load a wxImage from the block
7086 bool wxRichTextImageBlock::Load(wxImage& image)
7087 {
7088 if (!m_data)
7089 return false;
7090
7091 // Read in the image.
7092 #if wxUSE_STREAMS
7093 wxMemoryInputStream mstream(m_data, m_dataSize);
7094 bool success = image.LoadFile(mstream, GetImageType());
7095 #else
7096 wxString tempFile;
7097 bool success = wxGetTempFileName(_("image"), tempFile) ;
7098 wxASSERT(success);
7099
7100 if (!WriteBlock(tempFile, m_data, m_dataSize))
7101 {
7102 return false;
7103 }
7104 success = image.LoadFile(tempFile, GetImageType());
7105 wxRemoveFile(tempFile);
7106 #endif
7107
7108 return success;
7109 }
7110
7111 // Write data in hex to a stream
7112 bool wxRichTextImageBlock::WriteHex(wxOutputStream& stream)
7113 {
7114 const int bufSize = 512;
7115 char buf[bufSize+1];
7116
7117 int left = m_dataSize;
7118 int n, i, j;
7119 j = 0;
7120 while (left > 0)
7121 {
7122 if (left*2 > bufSize)
7123 {
7124 n = bufSize; left -= (bufSize/2);
7125 }
7126 else
7127 {
7128 n = left*2; left = 0;
7129 }
7130
7131 char* b = buf;
7132 for (i = 0; i < (n/2); i++)
7133 {
7134 wxDecToHex(m_data[j], b, b+1);
7135 b += 2; j ++;
7136 }
7137
7138 buf[n] = 0;
7139 stream.Write((const char*) buf, n);
7140 }
7141 return true;
7142 }
7143
7144 // Read data in hex from a stream
7145 bool wxRichTextImageBlock::ReadHex(wxInputStream& stream, int length, int imageType)
7146 {
7147 int dataSize = length/2;
7148
7149 if (m_data)
7150 delete[] m_data;
7151
7152 wxChar str[2];
7153 m_data = new unsigned char[dataSize];
7154 int i;
7155 for (i = 0; i < dataSize; i ++)
7156 {
7157 str[0] = (char)stream.GetC();
7158 str[1] = (char)stream.GetC();
7159
7160 m_data[i] = (unsigned char)wxHexToDec(str);
7161 }
7162
7163 m_dataSize = dataSize;
7164 m_imageType = imageType;
7165
7166 return true;
7167 }
7168
7169 // Allocate and read from stream as a block of memory
7170 unsigned char* wxRichTextImageBlock::ReadBlock(wxInputStream& stream, size_t size)
7171 {
7172 unsigned char* block = new unsigned char[size];
7173 if (!block)
7174 return NULL;
7175
7176 stream.Read(block, size);
7177
7178 return block;
7179 }
7180
7181 unsigned char* wxRichTextImageBlock::ReadBlock(const wxString& filename, size_t size)
7182 {
7183 wxFileInputStream stream(filename);
7184 if (!stream.Ok())
7185 return NULL;
7186
7187 return ReadBlock(stream, size);
7188 }
7189
7190 // Write memory block to stream
7191 bool wxRichTextImageBlock::WriteBlock(wxOutputStream& stream, unsigned char* block, size_t size)
7192 {
7193 stream.Write((void*) block, size);
7194 return stream.IsOk();
7195
7196 }
7197
7198 // Write memory block to file
7199 bool wxRichTextImageBlock::WriteBlock(const wxString& filename, unsigned char* block, size_t size)
7200 {
7201 wxFileOutputStream outStream(filename);
7202 if (!outStream.Ok())
7203 return false;
7204
7205 return WriteBlock(outStream, block, size);
7206 }
7207
7208 // Gets the extension for the block's type
7209 wxString wxRichTextImageBlock::GetExtension() const
7210 {
7211 wxImageHandler* handler = wxImage::FindHandler(GetImageType());
7212 if (handler)
7213 return handler->GetExtension();
7214 else
7215 return wxEmptyString;
7216 }
7217
7218 #if wxUSE_DATAOBJ
7219
7220 /*!
7221 * The data object for a wxRichTextBuffer
7222 */
7223
7224 const wxChar *wxRichTextBufferDataObject::ms_richTextBufferFormatId = wxT("wxShape");
7225
7226 wxRichTextBufferDataObject::wxRichTextBufferDataObject(wxRichTextBuffer* richTextBuffer)
7227 {
7228 m_richTextBuffer = richTextBuffer;
7229
7230 // this string should uniquely identify our format, but is otherwise
7231 // arbitrary
7232 m_formatRichTextBuffer.SetId(GetRichTextBufferFormatId());
7233
7234 SetFormat(m_formatRichTextBuffer);
7235 }
7236
7237 wxRichTextBufferDataObject::~wxRichTextBufferDataObject()
7238 {
7239 delete m_richTextBuffer;
7240 }
7241
7242 // after a call to this function, the richTextBuffer is owned by the caller and it
7243 // is responsible for deleting it!
7244 wxRichTextBuffer* wxRichTextBufferDataObject::GetRichTextBuffer()
7245 {
7246 wxRichTextBuffer* richTextBuffer = m_richTextBuffer;
7247 m_richTextBuffer = NULL;
7248
7249 return richTextBuffer;
7250 }
7251
7252 wxDataFormat wxRichTextBufferDataObject::GetPreferredFormat(Direction WXUNUSED(dir)) const
7253 {
7254 return m_formatRichTextBuffer;
7255 }
7256
7257 size_t wxRichTextBufferDataObject::GetDataSize() const
7258 {
7259 if (!m_richTextBuffer)
7260 return 0;
7261
7262 wxString bufXML;
7263
7264 {
7265 wxStringOutputStream stream(& bufXML);
7266 if (!m_richTextBuffer->SaveFile(stream, wxRICHTEXT_TYPE_XML))
7267 {
7268 wxLogError(wxT("Could not write the buffer to an XML stream.\nYou may have forgotten to add the XML file handler."));
7269 return 0;
7270 }
7271 }
7272
7273 #if wxUSE_UNICODE
7274 wxCharBuffer buffer = bufXML.mb_str(wxConvUTF8);
7275 return strlen(buffer) + 1;
7276 #else
7277 return bufXML.Length()+1;
7278 #endif
7279 }
7280
7281 bool wxRichTextBufferDataObject::GetDataHere(void *pBuf) const
7282 {
7283 if (!pBuf || !m_richTextBuffer)
7284 return false;
7285
7286 wxString bufXML;
7287
7288 {
7289 wxStringOutputStream stream(& bufXML);
7290 if (!m_richTextBuffer->SaveFile(stream, wxRICHTEXT_TYPE_XML))
7291 {
7292 wxLogError(wxT("Could not write the buffer to an XML stream.\nYou may have forgotten to add the XML file handler."));
7293 return 0;
7294 }
7295 }
7296
7297 #if wxUSE_UNICODE
7298 wxCharBuffer buffer = bufXML.mb_str(wxConvUTF8);
7299 size_t len = strlen(buffer);
7300 memcpy((char*) pBuf, (const char*) buffer, len);
7301 ((char*) pBuf)[len] = 0;
7302 #else
7303 size_t len = bufXML.Length();
7304 memcpy((char*) pBuf, (const char*) bufXML.c_str(), len);
7305 ((char*) pBuf)[len] = 0;
7306 #endif
7307
7308 return true;
7309 }
7310
7311 bool wxRichTextBufferDataObject::SetData(size_t WXUNUSED(len), const void *buf)
7312 {
7313 delete m_richTextBuffer;
7314 m_richTextBuffer = NULL;
7315
7316 wxString bufXML((const char*) buf, wxConvUTF8);
7317
7318 m_richTextBuffer = new wxRichTextBuffer;
7319
7320 wxStringInputStream stream(bufXML);
7321 if (!m_richTextBuffer->LoadFile(stream, wxRICHTEXT_TYPE_XML))
7322 {
7323 wxLogError(wxT("Could not read the buffer from an XML stream.\nYou may have forgotten to add the XML file handler."));
7324
7325 delete m_richTextBuffer;
7326 m_richTextBuffer = NULL;
7327
7328 return false;
7329 }
7330 return true;
7331 }
7332
7333 #endif
7334 // wxUSE_DATAOBJ
7335
7336
7337 /*
7338 * wxRichTextFontTable
7339 * Manages quick access to a pool of fonts for rendering rich text
7340 */
7341
7342 WX_DECLARE_STRING_HASH_MAP_WITH_DECL(wxFont, wxRichTextFontTableHashMap, class WXDLLIMPEXP_RICHTEXT);
7343
7344 class wxRichTextFontTableData: public wxObjectRefData
7345 {
7346 public:
7347 wxRichTextFontTableData() {}
7348
7349 wxFont FindFont(const wxTextAttr& fontSpec);
7350
7351 wxRichTextFontTableHashMap m_hashMap;
7352 };
7353
7354 wxFont wxRichTextFontTableData::FindFont(const wxTextAttr& fontSpec)
7355 {
7356 wxString facename(fontSpec.GetFontFaceName());
7357 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()));
7358 wxRichTextFontTableHashMap::iterator entry = m_hashMap.find(spec);
7359
7360 if ( entry == m_hashMap.end() )
7361 {
7362 wxFont font(fontSpec.GetFontSize(), wxDEFAULT, fontSpec.GetFontStyle(), fontSpec.GetFontWeight(), fontSpec.GetFontUnderlined(), facename.c_str());
7363 m_hashMap[spec] = font;
7364 return font;
7365 }
7366 else
7367 {
7368 return entry->second;
7369 }
7370 }
7371
7372 IMPLEMENT_DYNAMIC_CLASS(wxRichTextFontTable, wxObject)
7373
7374 wxRichTextFontTable::wxRichTextFontTable()
7375 {
7376 m_refData = new wxRichTextFontTableData;
7377 }
7378
7379 wxRichTextFontTable::wxRichTextFontTable(const wxRichTextFontTable& table)
7380 {
7381 (*this) = table;
7382 }
7383
7384 wxRichTextFontTable::~wxRichTextFontTable()
7385 {
7386 UnRef();
7387 }
7388
7389 bool wxRichTextFontTable::operator == (const wxRichTextFontTable& table) const
7390 {
7391 return (m_refData == table.m_refData);
7392 }
7393
7394 void wxRichTextFontTable::operator= (const wxRichTextFontTable& table)
7395 {
7396 Ref(table);
7397 }
7398
7399 wxFont wxRichTextFontTable::FindFont(const wxTextAttr& fontSpec)
7400 {
7401 wxRichTextFontTableData* data = (wxRichTextFontTableData*) m_refData;
7402 if (data)
7403 return data->FindFont(fontSpec);
7404 else
7405 return wxFont();
7406 }
7407
7408 void wxRichTextFontTable::Clear()
7409 {
7410 wxRichTextFontTableData* data = (wxRichTextFontTableData*) m_refData;
7411 if (data)
7412 data->m_hashMap.clear();
7413 }
7414
7415 #endif
7416 // wxUSE_RICHTEXT