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