Added further API for intercepting deletion and content insertion
[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 #include "wx/richtext/richtextsizepage.h"
45
46 #include "wx/listimpl.cpp"
47 #include "wx/arrimpl.cpp"
48
49 WX_DEFINE_LIST(wxRichTextObjectList)
50 WX_DEFINE_LIST(wxRichTextLineList)
51
52 // Switch off if the platform doesn't like it for some reason
53 #define wxRICHTEXT_USE_OPTIMIZED_DRAWING 1
54
55 // Use GetPartialTextExtents for platforms that support it natively
56 #define wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS 1
57
58 const wxChar wxRichTextLineBreakChar = (wxChar) 29;
59
60 // Helper classes for floating layout
61 struct wxRichTextFloatRectMap
62 {
63 wxRichTextFloatRectMap(int sY, int eY, int w, wxRichTextObject* obj)
64 {
65 startY = sY;
66 endY = eY;
67 width = w;
68 anchor = obj;
69 }
70
71 int startY, endY;
72 int width;
73 wxRichTextObject* anchor;
74 };
75
76 WX_DEFINE_SORTED_ARRAY(wxRichTextFloatRectMap*, wxRichTextFloatRectMapArray);
77
78 int wxRichTextFloatRectMapCmp(wxRichTextFloatRectMap* r1, wxRichTextFloatRectMap* r2)
79 {
80 return r1->startY - r2->startY;
81 }
82
83 class wxRichTextFloatCollector
84 {
85 public:
86 wxRichTextFloatCollector(const wxRect& availableRect);
87 ~wxRichTextFloatCollector();
88
89 // Collect the floating objects info in the given paragraph
90 void CollectFloat(wxRichTextParagraph* para);
91 void CollectFloat(wxRichTextParagraph* para, wxRichTextObject* floating);
92
93 // Return the last paragraph we collected
94 wxRichTextParagraph* LastParagraph();
95
96 // Given the start y position and the height of the line,
97 // find out how wide the line can be
98 wxRect GetAvailableRect(int startY, int endY);
99
100 // Given a floating box, find its fit position
101 int GetFitPosition(int direction, int start, int height) const;
102 int GetFitPosition(const wxRichTextFloatRectMapArray& array, int start, int height) const;
103
104 // Find the last y position
105 int GetLastRectBottom();
106
107 // Draw the floats inside a rect
108 void Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style);
109
110 // HitTest the floats
111 int HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, int flags);
112
113 // Get floating object count
114 int GetFloatingObjectCount() const { return m_left.GetCount() + m_right.GetCount(); }
115
116 // Get floating objects
117 bool GetFloatingObjects(wxRichTextObjectList& objects) const;
118
119 // Delete a float
120 bool DeleteFloat(wxRichTextObject* obj);
121
122 // Do we have this float already?
123 bool HasFloat(wxRichTextObject* obj);
124
125 bool HasFloats() const { return m_left.GetCount() >0 || m_right.GetCount() > 0; }
126
127 static int SearchAdjacentRect(const wxRichTextFloatRectMapArray& array, int point);
128
129 static int GetWidthFromFloatRect(const wxRichTextFloatRectMapArray& array, int index, int startY, int endY);
130
131 static void FreeFloatRectMapArray(wxRichTextFloatRectMapArray& array);
132
133 static void DrawFloat(const wxRichTextFloatRectMapArray& array, wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style);
134
135 static int HitTestFloat(const wxRichTextFloatRectMapArray& array, wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, int flags);
136
137 private:
138 wxRichTextFloatRectMapArray m_left;
139 wxRichTextFloatRectMapArray m_right;
140 //int m_width;
141 wxRect m_availableRect;
142 wxRichTextParagraph* m_para;
143 };
144
145 // Delete a float
146 bool wxRichTextFloatCollector::DeleteFloat(wxRichTextObject* obj)
147 {
148 size_t i;
149 for (i = 0; i < m_left.GetCount(); i++)
150 {
151 if (m_left[i]->anchor == obj)
152 {
153 m_left.RemoveAt(i);
154 return true;
155 }
156 }
157 for (i = 0; i < m_right.GetCount(); i++)
158 {
159 if (m_right[i]->anchor == obj)
160 {
161 m_right.RemoveAt(i);
162 return true;
163 }
164 }
165 return false;
166 }
167
168 // Do we have this float already?
169 bool wxRichTextFloatCollector::HasFloat(wxRichTextObject* obj)
170 {
171 size_t i;
172 for (i = 0; i < m_left.GetCount(); i++)
173 {
174 if (m_left[i]->anchor == obj)
175 {
176 return true;
177 }
178 }
179 for (i = 0; i < m_right.GetCount(); i++)
180 {
181 if (m_right[i]->anchor == obj)
182 {
183 return true;
184 }
185 }
186 return false;
187 }
188
189 // Get floating objects
190 bool wxRichTextFloatCollector::GetFloatingObjects(wxRichTextObjectList& objects) const
191 {
192 size_t i;
193 for (i = 0; i < m_left.GetCount(); i++)
194 objects.Append(m_left[i]->anchor);
195 for (i = 0; i < m_right.GetCount(); i++)
196 objects.Append(m_right[i]->anchor);
197 return true;
198 }
199
200
201 /*
202 * Binary search helper function
203 * The argument point is the Y coordinate, and this fuction
204 * always return the floating rect that contain this coordinate
205 * or under this coordinate.
206 */
207 int wxRichTextFloatCollector::SearchAdjacentRect(const wxRichTextFloatRectMapArray& array, int point)
208 {
209 int end = array.GetCount() - 1;
210 int start = 0;
211 int ret = 0;
212
213 wxASSERT(end >= 0);
214
215 while (true)
216 {
217 if (start > end)
218 {
219 break;
220 }
221
222 int mid = (start + end) / 2;
223 if (array[mid]->startY <= point && array[mid]->endY >= point)
224 return mid;
225 else if (array[mid]->startY > point)
226 {
227 end = mid - 1;
228 ret = mid;
229 }
230 else if (array[mid]->endY < point)
231 {
232 start = mid + 1;
233 ret = start;
234 }
235 }
236
237 return ret;
238 }
239
240 int wxRichTextFloatCollector::GetWidthFromFloatRect(const wxRichTextFloatRectMapArray& array, int index, int startY, int endY)
241 {
242 int ret = 0;
243 int len = array.GetCount();
244
245 wxASSERT(index >= 0 && index < len);
246
247 if (array[index]->startY < startY && array[index]->endY > startY)
248 ret = ret < array[index]->width ? array[index]->width : ret;
249 while (index < len && array[index]->startY <= endY)
250 {
251 ret = ret < array[index]->width ? array[index]->width : ret;
252 index++;
253 }
254
255 return ret;
256 }
257
258 wxRichTextFloatCollector::wxRichTextFloatCollector(const wxRect& rect) : m_left(wxRichTextFloatRectMapCmp), m_right(wxRichTextFloatRectMapCmp)
259 {
260 m_availableRect = rect;
261 m_para = NULL;
262 }
263
264 void wxRichTextFloatCollector::FreeFloatRectMapArray(wxRichTextFloatRectMapArray& array)
265 {
266 int len = array.GetCount();
267 for (int i = 0; i < len; i++)
268 delete array[i];
269 }
270
271 wxRichTextFloatCollector::~wxRichTextFloatCollector()
272 {
273 FreeFloatRectMapArray(m_left);
274 FreeFloatRectMapArray(m_right);
275 }
276
277 int wxRichTextFloatCollector::GetFitPosition(const wxRichTextFloatRectMapArray& array, int start, int height) const
278 {
279 if (array.GetCount() == 0)
280 return start;
281
282 int i = SearchAdjacentRect(array, start);
283 int last = start;
284 while (i < (int) array.GetCount())
285 {
286 if (array[i]->startY - last >= height)
287 return last + 1;
288 last = array[i]->endY;
289 i++;
290 }
291
292 return last + 1;
293 }
294
295 int wxRichTextFloatCollector::GetFitPosition(int direction, int start, int height) const
296 {
297 if (direction == wxTEXT_BOX_ATTR_FLOAT_LEFT)
298 return GetFitPosition(m_left, start, height);
299 else if (direction == wxTEXT_BOX_ATTR_FLOAT_RIGHT)
300 return GetFitPosition(m_right, start, height);
301 else
302 {
303 wxASSERT("Never should be here");
304 return start;
305 }
306 }
307
308 // Adds a floating image to the float collector.
309 // The actual positioning is done by wxRichTextParagraph::LayoutFloat.
310 void wxRichTextFloatCollector::CollectFloat(wxRichTextParagraph* para, wxRichTextObject* floating)
311 {
312 int direction = floating->GetFloatDirection();
313
314 wxPoint pos = floating->GetPosition();
315 wxSize size = floating->GetCachedSize();
316 wxRichTextFloatRectMap *map = new wxRichTextFloatRectMap(pos.y, pos.y + size.y, size.x, floating);
317 switch (direction)
318 {
319 case wxTEXT_BOX_ATTR_FLOAT_NONE:
320 delete map;
321 break;
322 case wxTEXT_BOX_ATTR_FLOAT_LEFT:
323 // Just a not-enough simple assertion
324 wxASSERT (m_left.Index(map) == wxNOT_FOUND);
325 m_left.Add(map);
326 break;
327 case wxTEXT_BOX_ATTR_FLOAT_RIGHT:
328 wxASSERT (m_right.Index(map) == wxNOT_FOUND);
329 m_right.Add(map);
330 break;
331 default:
332 delete map;
333 wxASSERT("Unrecognised float attribute.");
334 }
335
336 m_para = para;
337 }
338
339 void wxRichTextFloatCollector::CollectFloat(wxRichTextParagraph* para)
340 {
341 wxRichTextObjectList::compatibility_iterator node = para->GetChildren().GetFirst();
342 while (node)
343 {
344 wxRichTextObject* floating = node->GetData();
345
346 if (floating->IsFloating())
347 {
348 CollectFloat(para, floating);
349 }
350
351 node = node->GetNext();
352 }
353
354 m_para = para;
355 }
356
357 wxRichTextParagraph* wxRichTextFloatCollector::LastParagraph()
358 {
359 return m_para;
360 }
361
362 wxRect wxRichTextFloatCollector::GetAvailableRect(int startY, int endY)
363 {
364 int widthLeft = 0, widthRight = 0;
365 if (m_left.GetCount() != 0)
366 {
367 int i = SearchAdjacentRect(m_left, startY);
368 if (i < (int) m_left.GetCount())
369 widthLeft = GetWidthFromFloatRect(m_left, i, startY, endY);
370 }
371 if (m_right.GetCount() != 0)
372 {
373 int j = SearchAdjacentRect(m_right, startY);
374 if (j < (int) m_right.GetCount())
375 widthRight = GetWidthFromFloatRect(m_right, j, startY, endY);
376 }
377
378 // TODO: actually we want to use the actual image positions to find the
379 // available remaining space, since the image might not be right up against
380 // the left or right edge of the container.
381 return wxRect(widthLeft + m_availableRect.x, 0, m_availableRect.width - widthLeft - widthRight, 0);
382 }
383
384 int wxRichTextFloatCollector::GetLastRectBottom()
385 {
386 int ret = 0;
387 int len = m_left.GetCount();
388 if (len) {
389 ret = ret > m_left[len-1]->endY ? ret : m_left[len-1]->endY;
390 }
391 len = m_right.GetCount();
392 if (len) {
393 ret = ret > m_right[len-1]->endY ? ret : m_right[len-1]->endY;
394 }
395
396 return ret;
397 }
398
399 void wxRichTextFloatCollector::DrawFloat(const wxRichTextFloatRectMapArray& array, wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& WXUNUSED(range), const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
400 {
401 int start = rect.y;
402 int end = rect.y + rect.height;
403 int i, j;
404 i = SearchAdjacentRect(array, start);
405 if (i < 0 || i >= (int) array.GetCount())
406 return;
407 j = SearchAdjacentRect(array, end);
408 if (j < 0 || j >= (int) array.GetCount())
409 j = array.GetCount() - 1;
410 while (i <= j)
411 {
412 wxRichTextObject* obj = array[i]->anchor;
413 wxRichTextRange r = obj->GetRange();
414 obj->Draw(dc, context, r, selection, wxRect(obj->GetPosition(), obj->GetCachedSize()), descent, style);
415 i++;
416 }
417 }
418
419 void wxRichTextFloatCollector::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
420 {
421 if (m_left.GetCount() > 0)
422 DrawFloat(m_left, dc, context, range, selection, rect, descent, style);
423 if (m_right.GetCount() > 0)
424 DrawFloat(m_right, dc, context, range, selection, rect, descent, style);
425 }
426
427 int wxRichTextFloatCollector::HitTestFloat(const wxRichTextFloatRectMapArray& array, wxDC& WXUNUSED(dc), wxRichTextDrawingContext& WXUNUSED(context), const wxPoint& pt, long& textPosition, wxRichTextObject** obj, int WXUNUSED(flags))
428 {
429 int i;
430 if (array.GetCount() == 0)
431 return wxRICHTEXT_HITTEST_NONE;
432 i = SearchAdjacentRect(array, pt.y);
433 if (i < 0 || i >= (int) array.GetCount())
434 return wxRICHTEXT_HITTEST_NONE;
435 if (!array[i]->anchor->IsShown())
436 return wxRICHTEXT_HITTEST_NONE;
437
438 wxPoint point = array[i]->anchor->GetPosition();
439 wxSize size = array[i]->anchor->GetCachedSize();
440 if (point.x <= pt.x && point.x + size.x >= pt.x
441 && point.y <= pt.y && point.y + size.y >= pt.y)
442 {
443 textPosition = array[i]->anchor->GetRange().GetStart();
444 * obj = array[i]->anchor;
445 if (pt.x > (pt.x + pt.x + size.x) / 2)
446 return wxRICHTEXT_HITTEST_BEFORE;
447 else
448 return wxRICHTEXT_HITTEST_AFTER;
449 }
450
451 return wxRICHTEXT_HITTEST_NONE;
452 }
453
454 int wxRichTextFloatCollector::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, int flags)
455 {
456 int ret = HitTestFloat(m_left, dc, context, pt, textPosition, obj, flags);
457 if (ret == wxRICHTEXT_HITTEST_NONE)
458 {
459 ret = HitTestFloat(m_right, dc, context, pt, textPosition, obj, flags);
460 }
461 return ret;
462 }
463
464 // Helpers for efficiency
465 inline void wxCheckSetFont(wxDC& dc, const wxFont& font)
466 {
467 // JACS: did I do this some time ago when testing? Should we re-enable it?
468 #if 0
469 const wxFont& font1 = dc.GetFont();
470 if (font1.IsOk() && font.IsOk())
471 {
472 if (font1.GetPointSize() == font.GetPointSize() &&
473 font1.GetFamily() == font.GetFamily() &&
474 font1.GetStyle() == font.GetStyle() &&
475 font1.GetWeight() == font.GetWeight() &&
476 font1.GetUnderlined() == font.GetUnderlined() &&
477 font1.GetFamily() == font.GetFamily() &&
478 font1.GetFaceName() == font.GetFaceName())
479 return;
480 }
481 #endif
482 dc.SetFont(font);
483 }
484
485 inline void wxCheckSetPen(wxDC& dc, const wxPen& pen)
486 {
487 const wxPen& pen1 = dc.GetPen();
488 if (pen1.IsOk() && pen.IsOk())
489 {
490 if (pen1.GetWidth() == pen.GetWidth() &&
491 pen1.GetStyle() == pen.GetStyle() &&
492 pen1.GetColour() == pen.GetColour())
493 return;
494 }
495 dc.SetPen(pen);
496 }
497
498 inline void wxCheckSetBrush(wxDC& dc, const wxBrush& brush)
499 {
500 const wxBrush& brush1 = dc.GetBrush();
501 if (brush1.IsOk() && brush.IsOk())
502 {
503 if (brush1.GetStyle() == brush.GetStyle() &&
504 brush1.GetColour() == brush.GetColour())
505 return;
506 }
507 dc.SetBrush(brush);
508 }
509
510 /*!
511 * wxRichTextObject
512 * This is the base for drawable objects.
513 */
514
515 IMPLEMENT_CLASS(wxRichTextObject, wxObject)
516
517 wxRichTextObject::wxRichTextObject(wxRichTextObject* parent)
518 {
519 m_refCount = 1;
520 m_parent = parent;
521 m_descent = 0;
522 m_show = true;
523 }
524
525 wxRichTextObject::~wxRichTextObject()
526 {
527 }
528
529 void wxRichTextObject::Dereference()
530 {
531 m_refCount --;
532 if (m_refCount <= 0)
533 delete this;
534 }
535
536 /// Copy
537 void wxRichTextObject::Copy(const wxRichTextObject& obj)
538 {
539 m_size = obj.m_size;
540 m_maxSize = obj.m_maxSize;
541 m_minSize = obj.m_minSize;
542 m_pos = obj.m_pos;
543 m_range = obj.m_range;
544 m_ownRange = obj.m_ownRange;
545 m_attributes = obj.m_attributes;
546 m_properties = obj.m_properties;
547 m_descent = obj.m_descent;
548 m_show = obj.m_show;
549 }
550
551 // Get/set the top-level container of this object.
552 wxRichTextParagraphLayoutBox* wxRichTextObject::GetContainer() const
553 {
554 const wxRichTextObject* p = this;
555 while (p)
556 {
557 if (p->IsTopLevel())
558 {
559 return wxDynamicCast(p, wxRichTextParagraphLayoutBox);
560 }
561 p = p->GetParent();
562 }
563 return NULL;
564 }
565
566 void wxRichTextObject::SetMargins(int margin)
567 {
568 SetMargins(margin, margin, margin, margin);
569 }
570
571 void wxRichTextObject::SetMargins(int leftMargin, int rightMargin, int topMargin, int bottomMargin)
572 {
573 GetAttributes().GetTextBoxAttr().GetMargins().GetLeft().SetValue(leftMargin, wxTEXT_ATTR_UNITS_PIXELS);
574 GetAttributes().GetTextBoxAttr().GetMargins().GetRight().SetValue(rightMargin, wxTEXT_ATTR_UNITS_PIXELS);
575 GetAttributes().GetTextBoxAttr().GetMargins().GetTop().SetValue(topMargin, wxTEXT_ATTR_UNITS_PIXELS);
576 GetAttributes().GetTextBoxAttr().GetMargins().GetBottom().SetValue(bottomMargin, wxTEXT_ATTR_UNITS_PIXELS);
577 }
578
579 int wxRichTextObject::GetLeftMargin() const
580 {
581 return GetAttributes().GetTextBoxAttr().GetMargins().GetLeft().GetValue();
582 }
583
584 int wxRichTextObject::GetRightMargin() const
585 {
586 return GetAttributes().GetTextBoxAttr().GetMargins().GetRight().GetValue();
587 }
588
589 int wxRichTextObject::GetTopMargin() const
590 {
591 return GetAttributes().GetTextBoxAttr().GetMargins().GetTop().GetValue();
592 }
593
594 int wxRichTextObject::GetBottomMargin() const
595 {
596 return GetAttributes().GetTextBoxAttr().GetMargins().GetBottom().GetValue();
597 }
598
599 // Calculate the available content space in the given rectangle, given the
600 // margins, border and padding specified in the object's attributes.
601 wxRect wxRichTextObject::GetAvailableContentArea(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& outerRect) const
602 {
603 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
604 marginRect = outerRect;
605 wxRichTextAttr attr(GetAttributes());
606 context.ApplyVirtualAttributes(attr, (wxRichTextObject*) this);
607 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
608 return contentRect;
609 }
610
611 // Invalidate the buffer. With no argument, invalidates whole buffer.
612 void wxRichTextObject::Invalidate(const wxRichTextRange& invalidRange)
613 {
614 if (invalidRange != wxRICHTEXT_NONE)
615 {
616 SetCachedSize(wxDefaultSize);
617 SetMaxSize(wxDefaultSize);
618 SetMinSize(wxDefaultSize);
619 }
620 }
621
622 // Convert units in tenths of a millimetre to device units
623 int wxRichTextObject::ConvertTenthsMMToPixels(wxDC& dc, int units) const
624 {
625 // Unscale
626 double scale = 1.0;
627 if (GetBuffer())
628 scale = GetBuffer()->GetScale();
629 int p = ConvertTenthsMMToPixels(dc.GetPPI().x, units, scale);
630
631 return p;
632 }
633
634 // Convert units in tenths of a millimetre to device units
635 int wxRichTextObject::ConvertTenthsMMToPixels(int ppi, int units, double scale)
636 {
637 // There are ppi pixels in 254.1 "1/10 mm"
638
639 double pixels = ((double) units * (double)ppi) / 254.1;
640 if (scale != 1.0)
641 pixels /= scale;
642
643 // If the result is very small, make it at least one pixel in size.
644 if (pixels == 0 && units > 0)
645 pixels = 1;
646
647 return (int) pixels;
648 }
649
650 // Convert units in pixels to tenths of a millimetre
651 int wxRichTextObject::ConvertPixelsToTenthsMM(wxDC& dc, int pixels) const
652 {
653 int p = pixels;
654 double scale = 1.0;
655 if (GetBuffer())
656 scale = GetBuffer()->GetScale();
657
658 return ConvertPixelsToTenthsMM(dc.GetPPI().x, p, scale);
659 }
660
661 int wxRichTextObject::ConvertPixelsToTenthsMM(int ppi, int pixels, double scale)
662 {
663 // There are ppi pixels in 254.1 "1/10 mm"
664
665 double p = double(pixels);
666
667 if (scale != 1.0)
668 p *= scale;
669
670 int units = int( p * 254.1 / (double) ppi );
671 return units;
672 }
673
674 // Draw the borders and background for the given rectangle and attributes.
675 // Width and height are taken to be the outer margin size, not the content.
676 bool wxRichTextObject::DrawBoxAttributes(wxDC& dc, wxRichTextBuffer* buffer, const wxRichTextAttr& attr, const wxRect& boxRect, int flags)
677 {
678 // Assume boxRect is the area around the content
679 wxRect marginRect = boxRect;
680 wxRect contentRect, borderRect, paddingRect, outlineRect;
681
682 GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
683
684 // Margin is transparent. Draw background from margin.
685 if (attr.HasBackgroundColour() || (flags & wxRICHTEXT_DRAW_SELECTED))
686 {
687 wxColour colour;
688 if (flags & wxRICHTEXT_DRAW_SELECTED)
689 {
690 // TODO: get selection colour from control?
691 colour = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT);
692 }
693 else
694 colour = attr.GetBackgroundColour();
695
696 wxPen pen(colour);
697 wxBrush brush(colour);
698
699 dc.SetPen(pen);
700 dc.SetBrush(brush);
701 dc.DrawRectangle(borderRect);
702 }
703
704 if (flags & wxRICHTEXT_DRAW_GUIDELINES)
705 {
706 wxRichTextAttr editBorderAttr = attr;
707 // TODO: make guideline colour configurable
708 editBorderAttr.GetTextBoxAttr().GetBorder().SetColour(*wxLIGHT_GREY);
709 editBorderAttr.GetTextBoxAttr().GetBorder().SetWidth(1, wxTEXT_ATTR_UNITS_PIXELS);
710 editBorderAttr.GetTextBoxAttr().GetBorder().SetStyle(wxTEXT_BOX_ATTR_BORDER_SOLID);
711
712 DrawBorder(dc, buffer, editBorderAttr.GetTextBoxAttr().GetBorder(), borderRect, flags);
713 }
714
715 if (attr.GetTextBoxAttr().GetBorder().IsValid())
716 DrawBorder(dc, buffer, attr.GetTextBoxAttr().GetBorder(), borderRect);
717
718 if (attr.GetTextBoxAttr().GetOutline().IsValid())
719 DrawBorder(dc, buffer, attr.GetTextBoxAttr().GetOutline(), outlineRect);
720
721 return true;
722 }
723
724 // Draw a border
725 bool wxRichTextObject::DrawBorder(wxDC& dc, wxRichTextBuffer* buffer, const wxTextAttrBorders& attr, const wxRect& rect, int WXUNUSED(flags))
726 {
727 int borderLeft = 0, borderRight = 0, borderTop = 0, borderBottom = 0;
728 wxTextAttrDimensionConverter converter(dc, buffer ? buffer->GetScale() : 1.0);
729
730 if (attr.GetLeft().IsValid() && attr.GetLeft().GetStyle() != wxTEXT_BOX_ATTR_BORDER_NONE)
731 {
732 borderLeft = converter.GetPixels(attr.GetLeft().GetWidth());
733 wxColour col(attr.GetLeft().GetColour());
734
735 // If pen width is > 1, resorts to a solid rectangle.
736 if (borderLeft == 1)
737 {
738 int penStyle = wxSOLID;
739 if (attr.GetLeft().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DOTTED)
740 penStyle = wxDOT;
741 else if (attr.GetLeft().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DASHED)
742 penStyle = wxLONG_DASH;
743 wxPen pen(col, 1, penStyle);
744 dc.SetPen(pen);
745 dc.DrawLine(rect.x, rect.y, rect.x, rect.y + rect.height);
746
747 }
748 else if (borderLeft > 1)
749 {
750 wxPen pen(col);
751 wxBrush brush(col);
752 dc.SetPen(pen);
753 dc.SetBrush(brush);
754 dc.DrawRectangle(rect.x, rect.y, borderLeft, rect.height);
755 }
756 }
757
758 if (attr.GetRight().IsValid() && attr.GetRight().GetStyle() != wxTEXT_BOX_ATTR_BORDER_NONE)
759 {
760 borderRight = converter.GetPixels(attr.GetRight().GetWidth());
761
762 wxColour col(attr.GetRight().GetColour());
763
764 // If pen width is > 1, resorts to a solid rectangle.
765 if (borderRight == 1)
766 {
767 int penStyle = wxSOLID;
768 if (attr.GetRight().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DOTTED)
769 penStyle = wxDOT;
770 else if (attr.GetRight().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DASHED)
771 penStyle = wxLONG_DASH;
772 wxPen pen(col, 1, penStyle);
773 dc.SetPen(pen);
774 dc.DrawLine(rect.x + rect.width, rect.y, rect.x + rect.width, rect.y + rect.height + 1);
775
776 }
777 else if (borderRight > 1)
778 {
779 wxPen pen(col);
780 wxBrush brush(col);
781 dc.SetPen(pen);
782 dc.SetBrush(brush);
783 dc.DrawRectangle(rect.x + rect.width - borderRight, rect.y, borderRight, rect.height);
784 }
785 }
786
787 if (attr.GetTop().IsValid() && attr.GetTop().GetStyle() != wxTEXT_BOX_ATTR_BORDER_NONE)
788 {
789 borderTop = converter.GetPixels(attr.GetTop().GetWidth());
790
791 wxColour col(attr.GetTop().GetColour());
792
793 // If pen width is > 1, resorts to a solid rectangle.
794 if (borderTop == 1)
795 {
796 int penStyle = wxSOLID;
797 if (attr.GetTop().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DOTTED)
798 penStyle = wxDOT;
799 else if (attr.GetTop().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DASHED)
800 penStyle = wxLONG_DASH;
801 wxPen pen(col, 1, penStyle);
802 dc.SetPen(pen);
803 dc.DrawLine(rect.x, rect.y, rect.x + rect.width, rect.y);
804
805 }
806 else if (borderTop > 1)
807 {
808 wxPen pen(col);
809 wxBrush brush(col);
810 dc.SetPen(pen);
811 dc.SetBrush(brush);
812 dc.DrawRectangle(rect.x, rect.y, rect.width, borderTop);
813 }
814 }
815
816 if (attr.GetBottom().IsValid() && attr.GetBottom().GetStyle() != wxTEXT_BOX_ATTR_BORDER_NONE)
817 {
818 borderBottom = converter.GetPixels(attr.GetBottom().GetWidth());
819 wxColour col(attr.GetTop().GetColour());
820
821 // If pen width is > 1, resorts to a solid rectangle.
822 if (borderBottom == 1)
823 {
824 int penStyle = wxSOLID;
825 if (attr.GetBottom().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DOTTED)
826 penStyle = wxDOT;
827 else if (attr.GetBottom().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DASHED)
828 penStyle = wxLONG_DASH;
829 wxPen pen(col, 1, penStyle);
830 dc.SetPen(pen);
831 dc.DrawLine(rect.x, rect.y + rect.height, rect.x + rect.width, rect.y + rect.height);
832
833 }
834 else if (borderBottom > 1)
835 {
836 wxPen pen(col);
837 wxBrush brush(col);
838 dc.SetPen(pen);
839 dc.SetBrush(brush);
840 dc.DrawRectangle(rect.x, rect.y + rect.height - borderBottom, rect.width, borderBottom);
841 }
842 }
843
844 return true;
845 }
846
847 // Get the various rectangles of the box model in pixels. You can either specify contentRect (inner)
848 // or marginRect (outer), and the other must be the default rectangle (no width or height).
849 // Note that the outline doesn't affect the position of the rectangle, it's drawn in whatever space
850 // is available.
851 //
852 // | Margin | Border | Padding | CONTENT | Padding | Border | Margin |
853
854 bool wxRichTextObject::GetBoxRects(wxDC& dc, wxRichTextBuffer* buffer, const wxRichTextAttr& attr, wxRect& marginRect, wxRect& borderRect, wxRect& contentRect, wxRect& paddingRect, wxRect& outlineRect)
855 {
856 int borderLeft = 0, borderRight = 0, borderTop = 0, borderBottom = 0;
857 int outlineLeft = 0, outlineRight = 0, outlineTop = 0, outlineBottom = 0;
858 int paddingLeft = 0, paddingRight = 0, paddingTop = 0, paddingBottom = 0;
859 int marginLeft = 0, marginRight = 0, marginTop = 0, marginBottom = 0;
860
861 wxTextAttrDimensionConverter converter(dc, buffer ? buffer->GetScale() : 1.0);
862
863 if (attr.GetTextBoxAttr().GetMargins().GetLeft().IsValid())
864 marginLeft = converter.GetPixels(attr.GetTextBoxAttr().GetMargins().GetLeft());
865 if (attr.GetTextBoxAttr().GetMargins().GetRight().IsValid())
866 marginRight = converter.GetPixels(attr.GetTextBoxAttr().GetMargins().GetRight());
867 if (attr.GetTextBoxAttr().GetMargins().GetTop().IsValid())
868 marginTop = converter.GetPixels(attr.GetTextBoxAttr().GetMargins().GetTop());
869 if (attr.GetTextBoxAttr().GetMargins().GetBottom().IsValid())
870 marginBottom = converter.GetPixels(attr.GetTextBoxAttr().GetMargins().GetBottom());
871
872 if (attr.GetTextBoxAttr().GetBorder().GetLeft().GetWidth().IsValid())
873 borderLeft = converter.GetPixels(attr.GetTextBoxAttr().GetBorder().GetLeft().GetWidth());
874 if (attr.GetTextBoxAttr().GetBorder().GetRight().GetWidth().IsValid())
875 borderRight = converter.GetPixels(attr.GetTextBoxAttr().GetBorder().GetRight().GetWidth());
876 if (attr.GetTextBoxAttr().GetBorder().GetTop().GetWidth().IsValid())
877 borderTop = converter.GetPixels(attr.GetTextBoxAttr().GetBorder().GetTop().GetWidth());
878 if (attr.GetTextBoxAttr().GetBorder().GetBottom().GetWidth().IsValid())
879 borderBottom = converter.GetPixels(attr.GetTextBoxAttr().GetBorder().GetBottom().GetWidth());
880
881 if (attr.GetTextBoxAttr().GetPadding().GetLeft().IsValid())
882 paddingLeft = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetLeft());
883 if (attr.GetTextBoxAttr().GetPadding().GetRight().IsValid())
884 paddingRight = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetRight());
885 if (attr.GetTextBoxAttr().GetPadding().GetTop().IsValid())
886 paddingTop = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetTop());
887 if (attr.GetTextBoxAttr().GetPadding().GetBottom().IsValid())
888 paddingBottom = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetBottom());
889
890 if (attr.GetTextBoxAttr().GetOutline().GetLeft().GetWidth().IsValid())
891 outlineLeft = converter.GetPixels(attr.GetTextBoxAttr().GetOutline().GetLeft().GetWidth());
892 if (attr.GetTextBoxAttr().GetOutline().GetRight().GetWidth().IsValid())
893 outlineRight = converter.GetPixels(attr.GetTextBoxAttr().GetOutline().GetRight().GetWidth());
894 if (attr.GetTextBoxAttr().GetOutline().GetTop().GetWidth().IsValid())
895 outlineTop = converter.GetPixels(attr.GetTextBoxAttr().GetOutline().GetTop().GetWidth());
896 if (attr.GetTextBoxAttr().GetOutline().GetBottom().GetWidth().IsValid())
897 outlineBottom = converter.GetPixels(attr.GetTextBoxAttr().GetOutline().GetBottom().GetWidth());
898
899 int leftTotal = marginLeft + borderLeft + paddingLeft;
900 int rightTotal = marginRight + borderRight + paddingRight;
901 int topTotal = marginTop + borderTop + paddingTop;
902 int bottomTotal = marginBottom + borderBottom + paddingBottom;
903
904 if (marginRect != wxRect())
905 {
906 contentRect.x = marginRect.x + leftTotal;
907 contentRect.y = marginRect.y + topTotal;
908 contentRect.width = marginRect.width - (leftTotal + rightTotal);
909 contentRect.height = marginRect.height - (topTotal + bottomTotal);
910 }
911 else
912 {
913 marginRect.x = contentRect.x - leftTotal;
914 marginRect.y = contentRect.y - topTotal;
915 marginRect.width = contentRect.width + (leftTotal + rightTotal);
916 marginRect.height = contentRect.height + (topTotal + bottomTotal);
917 }
918
919 borderRect.x = marginRect.x + marginLeft;
920 borderRect.y = marginRect.y + marginTop;
921 borderRect.width = marginRect.width - (marginLeft + marginRight);
922 borderRect.height = marginRect.height - (marginTop + marginBottom);
923
924 paddingRect.x = marginRect.x + marginLeft + borderLeft;
925 paddingRect.y = marginRect.y + marginTop + borderTop;
926 paddingRect.width = marginRect.width - (marginLeft + marginRight + borderLeft + borderRight);
927 paddingRect.height = marginRect.height - (marginTop + marginBottom + borderTop + borderBottom);
928
929 // The outline is outside the margin and doesn't influence the overall box position or content size.
930 outlineRect.x = marginRect.x - outlineLeft;
931 outlineRect.y = marginRect.y - outlineTop;
932 outlineRect.width = marginRect.width + (outlineLeft + outlineRight);
933 outlineRect.height = marginRect.height + (outlineTop + outlineBottom);
934
935 return true;
936 }
937
938 // Get the total margin for the object in pixels, taking into account margin, padding and border size
939 bool wxRichTextObject::GetTotalMargin(wxDC& dc, wxRichTextBuffer* buffer, const wxRichTextAttr& attr, int& leftMargin, int& rightMargin,
940 int& topMargin, int& bottomMargin)
941 {
942 // Assume boxRect is the area around the content
943 wxRect contentRect, marginRect, borderRect, paddingRect, outlineRect;
944 marginRect = wxRect(0, 0, 1000, 1000);
945
946 GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
947
948 leftMargin = contentRect.GetLeft() - marginRect.GetLeft();
949 rightMargin = marginRect.GetRight() - contentRect.GetRight();
950 topMargin = contentRect.GetTop() - marginRect.GetTop();
951 bottomMargin = marginRect.GetBottom() - contentRect.GetBottom();
952
953 return true;
954 }
955
956 // Returns the rectangle which the child has available to it given restrictions specified in the
957 // child attribute, e.g. 50% width of the parent, 400 pixels, x position 20% of the parent, etc.
958 // availableContainerSpace might be a parent that the cell has to compute its width relative to.
959 // E.g. a cell that's 50% of its parent.
960 wxRect wxRichTextObject::AdjustAvailableSpace(wxDC& dc, wxRichTextBuffer* buffer, const wxRichTextAttr& WXUNUSED(parentAttr), const wxRichTextAttr& childAttr, const wxRect& availableParentSpace, const wxRect& availableContainerSpace)
961 {
962 wxRect rect = availableParentSpace;
963 double scale = 1.0;
964 if (buffer)
965 scale = buffer->GetScale();
966
967 wxTextAttrDimensionConverter converter(dc, scale, availableContainerSpace.GetSize());
968
969 if (childAttr.GetTextBoxAttr().GetWidth().IsValid())
970 rect.width = converter.GetPixels(childAttr.GetTextBoxAttr().GetWidth());
971
972 if (childAttr.GetTextBoxAttr().GetHeight().IsValid())
973 rect.height = converter.GetPixels(childAttr.GetTextBoxAttr().GetHeight());
974
975 // Can specify either left or right for the position (we're assuming we can't
976 // set the left and right edges to effectively set the size. Would we want to do that?)
977 if (childAttr.GetTextBoxAttr().GetPosition().GetLeft().IsValid())
978 {
979 rect.x = rect.x + converter.GetPixels(childAttr.GetTextBoxAttr().GetPosition().GetLeft());
980 }
981 else if (childAttr.GetTextBoxAttr().GetPosition().GetRight().IsValid())
982 {
983 int x = converter.GetPixels(childAttr.GetTextBoxAttr().GetPosition().GetRight());
984 if (childAttr.GetTextBoxAttr().GetPosition().GetRight().GetPosition() == wxTEXT_BOX_ATTR_POSITION_RELATIVE)
985 rect.x = availableContainerSpace.x + availableContainerSpace.width - rect.width;
986 else
987 rect.x += x;
988 }
989
990 if (childAttr.GetTextBoxAttr().GetPosition().GetTop().IsValid())
991 {
992 rect.y = rect.y + converter.GetPixels(childAttr.GetTextBoxAttr().GetPosition().GetTop());
993 }
994 else if (childAttr.GetTextBoxAttr().GetPosition().GetBottom().IsValid())
995 {
996 int y = converter.GetPixels(childAttr.GetTextBoxAttr().GetPosition().GetBottom());
997 if (childAttr.GetTextBoxAttr().GetPosition().GetBottom().GetPosition() == wxTEXT_BOX_ATTR_POSITION_RELATIVE)
998 rect.y = availableContainerSpace.y + availableContainerSpace.height - rect.height;
999 else
1000 rect.y += y;
1001 }
1002
1003 if (rect.GetWidth() > availableParentSpace.GetWidth())
1004 rect.SetWidth(availableParentSpace.GetWidth());
1005
1006 return rect;
1007 }
1008
1009 // Dump to output stream for debugging
1010 void wxRichTextObject::Dump(wxTextOutputStream& stream)
1011 {
1012 stream << GetClassInfo()->GetClassName() << wxT("\n");
1013 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");
1014 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");
1015 }
1016
1017 // Gets the containing buffer
1018 wxRichTextBuffer* wxRichTextObject::GetBuffer() const
1019 {
1020 const wxRichTextObject* obj = this;
1021 while (obj && !obj->IsKindOf(CLASSINFO(wxRichTextBuffer)))
1022 obj = obj->GetParent();
1023 return wxDynamicCast(obj, wxRichTextBuffer);
1024 }
1025
1026 // Get the absolute object position, by traversing up the child/parent hierarchy
1027 wxPoint wxRichTextObject::GetAbsolutePosition() const
1028 {
1029 wxPoint pt = GetPosition();
1030
1031 wxRichTextObject* p = GetParent();
1032 while (p)
1033 {
1034 pt = pt + p->GetPosition();
1035 p = p->GetParent();
1036 }
1037
1038 return pt;
1039 }
1040
1041 // Hit-testing: returns a flag indicating hit test details, plus
1042 // information about position
1043 int wxRichTextObject::HitTest(wxDC& WXUNUSED(dc), wxRichTextDrawingContext& WXUNUSED(context), const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int WXUNUSED(flags))
1044 {
1045 if (!IsShown())
1046 return wxRICHTEXT_HITTEST_NONE;
1047
1048 wxRect rect = GetRect();
1049 if (pt.x >= rect.x && pt.x < rect.x + rect.width &&
1050 pt.y >= rect.y && pt.y < rect.y + rect.height)
1051 {
1052 *obj = this;
1053 *contextObj = GetParentContainer();
1054 textPosition = GetRange().GetStart();
1055 return wxRICHTEXT_HITTEST_ON;
1056 }
1057 else
1058 return wxRICHTEXT_HITTEST_NONE;
1059 }
1060
1061 // Lays out the object first with a given amount of space, and then if no width was specified in attr,
1062 // lays out the object again using the maximum ('best') size
1063 bool wxRichTextObject::LayoutToBestSize(wxDC& dc, wxRichTextDrawingContext& context, wxRichTextBuffer* buffer,
1064 const wxRichTextAttr& parentAttr, const wxRichTextAttr& attr,
1065 const wxRect& availableParentSpace, const wxRect& availableContainerSpace,
1066 int style)
1067 {
1068 wxRect availableChildRect = AdjustAvailableSpace(dc, buffer, parentAttr, attr, availableParentSpace, availableContainerSpace);
1069 wxRect originalAvailableRect = availableChildRect;
1070 Layout(dc, context, availableChildRect, availableContainerSpace, style);
1071
1072 wxSize maxSize = GetMaxSize();
1073
1074 // Don't ignore if maxSize.x is zero, since we need to redo the paragraph's lines
1075 // on this basis
1076 if (!attr.GetTextBoxAttr().GetWidth().IsValid() && maxSize.x < availableChildRect.width)
1077 {
1078 // Redo the layout with a fixed, minimum size this time.
1079 Invalidate(wxRICHTEXT_ALL);
1080 wxRichTextAttr newAttr(attr);
1081 newAttr.GetTextBoxAttr().GetWidth().SetValue(maxSize.x, wxTEXT_ATTR_UNITS_PIXELS);
1082 newAttr.GetTextBoxAttr().GetWidth().SetPosition(wxTEXT_BOX_ATTR_POSITION_ABSOLUTE);
1083
1084 availableChildRect = AdjustAvailableSpace(dc, buffer, parentAttr, newAttr, availableParentSpace, availableContainerSpace);
1085
1086 // If a paragraph, align the whole paragraph.
1087 // Problem with this: if we're limited by a floating object, a line may be centered
1088 // w.r.t. the smaller resulting box rather than the actual available width.
1089 if (attr.HasAlignment() && !GetContainer()->GetFloatCollector()->HasFloats()) // FIXME: aligning whole paragraph not compatible with floating objects
1090 {
1091 // centering, right-justification
1092 if (attr.GetAlignment() == wxTEXT_ALIGNMENT_CENTRE)
1093 {
1094 availableChildRect.x = (originalAvailableRect.GetWidth() - availableChildRect.GetWidth())/2 + availableChildRect.x;
1095 }
1096 else if (attr.GetAlignment() == wxTEXT_ALIGNMENT_RIGHT)
1097 {
1098 availableChildRect.x = availableChildRect.x + originalAvailableRect.GetWidth() - availableChildRect.GetWidth();
1099 }
1100 }
1101
1102 Layout(dc, context, availableChildRect, availableContainerSpace, style);
1103 }
1104
1105 /*
1106 __________________
1107 | ____________ |
1108 | | | |
1109
1110
1111 */
1112
1113 return true;
1114 }
1115
1116 // Move the object recursively, by adding the offset from old to new
1117 void wxRichTextObject::Move(const wxPoint& pt)
1118 {
1119 SetPosition(pt);
1120 }
1121
1122
1123 /*!
1124 * wxRichTextCompositeObject
1125 * This is the base for drawable objects.
1126 */
1127
1128 IMPLEMENT_CLASS(wxRichTextCompositeObject, wxRichTextObject)
1129
1130 wxRichTextCompositeObject::wxRichTextCompositeObject(wxRichTextObject* parent):
1131 wxRichTextObject(parent)
1132 {
1133 }
1134
1135 wxRichTextCompositeObject::~wxRichTextCompositeObject()
1136 {
1137 DeleteChildren();
1138 }
1139
1140 /// Get the nth child
1141 wxRichTextObject* wxRichTextCompositeObject::GetChild(size_t n) const
1142 {
1143 wxASSERT ( n < m_children.GetCount() );
1144
1145 return m_children.Item(n)->GetData();
1146 }
1147
1148 /// Append a child, returning the position
1149 size_t wxRichTextCompositeObject::AppendChild(wxRichTextObject* child)
1150 {
1151 m_children.Append(child);
1152 child->SetParent(this);
1153 return m_children.GetCount() - 1;
1154 }
1155
1156 /// Insert the child in front of the given object, or at the beginning
1157 bool wxRichTextCompositeObject::InsertChild(wxRichTextObject* child, wxRichTextObject* inFrontOf)
1158 {
1159 if (inFrontOf)
1160 {
1161 wxRichTextObjectList::compatibility_iterator node = m_children.Find(inFrontOf);
1162 m_children.Insert(node, child);
1163 }
1164 else
1165 m_children.Insert(child);
1166 child->SetParent(this);
1167
1168 return true;
1169 }
1170
1171 /// Delete the child
1172 bool wxRichTextCompositeObject::RemoveChild(wxRichTextObject* child, bool deleteChild)
1173 {
1174 wxRichTextObjectList::compatibility_iterator node = m_children.Find(child);
1175 if (node)
1176 {
1177 wxRichTextObject* obj = node->GetData();
1178 m_children.Erase(node);
1179 if (deleteChild)
1180 delete obj;
1181
1182 return true;
1183 }
1184 return false;
1185 }
1186
1187 /// Delete all children
1188 bool wxRichTextCompositeObject::DeleteChildren()
1189 {
1190 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1191 while (node)
1192 {
1193 wxRichTextObjectList::compatibility_iterator oldNode = node;
1194
1195 wxRichTextObject* child = node->GetData();
1196 child->Dereference(); // Only delete if reference count is zero
1197
1198 node = node->GetNext();
1199 m_children.Erase(oldNode);
1200 }
1201
1202 return true;
1203 }
1204
1205 /// Get the child count
1206 size_t wxRichTextCompositeObject::GetChildCount() const
1207 {
1208 return m_children.GetCount();
1209 }
1210
1211 /// Copy
1212 void wxRichTextCompositeObject::Copy(const wxRichTextCompositeObject& obj)
1213 {
1214 wxRichTextObject::Copy(obj);
1215
1216 DeleteChildren();
1217
1218 wxRichTextObjectList::compatibility_iterator node = obj.m_children.GetFirst();
1219 while (node)
1220 {
1221 wxRichTextObject* child = node->GetData();
1222 wxRichTextObject* newChild = child->Clone();
1223 newChild->SetParent(this);
1224 m_children.Append(newChild);
1225
1226 node = node->GetNext();
1227 }
1228 }
1229
1230 /// Hit-testing: returns a flag indicating hit test details, plus
1231 /// information about position
1232 int wxRichTextCompositeObject::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int flags)
1233 {
1234 if (!IsShown())
1235 return wxRICHTEXT_HITTEST_NONE;
1236
1237 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1238 while (node)
1239 {
1240 wxRichTextObject* child = node->GetData();
1241
1242 if (child->IsShown() && child->IsTopLevel() && (flags & wxRICHTEXT_HITTEST_NO_NESTED_OBJECTS))
1243 {
1244 // Just check if we hit the overall object
1245 int ret = child->wxRichTextObject::HitTest(dc, context, pt, textPosition, obj, contextObj, flags);
1246 if (ret != wxRICHTEXT_HITTEST_NONE)
1247 return ret;
1248 }
1249 else if (child->IsShown())
1250 {
1251 int ret = child->HitTest(dc, context, pt, textPosition, obj, contextObj, flags);
1252 if (ret != wxRICHTEXT_HITTEST_NONE)
1253 return ret;
1254 }
1255
1256 node = node->GetNext();
1257 }
1258
1259 return wxRICHTEXT_HITTEST_NONE;
1260 }
1261
1262 /// Finds the absolute position and row height for the given character position
1263 bool wxRichTextCompositeObject::FindPosition(wxDC& dc, wxRichTextDrawingContext& context, long index, wxPoint& pt, int* height, bool forceLineStart)
1264 {
1265 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1266 while (node)
1267 {
1268 wxRichTextObject* child = node->GetData();
1269
1270 // Don't recurse if the child is a top-level object,
1271 // such as a text box, because the character position will no longer
1272 // apply. By definition, a top-level object has its own range of
1273 // character positions.
1274 if (!child->IsTopLevel() && child->FindPosition(dc, context, index, pt, height, forceLineStart))
1275 return true;
1276
1277 node = node->GetNext();
1278 }
1279
1280 return false;
1281 }
1282
1283 /// Calculate range
1284 void wxRichTextCompositeObject::CalculateRange(long start, long& end)
1285 {
1286 long current = start;
1287 long lastEnd = current;
1288
1289 if (IsTopLevel())
1290 {
1291 current = 0;
1292 lastEnd = 0;
1293 }
1294
1295 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1296 while (node)
1297 {
1298 wxRichTextObject* child = node->GetData();
1299 long childEnd = 0;
1300
1301 child->CalculateRange(current, childEnd);
1302 lastEnd = childEnd;
1303
1304 current = childEnd + 1;
1305
1306 node = node->GetNext();
1307 }
1308
1309 if (IsTopLevel())
1310 {
1311 // A top-level object always has a range of size 1,
1312 // because its children don't count at this level.
1313 end = start;
1314 m_range.SetRange(start, start);
1315
1316 // An object with no children has zero length
1317 if (m_children.GetCount() == 0)
1318 lastEnd --;
1319 m_ownRange.SetRange(0, lastEnd);
1320 }
1321 else
1322 {
1323 end = lastEnd;
1324
1325 // An object with no children has zero length
1326 if (m_children.GetCount() == 0)
1327 end --;
1328
1329 m_range.SetRange(start, end);
1330 }
1331 }
1332
1333 /// Delete range from layout.
1334 bool wxRichTextCompositeObject::DeleteRange(const wxRichTextRange& range)
1335 {
1336 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1337
1338 while (node)
1339 {
1340 wxRichTextObject* obj = (wxRichTextObject*) node->GetData();
1341 wxRichTextObjectList::compatibility_iterator next = node->GetNext();
1342
1343 // Delete the range in each paragraph
1344
1345 // When a chunk has been deleted, internally the content does not
1346 // now match the ranges.
1347 // However, so long as deletion is not done on the same object twice this is OK.
1348 // If you may delete content from the same object twice, recalculate
1349 // the ranges inbetween DeleteRange calls by calling CalculateRanges, and
1350 // adjust the range you're deleting accordingly.
1351
1352 if (!obj->GetRange().IsOutside(range))
1353 {
1354 // No need to delete within a top-level object; just removing this object will do fine
1355 if (!obj->IsTopLevel())
1356 obj->DeleteRange(range);
1357
1358 // Delete an empty object, or paragraph within this range.
1359 if (obj->IsEmpty() ||
1360 (range.GetStart() <= obj->GetRange().GetStart() && range.GetEnd() >= obj->GetRange().GetEnd()))
1361 {
1362 // An empty paragraph has length 1, so won't be deleted unless the
1363 // whole range is deleted.
1364 RemoveChild(obj, true);
1365 }
1366 }
1367
1368 node = next;
1369 }
1370
1371 return true;
1372 }
1373
1374 /// Get any text in this object for the given range
1375 wxString wxRichTextCompositeObject::GetTextForRange(const wxRichTextRange& range) const
1376 {
1377 wxString text;
1378 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1379 while (node)
1380 {
1381 wxRichTextObject* child = node->GetData();
1382 wxRichTextRange childRange = range;
1383 if (!child->GetRange().IsOutside(range))
1384 {
1385 childRange.LimitTo(child->GetRange());
1386
1387 wxString childText = child->GetTextForRange(childRange);
1388
1389 text += childText;
1390 }
1391 node = node->GetNext();
1392 }
1393
1394 return text;
1395 }
1396
1397 /// Get the child object at the given character position
1398 wxRichTextObject* wxRichTextCompositeObject::GetChildAtPosition(long pos) const
1399 {
1400 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1401 while (node)
1402 {
1403 wxRichTextObject* child = node->GetData();
1404 if (child->GetRange().GetStart() == pos)
1405 return child;
1406 node = node->GetNext();
1407 }
1408 return NULL;
1409 }
1410
1411 /// Recursively merge all pieces that can be merged.
1412 bool wxRichTextCompositeObject::Defragment(const wxRichTextRange& range)
1413 {
1414 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1415 while (node)
1416 {
1417 wxRichTextObject* child = node->GetData();
1418 if (range == wxRICHTEXT_ALL || !child->GetRange().IsOutside(range))
1419 {
1420 wxRichTextCompositeObject* composite = wxDynamicCast(child, wxRichTextCompositeObject);
1421 if (composite)
1422 composite->Defragment();
1423
1424 if (node->GetNext())
1425 {
1426 wxRichTextObject* nextChild = node->GetNext()->GetData();
1427 if (child->CanMerge(nextChild) && child->Merge(nextChild))
1428 {
1429 nextChild->Dereference();
1430 m_children.Erase(node->GetNext());
1431
1432 // Don't set node -- we'll see if we can merge again with the next
1433 // child.
1434 }
1435 else
1436 node = node->GetNext();
1437 }
1438 else
1439 node = node->GetNext();
1440 }
1441 else
1442 node = node->GetNext();
1443 }
1444
1445 // Delete any remaining empty objects, but leave at least one empty object per composite object.
1446 if (GetChildCount() > 1)
1447 {
1448 node = m_children.GetFirst();
1449 while (node)
1450 {
1451 wxRichTextObjectList::compatibility_iterator next = node->GetNext();
1452 wxRichTextObject* child = node->GetData();
1453 if (range == wxRICHTEXT_ALL || !child->GetRange().IsOutside(range))
1454 {
1455 if (child->IsEmpty())
1456 {
1457 child->Dereference();
1458 m_children.Erase(node);
1459 }
1460 node = next;
1461 }
1462 else
1463 node = node->GetNext();
1464 }
1465 }
1466
1467 return true;
1468 }
1469
1470 /// Dump to output stream for debugging
1471 void wxRichTextCompositeObject::Dump(wxTextOutputStream& stream)
1472 {
1473 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1474 while (node)
1475 {
1476 wxRichTextObject* child = node->GetData();
1477 child->Dump(stream);
1478 node = node->GetNext();
1479 }
1480 }
1481
1482 /// Get/set the object size for the given range. Returns false if the range
1483 /// is invalid for this object.
1484 bool wxRichTextCompositeObject::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, wxPoint position, wxArrayInt* partialExtents) const
1485 {
1486 if (!range.IsWithin(GetRange()))
1487 return false;
1488
1489 wxSize sz;
1490
1491 wxArrayInt childExtents;
1492 wxArrayInt* p;
1493 if (partialExtents)
1494 p = & childExtents;
1495 else
1496 p = NULL;
1497
1498 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1499 while (node)
1500 {
1501 wxRichTextObject* child = node->GetData();
1502 if (!child->GetRange().IsOutside(range))
1503 {
1504 // Floating objects have a zero size within the paragraph.
1505 if (child->IsFloating())
1506 {
1507 if (partialExtents)
1508 {
1509 int lastSize;
1510 if (partialExtents->GetCount() > 0)
1511 lastSize = (*partialExtents)[partialExtents->GetCount()-1];
1512 else
1513 lastSize = 0;
1514
1515 partialExtents->Add(0 /* zero size */ + lastSize);
1516 }
1517 }
1518 else
1519 {
1520 wxSize childSize;
1521
1522 wxRichTextRange rangeToUse = range;
1523 rangeToUse.LimitTo(child->GetRange());
1524 if (child->IsTopLevel())
1525 rangeToUse = child->GetOwnRange();
1526
1527 int childDescent = 0;
1528
1529 // At present wxRICHTEXT_HEIGHT_ONLY is only fast if we're already cached the size,
1530 // but it's only going to be used after caching has taken place.
1531 if ((flags & wxRICHTEXT_HEIGHT_ONLY) && child->GetCachedSize().y != 0)
1532 {
1533 childDescent = child->GetDescent();
1534 childSize = child->GetCachedSize();
1535
1536 sz.y = wxMax(sz.y, childSize.y);
1537 sz.x += childSize.x;
1538 descent = wxMax(descent, childDescent);
1539 }
1540 else if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, context, flags, wxPoint(position.x + sz.x, position.y), p))
1541 {
1542 sz.y = wxMax(sz.y, childSize.y);
1543 sz.x += childSize.x;
1544 descent = wxMax(descent, childDescent);
1545
1546 if ((flags & wxRICHTEXT_CACHE_SIZE) && (rangeToUse == child->GetRange() || child->IsTopLevel()))
1547 {
1548 child->SetCachedSize(childSize);
1549 child->SetDescent(childDescent);
1550 }
1551
1552 if (partialExtents)
1553 {
1554 int lastSize;
1555 if (partialExtents->GetCount() > 0)
1556 lastSize = (*partialExtents)[partialExtents->GetCount()-1];
1557 else
1558 lastSize = 0;
1559
1560 size_t i;
1561 for (i = 0; i < childExtents.GetCount(); i++)
1562 {
1563 partialExtents->Add(childExtents[i] + lastSize);
1564 }
1565 }
1566 }
1567 }
1568
1569 if (p)
1570 p->Clear();
1571 }
1572
1573 node = node->GetNext();
1574 }
1575 size = sz;
1576 return true;
1577 }
1578
1579 // Invalidate the buffer. With no argument, invalidates whole buffer.
1580 void wxRichTextCompositeObject::Invalidate(const wxRichTextRange& invalidRange)
1581 {
1582 wxRichTextObject::Invalidate(invalidRange);
1583
1584 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1585 while (node)
1586 {
1587 wxRichTextObject* child = node->GetData();
1588 if (invalidRange != wxRICHTEXT_ALL && invalidRange != wxRICHTEXT_NONE && child->GetRange().IsOutside(invalidRange))
1589 {
1590 // Skip
1591 }
1592 else if (child->IsTopLevel())
1593 {
1594 if (invalidRange == wxRICHTEXT_NONE)
1595 child->Invalidate(wxRICHTEXT_NONE);
1596 else
1597 child->Invalidate(wxRICHTEXT_ALL); // All children must be invalidated if within parent range
1598 }
1599 else
1600 child->Invalidate(invalidRange);
1601 node = node->GetNext();
1602 }
1603 }
1604
1605 // Move the object recursively, by adding the offset from old to new
1606 void wxRichTextCompositeObject::Move(const wxPoint& pt)
1607 {
1608 wxPoint oldPos = GetPosition();
1609 SetPosition(pt);
1610 wxPoint offset = pt - oldPos;
1611
1612 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1613 while (node)
1614 {
1615 wxRichTextObject* child = node->GetData();
1616 wxPoint childPos = child->GetPosition() + offset;
1617 child->Move(childPos);
1618 node = node->GetNext();
1619 }
1620 }
1621
1622
1623 /*!
1624 * wxRichTextParagraphLayoutBox
1625 * This box knows how to lay out paragraphs.
1626 */
1627
1628 IMPLEMENT_DYNAMIC_CLASS(wxRichTextParagraphLayoutBox, wxRichTextCompositeObject)
1629
1630 wxRichTextParagraphLayoutBox::wxRichTextParagraphLayoutBox(wxRichTextObject* parent):
1631 wxRichTextCompositeObject(parent)
1632 {
1633 Init();
1634 }
1635
1636 wxRichTextParagraphLayoutBox::~wxRichTextParagraphLayoutBox()
1637 {
1638 if (m_floatCollector)
1639 {
1640 delete m_floatCollector;
1641 m_floatCollector = NULL;
1642 }
1643 }
1644
1645 /// Initialize the object.
1646 void wxRichTextParagraphLayoutBox::Init()
1647 {
1648 m_ctrl = NULL;
1649
1650 // For now, assume is the only box and has no initial size.
1651 m_range = wxRichTextRange(0, -1);
1652 m_ownRange = wxRichTextRange(0, -1);
1653
1654 m_invalidRange = wxRICHTEXT_ALL;
1655
1656 SetMargins(4);
1657 m_partialParagraph = false;
1658 m_floatCollector = NULL;
1659 }
1660
1661 void wxRichTextParagraphLayoutBox::Clear()
1662 {
1663 DeleteChildren();
1664
1665 if (m_floatCollector)
1666 delete m_floatCollector;
1667 m_floatCollector = NULL;
1668 m_partialParagraph = false;
1669 }
1670
1671 /// Copy
1672 void wxRichTextParagraphLayoutBox::Copy(const wxRichTextParagraphLayoutBox& obj)
1673 {
1674 Clear();
1675
1676 wxRichTextCompositeObject::Copy(obj);
1677
1678 m_partialParagraph = obj.m_partialParagraph;
1679 m_defaultAttributes = obj.m_defaultAttributes;
1680 }
1681
1682 // Gather information about floating objects; only gather floats for those paragraphs that
1683 // will not be formatted again due to optimization, after which floats will be gathered per-paragraph
1684 // during layout.
1685 bool wxRichTextParagraphLayoutBox::UpdateFloatingObjects(const wxRect& availableRect, wxRichTextObject* untilObj)
1686 {
1687 if (m_floatCollector != NULL)
1688 delete m_floatCollector;
1689 m_floatCollector = new wxRichTextFloatCollector(availableRect);
1690 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1691 // Only gather floats up to the point we'll start formatting paragraphs.
1692 while (untilObj && node && node->GetData() != untilObj)
1693 {
1694 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
1695 wxASSERT (child != NULL);
1696 if (child)
1697 m_floatCollector->CollectFloat(child);
1698 node = node->GetNext();
1699 }
1700
1701 return true;
1702 }
1703
1704 // Returns the style sheet associated with the overall buffer.
1705 wxRichTextStyleSheet* wxRichTextParagraphLayoutBox::GetStyleSheet() const
1706 {
1707 return GetBuffer() ? GetBuffer()->GetStyleSheet() : (wxRichTextStyleSheet*) NULL;
1708 }
1709
1710 // Get the number of floating objects at this level
1711 int wxRichTextParagraphLayoutBox::GetFloatingObjectCount() const
1712 {
1713 if (m_floatCollector)
1714 return m_floatCollector->GetFloatingObjectCount();
1715 else
1716 return 0;
1717 }
1718
1719 // Get a list of floating objects
1720 bool wxRichTextParagraphLayoutBox::GetFloatingObjects(wxRichTextObjectList& objects) const
1721 {
1722 if (m_floatCollector)
1723 {
1724 return m_floatCollector->GetFloatingObjects(objects);
1725 }
1726 else
1727 return false;
1728 }
1729
1730 // Calculate ranges
1731 void wxRichTextParagraphLayoutBox::UpdateRanges()
1732 {
1733 long start = 0;
1734 if (GetParent())
1735 start = GetRange().GetStart();
1736 long end;
1737 CalculateRange(start, end);
1738 }
1739
1740 // HitTest
1741 int wxRichTextParagraphLayoutBox::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int flags)
1742 {
1743 if (!IsShown())
1744 return wxRICHTEXT_HITTEST_NONE;
1745
1746 int ret = wxRICHTEXT_HITTEST_NONE;
1747 if (m_floatCollector && (flags & wxRICHTEXT_HITTEST_NO_FLOATING_OBJECTS) == 0)
1748 ret = m_floatCollector->HitTest(dc, context, pt, textPosition, obj, flags);
1749
1750 if (ret == wxRICHTEXT_HITTEST_NONE)
1751 return wxRichTextCompositeObject::HitTest(dc, context, pt, textPosition, obj, contextObj, flags);
1752 else
1753 {
1754 *contextObj = this;
1755 return ret;
1756 }
1757 }
1758
1759 /// Draw the floating objects
1760 void wxRichTextParagraphLayoutBox::DrawFloats(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
1761 {
1762 if (m_floatCollector)
1763 m_floatCollector->Draw(dc, context, range, selection, rect, descent, style);
1764 }
1765
1766 void wxRichTextParagraphLayoutBox::MoveAnchoredObjectToParagraph(wxRichTextParagraph* from, wxRichTextParagraph* to, wxRichTextObject* obj)
1767 {
1768 if (from == to)
1769 return;
1770
1771 from->RemoveChild(obj);
1772 to->AppendChild(obj);
1773 }
1774
1775 /// Draw the item
1776 bool wxRichTextParagraphLayoutBox::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
1777 {
1778 if (!IsShown())
1779 return true;
1780
1781 wxRect thisRect(GetPosition(), GetCachedSize());
1782
1783 wxRichTextAttr attr(GetAttributes());
1784 context.ApplyVirtualAttributes(attr, this);
1785
1786 int flags = style;
1787 if (selection.IsValid() && GetParentContainer() != this && selection.WithinSelection(GetRange().GetStart(), GetParentContainer()))
1788 flags |= wxRICHTEXT_DRAW_SELECTED;
1789
1790 // Don't draw guidelines if at top level
1791 int theseFlags = flags;
1792 if (!GetParent())
1793 theseFlags &= ~wxRICHTEXT_DRAW_GUIDELINES;
1794 DrawBoxAttributes(dc, GetBuffer(), attr, thisRect, theseFlags);
1795
1796 DrawFloats(dc, context, range, selection, rect, descent, style);
1797 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1798 while (node)
1799 {
1800 wxRichTextObject* child = node->GetData();
1801
1802 if (child && !child->GetRange().IsOutside(range))
1803 {
1804 wxRect childRect(child->GetPosition(), child->GetCachedSize());
1805 wxRichTextRange childRange = range;
1806 if (child->IsTopLevel())
1807 {
1808 childRange = child->GetOwnRange();
1809 }
1810
1811 if (((style & wxRICHTEXT_DRAW_IGNORE_CACHE) == 0) && childRect.GetTop() > rect.GetBottom())
1812 {
1813 // Stop drawing
1814 break;
1815 }
1816 else if (((style & wxRICHTEXT_DRAW_IGNORE_CACHE) == 0) && childRect.GetBottom() < rect.GetTop())
1817 {
1818 // Skip
1819 }
1820 else
1821 child->Draw(dc, context, childRange, selection, rect, descent, style);
1822 }
1823
1824 node = node->GetNext();
1825 }
1826 return true;
1827 }
1828
1829 /// Lay the item out
1830 bool wxRichTextParagraphLayoutBox::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& parentRect, int style)
1831 {
1832 SetPosition(rect.GetPosition());
1833
1834 if (!IsShown())
1835 return true;
1836
1837 wxRect availableSpace;
1838 bool formatRect = (style & wxRICHTEXT_LAYOUT_SPECIFIED_RECT) == wxRICHTEXT_LAYOUT_SPECIFIED_RECT;
1839
1840 wxRichTextAttr attr(GetAttributes());
1841 context.ApplyVirtualAttributes(attr, this);
1842
1843 // If only laying out a specific area, the passed rect has a different meaning:
1844 // the visible part of the buffer. This is used in wxRichTextCtrl::OnSize,
1845 // so that during a size, only the visible part will be relaid out, or
1846 // it would take too long causing flicker. As an approximation, we assume that
1847 // everything up to the start of the visible area is laid out correctly.
1848 if (formatRect)
1849 {
1850 wxRect rect2(0, 0, rect.width, rect.height);
1851 availableSpace = GetAvailableContentArea(dc, context, rect2);
1852
1853 // Invalidate the part of the buffer from the first visible line
1854 // to the end. If other parts of the buffer are currently invalid,
1855 // then they too will be taken into account if they are above
1856 // the visible point.
1857 long startPos = 0;
1858 wxRichTextLine* line = GetLineAtYPosition(rect.y);
1859 if (line)
1860 startPos = line->GetAbsoluteRange().GetStart();
1861
1862 Invalidate(wxRichTextRange(startPos, GetOwnRange().GetEnd()));
1863 }
1864 else
1865 {
1866 availableSpace = GetAvailableContentArea(dc, context, rect);
1867 }
1868
1869 int leftMargin, rightMargin, topMargin, bottomMargin;
1870 wxRichTextObject::GetTotalMargin(dc, GetBuffer(), attr, leftMargin, rightMargin,
1871 topMargin, bottomMargin);
1872
1873 int maxWidth = 0;
1874 int maxHeight = 0;
1875
1876 // The maximum paragraph maximum width, so we can set the overall maximum width for this object
1877 int maxMaxWidth = 0;
1878
1879 // The maximum paragraph minimum width, so we can set the overall minimum width for this object
1880 int maxMinWidth = 0;
1881
1882 // If we have vertical alignment, we must recalculate everything.
1883 bool hasVerticalAlignment = (attr.GetTextBoxAttr().HasVerticalAlignment() &&
1884 (attr.GetTextBoxAttr().GetVerticalAlignment() > wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT_TOP));
1885
1886 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1887
1888 bool layoutAll = true;
1889
1890 // Get invalid range, rounding to paragraph start/end.
1891 wxRichTextRange invalidRange = GetInvalidRange(true);
1892
1893 if (invalidRange == wxRICHTEXT_NONE && !formatRect)
1894 return true;
1895
1896 if (invalidRange == wxRICHTEXT_ALL || hasVerticalAlignment)
1897 layoutAll = true;
1898 else // If we know what range is affected, start laying out from that point on.
1899 if (invalidRange.GetStart() >= GetOwnRange().GetStart())
1900 {
1901 wxRichTextParagraph* firstParagraph = GetParagraphAtPosition(invalidRange.GetStart());
1902 if (firstParagraph)
1903 {
1904 wxRichTextObjectList::compatibility_iterator firstNode = m_children.Find(firstParagraph);
1905 wxRichTextObjectList::compatibility_iterator previousNode;
1906 if ( firstNode )
1907 previousNode = firstNode->GetPrevious();
1908 if (firstNode)
1909 {
1910 if (previousNode)
1911 {
1912 wxRichTextParagraph* previousParagraph = wxDynamicCast(previousNode->GetData(), wxRichTextParagraph);
1913 availableSpace.y = previousParagraph->GetPosition().y + previousParagraph->GetCachedSize().y;
1914 }
1915
1916 // Now we're going to start iterating from the first affected paragraph.
1917 node = firstNode;
1918
1919 layoutAll = false;
1920 }
1921 }
1922 }
1923
1924 // Gather information about only those floating objects that will not be formatted,
1925 // after which floats will be gathered per-paragraph during layout.
1926 UpdateFloatingObjects(availableSpace, node ? node->GetData() : (wxRichTextObject*) NULL);
1927
1928 // A way to force speedy rest-of-buffer layout (the 'else' below)
1929 bool forceQuickLayout = false;
1930
1931 // First get the size of the paragraphs we won't be laying out
1932 wxRichTextObjectList::compatibility_iterator n = m_children.GetFirst();
1933 while (n && n != node)
1934 {
1935 wxRichTextParagraph* child = wxDynamicCast(n->GetData(), wxRichTextParagraph);
1936 if (child)
1937 {
1938 maxWidth = wxMax(maxWidth, child->GetCachedSize().x);
1939 maxMinWidth = wxMax(maxMinWidth, child->GetMinSize().x);
1940 maxMaxWidth = wxMax(maxMaxWidth, child->GetMaxSize().x);
1941 }
1942 n = n->GetNext();
1943 }
1944
1945 while (node)
1946 {
1947 // Assume this box only contains paragraphs
1948
1949 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
1950 // Unsure if this is needed
1951 // wxCHECK_MSG( child, false, wxT("Unknown object in layout") );
1952
1953 if (child && child->IsShown())
1954 {
1955 // TODO: what if the child hasn't been laid out (e.g. involved in Undo) but still has 'old' lines
1956 if ( !forceQuickLayout &&
1957 (layoutAll ||
1958 child->GetLines().IsEmpty() ||
1959 !child->GetRange().IsOutside(invalidRange)) )
1960 {
1961 // Lays out the object first with a given amount of space, and then if no width was specified in attr,
1962 // lays out the object again using the minimum size
1963 child->LayoutToBestSize(dc, context, GetBuffer(),
1964 attr, child->GetAttributes(), availableSpace, rect, style&~wxRICHTEXT_LAYOUT_SPECIFIED_RECT);
1965
1966 // Layout must set the cached size
1967 availableSpace.y += child->GetCachedSize().y;
1968 maxWidth = wxMax(maxWidth, child->GetCachedSize().x);
1969 maxMinWidth = wxMax(maxMinWidth, child->GetMinSize().x);
1970 maxMaxWidth = wxMax(maxMaxWidth, child->GetMaxSize().x);
1971
1972 // If we're just formatting the visible part of the buffer,
1973 // and we're now past the bottom of the window, and we don't have any
1974 // floating objects (since they may cause wrapping to change for the rest of the
1975 // the buffer), start quick layout.
1976 if (!hasVerticalAlignment && formatRect && child->GetPosition().y > rect.GetBottom() && GetFloatingObjectCount() == 0)
1977 forceQuickLayout = true;
1978 }
1979 else
1980 {
1981 // We're outside the immediately affected range, so now let's just
1982 // move everything up or down. This assumes that all the children have previously
1983 // been laid out and have wrapped line lists associated with them.
1984 // TODO: check all paragraphs before the affected range.
1985
1986 int inc = availableSpace.y - child->GetPosition().y;
1987
1988 while (node)
1989 {
1990 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
1991 if (child)
1992 {
1993 if (child->GetLines().GetCount() == 0)
1994 {
1995 // Lays out the object first with a given amount of space, and then if no width was specified in attr,
1996 // lays out the object again using the minimum size
1997 child->LayoutToBestSize(dc, context, GetBuffer(),
1998 attr, child->GetAttributes(), availableSpace, rect, style&~wxRICHTEXT_LAYOUT_SPECIFIED_RECT);
1999
2000 //child->Layout(dc, availableChildRect, style);
2001 }
2002 else
2003 child->Move(wxPoint(child->GetPosition().x, child->GetPosition().y + inc));
2004
2005 availableSpace.y += child->GetCachedSize().y;
2006 maxWidth = wxMax(maxWidth, child->GetCachedSize().x);
2007 maxMinWidth = wxMax(maxMinWidth, child->GetMinSize().x);
2008 maxMaxWidth = wxMax(maxMaxWidth, child->GetMaxSize().x);
2009 }
2010
2011 node = node->GetNext();
2012 }
2013 break;
2014 }
2015 }
2016
2017 node = node->GetNext();
2018 }
2019
2020 node = m_children.GetLast();
2021 if (node && node->GetData()->IsShown())
2022 {
2023 wxRichTextObject* child = node->GetData();
2024 maxHeight = child->GetPosition().y - (GetPosition().y + topMargin) + child->GetCachedSize().y;
2025 }
2026 else
2027 maxHeight = 0; // topMargin + bottomMargin;
2028
2029 if (attr.GetTextBoxAttr().GetSize().GetWidth().IsValid())
2030 {
2031 wxRect r = AdjustAvailableSpace(dc, GetBuffer(), wxRichTextAttr() /* not used */, attr, parentRect, parentRect);
2032 int w = r.GetWidth();
2033
2034 // Convert external to content rect
2035 w = w - leftMargin - rightMargin;
2036 maxWidth = wxMax(maxWidth, w);
2037 maxMaxWidth = wxMax(maxMaxWidth, w);
2038 }
2039
2040 // TODO: (also in para layout) should set the
2041 // object's size to an absolute one if specified,
2042 // but if not specified, calculate it from content.
2043
2044 // We need to add back the margins etc.
2045 {
2046 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
2047 contentRect = wxRect(wxPoint(0, 0), wxSize(maxWidth, maxHeight));
2048 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
2049 SetCachedSize(marginRect.GetSize());
2050 }
2051
2052 // The maximum size is the greatest of all maximum widths for all paragraphs.
2053 {
2054 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
2055 contentRect = wxRect(wxPoint(0, 0), wxSize(maxMaxWidth, maxHeight)); // Actually max height is a lie, we can't know it
2056 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
2057 SetMaxSize(marginRect.GetSize());
2058 }
2059
2060 // The minimum size is the greatest of all minimum widths for all paragraphs.
2061 {
2062 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
2063 contentRect = wxRect(wxPoint(0, 0), wxSize(maxMinWidth, maxHeight)); // Actually max height is a lie, we can't know it
2064 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
2065 SetMinSize(marginRect.GetSize());
2066 }
2067
2068 if (attr.GetTextBoxAttr().HasVerticalAlignment() &&
2069 (attr.GetTextBoxAttr().GetVerticalAlignment() > wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT_TOP))
2070 {
2071 int yOffset = 0;
2072 int leftOverSpace = availableSpace.height - topMargin - bottomMargin - maxHeight;
2073 if (leftOverSpace > 0)
2074 {
2075 if (attr.GetTextBoxAttr().GetVerticalAlignment() == wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT_CENTRE)
2076 {
2077 yOffset = (leftOverSpace/2);
2078 }
2079 else if (attr.GetTextBoxAttr().GetVerticalAlignment() == wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT_BOTTOM)
2080 {
2081 yOffset = leftOverSpace;
2082 }
2083 }
2084
2085 // Move all the children to vertically align the content
2086 // This doesn't take into account floating objects, unfortunately.
2087 if (yOffset != 0)
2088 {
2089 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2090 while (node)
2091 {
2092 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2093 if (child)
2094 child->Move(wxPoint(child->GetPosition().x, child->GetPosition().y + yOffset));
2095
2096 node = node->GetNext();
2097 }
2098 }
2099 }
2100
2101 m_invalidRange = wxRICHTEXT_NONE;
2102
2103 return true;
2104 }
2105
2106 /// Get/set the size for the given range.
2107 bool wxRichTextParagraphLayoutBox::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, wxPoint position, wxArrayInt* WXUNUSED(partialExtents)) const
2108 {
2109 wxSize sz;
2110
2111 wxRichTextObjectList::compatibility_iterator startPara = wxRichTextObjectList::compatibility_iterator();
2112 wxRichTextObjectList::compatibility_iterator endPara = wxRichTextObjectList::compatibility_iterator();
2113
2114 // First find the first paragraph whose starting position is within the range.
2115 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2116 while (node)
2117 {
2118 // child is a paragraph
2119 wxRichTextObject* child = node->GetData();
2120 const wxRichTextRange& r = child->GetRange();
2121
2122 if (r.GetStart() <= range.GetStart() && r.GetEnd() >= range.GetStart())
2123 {
2124 startPara = node;
2125 break;
2126 }
2127
2128 node = node->GetNext();
2129 }
2130
2131 // Next find the last paragraph containing part of the range
2132 node = m_children.GetFirst();
2133 while (node)
2134 {
2135 // child is a paragraph
2136 wxRichTextObject* child = node->GetData();
2137 const wxRichTextRange& r = child->GetRange();
2138
2139 if (r.GetStart() <= range.GetEnd() && r.GetEnd() >= range.GetEnd())
2140 {
2141 endPara = node;
2142 break;
2143 }
2144
2145 node = node->GetNext();
2146 }
2147
2148 if (!startPara || !endPara)
2149 return false;
2150
2151 // Now we can add up the sizes
2152 for (node = startPara; node ; node = node->GetNext())
2153 {
2154 // child is a paragraph
2155 wxRichTextObject* child = node->GetData();
2156 const wxRichTextRange& childRange = child->GetRange();
2157 wxRichTextRange rangeToFind = range;
2158 rangeToFind.LimitTo(childRange);
2159
2160 if (child->IsTopLevel())
2161 rangeToFind = child->GetOwnRange();
2162
2163 wxSize childSize;
2164
2165 int childDescent = 0;
2166 child->GetRangeSize(rangeToFind, childSize, childDescent, dc, context, flags, position);
2167
2168 descent = wxMax(childDescent, descent);
2169
2170 sz.x = wxMax(sz.x, childSize.x);
2171 sz.y += childSize.y;
2172
2173 if (node == endPara)
2174 break;
2175 }
2176
2177 size = sz;
2178
2179 return true;
2180 }
2181
2182 /// Get the paragraph at the given position
2183 wxRichTextParagraph* wxRichTextParagraphLayoutBox::GetParagraphAtPosition(long pos, bool caretPosition) const
2184 {
2185 if (caretPosition)
2186 pos ++;
2187
2188 // First find the first paragraph whose starting position is within the range.
2189 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2190 while (node)
2191 {
2192 // child is a paragraph
2193 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2194 // wxASSERT (child != NULL);
2195
2196 if (child)
2197 {
2198 // Return first child in buffer if position is -1
2199 // if (pos == -1)
2200 // return child;
2201
2202 if (child->GetRange().Contains(pos))
2203 return child;
2204 }
2205
2206 node = node->GetNext();
2207 }
2208 return NULL;
2209 }
2210
2211 /// Get the line at the given position
2212 wxRichTextLine* wxRichTextParagraphLayoutBox::GetLineAtPosition(long pos, bool caretPosition) const
2213 {
2214 if (caretPosition)
2215 pos ++;
2216
2217 // First find the first paragraph whose starting position is within the range.
2218 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2219 while (node)
2220 {
2221 wxRichTextObject* obj = (wxRichTextObject*) node->GetData();
2222 if (obj->GetRange().Contains(pos))
2223 {
2224 // child is a paragraph
2225 wxRichTextParagraph* child = wxDynamicCast(obj, wxRichTextParagraph);
2226 // wxASSERT (child != NULL);
2227
2228 if (child)
2229 {
2230 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
2231 while (node2)
2232 {
2233 wxRichTextLine* line = node2->GetData();
2234
2235 wxRichTextRange range = line->GetAbsoluteRange();
2236
2237 if (range.Contains(pos) ||
2238
2239 // If the position is end-of-paragraph, then return the last line of
2240 // of the paragraph.
2241 ((range.GetEnd() == child->GetRange().GetEnd()-1) && (pos == child->GetRange().GetEnd())))
2242 return line;
2243
2244 node2 = node2->GetNext();
2245 }
2246 }
2247 }
2248
2249 node = node->GetNext();
2250 }
2251
2252 int lineCount = GetLineCount();
2253 if (lineCount > 0)
2254 return GetLineForVisibleLineNumber(lineCount-1);
2255 else
2256 return NULL;
2257 }
2258
2259 /// Get the line at the given y pixel position, or the last line.
2260 wxRichTextLine* wxRichTextParagraphLayoutBox::GetLineAtYPosition(int y) const
2261 {
2262 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2263 while (node)
2264 {
2265 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2266 // wxASSERT (child != NULL);
2267
2268 if (child)
2269 {
2270 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
2271 while (node2)
2272 {
2273 wxRichTextLine* line = node2->GetData();
2274
2275 wxRect rect(line->GetRect());
2276
2277 if (y <= rect.GetBottom())
2278 return line;
2279
2280 node2 = node2->GetNext();
2281 }
2282 }
2283
2284 node = node->GetNext();
2285 }
2286
2287 // Return last line
2288 int lineCount = GetLineCount();
2289 if (lineCount > 0)
2290 return GetLineForVisibleLineNumber(lineCount-1);
2291 else
2292 return NULL;
2293 }
2294
2295 /// Get the number of visible lines
2296 int wxRichTextParagraphLayoutBox::GetLineCount() const
2297 {
2298 int count = 0;
2299
2300 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2301 while (node)
2302 {
2303 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2304 // wxASSERT (child != NULL);
2305
2306 if (child)
2307 count += child->GetLines().GetCount();
2308
2309 node = node->GetNext();
2310 }
2311 return count;
2312 }
2313
2314
2315 /// Get the paragraph for a given line
2316 wxRichTextParagraph* wxRichTextParagraphLayoutBox::GetParagraphForLine(wxRichTextLine* line) const
2317 {
2318 return GetParagraphAtPosition(line->GetAbsoluteRange().GetStart());
2319 }
2320
2321 /// Get the line size at the given position
2322 wxSize wxRichTextParagraphLayoutBox::GetLineSizeAtPosition(long pos, bool caretPosition) const
2323 {
2324 wxRichTextLine* line = GetLineAtPosition(pos, caretPosition);
2325 if (line)
2326 {
2327 return line->GetSize();
2328 }
2329 else
2330 return wxSize(0, 0);
2331 }
2332
2333
2334 /// Convenience function to add a paragraph of text
2335 wxRichTextRange wxRichTextParagraphLayoutBox::AddParagraph(const wxString& text, wxRichTextAttr* paraStyle)
2336 {
2337 // Don't use the base style, just the default style, and the base style will
2338 // be combined at display time.
2339 // Divide into paragraph and character styles.
2340
2341 wxRichTextAttr defaultCharStyle;
2342 wxRichTextAttr defaultParaStyle;
2343
2344 // If the default style is a named paragraph style, don't apply any character formatting
2345 // to the initial text string.
2346 if (GetDefaultStyle().HasParagraphStyleName() && GetStyleSheet())
2347 {
2348 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(GetDefaultStyle().GetParagraphStyleName());
2349 if (def)
2350 defaultParaStyle = def->GetStyleMergedWithBase(GetStyleSheet());
2351 }
2352 else
2353 wxRichTextSplitParaCharStyles(GetDefaultStyle(), defaultParaStyle, defaultCharStyle);
2354
2355 wxRichTextAttr* pStyle = paraStyle ? paraStyle : (wxRichTextAttr*) & defaultParaStyle;
2356 wxRichTextAttr* cStyle = & defaultCharStyle;
2357
2358 wxRichTextParagraph* para = new wxRichTextParagraph(text, this, pStyle, cStyle);
2359
2360 AppendChild(para);
2361
2362 UpdateRanges();
2363
2364 return para->GetRange();
2365 }
2366
2367 /// Adds multiple paragraphs, based on newlines.
2368 wxRichTextRange wxRichTextParagraphLayoutBox::AddParagraphs(const wxString& text, wxRichTextAttr* paraStyle)
2369 {
2370 // Don't use the base style, just the default style, and the base style will
2371 // be combined at display time.
2372 // Divide into paragraph and character styles.
2373
2374 wxRichTextAttr defaultCharStyle;
2375 wxRichTextAttr defaultParaStyle;
2376
2377 // If the default style is a named paragraph style, don't apply any character formatting
2378 // to the initial text string.
2379 if (GetDefaultStyle().HasParagraphStyleName() && GetStyleSheet())
2380 {
2381 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(GetDefaultStyle().GetParagraphStyleName());
2382 if (def)
2383 defaultParaStyle = def->GetStyleMergedWithBase(GetStyleSheet());
2384 }
2385 else
2386 wxRichTextSplitParaCharStyles(GetDefaultStyle(), defaultParaStyle, defaultCharStyle);
2387
2388 wxRichTextAttr* pStyle = paraStyle ? paraStyle : (wxRichTextAttr*) & defaultParaStyle;
2389 wxRichTextAttr* cStyle = & defaultCharStyle;
2390
2391 wxRichTextParagraph* firstPara = NULL;
2392 wxRichTextParagraph* lastPara = NULL;
2393
2394 wxRichTextRange range(-1, -1);
2395
2396 size_t i = 0;
2397 size_t len = text.length();
2398 wxString line;
2399 wxRichTextParagraph* para = new wxRichTextParagraph(wxEmptyString, this, pStyle, cStyle);
2400
2401 AppendChild(para);
2402
2403 firstPara = para;
2404 lastPara = para;
2405
2406 while (i < len)
2407 {
2408 wxChar ch = text[i];
2409 if (ch == wxT('\n') || ch == wxT('\r'))
2410 {
2411 if (i != (len-1))
2412 {
2413 wxRichTextPlainText* plainText = (wxRichTextPlainText*) para->GetChildren().GetFirst()->GetData();
2414 plainText->SetText(line);
2415
2416 para = new wxRichTextParagraph(wxEmptyString, this, pStyle, cStyle);
2417
2418 AppendChild(para);
2419
2420 lastPara = para;
2421 line = wxEmptyString;
2422 }
2423 }
2424 else
2425 line += ch;
2426
2427 i ++;
2428 }
2429
2430 if (!line.empty())
2431 {
2432 wxRichTextPlainText* plainText = (wxRichTextPlainText*) para->GetChildren().GetFirst()->GetData();
2433 plainText->SetText(line);
2434 }
2435
2436 UpdateRanges();
2437
2438 return wxRichTextRange(firstPara->GetRange().GetStart(), lastPara->GetRange().GetEnd());
2439 }
2440
2441 /// Convenience function to add an image
2442 wxRichTextRange wxRichTextParagraphLayoutBox::AddImage(const wxImage& image, wxRichTextAttr* paraStyle)
2443 {
2444 // Don't use the base style, just the default style, and the base style will
2445 // be combined at display time.
2446 // Divide into paragraph and character styles.
2447
2448 wxRichTextAttr defaultCharStyle;
2449 wxRichTextAttr defaultParaStyle;
2450
2451 // If the default style is a named paragraph style, don't apply any character formatting
2452 // to the initial text string.
2453 if (GetDefaultStyle().HasParagraphStyleName() && GetStyleSheet())
2454 {
2455 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(GetDefaultStyle().GetParagraphStyleName());
2456 if (def)
2457 defaultParaStyle = def->GetStyleMergedWithBase(GetStyleSheet());
2458 }
2459 else
2460 wxRichTextSplitParaCharStyles(GetDefaultStyle(), defaultParaStyle, defaultCharStyle);
2461
2462 wxRichTextAttr* pStyle = paraStyle ? paraStyle : (wxRichTextAttr*) & defaultParaStyle;
2463 wxRichTextAttr* cStyle = & defaultCharStyle;
2464
2465 wxRichTextParagraph* para = new wxRichTextParagraph(this, pStyle);
2466 AppendChild(para);
2467 para->AppendChild(new wxRichTextImage(image, this, cStyle));
2468
2469 UpdateRanges();
2470
2471 return para->GetRange();
2472 }
2473
2474
2475 /// Insert fragment into this box at the given position. If partialParagraph is true,
2476 /// it is assumed that the last (or only) paragraph is just a piece of data with no paragraph
2477 /// marker.
2478
2479 bool wxRichTextParagraphLayoutBox::InsertFragment(long position, wxRichTextParagraphLayoutBox& fragment)
2480 {
2481 // First, find the first paragraph whose starting position is within the range.
2482 wxRichTextParagraph* para = GetParagraphAtPosition(position);
2483 if (para)
2484 {
2485 wxRichTextAttr originalAttr = para->GetAttributes();
2486
2487 wxRichTextObjectList::compatibility_iterator node = m_children.Find(para);
2488
2489 // Now split at this position, returning the object to insert the new
2490 // ones in front of.
2491 wxRichTextObject* nextObject = para->SplitAt(position);
2492
2493 // Special case: partial paragraph, just one paragraph. Might be a small amount of
2494 // text, for example, so let's optimize.
2495
2496 if (fragment.GetPartialParagraph() && fragment.GetChildren().GetCount() == 1)
2497 {
2498 // Add the first para to this para...
2499 wxRichTextObjectList::compatibility_iterator firstParaNode = fragment.GetChildren().GetFirst();
2500 if (!firstParaNode)
2501 return false;
2502
2503 // Iterate through the fragment paragraph inserting the content into this paragraph.
2504 wxRichTextParagraph* firstPara = wxDynamicCast(firstParaNode->GetData(), wxRichTextParagraph);
2505 wxASSERT (firstPara != NULL);
2506
2507 wxRichTextObjectList::compatibility_iterator objectNode = firstPara->GetChildren().GetFirst();
2508 while (objectNode)
2509 {
2510 wxRichTextObject* newObj = objectNode->GetData()->Clone();
2511
2512 if (!nextObject)
2513 {
2514 // Append
2515 para->AppendChild(newObj);
2516 }
2517 else
2518 {
2519 // Insert before nextObject
2520 para->InsertChild(newObj, nextObject);
2521 }
2522
2523 objectNode = objectNode->GetNext();
2524 }
2525
2526 return true;
2527 }
2528 else
2529 {
2530 // Procedure for inserting a fragment consisting of a number of
2531 // paragraphs:
2532 //
2533 // 1. Remove and save the content that's after the insertion point, for adding
2534 // back once we've added the fragment.
2535 // 2. Add the content from the first fragment paragraph to the current
2536 // paragraph.
2537 // 3. Add remaining fragment paragraphs after the current paragraph.
2538 // 4. Add back the saved content from the first paragraph. If partialParagraph
2539 // is true, add it to the last paragraph added and not a new one.
2540
2541 // 1. Remove and save objects after split point.
2542 wxList savedObjects;
2543 if (nextObject)
2544 para->MoveToList(nextObject, savedObjects);
2545
2546 // 2. Add the content from the 1st fragment paragraph.
2547 wxRichTextObjectList::compatibility_iterator firstParaNode = fragment.GetChildren().GetFirst();
2548 if (!firstParaNode)
2549 return false;
2550
2551 wxRichTextParagraph* firstPara = wxDynamicCast(firstParaNode->GetData(), wxRichTextParagraph);
2552 wxASSERT(firstPara != NULL);
2553
2554 if (!(fragment.GetAttributes().GetFlags() & wxTEXT_ATTR_KEEP_FIRST_PARA_STYLE))
2555 para->SetAttributes(firstPara->GetAttributes());
2556
2557 // Save empty paragraph attributes for appending later
2558 // These are character attributes deliberately set for a new paragraph. Without this,
2559 // we couldn't pass default attributes when appending a new paragraph.
2560 wxRichTextAttr emptyParagraphAttributes;
2561
2562 wxRichTextObjectList::compatibility_iterator objectNode = firstPara->GetChildren().GetFirst();
2563
2564 if (objectNode && firstPara->GetChildren().GetCount() == 1 && objectNode->GetData()->IsEmpty())
2565 emptyParagraphAttributes = objectNode->GetData()->GetAttributes();
2566
2567 while (objectNode)
2568 {
2569 wxRichTextObject* newObj = objectNode->GetData()->Clone();
2570
2571 // Append
2572 para->AppendChild(newObj);
2573
2574 objectNode = objectNode->GetNext();
2575 }
2576
2577 // 3. Add remaining fragment paragraphs after the current paragraph.
2578 wxRichTextObjectList::compatibility_iterator nextParagraphNode = node->GetNext();
2579 wxRichTextObject* nextParagraph = NULL;
2580 if (nextParagraphNode)
2581 nextParagraph = nextParagraphNode->GetData();
2582
2583 wxRichTextObjectList::compatibility_iterator i = fragment.GetChildren().GetFirst()->GetNext();
2584 wxRichTextParagraph* finalPara = para;
2585
2586 bool needExtraPara = (!i || !fragment.GetPartialParagraph());
2587
2588 // If there was only one paragraph, we need to insert a new one.
2589 while (i)
2590 {
2591 wxRichTextParagraph* para = wxDynamicCast(i->GetData(), wxRichTextParagraph);
2592 wxASSERT( para != NULL );
2593
2594 finalPara = (wxRichTextParagraph*) para->Clone();
2595
2596 if (nextParagraph)
2597 InsertChild(finalPara, nextParagraph);
2598 else
2599 AppendChild(finalPara);
2600
2601 i = i->GetNext();
2602 }
2603
2604 // If there was only one paragraph, or we have full paragraphs in our fragment,
2605 // we need to insert a new one.
2606 if (needExtraPara)
2607 {
2608 finalPara = new wxRichTextParagraph;
2609
2610 if (nextParagraph)
2611 InsertChild(finalPara, nextParagraph);
2612 else
2613 AppendChild(finalPara);
2614 }
2615
2616 // 4. Add back the remaining content.
2617 if (finalPara)
2618 {
2619 if (nextObject)
2620 finalPara->MoveFromList(savedObjects);
2621
2622 // Ensure there's at least one object
2623 if (finalPara->GetChildCount() == 0)
2624 {
2625 wxRichTextPlainText* text = new wxRichTextPlainText(wxEmptyString);
2626 text->SetAttributes(emptyParagraphAttributes);
2627
2628 finalPara->AppendChild(text);
2629 }
2630 }
2631
2632 if ((fragment.GetAttributes().GetFlags() & wxTEXT_ATTR_KEEP_FIRST_PARA_STYLE) && firstPara)
2633 finalPara->SetAttributes(firstPara->GetAttributes());
2634 else if (finalPara && finalPara != para)
2635 finalPara->SetAttributes(originalAttr);
2636
2637 return true;
2638 }
2639 }
2640 else
2641 {
2642 // Append
2643 wxRichTextObjectList::compatibility_iterator i = fragment.GetChildren().GetFirst();
2644 while (i)
2645 {
2646 wxRichTextParagraph* para = wxDynamicCast(i->GetData(), wxRichTextParagraph);
2647 wxASSERT( para != NULL );
2648
2649 AppendChild(para->Clone());
2650
2651 i = i->GetNext();
2652 }
2653
2654 return true;
2655 }
2656 }
2657
2658 /// Make a copy of the fragment corresponding to the given range, putting it in 'fragment'.
2659 /// If there was an incomplete paragraph at the end, partialParagraph is set to true.
2660 bool wxRichTextParagraphLayoutBox::CopyFragment(const wxRichTextRange& range, wxRichTextParagraphLayoutBox& fragment)
2661 {
2662 wxRichTextObjectList::compatibility_iterator i = GetChildren().GetFirst();
2663 while (i)
2664 {
2665 wxRichTextParagraph* para = wxDynamicCast(i->GetData(), wxRichTextParagraph);
2666 wxASSERT( para != NULL );
2667
2668 if (!para->GetRange().IsOutside(range))
2669 {
2670 fragment.AppendChild(para->Clone());
2671 }
2672 i = i->GetNext();
2673 }
2674
2675 // Now top and tail the first and last paragraphs in our new fragment (which might be the same).
2676 if (!fragment.IsEmpty())
2677 {
2678 wxRichTextParagraph* firstPara = wxDynamicCast(fragment.GetChildren().GetFirst()->GetData(), wxRichTextParagraph);
2679 wxASSERT( firstPara != NULL );
2680
2681 wxRichTextParagraph* lastPara = wxDynamicCast(fragment.GetChildren().GetLast()->GetData(), wxRichTextParagraph);
2682 wxASSERT( lastPara != NULL );
2683
2684 if (!firstPara || !lastPara)
2685 return false;
2686
2687 bool isFragment = (range.GetEnd() < lastPara->GetRange().GetEnd());
2688
2689 long firstPos = firstPara->GetRange().GetStart();
2690
2691 // Adjust for renumbering from zero
2692 wxRichTextRange topTailRange(range.GetStart() - firstPos, range.GetEnd() - firstPos);
2693
2694 long end;
2695 fragment.CalculateRange(0, end);
2696
2697 // Chop off the start of the paragraph
2698 if (topTailRange.GetStart() > 0)
2699 {
2700 wxRichTextRange r(0, topTailRange.GetStart()-1);
2701 firstPara->DeleteRange(r);
2702
2703 // Make sure the numbering is correct
2704 fragment.CalculateRange(0, end);
2705
2706 // Now, we've deleted some positions, so adjust the range
2707 // accordingly.
2708 topTailRange.SetStart(range.GetLength());
2709 topTailRange.SetEnd(fragment.GetOwnRange().GetEnd());
2710 }
2711 else
2712 {
2713 topTailRange.SetStart(range.GetLength());
2714 topTailRange.SetEnd(fragment.GetOwnRange().GetEnd());
2715 }
2716
2717 if (topTailRange.GetStart() < lastPara->GetRange().GetEnd())
2718 {
2719 lastPara->DeleteRange(topTailRange);
2720
2721 // Make sure the numbering is correct
2722 long end;
2723 fragment.CalculateRange(0, end);
2724
2725 // We only have part of a paragraph at the end
2726 fragment.SetPartialParagraph(true);
2727 }
2728 else
2729 {
2730 // We have a partial paragraph (don't save last new paragraph marker)
2731 // or complete paragraph
2732 fragment.SetPartialParagraph(isFragment);
2733 }
2734 }
2735
2736 return true;
2737 }
2738
2739 /// Given a position, get the number of the visible line (potentially many to a paragraph),
2740 /// starting from zero at the start of the buffer.
2741 long wxRichTextParagraphLayoutBox::GetVisibleLineNumber(long pos, bool caretPosition, bool startOfLine) const
2742 {
2743 if (caretPosition)
2744 pos ++;
2745
2746 int lineCount = 0;
2747
2748 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2749 while (node)
2750 {
2751 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2752 // wxASSERT( child != NULL );
2753
2754 if (child)
2755 {
2756 if (child->GetRange().Contains(pos))
2757 {
2758 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
2759 while (node2)
2760 {
2761 wxRichTextLine* line = node2->GetData();
2762 wxRichTextRange lineRange = line->GetAbsoluteRange();
2763
2764 if (lineRange.Contains(pos) || pos == lineRange.GetStart())
2765 {
2766 // If the caret is displayed at the end of the previous wrapped line,
2767 // we want to return the line it's _displayed_ at (not the actual line
2768 // containing the position).
2769 if (lineRange.GetStart() == pos && !startOfLine && child->GetRange().GetStart() != pos)
2770 return lineCount - 1;
2771 else
2772 return lineCount;
2773 }
2774
2775 lineCount ++;
2776
2777 node2 = node2->GetNext();
2778 }
2779 // If we didn't find it in the lines, it must be
2780 // the last position of the paragraph. So return the last line.
2781 return lineCount-1;
2782 }
2783 else
2784 lineCount += child->GetLines().GetCount();
2785 }
2786
2787 node = node->GetNext();
2788 }
2789
2790 // Not found
2791 return -1;
2792 }
2793
2794 /// Given a line number, get the corresponding wxRichTextLine object.
2795 wxRichTextLine* wxRichTextParagraphLayoutBox::GetLineForVisibleLineNumber(long lineNumber) const
2796 {
2797 int lineCount = 0;
2798
2799 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2800 while (node)
2801 {
2802 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2803 // wxASSERT(child != NULL);
2804
2805 if (child)
2806 {
2807 if (lineNumber < (int) (child->GetLines().GetCount() + lineCount))
2808 {
2809 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
2810 while (node2)
2811 {
2812 wxRichTextLine* line = node2->GetData();
2813
2814 if (lineCount == lineNumber)
2815 return line;
2816
2817 lineCount ++;
2818
2819 node2 = node2->GetNext();
2820 }
2821 }
2822 else
2823 lineCount += child->GetLines().GetCount();
2824 }
2825
2826 node = node->GetNext();
2827 }
2828
2829 // Didn't find it
2830 return NULL;
2831 }
2832
2833 /// Delete range from layout.
2834 bool wxRichTextParagraphLayoutBox::DeleteRange(const wxRichTextRange& range)
2835 {
2836 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2837
2838 wxRichTextParagraph* firstPara = NULL;
2839 while (node)
2840 {
2841 wxRichTextParagraph* obj = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2842 // wxASSERT (obj != NULL);
2843
2844 wxRichTextObjectList::compatibility_iterator next = node->GetNext();
2845
2846 if (obj)
2847 {
2848 // Delete the range in each paragraph
2849
2850 if (!obj->GetRange().IsOutside(range))
2851 {
2852 // Deletes the content of this object within the given range
2853 obj->DeleteRange(range);
2854
2855 wxRichTextRange thisRange = obj->GetRange();
2856 wxRichTextAttr thisAttr = obj->GetAttributes();
2857
2858 // If the whole paragraph is within the range to delete,
2859 // delete the whole thing.
2860 if (range.GetStart() <= thisRange.GetStart() && range.GetEnd() >= thisRange.GetEnd())
2861 {
2862 // Delete the whole object
2863 RemoveChild(obj, true);
2864 obj = NULL;
2865 }
2866 else if (!firstPara)
2867 firstPara = obj;
2868
2869 // If the range includes the paragraph end, we need to join this
2870 // and the next paragraph.
2871 if (range.GetEnd() <= thisRange.GetEnd())
2872 {
2873 // We need to move the objects from the next paragraph
2874 // to this paragraph
2875
2876 wxRichTextParagraph* nextParagraph = NULL;
2877 if ((range.GetEnd() < thisRange.GetEnd()) && obj)
2878 nextParagraph = obj;
2879 else
2880 {
2881 // We're ending at the end of the paragraph, so merge the _next_ paragraph.
2882 if (next)
2883 nextParagraph = wxDynamicCast(next->GetData(), wxRichTextParagraph);
2884 }
2885
2886 bool applyFinalParagraphStyle = firstPara && nextParagraph && nextParagraph != firstPara;
2887
2888 wxRichTextAttr nextParaAttr;
2889 if (applyFinalParagraphStyle)
2890 {
2891 // Special case when deleting the end of a paragraph - use _this_ paragraph's style,
2892 // not the next one.
2893 if (range.GetStart() == range.GetEnd() && range.GetStart() == thisRange.GetEnd())
2894 nextParaAttr = thisAttr;
2895 else
2896 nextParaAttr = nextParagraph->GetAttributes();
2897 }
2898
2899 if (firstPara && nextParagraph && firstPara != nextParagraph)
2900 {
2901 // Move the objects to the previous para
2902 wxRichTextObjectList::compatibility_iterator node1 = nextParagraph->GetChildren().GetFirst();
2903
2904 while (node1)
2905 {
2906 wxRichTextObject* obj1 = node1->GetData();
2907
2908 firstPara->AppendChild(obj1);
2909
2910 wxRichTextObjectList::compatibility_iterator next1 = node1->GetNext();
2911 nextParagraph->GetChildren().Erase(node1);
2912
2913 node1 = next1;
2914 }
2915
2916 // Delete the paragraph
2917 RemoveChild(nextParagraph, true);
2918 }
2919
2920 // Avoid empty paragraphs
2921 if (firstPara && firstPara->GetChildren().GetCount() == 0)
2922 {
2923 wxRichTextPlainText* text = new wxRichTextPlainText(wxEmptyString);
2924 firstPara->AppendChild(text);
2925 }
2926
2927 if (applyFinalParagraphStyle)
2928 firstPara->SetAttributes(nextParaAttr);
2929
2930 return true;
2931 }
2932 }
2933 }
2934
2935 node = next;
2936 }
2937
2938 return true;
2939 }
2940
2941 /// Get any text in this object for the given range
2942 wxString wxRichTextParagraphLayoutBox::GetTextForRange(const wxRichTextRange& range) const
2943 {
2944 int lineCount = 0;
2945 wxString text;
2946 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2947 while (node)
2948 {
2949 wxRichTextObject* child = node->GetData();
2950 if (!child->GetRange().IsOutside(range))
2951 {
2952 wxRichTextRange childRange = range;
2953 childRange.LimitTo(child->GetRange());
2954
2955 wxString childText = child->GetTextForRange(childRange);
2956
2957 text += childText;
2958
2959 if ((childRange.GetEnd() == child->GetRange().GetEnd()) && node->GetNext())
2960 text += wxT("\n");
2961
2962 lineCount ++;
2963 }
2964 node = node->GetNext();
2965 }
2966
2967 return text;
2968 }
2969
2970 /// Get all the text
2971 wxString wxRichTextParagraphLayoutBox::GetText() const
2972 {
2973 return GetTextForRange(GetOwnRange());
2974 }
2975
2976 /// Get the paragraph by number
2977 wxRichTextParagraph* wxRichTextParagraphLayoutBox::GetParagraphAtLine(long paragraphNumber) const
2978 {
2979 if ((size_t) paragraphNumber >= GetChildCount())
2980 return NULL;
2981
2982 return (wxRichTextParagraph*) GetChild((size_t) paragraphNumber);
2983 }
2984
2985 /// Get the length of the paragraph
2986 int wxRichTextParagraphLayoutBox::GetParagraphLength(long paragraphNumber) const
2987 {
2988 wxRichTextParagraph* para = GetParagraphAtLine(paragraphNumber);
2989 if (para)
2990 return para->GetRange().GetLength() - 1; // don't include newline
2991 else
2992 return 0;
2993 }
2994
2995 /// Get the text of the paragraph
2996 wxString wxRichTextParagraphLayoutBox::GetParagraphText(long paragraphNumber) const
2997 {
2998 wxRichTextParagraph* para = GetParagraphAtLine(paragraphNumber);
2999 if (para)
3000 return para->GetTextForRange(para->GetRange());
3001 else
3002 return wxEmptyString;
3003 }
3004
3005 /// Convert zero-based line column and paragraph number to a position.
3006 long wxRichTextParagraphLayoutBox::XYToPosition(long x, long y) const
3007 {
3008 wxRichTextParagraph* para = GetParagraphAtLine(y);
3009 if (para)
3010 {
3011 return para->GetRange().GetStart() + x;
3012 }
3013 else
3014 return -1;
3015 }
3016
3017 /// Convert zero-based position to line column and paragraph number
3018 bool wxRichTextParagraphLayoutBox::PositionToXY(long pos, long* x, long* y) const
3019 {
3020 wxRichTextParagraph* para = GetParagraphAtPosition(pos);
3021 if (para)
3022 {
3023 int count = 0;
3024 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3025 while (node)
3026 {
3027 wxRichTextObject* child = node->GetData();
3028 if (child == para)
3029 break;
3030 count ++;
3031 node = node->GetNext();
3032 }
3033
3034 *y = count;
3035 *x = pos - para->GetRange().GetStart();
3036
3037 return true;
3038 }
3039 else
3040 return false;
3041 }
3042
3043 /// Get the leaf object in a paragraph at this position.
3044 /// Given a line number, get the corresponding wxRichTextLine object.
3045 wxRichTextObject* wxRichTextParagraphLayoutBox::GetLeafObjectAtPosition(long position) const
3046 {
3047 wxRichTextParagraph* para = GetParagraphAtPosition(position);
3048 if (para)
3049 {
3050 wxRichTextObjectList::compatibility_iterator node = para->GetChildren().GetFirst();
3051
3052 while (node)
3053 {
3054 wxRichTextObject* child = node->GetData();
3055 if (child->GetRange().Contains(position))
3056 return child;
3057
3058 node = node->GetNext();
3059 }
3060 if (position == para->GetRange().GetEnd() && para->GetChildCount() > 0)
3061 return para->GetChildren().GetLast()->GetData();
3062 }
3063 return NULL;
3064 }
3065
3066 /// Set character or paragraph text attributes: apply character styles only to immediate text nodes
3067 bool wxRichTextParagraphLayoutBox::SetStyle(const wxRichTextRange& range, const wxRichTextAttr& style, int flags)
3068 {
3069 bool characterStyle = false;
3070 bool paragraphStyle = false;
3071
3072 if (style.IsCharacterStyle())
3073 characterStyle = true;
3074 if (style.IsParagraphStyle())
3075 paragraphStyle = true;
3076
3077 wxRichTextBuffer* buffer = GetBuffer();
3078
3079 bool withUndo = ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0);
3080 bool applyMinimal = ((flags & wxRICHTEXT_SETSTYLE_OPTIMIZE) != 0);
3081 bool parasOnly = ((flags & wxRICHTEXT_SETSTYLE_PARAGRAPHS_ONLY) != 0);
3082 bool charactersOnly = ((flags & wxRICHTEXT_SETSTYLE_CHARACTERS_ONLY) != 0);
3083 bool resetExistingStyle = ((flags & wxRICHTEXT_SETSTYLE_RESET) != 0);
3084 bool removeStyle = ((flags & wxRICHTEXT_SETSTYLE_REMOVE) != 0);
3085
3086 // Apply paragraph style first, if any
3087 wxRichTextAttr wholeStyle(style);
3088
3089 if (!removeStyle && wholeStyle.HasParagraphStyleName() && buffer->GetStyleSheet())
3090 {
3091 wxRichTextParagraphStyleDefinition* def = buffer->GetStyleSheet()->FindParagraphStyle(wholeStyle.GetParagraphStyleName());
3092 if (def)
3093 wxRichTextApplyStyle(wholeStyle, def->GetStyleMergedWithBase(buffer->GetStyleSheet()));
3094 }
3095
3096 // Limit the attributes to be set to the content to only character attributes.
3097 wxRichTextAttr characterAttributes(wholeStyle);
3098 characterAttributes.SetFlags(characterAttributes.GetFlags() & (wxTEXT_ATTR_CHARACTER));
3099
3100 if (!removeStyle && characterAttributes.HasCharacterStyleName() && buffer->GetStyleSheet())
3101 {
3102 wxRichTextCharacterStyleDefinition* def = buffer->GetStyleSheet()->FindCharacterStyle(characterAttributes.GetCharacterStyleName());
3103 if (def)
3104 wxRichTextApplyStyle(characterAttributes, def->GetStyleMergedWithBase(buffer->GetStyleSheet()));
3105 }
3106
3107 // If we are associated with a control, make undoable; otherwise, apply immediately
3108 // to the data.
3109
3110 bool haveControl = (buffer->GetRichTextCtrl() != NULL);
3111
3112 wxRichTextAction* action = NULL;
3113
3114 if (haveControl && withUndo)
3115 {
3116 action = new wxRichTextAction(NULL, _("Change Style"), wxRICHTEXT_CHANGE_STYLE, buffer, this, buffer->GetRichTextCtrl());
3117 action->SetRange(range);
3118 action->SetPosition(buffer->GetRichTextCtrl()->GetCaretPosition());
3119 }
3120
3121 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3122 while (node)
3123 {
3124 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3125 // wxASSERT (para != NULL);
3126
3127 if (para && para->GetChildCount() > 0)
3128 {
3129 // Stop searching if we're beyond the range of interest
3130 if (para->GetRange().GetStart() > range.GetEnd())
3131 break;
3132
3133 if (!para->GetRange().IsOutside(range))
3134 {
3135 // We'll be using a copy of the paragraph to make style changes,
3136 // not updating the buffer directly.
3137 wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL);
3138
3139 if (haveControl && withUndo)
3140 {
3141 newPara = new wxRichTextParagraph(*para);
3142 action->GetNewParagraphs().AppendChild(newPara);
3143
3144 // Also store the old ones for Undo
3145 action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para));
3146 }
3147 else
3148 newPara = para;
3149
3150 // If we're specifying paragraphs only, then we really mean character formatting
3151 // to be included in the paragraph style
3152 if ((paragraphStyle || parasOnly) && !charactersOnly)
3153 {
3154 if (removeStyle)
3155 {
3156 // Removes the given style from the paragraph
3157 wxRichTextRemoveStyle(newPara->GetAttributes(), style);
3158 }
3159 else if (resetExistingStyle)
3160 newPara->GetAttributes() = wholeStyle;
3161 else
3162 {
3163 if (applyMinimal)
3164 {
3165 // Only apply attributes that will make a difference to the combined
3166 // style as seen on the display
3167 wxRichTextAttr combinedAttr(para->GetCombinedAttributes(true));
3168 wxRichTextApplyStyle(newPara->GetAttributes(), wholeStyle, & combinedAttr);
3169 }
3170 else
3171 wxRichTextApplyStyle(newPara->GetAttributes(), wholeStyle);
3172 }
3173 }
3174
3175 // When applying paragraph styles dynamically, don't change the text objects' attributes
3176 // since they will computed as needed. Only apply the character styling if it's _only_
3177 // character styling. This policy is subject to change and might be put under user control.
3178
3179 // Hm. we might well be applying a mix of paragraph and character styles, in which
3180 // case we _do_ want to apply character styles regardless of what para styles are set.
3181 // But if we're applying a paragraph style, which has some character attributes, but
3182 // we only want the paragraphs to hold this character style, then we _don't_ want to
3183 // apply the character style. So we need to be able to choose.
3184
3185 if (!parasOnly && (characterStyle|charactersOnly) && range.GetStart() != newPara->GetRange().GetEnd())
3186 {
3187 wxRichTextRange childRange(range);
3188 childRange.LimitTo(newPara->GetRange());
3189
3190 // Find the starting position and if necessary split it so
3191 // we can start applying a different style.
3192 // TODO: check that the style actually changes or is different
3193 // from style outside of range
3194 wxRichTextObject* firstObject wxDUMMY_INITIALIZE(NULL);
3195 wxRichTextObject* lastObject wxDUMMY_INITIALIZE(NULL);
3196
3197 if (childRange.GetStart() == newPara->GetRange().GetStart())
3198 firstObject = newPara->GetChildren().GetFirst()->GetData();
3199 else
3200 firstObject = newPara->SplitAt(range.GetStart());
3201
3202 // Increment by 1 because we're apply the style one _after_ the split point
3203 long splitPoint = childRange.GetEnd();
3204 if (splitPoint != newPara->GetRange().GetEnd())
3205 splitPoint ++;
3206
3207 // Find last object
3208 if (splitPoint == newPara->GetRange().GetEnd())
3209 lastObject = newPara->GetChildren().GetLast()->GetData();
3210 else
3211 // lastObject is set as a side-effect of splitting. It's
3212 // returned as the object before the new object.
3213 (void) newPara->SplitAt(splitPoint, & lastObject);
3214
3215 wxASSERT(firstObject != NULL);
3216 wxASSERT(lastObject != NULL);
3217
3218 if (!firstObject || !lastObject)
3219 continue;
3220
3221 wxRichTextObjectList::compatibility_iterator firstNode = newPara->GetChildren().Find(firstObject);
3222 wxRichTextObjectList::compatibility_iterator lastNode = newPara->GetChildren().Find(lastObject);
3223
3224 wxASSERT(firstNode);
3225 wxASSERT(lastNode);
3226
3227 wxRichTextObjectList::compatibility_iterator node2 = firstNode;
3228
3229 while (node2)
3230 {
3231 wxRichTextObject* child = node2->GetData();
3232
3233 if (removeStyle)
3234 {
3235 // Removes the given style from the paragraph
3236 wxRichTextRemoveStyle(child->GetAttributes(), style);
3237 }
3238 else if (resetExistingStyle)
3239 child->GetAttributes() = characterAttributes;
3240 else
3241 {
3242 if (applyMinimal)
3243 {
3244 // Only apply attributes that will make a difference to the combined
3245 // style as seen on the display
3246 wxRichTextAttr combinedAttr(newPara->GetCombinedAttributes(child->GetAttributes(), true));
3247 wxRichTextApplyStyle(child->GetAttributes(), characterAttributes, & combinedAttr);
3248 }
3249 else
3250 wxRichTextApplyStyle(child->GetAttributes(), characterAttributes);
3251 }
3252
3253 if (node2 == lastNode)
3254 break;
3255
3256 node2 = node2->GetNext();
3257 }
3258 }
3259 }
3260 }
3261
3262 node = node->GetNext();
3263 }
3264
3265 // Do action, or delay it until end of batch.
3266 if (haveControl && withUndo)
3267 buffer->SubmitAction(action);
3268
3269 return true;
3270 }
3271
3272 // Just change the attributes for this single object.
3273 void wxRichTextParagraphLayoutBox::SetStyle(wxRichTextObject* obj, const wxRichTextAttr& textAttr, int flags)
3274 {
3275 wxRichTextBuffer* buffer = GetBuffer();
3276 bool withUndo = flags & wxRICHTEXT_SETSTYLE_WITH_UNDO;
3277 bool resetExistingStyle = ((flags & wxRICHTEXT_SETSTYLE_RESET) != 0);
3278 bool haveControl = (buffer->GetRichTextCtrl() != NULL);
3279
3280 wxRichTextAction *action = NULL;
3281 wxRichTextAttr newAttr = obj->GetAttributes();
3282 if (resetExistingStyle)
3283 newAttr = textAttr;
3284 else
3285 newAttr.Apply(textAttr);
3286
3287 if (haveControl && withUndo)
3288 {
3289 action = new wxRichTextAction(NULL, _("Change Object Style"), wxRICHTEXT_CHANGE_ATTRIBUTES, buffer, obj->GetContainer(), buffer->GetRichTextCtrl());
3290 action->SetRange(obj->GetRange().FromInternal());
3291 action->SetPosition(buffer->GetRichTextCtrl()->GetCaretPosition());
3292 action->MakeObject(obj);
3293
3294 action->GetAttributes() = newAttr;
3295 }
3296 else
3297 obj->GetAttributes() = newAttr;
3298
3299 if (haveControl && withUndo)
3300 buffer->SubmitAction(action);
3301 }
3302
3303 /// Get the text attributes for this position.
3304 bool wxRichTextParagraphLayoutBox::GetStyle(long position, wxRichTextAttr& style)
3305 {
3306 return DoGetStyle(position, style, true);
3307 }
3308
3309 bool wxRichTextParagraphLayoutBox::GetUncombinedStyle(long position, wxRichTextAttr& style)
3310 {
3311 return DoGetStyle(position, style, false);
3312 }
3313
3314 /// Implementation helper for GetStyle. If combineStyles is true, combine base, paragraph and
3315 /// context attributes.
3316 bool wxRichTextParagraphLayoutBox::DoGetStyle(long position, wxRichTextAttr& style, bool combineStyles)
3317 {
3318 wxRichTextObject* obj wxDUMMY_INITIALIZE(NULL);
3319
3320 if (style.IsParagraphStyle())
3321 {
3322 obj = GetParagraphAtPosition(position);
3323 if (obj)
3324 {
3325 if (combineStyles)
3326 {
3327 // Start with the base style
3328 style = GetAttributes();
3329
3330 // Apply the paragraph style
3331 wxRichTextApplyStyle(style, obj->GetAttributes());
3332 }
3333 else
3334 style = obj->GetAttributes();
3335
3336 return true;
3337 }
3338 }
3339 else
3340 {
3341 obj = GetLeafObjectAtPosition(position);
3342 if (obj)
3343 {
3344 if (combineStyles)
3345 {
3346 wxRichTextParagraph* para = wxDynamicCast(obj->GetParent(), wxRichTextParagraph);
3347 style = para ? para->GetCombinedAttributes(obj->GetAttributes()) : obj->GetAttributes();
3348 }
3349 else
3350 style = obj->GetAttributes();
3351
3352 return true;
3353 }
3354 }
3355 return false;
3356 }
3357
3358 static bool wxHasStyle(long flags, long style)
3359 {
3360 return (flags & style) != 0;
3361 }
3362
3363 /// Combines 'style' with 'currentStyle' for the purpose of summarising the attributes of a range of
3364 /// content.
3365 bool wxRichTextParagraphLayoutBox::CollectStyle(wxRichTextAttr& currentStyle, const wxRichTextAttr& style, wxRichTextAttr& clashingAttr, wxRichTextAttr& absentAttr)
3366 {
3367 currentStyle.CollectCommonAttributes(style, clashingAttr, absentAttr);
3368
3369 return true;
3370 }
3371
3372 /// Get the combined style for a range - if any attribute is different within the range,
3373 /// that attribute is not present within the flags.
3374 /// *** Note that this is not recursive, and so assumes that content inside a paragraph is not itself
3375 /// nested.
3376 bool wxRichTextParagraphLayoutBox::GetStyleForRange(const wxRichTextRange& range, wxRichTextAttr& style)
3377 {
3378 style = wxRichTextAttr();
3379
3380 wxRichTextAttr clashingAttr;
3381 wxRichTextAttr absentAttrPara, absentAttrChar;
3382
3383 wxRichTextObjectList::compatibility_iterator node = GetChildren().GetFirst();
3384 while (node)
3385 {
3386 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3387 if (para && !(para->GetRange().GetStart() > range.GetEnd() || para->GetRange().GetEnd() < range.GetStart()))
3388 {
3389 if (para->GetChildren().GetCount() == 0)
3390 {
3391 wxRichTextAttr paraStyle = para->GetCombinedAttributes(true /* use box attributes */);
3392
3393 CollectStyle(style, paraStyle, clashingAttr, absentAttrPara);
3394 }
3395 else
3396 {
3397 wxRichTextRange paraRange(para->GetRange());
3398 paraRange.LimitTo(range);
3399
3400 // First collect paragraph attributes only
3401 wxRichTextAttr paraStyle = para->GetCombinedAttributes();
3402 paraStyle.SetFlags(paraStyle.GetFlags() & wxTEXT_ATTR_PARAGRAPH);
3403 CollectStyle(style, paraStyle, clashingAttr, absentAttrPara);
3404
3405 wxRichTextObjectList::compatibility_iterator childNode = para->GetChildren().GetFirst();
3406
3407 while (childNode)
3408 {
3409 wxRichTextObject* child = childNode->GetData();
3410 if (!(child->GetRange().GetStart() > range.GetEnd() || child->GetRange().GetEnd() < range.GetStart()))
3411 {
3412 wxRichTextAttr childStyle = para->GetCombinedAttributes(child->GetAttributes(), true /* include box attributes */);
3413
3414 // Now collect character attributes only
3415 childStyle.SetFlags(childStyle.GetFlags() & wxTEXT_ATTR_CHARACTER);
3416
3417 CollectStyle(style, childStyle, clashingAttr, absentAttrChar);
3418 }
3419
3420 childNode = childNode->GetNext();
3421 }
3422 }
3423 }
3424 node = node->GetNext();
3425 }
3426 return true;
3427 }
3428
3429 /// Set default style
3430 bool wxRichTextParagraphLayoutBox::SetDefaultStyle(const wxRichTextAttr& style)
3431 {
3432 m_defaultAttributes = style;
3433 return true;
3434 }
3435
3436 /// Test if this whole range has character attributes of the specified kind. If any
3437 /// of the attributes are different within the range, the test fails. You
3438 /// can use this to implement, for example, bold button updating. style must have
3439 /// flags indicating which attributes are of interest.
3440 bool wxRichTextParagraphLayoutBox::HasCharacterAttributes(const wxRichTextRange& range, const wxRichTextAttr& style) const
3441 {
3442 int foundCount = 0;
3443 int matchingCount = 0;
3444
3445 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3446 while (node)
3447 {
3448 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3449 // wxASSERT (para != NULL);
3450
3451 if (para)
3452 {
3453 // Stop searching if we're beyond the range of interest
3454 if (para->GetRange().GetStart() > range.GetEnd())
3455 return foundCount == matchingCount && foundCount != 0;
3456
3457 if (!para->GetRange().IsOutside(range))
3458 {
3459 wxRichTextObjectList::compatibility_iterator node2 = para->GetChildren().GetFirst();
3460
3461 while (node2)
3462 {
3463 wxRichTextObject* child = node2->GetData();
3464 // Allow for empty string if no buffer
3465 wxRichTextRange childRange = child->GetRange();
3466 if (childRange.GetLength() == 0 && GetRange().GetLength() == 1)
3467 childRange.SetEnd(childRange.GetEnd()+1);
3468
3469 if (!childRange.IsOutside(range) && child->IsKindOf(CLASSINFO(wxRichTextPlainText)))
3470 {
3471 foundCount ++;
3472 wxRichTextAttr textAttr = para->GetCombinedAttributes(child->GetAttributes());
3473
3474 if (wxTextAttrEqPartial(textAttr, style))
3475 matchingCount ++;
3476 }
3477
3478 node2 = node2->GetNext();
3479 }
3480 }
3481 }
3482
3483 node = node->GetNext();
3484 }
3485
3486 return foundCount == matchingCount && foundCount != 0;
3487 }
3488
3489 /// Test if this whole range has paragraph attributes of the specified kind. If any
3490 /// of the attributes are different within the range, the test fails. You
3491 /// can use this to implement, for example, centering button updating. style must have
3492 /// flags indicating which attributes are of interest.
3493 bool wxRichTextParagraphLayoutBox::HasParagraphAttributes(const wxRichTextRange& range, const wxRichTextAttr& style) const
3494 {
3495 int foundCount = 0;
3496 int matchingCount = 0;
3497
3498 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3499 while (node)
3500 {
3501 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3502 // wxASSERT (para != NULL);
3503
3504 if (para)
3505 {
3506 // Stop searching if we're beyond the range of interest
3507 if (para->GetRange().GetStart() > range.GetEnd())
3508 return foundCount == matchingCount && foundCount != 0;
3509
3510 if (!para->GetRange().IsOutside(range))
3511 {
3512 wxRichTextAttr textAttr = GetAttributes();
3513 // Apply the paragraph style
3514 wxRichTextApplyStyle(textAttr, para->GetAttributes());
3515
3516 foundCount ++;
3517 if (wxTextAttrEqPartial(textAttr, style))
3518 matchingCount ++;
3519 }
3520 }
3521
3522 node = node->GetNext();
3523 }
3524 return foundCount == matchingCount && foundCount != 0;
3525 }
3526
3527 void wxRichTextParagraphLayoutBox::PrepareContent(wxRichTextParagraphLayoutBox& container)
3528 {
3529 wxRichTextBuffer* buffer = GetBuffer();
3530 if (buffer && buffer->GetRichTextCtrl())
3531 buffer->GetRichTextCtrl()->PrepareContent(container);
3532 }
3533
3534
3535 /// Set character or paragraph properties
3536 bool wxRichTextParagraphLayoutBox::SetProperties(const wxRichTextRange& range, const wxRichTextProperties& properties, int flags)
3537 {
3538 wxRichTextBuffer* buffer = GetBuffer();
3539
3540 bool withUndo = ((flags & wxRICHTEXT_SETPROPERTIES_WITH_UNDO) != 0);
3541 bool parasOnly = ((flags & wxRICHTEXT_SETPROPERTIES_PARAGRAPHS_ONLY) != 0);
3542 bool charactersOnly = ((flags & wxRICHTEXT_SETPROPERTIES_CHARACTERS_ONLY) != 0);
3543 bool resetExistingProperties = ((flags & wxRICHTEXT_SETPROPERTIES_RESET) != 0);
3544 bool removeProperties = ((flags & wxRICHTEXT_SETPROPERTIES_REMOVE) != 0);
3545
3546 // If we are associated with a control, make undoable; otherwise, apply immediately
3547 // to the data.
3548
3549 bool haveControl = (buffer->GetRichTextCtrl() != NULL);
3550
3551 wxRichTextAction* action = NULL;
3552
3553 if (haveControl && withUndo)
3554 {
3555 action = new wxRichTextAction(NULL, _("Change Properties"), wxRICHTEXT_CHANGE_PROPERTIES, buffer, this, buffer->GetRichTextCtrl());
3556 action->SetRange(range);
3557 action->SetPosition(buffer->GetRichTextCtrl()->GetCaretPosition());
3558 }
3559
3560 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3561 while (node)
3562 {
3563 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3564 // wxASSERT (para != NULL);
3565
3566 if (para && para->GetChildCount() > 0)
3567 {
3568 // Stop searching if we're beyond the range of interest
3569 if (para->GetRange().GetStart() > range.GetEnd())
3570 break;
3571
3572 if (!para->GetRange().IsOutside(range))
3573 {
3574 // We'll be using a copy of the paragraph to make style changes,
3575 // not updating the buffer directly.
3576 wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL);
3577
3578 if (haveControl && withUndo)
3579 {
3580 newPara = new wxRichTextParagraph(*para);
3581 action->GetNewParagraphs().AppendChild(newPara);
3582
3583 // Also store the old ones for Undo
3584 action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para));
3585 }
3586 else
3587 newPara = para;
3588
3589 if (parasOnly)
3590 {
3591 if (removeProperties)
3592 {
3593 // Removes the given style from the paragraph
3594 // TODO
3595 newPara->GetProperties().RemoveProperties(properties);
3596 }
3597 else if (resetExistingProperties)
3598 newPara->GetProperties() = properties;
3599 else
3600 newPara->GetProperties().MergeProperties(properties);
3601 }
3602
3603 // When applying paragraph styles dynamically, don't change the text objects' attributes
3604 // since they will computed as needed. Only apply the character styling if it's _only_
3605 // character styling. This policy is subject to change and might be put under user control.
3606
3607 // Hm. we might well be applying a mix of paragraph and character styles, in which
3608 // case we _do_ want to apply character styles regardless of what para styles are set.
3609 // But if we're applying a paragraph style, which has some character attributes, but
3610 // we only want the paragraphs to hold this character style, then we _don't_ want to
3611 // apply the character style. So we need to be able to choose.
3612
3613 if (!parasOnly && charactersOnly && range.GetStart() != newPara->GetRange().GetEnd())
3614 {
3615 wxRichTextRange childRange(range);
3616 childRange.LimitTo(newPara->GetRange());
3617
3618 // Find the starting position and if necessary split it so
3619 // we can start applying different properties.
3620 // TODO: check that the properties actually change or are different
3621 // from properties outside of range
3622 wxRichTextObject* firstObject wxDUMMY_INITIALIZE(NULL);
3623 wxRichTextObject* lastObject wxDUMMY_INITIALIZE(NULL);
3624
3625 if (childRange.GetStart() == newPara->GetRange().GetStart())
3626 firstObject = newPara->GetChildren().GetFirst()->GetData();
3627 else
3628 firstObject = newPara->SplitAt(range.GetStart());
3629
3630 // Increment by 1 because we're apply the style one _after_ the split point
3631 long splitPoint = childRange.GetEnd();
3632 if (splitPoint != newPara->GetRange().GetEnd())
3633 splitPoint ++;
3634
3635 // Find last object
3636 if (splitPoint == newPara->GetRange().GetEnd())
3637 lastObject = newPara->GetChildren().GetLast()->GetData();
3638 else
3639 // lastObject is set as a side-effect of splitting. It's
3640 // returned as the object before the new object.
3641 (void) newPara->SplitAt(splitPoint, & lastObject);
3642
3643 wxASSERT(firstObject != NULL);
3644 wxASSERT(lastObject != NULL);
3645
3646 if (!firstObject || !lastObject)
3647 continue;
3648
3649 wxRichTextObjectList::compatibility_iterator firstNode = newPara->GetChildren().Find(firstObject);
3650 wxRichTextObjectList::compatibility_iterator lastNode = newPara->GetChildren().Find(lastObject);
3651
3652 wxASSERT(firstNode);
3653 wxASSERT(lastNode);
3654
3655 wxRichTextObjectList::compatibility_iterator node2 = firstNode;
3656
3657 while (node2)
3658 {
3659 wxRichTextObject* child = node2->GetData();
3660
3661 if (removeProperties)
3662 {
3663 // Removes the given properties from the paragraph
3664 child->GetProperties().RemoveProperties(properties);
3665 }
3666 else if (resetExistingProperties)
3667 child->GetProperties() = properties;
3668 else
3669 {
3670 child->GetProperties().MergeProperties(properties);
3671 }
3672
3673 if (node2 == lastNode)
3674 break;
3675
3676 node2 = node2->GetNext();
3677 }
3678 }
3679 }
3680 }
3681
3682 node = node->GetNext();
3683 }
3684
3685 // Do action, or delay it until end of batch.
3686 if (haveControl && withUndo)
3687 buffer->SubmitAction(action);
3688
3689 return true;
3690 }
3691
3692 void wxRichTextParagraphLayoutBox::Reset()
3693 {
3694 Clear();
3695
3696 wxRichTextBuffer* buffer = GetBuffer();
3697 if (buffer && buffer->GetRichTextCtrl())
3698 {
3699 wxRichTextEvent event(wxEVT_COMMAND_RICHTEXT_BUFFER_RESET, buffer->GetRichTextCtrl()->GetId());
3700 event.SetEventObject(buffer->GetRichTextCtrl());
3701 event.SetContainer(this);
3702
3703 buffer->SendEvent(event, true);
3704 }
3705
3706 AddParagraph(wxEmptyString);
3707
3708 PrepareContent(*this);
3709
3710 InvalidateHierarchy(wxRICHTEXT_ALL);
3711 }
3712
3713 /// Invalidate the buffer. With no argument, invalidates whole buffer.
3714 void wxRichTextParagraphLayoutBox::Invalidate(const wxRichTextRange& invalidRange)
3715 {
3716 wxRichTextCompositeObject::Invalidate(invalidRange);
3717
3718 DoInvalidate(invalidRange);
3719 }
3720
3721 // Do the (in)validation for this object only
3722 void wxRichTextParagraphLayoutBox::DoInvalidate(const wxRichTextRange& invalidRange)
3723 {
3724 if (invalidRange == wxRICHTEXT_ALL)
3725 {
3726 m_invalidRange = wxRICHTEXT_ALL;
3727 }
3728 // Already invalidating everything
3729 else if (m_invalidRange == wxRICHTEXT_ALL)
3730 {
3731 }
3732 else
3733 {
3734 if ((invalidRange.GetStart() < m_invalidRange.GetStart()) || m_invalidRange.GetStart() == -1)
3735 m_invalidRange.SetStart(invalidRange.GetStart());
3736 if (invalidRange.GetEnd() > m_invalidRange.GetEnd())
3737 m_invalidRange.SetEnd(invalidRange.GetEnd());
3738 }
3739 }
3740
3741 // Do the (in)validation both up and down the hierarchy
3742 void wxRichTextParagraphLayoutBox::InvalidateHierarchy(const wxRichTextRange& invalidRange)
3743 {
3744 Invalidate(invalidRange);
3745
3746 if (invalidRange != wxRICHTEXT_NONE)
3747 {
3748 // Now go up the hierarchy
3749 wxRichTextObject* thisObj = this;
3750 wxRichTextObject* p = GetParent();
3751 while (p)
3752 {
3753 wxRichTextParagraphLayoutBox* l = wxDynamicCast(p, wxRichTextParagraphLayoutBox);
3754 if (l)
3755 l->DoInvalidate(thisObj->GetRange());
3756
3757 thisObj = p;
3758 p = p->GetParent();
3759 }
3760 }
3761 }
3762
3763 /// Get invalid range, rounding to entire paragraphs if argument is true.
3764 wxRichTextRange wxRichTextParagraphLayoutBox::GetInvalidRange(bool wholeParagraphs) const
3765 {
3766 if (m_invalidRange == wxRICHTEXT_ALL || m_invalidRange == wxRICHTEXT_NONE)
3767 return m_invalidRange;
3768
3769 wxRichTextRange range = m_invalidRange;
3770
3771 if (wholeParagraphs)
3772 {
3773 wxRichTextParagraph* para1 = GetParagraphAtPosition(range.GetStart());
3774 if (para1)
3775 range.SetStart(para1->GetRange().GetStart());
3776 // floating layout make all child should be relayout
3777 range.SetEnd(GetOwnRange().GetEnd());
3778 }
3779 return range;
3780 }
3781
3782 /// Apply the style sheet to the buffer, for example if the styles have changed.
3783 bool wxRichTextParagraphLayoutBox::ApplyStyleSheet(wxRichTextStyleSheet* styleSheet)
3784 {
3785 wxASSERT(styleSheet != NULL);
3786 if (!styleSheet)
3787 return false;
3788
3789 int foundCount = 0;
3790
3791 wxRichTextAttr attr(GetBasicStyle());
3792 if (GetBasicStyle().HasParagraphStyleName())
3793 {
3794 wxRichTextParagraphStyleDefinition* paraDef = styleSheet->FindParagraphStyle(GetBasicStyle().GetParagraphStyleName());
3795 if (paraDef)
3796 {
3797 attr.Apply(paraDef->GetStyleMergedWithBase(styleSheet));
3798 SetBasicStyle(attr);
3799 foundCount ++;
3800 }
3801 }
3802
3803 if (GetBasicStyle().HasCharacterStyleName())
3804 {
3805 wxRichTextCharacterStyleDefinition* charDef = styleSheet->FindCharacterStyle(GetBasicStyle().GetCharacterStyleName());
3806 if (charDef)
3807 {
3808 attr.Apply(charDef->GetStyleMergedWithBase(styleSheet));
3809 SetBasicStyle(attr);
3810 foundCount ++;
3811 }
3812 }
3813
3814 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3815 while (node)
3816 {
3817 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3818 // wxASSERT (para != NULL);
3819
3820 if (para)
3821 {
3822 // Combine paragraph and list styles. If there is a list style in the original attributes,
3823 // the current indentation overrides anything else and is used to find the item indentation.
3824 // Also, for applying paragraph styles, consider having 2 modes: (1) we merge with what we have,
3825 // thereby taking into account all user changes, (2) reset the style completely (except for indentation/list
3826 // exception as above).
3827 // Problem: when changing from one list style to another, there's a danger that the level info will get lost.
3828 // So when changing a list style interactively, could retrieve level based on current style, then
3829 // set appropriate indent and apply new style.
3830
3831 int outline = -1;
3832 int num = -1;
3833 if (para->GetAttributes().HasOutlineLevel())
3834 outline = para->GetAttributes().GetOutlineLevel();
3835 if (para->GetAttributes().HasBulletNumber())
3836 num = para->GetAttributes().GetBulletNumber();
3837
3838 if (!para->GetAttributes().GetParagraphStyleName().IsEmpty() && !para->GetAttributes().GetListStyleName().IsEmpty())
3839 {
3840 int currentIndent = para->GetAttributes().GetLeftIndent();
3841
3842 wxRichTextParagraphStyleDefinition* paraDef = styleSheet->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName());
3843 wxRichTextListStyleDefinition* listDef = styleSheet->FindListStyle(para->GetAttributes().GetListStyleName());
3844 if (paraDef && !listDef)
3845 {
3846 para->GetAttributes() = paraDef->GetStyleMergedWithBase(styleSheet);
3847 foundCount ++;
3848 }
3849 else if (listDef && !paraDef)
3850 {
3851 // Set overall style defined for the list style definition
3852 para->GetAttributes() = listDef->GetStyleMergedWithBase(styleSheet);
3853
3854 // Apply the style for this level
3855 wxRichTextApplyStyle(para->GetAttributes(), * listDef->GetLevelAttributes(listDef->FindLevelForIndent(currentIndent)));
3856 foundCount ++;
3857 }
3858 else if (listDef && paraDef)
3859 {
3860 // Combines overall list style, style for level, and paragraph style
3861 para->GetAttributes() = listDef->CombineWithParagraphStyle(currentIndent, paraDef->GetStyleMergedWithBase(styleSheet));
3862 foundCount ++;
3863 }
3864 }
3865 else if (para->GetAttributes().GetParagraphStyleName().IsEmpty() && !para->GetAttributes().GetListStyleName().IsEmpty())
3866 {
3867 int currentIndent = para->GetAttributes().GetLeftIndent();
3868
3869 wxRichTextListStyleDefinition* listDef = styleSheet->FindListStyle(para->GetAttributes().GetListStyleName());
3870
3871 // Overall list definition style
3872 para->GetAttributes() = listDef->GetStyleMergedWithBase(styleSheet);
3873
3874 // Style for this level
3875 wxRichTextApplyStyle(para->GetAttributes(), * listDef->GetLevelAttributes(listDef->FindLevelForIndent(currentIndent)));
3876
3877 foundCount ++;
3878 }
3879 else if (!para->GetAttributes().GetParagraphStyleName().IsEmpty() && para->GetAttributes().GetListStyleName().IsEmpty())
3880 {
3881 wxRichTextParagraphStyleDefinition* def = styleSheet->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName());
3882 if (def)
3883 {
3884 para->GetAttributes() = def->GetStyleMergedWithBase(styleSheet);
3885 foundCount ++;
3886 }
3887 }
3888
3889 if (outline != -1)
3890 para->GetAttributes().SetOutlineLevel(outline);
3891 if (num != -1)
3892 para->GetAttributes().SetBulletNumber(num);
3893 }
3894
3895 node = node->GetNext();
3896 }
3897 return foundCount != 0;
3898 }
3899
3900 /// Set list style
3901 bool wxRichTextParagraphLayoutBox::SetListStyle(const wxRichTextRange& range, wxRichTextListStyleDefinition* def, int flags, int startFrom, int specifiedLevel)
3902 {
3903 wxRichTextBuffer* buffer = GetBuffer();
3904 wxRichTextStyleSheet* styleSheet = buffer->GetStyleSheet();
3905
3906 bool withUndo = ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0);
3907 // bool applyMinimal = ((flags & wxRICHTEXT_SETSTYLE_OPTIMIZE) != 0);
3908 bool specifyLevel = ((flags & wxRICHTEXT_SETSTYLE_SPECIFY_LEVEL) != 0);
3909 bool renumber = ((flags & wxRICHTEXT_SETSTYLE_RENUMBER) != 0);
3910
3911 // Current number, if numbering
3912 int n = startFrom;
3913
3914 wxASSERT (!specifyLevel || (specifyLevel && (specifiedLevel >= 0)));
3915
3916 // If we are associated with a control, make undoable; otherwise, apply immediately
3917 // to the data.
3918
3919 bool haveControl = (buffer->GetRichTextCtrl() != NULL);
3920
3921 wxRichTextAction* action = NULL;
3922
3923 if (haveControl && withUndo)
3924 {
3925 action = new wxRichTextAction(NULL, _("Change List Style"), wxRICHTEXT_CHANGE_STYLE, buffer, this, buffer->GetRichTextCtrl());
3926 action->SetRange(range);
3927 action->SetPosition(buffer->GetRichTextCtrl()->GetCaretPosition());
3928 }
3929
3930 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3931 while (node)
3932 {
3933 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3934 // wxASSERT (para != NULL);
3935
3936 if (para && para->GetChildCount() > 0)
3937 {
3938 // Stop searching if we're beyond the range of interest
3939 if (para->GetRange().GetStart() > range.GetEnd())
3940 break;
3941
3942 if (!para->GetRange().IsOutside(range))
3943 {
3944 // We'll be using a copy of the paragraph to make style changes,
3945 // not updating the buffer directly.
3946 wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL);
3947
3948 if (haveControl && withUndo)
3949 {
3950 newPara = new wxRichTextParagraph(*para);
3951 action->GetNewParagraphs().AppendChild(newPara);
3952
3953 // Also store the old ones for Undo
3954 action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para));
3955 }
3956 else
3957 newPara = para;
3958
3959 if (def)
3960 {
3961 int thisIndent = newPara->GetAttributes().GetLeftIndent();
3962 int thisLevel = specifyLevel ? specifiedLevel : def->FindLevelForIndent(thisIndent);
3963
3964 // How is numbering going to work?
3965 // If we are renumbering, or numbering for the first time, we need to keep
3966 // track of the number for each level. But we might be simply applying a different
3967 // list style.
3968 // In Word, applying a style to several paragraphs, even if at different levels,
3969 // reverts the level back to the same one. So we could do the same here.
3970 // Renumbering will need to be done when we promote/demote a paragraph.
3971
3972 // Apply the overall list style, and item style for this level
3973 wxRichTextAttr listStyle(def->GetCombinedStyleForLevel(thisLevel, styleSheet));
3974 wxRichTextApplyStyle(newPara->GetAttributes(), listStyle);
3975
3976 // Now we need to do numbering
3977 if (renumber)
3978 {
3979 newPara->GetAttributes().SetBulletNumber(n);
3980 }
3981
3982 n ++;
3983 }
3984 else if (!newPara->GetAttributes().GetListStyleName().IsEmpty())
3985 {
3986 // if def is NULL, remove list style, applying any associated paragraph style
3987 // to restore the attributes
3988
3989 newPara->GetAttributes().SetListStyleName(wxEmptyString);
3990 newPara->GetAttributes().SetLeftIndent(0, 0);
3991 newPara->GetAttributes().SetBulletText(wxEmptyString);
3992
3993 // Eliminate the main list-related attributes
3994 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);
3995
3996 if (styleSheet && !newPara->GetAttributes().GetParagraphStyleName().IsEmpty())
3997 {
3998 wxRichTextParagraphStyleDefinition* def = styleSheet->FindParagraphStyle(newPara->GetAttributes().GetParagraphStyleName());
3999 if (def)
4000 {
4001 newPara->GetAttributes() = def->GetStyleMergedWithBase(styleSheet);
4002 }
4003 }
4004 }
4005 }
4006 }
4007
4008 node = node->GetNext();
4009 }
4010
4011 // Do action, or delay it until end of batch.
4012 if (haveControl && withUndo)
4013 buffer->SubmitAction(action);
4014
4015 return true;
4016 }
4017
4018 bool wxRichTextParagraphLayoutBox::SetListStyle(const wxRichTextRange& range, const wxString& defName, int flags, int startFrom, int specifiedLevel)
4019 {
4020 wxRichTextBuffer* buffer = GetBuffer();
4021 if (buffer && buffer->GetStyleSheet())
4022 {
4023 wxRichTextListStyleDefinition* def = buffer->GetStyleSheet()->FindListStyle(defName);
4024 if (def)
4025 return SetListStyle(range, def, flags, startFrom, specifiedLevel);
4026 }
4027 return false;
4028 }
4029
4030 /// Clear list for given range
4031 bool wxRichTextParagraphLayoutBox::ClearListStyle(const wxRichTextRange& range, int flags)
4032 {
4033 return SetListStyle(range, NULL, flags);
4034 }
4035
4036 /// Number/renumber any list elements in the given range
4037 bool wxRichTextParagraphLayoutBox::NumberList(const wxRichTextRange& range, wxRichTextListStyleDefinition* def, int flags, int startFrom, int specifiedLevel)
4038 {
4039 return DoNumberList(range, range, 0, def, flags, startFrom, specifiedLevel);
4040 }
4041
4042 /// Number/renumber any list elements in the given range. Also do promotion or demotion of items, if specified
4043 bool wxRichTextParagraphLayoutBox::DoNumberList(const wxRichTextRange& range, const wxRichTextRange& promotionRange, int promoteBy,
4044 wxRichTextListStyleDefinition* def, int flags, int startFrom, int specifiedLevel)
4045 {
4046 wxRichTextBuffer* buffer = GetBuffer();
4047 wxRichTextStyleSheet* styleSheet = buffer->GetStyleSheet();
4048
4049 bool withUndo = ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0);
4050 // bool applyMinimal = ((flags & wxRICHTEXT_SETSTYLE_OPTIMIZE) != 0);
4051 #if wxDEBUG_LEVEL
4052 bool specifyLevel = ((flags & wxRICHTEXT_SETSTYLE_SPECIFY_LEVEL) != 0);
4053 #endif
4054
4055 bool renumber = ((flags & wxRICHTEXT_SETSTYLE_RENUMBER) != 0);
4056
4057 // Max number of levels
4058 const int maxLevels = 10;
4059
4060 // The level we're looking at now
4061 int currentLevel = -1;
4062
4063 // The item number for each level
4064 int levels[maxLevels];
4065 int i;
4066
4067 // Reset all numbering
4068 for (i = 0; i < maxLevels; i++)
4069 {
4070 if (startFrom != -1)
4071 levels[i] = startFrom-1;
4072 else if (renumber) // start again
4073 levels[i] = 0;
4074 else
4075 levels[i] = -1; // start from the number we found, if any
4076 }
4077
4078 #if wxDEBUG_LEVEL
4079 wxASSERT(!specifyLevel || (specifyLevel && (specifiedLevel >= 0)));
4080 #endif
4081
4082 // If we are associated with a control, make undoable; otherwise, apply immediately
4083 // to the data.
4084
4085 bool haveControl = (buffer->GetRichTextCtrl() != NULL);
4086
4087 wxRichTextAction* action = NULL;
4088
4089 if (haveControl && withUndo)
4090 {
4091 action = new wxRichTextAction(NULL, _("Renumber List"), wxRICHTEXT_CHANGE_STYLE, buffer, this, buffer->GetRichTextCtrl());
4092 action->SetRange(range);
4093 action->SetPosition(buffer->GetRichTextCtrl()->GetCaretPosition());
4094 }
4095
4096 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
4097 while (node)
4098 {
4099 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
4100 // wxASSERT (para != NULL);
4101
4102 if (para && para->GetChildCount() > 0)
4103 {
4104 // Stop searching if we're beyond the range of interest
4105 if (para->GetRange().GetStart() > range.GetEnd())
4106 break;
4107
4108 if (!para->GetRange().IsOutside(range))
4109 {
4110 // We'll be using a copy of the paragraph to make style changes,
4111 // not updating the buffer directly.
4112 wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL);
4113
4114 if (haveControl && withUndo)
4115 {
4116 newPara = new wxRichTextParagraph(*para);
4117 action->GetNewParagraphs().AppendChild(newPara);
4118
4119 // Also store the old ones for Undo
4120 action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para));
4121 }
4122 else
4123 newPara = para;
4124
4125 wxRichTextListStyleDefinition* defToUse = def;
4126 if (!defToUse)
4127 {
4128 if (styleSheet && !newPara->GetAttributes().GetListStyleName().IsEmpty())
4129 defToUse = styleSheet->FindListStyle(newPara->GetAttributes().GetListStyleName());
4130 }
4131
4132 if (defToUse)
4133 {
4134 int thisIndent = newPara->GetAttributes().GetLeftIndent();
4135 int thisLevel = defToUse->FindLevelForIndent(thisIndent);
4136
4137 // If we've specified a level to apply to all, change the level.
4138 if (specifiedLevel != -1)
4139 thisLevel = specifiedLevel;
4140
4141 // Do promotion if specified
4142 if ((promoteBy != 0) && !para->GetRange().IsOutside(promotionRange))
4143 {
4144 thisLevel = thisLevel - promoteBy;
4145 if (thisLevel < 0)
4146 thisLevel = 0;
4147 if (thisLevel > 9)
4148 thisLevel = 9;
4149 }
4150
4151 // Apply the overall list style, and item style for this level
4152 wxRichTextAttr listStyle(defToUse->GetCombinedStyleForLevel(thisLevel, styleSheet));
4153 wxRichTextApplyStyle(newPara->GetAttributes(), listStyle);
4154
4155 // OK, we've (re)applied the style, now let's get the numbering right.
4156
4157 if (currentLevel == -1)
4158 currentLevel = thisLevel;
4159
4160 // Same level as before, do nothing except increment level's number afterwards
4161 if (currentLevel == thisLevel)
4162 {
4163 }
4164 // A deeper level: start renumbering all levels after current level
4165 else if (thisLevel > currentLevel)
4166 {
4167 for (i = currentLevel+1; i <= thisLevel; i++)
4168 {
4169 levels[i] = 0;
4170 }
4171 currentLevel = thisLevel;
4172 }
4173 else if (thisLevel < currentLevel)
4174 {
4175 currentLevel = thisLevel;
4176 }
4177
4178 // Use the current numbering if -1 and we have a bullet number already
4179 if (levels[currentLevel] == -1)
4180 {
4181 if (newPara->GetAttributes().HasBulletNumber())
4182 levels[currentLevel] = newPara->GetAttributes().GetBulletNumber();
4183 else
4184 levels[currentLevel] = 1;
4185 }
4186 else
4187 {
4188 levels[currentLevel] ++;
4189 }
4190
4191 newPara->GetAttributes().SetBulletNumber(levels[currentLevel]);
4192
4193 // Create the bullet text if an outline list
4194 if (listStyle.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE)
4195 {
4196 wxString text;
4197 for (i = 0; i <= currentLevel; i++)
4198 {
4199 if (!text.IsEmpty())
4200 text += wxT(".");
4201 text += wxString::Format(wxT("%d"), levels[i]);
4202 }
4203 newPara->GetAttributes().SetBulletText(text);
4204 }
4205 }
4206 }
4207 }
4208
4209 node = node->GetNext();
4210 }
4211
4212 // Do action, or delay it until end of batch.
4213 if (haveControl && withUndo)
4214 buffer->SubmitAction(action);
4215
4216 return true;
4217 }
4218
4219 bool wxRichTextParagraphLayoutBox::NumberList(const wxRichTextRange& range, const wxString& defName, int flags, int startFrom, int specifiedLevel)
4220 {
4221 wxRichTextBuffer* buffer = GetBuffer();
4222 if (buffer->GetStyleSheet())
4223 {
4224 wxRichTextListStyleDefinition* def = NULL;
4225 if (!defName.IsEmpty())
4226 def = buffer->GetStyleSheet()->FindListStyle(defName);
4227 return NumberList(range, def, flags, startFrom, specifiedLevel);
4228 }
4229 return false;
4230 }
4231
4232 /// Promote the list items within the given range. promoteBy can be a positive or negative number, e.g. 1 or -1
4233 bool wxRichTextParagraphLayoutBox::PromoteList(int promoteBy, const wxRichTextRange& range, wxRichTextListStyleDefinition* def, int flags, int specifiedLevel)
4234 {
4235 // TODO
4236 // One strategy is to first work out the range within which renumbering must occur. Then could pass these two ranges
4237 // to NumberList with a flag indicating promotion is required within one of the ranges.
4238 // Find first and last paragraphs in range. Then for first, calculate new indentation and look back until we find
4239 // a paragraph that either has no list style, or has one that is different or whose indentation is less.
4240 // We start renumbering from the para after that different para we found. We specify that the numbering of that
4241 // list position will start from 1.
4242 // Similarly, we look after the last para in the promote range for an indentation that is less (or no list style).
4243 // We can end the renumbering at this point.
4244
4245 // For now, only renumber within the promotion range.
4246
4247 return DoNumberList(range, range, promoteBy, def, flags, 1, specifiedLevel);
4248 }
4249
4250 bool wxRichTextParagraphLayoutBox::PromoteList(int promoteBy, const wxRichTextRange& range, const wxString& defName, int flags, int specifiedLevel)
4251 {
4252 wxRichTextBuffer* buffer = GetBuffer();
4253 if (buffer->GetStyleSheet())
4254 {
4255 wxRichTextListStyleDefinition* def = NULL;
4256 if (!defName.IsEmpty())
4257 def = buffer->GetStyleSheet()->FindListStyle(defName);
4258 return PromoteList(promoteBy, range, def, flags, specifiedLevel);
4259 }
4260 return false;
4261 }
4262
4263 /// Fills in the attributes for numbering a paragraph after previousParagraph. It also finds the
4264 /// position of the paragraph that it had to start looking from.
4265 bool wxRichTextParagraphLayoutBox::FindNextParagraphNumber(wxRichTextParagraph* previousParagraph, wxRichTextAttr& attr) const
4266 {
4267 if (!previousParagraph->GetAttributes().HasFlag(wxTEXT_ATTR_BULLET_STYLE) || previousParagraph->GetAttributes().GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_NONE)
4268 return false;
4269
4270 wxRichTextBuffer* buffer = GetBuffer();
4271 wxRichTextStyleSheet* styleSheet = buffer->GetStyleSheet();
4272 if (styleSheet && !previousParagraph->GetAttributes().GetListStyleName().IsEmpty())
4273 {
4274 wxRichTextListStyleDefinition* def = styleSheet->FindListStyle(previousParagraph->GetAttributes().GetListStyleName());
4275 if (def)
4276 {
4277 // int thisIndent = previousParagraph->GetAttributes().GetLeftIndent();
4278 // int thisLevel = def->FindLevelForIndent(thisIndent);
4279
4280 bool isOutline = (previousParagraph->GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE) != 0;
4281
4282 attr.SetFlags(previousParagraph->GetAttributes().GetFlags() & (wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_BULLET_NUMBER|wxTEXT_ATTR_BULLET_TEXT|wxTEXT_ATTR_BULLET_NAME));
4283 if (previousParagraph->GetAttributes().HasBulletName())
4284 attr.SetBulletName(previousParagraph->GetAttributes().GetBulletName());
4285 attr.SetBulletStyle(previousParagraph->GetAttributes().GetBulletStyle());
4286 attr.SetListStyleName(previousParagraph->GetAttributes().GetListStyleName());
4287
4288 int nextNumber = previousParagraph->GetAttributes().GetBulletNumber() + 1;
4289 attr.SetBulletNumber(nextNumber);
4290
4291 if (isOutline)
4292 {
4293 wxString text = previousParagraph->GetAttributes().GetBulletText();
4294 if (!text.IsEmpty())
4295 {
4296 int pos = text.Find(wxT('.'), true);
4297 if (pos != wxNOT_FOUND)
4298 {
4299 text = text.Mid(0, text.Length() - pos - 1);
4300 }
4301 else
4302 text = wxEmptyString;
4303 if (!text.IsEmpty())
4304 text += wxT(".");
4305 text += wxString::Format(wxT("%d"), nextNumber);
4306 attr.SetBulletText(text);
4307 }
4308 }
4309
4310 return true;
4311 }
4312 else
4313 return false;
4314 }
4315 else
4316 return false;
4317 }
4318
4319 /*!
4320 * wxRichTextParagraph
4321 * This object represents a single paragraph (or in a straight text editor, a line).
4322 */
4323
4324 IMPLEMENT_DYNAMIC_CLASS(wxRichTextParagraph, wxRichTextCompositeObject)
4325
4326 wxArrayInt wxRichTextParagraph::sm_defaultTabs;
4327
4328 wxRichTextParagraph::wxRichTextParagraph(wxRichTextObject* parent, wxRichTextAttr* style):
4329 wxRichTextCompositeObject(parent)
4330 {
4331 if (style)
4332 SetAttributes(*style);
4333 }
4334
4335 wxRichTextParagraph::wxRichTextParagraph(const wxString& text, wxRichTextObject* parent, wxRichTextAttr* paraStyle, wxRichTextAttr* charStyle):
4336 wxRichTextCompositeObject(parent)
4337 {
4338 if (paraStyle)
4339 SetAttributes(*paraStyle);
4340
4341 AppendChild(new wxRichTextPlainText(text, this, charStyle));
4342 }
4343
4344 wxRichTextParagraph::~wxRichTextParagraph()
4345 {
4346 ClearLines();
4347 }
4348
4349 /// Draw the item
4350 bool wxRichTextParagraph::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int WXUNUSED(descent), int style)
4351 {
4352 if (!IsShown())
4353 return true;
4354
4355 // Currently we don't merge these attributes with the parent, but we
4356 // should consider whether we should (e.g. if we set a border colour
4357 // for all paragraphs). But generally box attributes are likely to be
4358 // different for different objects.
4359 wxRect paraRect = GetRect();
4360 wxRichTextAttr attr = GetCombinedAttributes();
4361 context.ApplyVirtualAttributes(attr, this);
4362
4363 DrawBoxAttributes(dc, GetBuffer(), attr, paraRect);
4364
4365 // Draw the bullet, if any
4366 if (attr.GetBulletStyle() != wxTEXT_ATTR_BULLET_STYLE_NONE)
4367 {
4368 if (attr.GetLeftSubIndent() != 0)
4369 {
4370 int spaceBeforePara = ConvertTenthsMMToPixels(dc, attr.GetParagraphSpacingBefore());
4371 int leftIndent = ConvertTenthsMMToPixels(dc, attr.GetLeftIndent());
4372
4373 wxRichTextAttr bulletAttr(attr);
4374
4375 // Combine with the font of the first piece of content, if one is specified
4376 if (GetChildren().GetCount() > 0)
4377 {
4378 wxRichTextObject* firstObj = (wxRichTextObject*) GetChildren().GetFirst()->GetData();
4379 if (!firstObj->IsFloatable() && firstObj->GetAttributes().HasFont())
4380 {
4381 wxRichTextApplyStyle(bulletAttr, firstObj->GetAttributes());
4382 }
4383 }
4384
4385 // Get line height from first line, if any
4386 wxRichTextLine* line = m_cachedLines.GetFirst() ? (wxRichTextLine* ) m_cachedLines.GetFirst()->GetData() : NULL;
4387
4388 wxPoint linePos;
4389 int lineHeight wxDUMMY_INITIALIZE(0);
4390 if (line)
4391 {
4392 lineHeight = line->GetSize().y;
4393 linePos = line->GetPosition() + GetPosition();
4394 }
4395 else
4396 {
4397 wxFont font;
4398 if (bulletAttr.HasFont() && GetBuffer())
4399 font = GetBuffer()->GetFontTable().FindFont(bulletAttr);
4400 else
4401 font = (*wxNORMAL_FONT);
4402
4403 wxCheckSetFont(dc, font);
4404
4405 lineHeight = dc.GetCharHeight();
4406 linePos = GetPosition();
4407 linePos.y += spaceBeforePara;
4408 }
4409
4410 wxRect bulletRect(GetPosition().x + leftIndent, linePos.y, linePos.x - (GetPosition().x + leftIndent), lineHeight);
4411
4412 if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_BITMAP)
4413 {
4414 if (wxRichTextBuffer::GetRenderer())
4415 wxRichTextBuffer::GetRenderer()->DrawBitmapBullet(this, dc, bulletAttr, bulletRect);
4416 }
4417 else if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_STANDARD)
4418 {
4419 if (wxRichTextBuffer::GetRenderer())
4420 wxRichTextBuffer::GetRenderer()->DrawStandardBullet(this, dc, bulletAttr, bulletRect);
4421 }
4422 else
4423 {
4424 wxString bulletText = GetBulletText();
4425
4426 if (!bulletText.empty() && wxRichTextBuffer::GetRenderer())
4427 wxRichTextBuffer::GetRenderer()->DrawTextBullet(this, dc, bulletAttr, bulletRect, bulletText);
4428 }
4429 }
4430 }
4431
4432 // Draw the range for each line, one object at a time.
4433
4434 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
4435 while (node)
4436 {
4437 wxRichTextLine* line = node->GetData();
4438 wxRichTextRange lineRange = line->GetAbsoluteRange();
4439
4440 // Lines are specified relative to the paragraph
4441
4442 wxPoint linePosition = line->GetPosition() + GetPosition();
4443
4444 // Don't draw if off the screen
4445 if (((style & wxRICHTEXT_DRAW_IGNORE_CACHE) != 0) || ((linePosition.y + line->GetSize().y) >= rect.y && linePosition.y <= rect.y + rect.height))
4446 {
4447 wxPoint objectPosition = linePosition;
4448 int maxDescent = line->GetDescent();
4449
4450 // Loop through objects until we get to the one within range
4451 wxRichTextObjectList::compatibility_iterator node2 = m_children.GetFirst();
4452
4453 int i = 0;
4454 while (node2)
4455 {
4456 wxRichTextObject* child = node2->GetData();
4457
4458 if (!child->IsFloating() && child->GetRange().GetLength() > 0 && !child->GetRange().IsOutside(lineRange) && !lineRange.IsOutside(range))
4459 {
4460 // Draw this part of the line at the correct position
4461 wxRichTextRange objectRange(child->GetRange());
4462 objectRange.LimitTo(lineRange);
4463
4464 wxSize objectSize;
4465 if (child->IsTopLevel())
4466 {
4467 objectSize = child->GetCachedSize();
4468 objectRange = child->GetOwnRange();
4469 }
4470 else
4471 {
4472 #if wxRICHTEXT_USE_OPTIMIZED_LINE_DRAWING && wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
4473 if (i < (int) line->GetObjectSizes().GetCount())
4474 {
4475 objectSize.x = line->GetObjectSizes()[(size_t) i];
4476 }
4477 else
4478 #endif
4479 {
4480 int descent = 0;
4481 child->GetRangeSize(objectRange, objectSize, descent, dc, context, wxRICHTEXT_UNFORMATTED, objectPosition);
4482 }
4483 }
4484
4485 // Use the child object's width, but the whole line's height
4486 wxRect childRect(objectPosition, wxSize(objectSize.x, line->GetSize().y));
4487 child->Draw(dc, context, objectRange, selection, childRect, maxDescent, style);
4488
4489 objectPosition.x += objectSize.x;
4490 i ++;
4491 }
4492 else if (child->GetRange().GetStart() > lineRange.GetEnd())
4493 // Can break out of inner loop now since we've passed this line's range
4494 break;
4495
4496 node2 = node2->GetNext();
4497 }
4498 }
4499
4500 node = node->GetNext();
4501 }
4502
4503 return true;
4504 }
4505
4506 // Get the range width using partial extents calculated for the whole paragraph.
4507 static int wxRichTextGetRangeWidth(const wxRichTextParagraph& para, const wxRichTextRange& range, const wxArrayInt& partialExtents)
4508 {
4509 wxASSERT(partialExtents.GetCount() >= (size_t) range.GetLength());
4510
4511 if (partialExtents.GetCount() < (size_t) range.GetLength())
4512 return 0;
4513
4514 int leftMostPos = 0;
4515 if (range.GetStart() - para.GetRange().GetStart() > 0)
4516 leftMostPos = partialExtents[range.GetStart() - para.GetRange().GetStart() - 1];
4517
4518 int rightMostPos = partialExtents[range.GetEnd() - para.GetRange().GetStart()];
4519
4520 int w = rightMostPos - leftMostPos;
4521
4522 return w;
4523 }
4524
4525 /// Lay the item out
4526 bool wxRichTextParagraph::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& parentRect, int style)
4527 {
4528 // Deal with floating objects firstly before the normal layout
4529 wxRichTextBuffer* buffer = GetBuffer();
4530 wxASSERT(buffer);
4531 wxRichTextFloatCollector* collector = GetContainer()->GetFloatCollector();
4532 wxASSERT(collector);
4533 LayoutFloat(dc, context, rect, style, collector);
4534
4535 wxRichTextAttr attr = GetCombinedAttributes();
4536 context.ApplyVirtualAttributes(attr, this);
4537
4538 // ClearLines();
4539
4540 // Increase the size of the paragraph due to spacing
4541 int spaceBeforePara = ConvertTenthsMMToPixels(dc, attr.GetParagraphSpacingBefore());
4542 int spaceAfterPara = ConvertTenthsMMToPixels(dc, attr.GetParagraphSpacingAfter());
4543 int leftIndent = ConvertTenthsMMToPixels(dc, attr.GetLeftIndent());
4544 int leftSubIndent = ConvertTenthsMMToPixels(dc, attr.GetLeftSubIndent());
4545 int rightIndent = ConvertTenthsMMToPixels(dc, attr.GetRightIndent());
4546
4547 int lineSpacing = 0;
4548
4549 // Let's assume line spacing of 10 is normal, 15 is 1.5, 20 is 2, etc.
4550 if (attr.HasLineSpacing() && attr.GetLineSpacing() > 0 && attr.GetFont().IsOk())
4551 {
4552 wxCheckSetFont(dc, attr.GetFont());
4553 lineSpacing = (int) (double(dc.GetCharHeight()) * (double(attr.GetLineSpacing())/10.0 - 1.0));
4554 }
4555
4556 // Start position for each line relative to the paragraph
4557 int startPositionFirstLine = leftIndent;
4558 int startPositionSubsequentLines = leftIndent + leftSubIndent;
4559
4560 // If we have a bullet in this paragraph, the start position for the first line's text
4561 // is actually leftIndent + leftSubIndent.
4562 if (attr.GetBulletStyle() != wxTEXT_ATTR_BULLET_STYLE_NONE)
4563 startPositionFirstLine = startPositionSubsequentLines;
4564
4565 long lastEndPos = GetRange().GetStart()-1;
4566 long lastCompletedEndPos = lastEndPos;
4567
4568 int currentWidth = 0;
4569 SetPosition(rect.GetPosition());
4570
4571 wxPoint currentPosition(0, spaceBeforePara); // We will calculate lines relative to paragraph
4572 int lineHeight = 0;
4573 int maxWidth = 0;
4574 int maxHeight = currentPosition.y;
4575 int maxAscent = 0;
4576 int maxDescent = 0;
4577 int lineCount = 0;
4578 int lineAscent = 0;
4579 int lineDescent = 0;
4580
4581 wxRichTextObjectList::compatibility_iterator node;
4582
4583 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
4584 wxUnusedVar(style);
4585 wxArrayInt partialExtents;
4586
4587 wxSize paraSize;
4588 int paraDescent = 0;
4589
4590 // This calculates the partial text extents
4591 GetRangeSize(GetRange(), paraSize, paraDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_CACHE_SIZE, rect.GetPosition(), & partialExtents);
4592 #else
4593 node = m_children.GetFirst();
4594 while (node)
4595 {
4596 wxRichTextObject* child = node->GetData();
4597
4598 //child->SetCachedSize(wxDefaultSize);
4599 child->Layout(dc, context, rect, style);
4600
4601 node = node->GetNext();
4602 }
4603
4604 #endif
4605
4606 // Split up lines
4607
4608 // We may need to go back to a previous child, in which case create the new line,
4609 // find the child corresponding to the start position of the string, and
4610 // continue.
4611
4612 wxRect availableRect;
4613
4614 node = m_children.GetFirst();
4615 while (node)
4616 {
4617 wxRichTextObject* child = node->GetData();
4618
4619 // If floating, ignore. We already laid out floats.
4620 // Also ignore if empty object, except if we haven't got any
4621 // size yet.
4622 if (child->IsFloating() || !child->IsShown() ||
4623 (child->GetRange().GetLength() == 0 && maxHeight > spaceBeforePara)
4624 )
4625 {
4626 node = node->GetNext();
4627 continue;
4628 }
4629
4630 // If this is e.g. a composite text box, it will need to be laid out itself.
4631 // But if just a text fragment or image, for example, this will
4632 // do nothing. NB: won't we need to set the position after layout?
4633 // since for example if position is dependent on vertical line size, we
4634 // can't tell the position until the size is determined. So possibly introduce
4635 // another layout phase.
4636
4637 // We may only be looking at part of a child, if we searched back for wrapping
4638 // and found a suitable point some way into the child. So get the size for the fragment
4639 // if necessary.
4640
4641 long nextBreakPos = GetFirstLineBreakPosition(lastEndPos+1);
4642 long lastPosToUse = child->GetRange().GetEnd();
4643 bool lineBreakInThisObject = (nextBreakPos > -1 && nextBreakPos <= child->GetRange().GetEnd());
4644
4645 if (lineBreakInThisObject)
4646 lastPosToUse = nextBreakPos;
4647
4648 wxSize childSize;
4649 int childDescent = 0;
4650
4651 int startOffset = (lineCount == 0 ? startPositionFirstLine : startPositionSubsequentLines);
4652 availableRect = wxRect(rect.x + startOffset, rect.y + currentPosition.y,
4653 rect.width - startOffset - rightIndent, rect.height);
4654
4655 if (child->IsTopLevel())
4656 {
4657 wxSize oldSize = child->GetCachedSize();
4658
4659 child->Invalidate(wxRICHTEXT_ALL);
4660 child->SetPosition(wxPoint(0, 0));
4661
4662 // Lays out the object first with a given amount of space, and then if no width was specified in attr,
4663 // lays out the object again using the minimum size
4664 // The position will be determined by its location in its line,
4665 // and not by the child's actual position.
4666 child->LayoutToBestSize(dc, context, buffer,
4667 attr, child->GetAttributes(), availableRect, parentRect, style);
4668
4669 if (oldSize != child->GetCachedSize())
4670 {
4671 partialExtents.Clear();
4672
4673 // Recalculate the partial text extents since the child object changed size
4674 GetRangeSize(GetRange(), paraSize, paraDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_CACHE_SIZE, wxPoint(0,0), & partialExtents);
4675 }
4676 }
4677
4678 // Problem: we need to layout composites here for which we need the available width,
4679 // but we can't get the available width without using the float collector which
4680 // needs to know the object height.
4681
4682 if ((nextBreakPos == -1) && (lastEndPos == child->GetRange().GetStart() - 1)) // i.e. we want to get the whole thing
4683 {
4684 childSize = child->GetCachedSize();
4685 childDescent = child->GetDescent();
4686 }
4687 else
4688 {
4689 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
4690 // Get height only, then the width using the partial extents
4691 GetRangeSize(wxRichTextRange(lastEndPos+1, lastPosToUse), childSize, childDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_HEIGHT_ONLY);
4692 childSize.x = wxRichTextGetRangeWidth(*this, wxRichTextRange(lastEndPos+1, lastPosToUse), partialExtents);
4693 #else
4694 GetRangeSize(wxRichTextRange(lastEndPos+1, lastPosToUse), childSize, childDescent, dc, context, wxRICHTEXT_UNFORMATTED, rect.GetPosition());
4695 #endif
4696 }
4697
4698 bool doLoop = true;
4699 int loopIterations = 0;
4700
4701 // If there are nested objects that need to lay themselves out, we have to do this in a
4702 // loop because the height of the object may well depend on the available width.
4703 // And because of floating object positioning, the available width depends on the
4704 // height of the object and whether it will clash with the floating objects.
4705 // So, we see whether the available width changes due to the presence of floating images.
4706 // If it does, then we'll use the new restricted width to find the object height again.
4707 // If this causes another restriction in the available width, we'll try again, until
4708 // either we lose patience or the available width settles down.
4709 do
4710 {
4711 loopIterations ++;
4712
4713 wxRect oldAvailableRect = availableRect;
4714
4715 // Available width depends on the floating objects and the line height.
4716 // Note: the floating objects may be placed vertically along the two side of
4717 // buffer, so we may have different available line widths with different
4718 // [startY, endY]. So, we can't determine how wide the available
4719 // space is until we know the exact line height.
4720 lineDescent = wxMax(childDescent, maxDescent);
4721 lineAscent = wxMax(childSize.y-childDescent, maxAscent);
4722 lineHeight = lineDescent + lineAscent;
4723 wxRect floatAvailableRect = collector->GetAvailableRect(rect.y + currentPosition.y, rect.y + currentPosition.y + lineHeight);
4724
4725 // Adjust availableRect to the space that is available when taking floating objects into account.
4726
4727 if (floatAvailableRect.x + startOffset > availableRect.x)
4728 {
4729 int newX = floatAvailableRect.x + startOffset;
4730 int newW = availableRect.width - (newX - availableRect.x);
4731 availableRect.x = newX;
4732 availableRect.width = newW;
4733 }
4734
4735 if (floatAvailableRect.width < availableRect.width)
4736 availableRect.width = floatAvailableRect.width;
4737
4738 currentPosition.x = availableRect.x - rect.x;
4739
4740 if (child->IsTopLevel() && loopIterations <= 20)
4741 {
4742 if (availableRect != oldAvailableRect)
4743 {
4744 wxSize oldSize = child->GetCachedSize();
4745
4746 //child->SetCachedSize(wxDefaultSize);
4747 // Lays out the object first with a given amount of space, and then if no width was specified in attr,
4748 // lays out the object again using the minimum size
4749 child->Invalidate(wxRICHTEXT_ALL);
4750 child->LayoutToBestSize(dc, context, buffer,
4751 attr, child->GetAttributes(), availableRect, parentRect, style);
4752 childSize = child->GetCachedSize();
4753 childDescent = child->GetDescent();
4754 //child->SetPosition(availableRect.GetPosition());
4755
4756 if (oldSize != child->GetCachedSize())
4757 {
4758 partialExtents.Clear();
4759
4760 // Recalculate the partial text extents since the child object changed size
4761 GetRangeSize(GetRange(), paraSize, paraDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_CACHE_SIZE, wxPoint(0,0), & partialExtents);
4762 }
4763
4764 // Go around the loop finding the available rect for the given floating objects
4765 }
4766 else
4767 doLoop = false;
4768 }
4769 else
4770 doLoop = false;
4771 }
4772 while (doLoop);
4773
4774 // Cases:
4775 // 1) There was a line break BEFORE the natural break
4776 // 2) There was a line break AFTER the natural break
4777 // 3) It's the last line
4778 // 4) The child still fits (carry on) - 'else' clause
4779
4780 if ((lineBreakInThisObject && (childSize.x + currentWidth <= availableRect.width))
4781 ||
4782 (childSize.x + currentWidth > availableRect.width)
4783 ||
4784 ((childSize.x + currentWidth <= availableRect.width) && !node->GetNext())
4785
4786 )
4787 {
4788 if (child->IsTopLevel())
4789 {
4790 // We can move it to the correct position at this point
4791 child->Move(GetPosition() + wxPoint(currentWidth, currentPosition.y));
4792 }
4793
4794 long wrapPosition = 0;
4795 if ((childSize.x + currentWidth <= availableRect.width) && !node->GetNext() && !lineBreakInThisObject)
4796 wrapPosition = child->GetRange().GetEnd();
4797 else
4798
4799 // Find a place to wrap. This may walk back to previous children,
4800 // for example if a word spans several objects.
4801 // Note: one object must contains only one wxTextAtrr, so the line height will not
4802 // change inside one object. Thus, we can pass the remain line width to the
4803 // FindWrapPosition function.
4804 if (!FindWrapPosition(wxRichTextRange(lastCompletedEndPos+1, child->GetRange().GetEnd()), dc, context, availableRect.width, wrapPosition, & partialExtents))
4805 {
4806 // If the function failed, just cut it off at the end of this child.
4807 wrapPosition = child->GetRange().GetEnd();
4808 }
4809
4810 // FindWrapPosition can still return a value that will put us in an endless wrapping loop
4811 if (wrapPosition <= lastCompletedEndPos)
4812 wrapPosition = wxMax(lastCompletedEndPos+1,child->GetRange().GetEnd());
4813
4814 // Line end position shouldn't be the same as the end, or greater.
4815 if (wrapPosition >= GetRange().GetEnd())
4816 wrapPosition = GetRange().GetEnd()-1;
4817
4818 // wxLogDebug(wxT("Split at %ld"), wrapPosition);
4819
4820 // Let's find the actual size of the current line now
4821 wxSize actualSize;
4822 wxRichTextRange actualRange(lastCompletedEndPos+1, wrapPosition);
4823
4824 /// Use previous descent, not the wrapping descent we just found, since this may be too big
4825 /// for the fragment we're about to add.
4826 childDescent = maxDescent;
4827
4828 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
4829 if (!child->IsEmpty())
4830 {
4831 // Get height only, then the width using the partial extents
4832 GetRangeSize(actualRange, actualSize, childDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_HEIGHT_ONLY);
4833 actualSize.x = wxRichTextGetRangeWidth(*this, actualRange, partialExtents);
4834 }
4835 else
4836 #endif
4837 GetRangeSize(actualRange, actualSize, childDescent, dc, context, wxRICHTEXT_UNFORMATTED);
4838
4839 currentWidth = actualSize.x;
4840 maxDescent = wxMax(childDescent, maxDescent);
4841 maxAscent = wxMax(actualSize.y-childDescent, maxAscent);
4842 lineHeight = maxDescent + maxAscent;
4843
4844 if (lineHeight == 0 && buffer)
4845 {
4846 wxFont font(buffer->GetFontTable().FindFont(attr));
4847 wxCheckSetFont(dc, font);
4848 lineHeight = dc.GetCharHeight();
4849 }
4850
4851 if (maxDescent == 0)
4852 {
4853 int w, h;
4854 dc.GetTextExtent(wxT("X"), & w, &h, & maxDescent);
4855 }
4856
4857 // Add a new line
4858 wxRichTextLine* line = AllocateLine(lineCount);
4859
4860 // Set relative range so we won't have to change line ranges when paragraphs are moved
4861 line->SetRange(wxRichTextRange(actualRange.GetStart() - GetRange().GetStart(), actualRange.GetEnd() - GetRange().GetStart()));
4862 line->SetPosition(currentPosition);
4863 line->SetSize(wxSize(currentWidth, lineHeight));
4864 line->SetDescent(maxDescent);
4865
4866 maxHeight = currentPosition.y + lineHeight;
4867
4868 // Now move down a line. TODO: add margins, spacing
4869 currentPosition.y += lineHeight;
4870 currentPosition.y += lineSpacing;
4871 maxDescent = 0;
4872 maxAscent = 0;
4873 maxWidth = wxMax(maxWidth, currentWidth+startOffset);
4874 currentWidth = 0;
4875
4876 lineCount ++;
4877
4878 // TODO: account for zero-length objects, such as fields
4879 // wxASSERT(wrapPosition > lastCompletedEndPos);
4880
4881 lastEndPos = wrapPosition;
4882 lastCompletedEndPos = lastEndPos;
4883
4884 lineHeight = 0;
4885
4886 if (wrapPosition < GetRange().GetEnd()-1)
4887 {
4888 // May need to set the node back to a previous one, due to searching back in wrapping
4889 wxRichTextObject* childAfterWrapPosition = FindObjectAtPosition(wrapPosition+1);
4890 if (childAfterWrapPosition)
4891 node = m_children.Find(childAfterWrapPosition);
4892 else
4893 node = node->GetNext();
4894 }
4895 else
4896 node = node->GetNext();
4897
4898 // Apply paragraph styles such as alignment to the wrapped line
4899 ApplyParagraphStyle(line, attr, availableRect, dc);
4900 }
4901 else
4902 {
4903 // We still fit, so don't add a line, and keep going
4904 currentWidth += childSize.x;
4905 maxDescent = wxMax(childDescent, maxDescent);
4906 maxAscent = wxMax(childSize.y-childDescent, maxAscent);
4907 lineHeight = maxDescent + maxAscent;
4908
4909 maxWidth = wxMax(maxWidth, currentWidth+startOffset);
4910 lastEndPos = child->GetRange().GetEnd();
4911
4912 node = node->GetNext();
4913 }
4914 }
4915
4916 //wxASSERT(!(lastCompletedEndPos != -1 && lastCompletedEndPos < GetRange().GetEnd()-1));
4917
4918 // Remove remaining unused line objects, if any
4919 ClearUnusedLines(lineCount);
4920
4921 // We need to add back the margins etc.
4922 {
4923 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
4924 contentRect = wxRect(wxPoint(0, 0), wxSize(maxWidth, currentPosition.y + spaceAfterPara));
4925 GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
4926 SetCachedSize(marginRect.GetSize());
4927 }
4928
4929 // The maximum size is the length of the paragraph stretched out into a line.
4930 // So if there were a single word, or an image, or a fixed-size text box, the object could be shrunk around
4931 // this size. TODO: take into account line breaks.
4932 {
4933 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
4934 contentRect = wxRect(wxPoint(0, 0), wxSize(paraSize.x + wxMax(leftIndent, leftIndent + leftSubIndent) + rightIndent, currentPosition.y + spaceAfterPara));
4935 GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
4936 SetMaxSize(marginRect.GetSize());
4937 }
4938
4939 // Find the greatest minimum size. Currently we only look at non-text objects,
4940 // which isn't ideal but it would be slow to find the maximum word width to
4941 // use as the minimum.
4942 {
4943 int minWidth = 0;
4944 node = m_children.GetFirst();
4945 while (node)
4946 {
4947 wxRichTextObject* child = node->GetData();
4948
4949 // If floating, ignore. We already laid out floats.
4950 // Also ignore if empty object, except if we haven't got any
4951 // size yet.
4952 if (!child->IsFloating() && child->GetRange().GetLength() != 0 && !child->IsKindOf(CLASSINFO(wxRichTextPlainText)))
4953 {
4954 if (child->GetCachedSize().x > minWidth)
4955 minWidth = child->GetMinSize().x;
4956 }
4957 node = node->GetNext();
4958 }
4959
4960 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
4961 contentRect = wxRect(wxPoint(0, 0), wxSize(minWidth, currentPosition.y + spaceAfterPara));
4962 GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
4963 SetMinSize(marginRect.GetSize());
4964 }
4965
4966
4967 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
4968 #if wxRICHTEXT_USE_OPTIMIZED_LINE_DRAWING
4969 // Use the text extents to calculate the size of each fragment in each line
4970 wxRichTextLineList::compatibility_iterator lineNode = m_cachedLines.GetFirst();
4971 while (lineNode)
4972 {
4973 wxRichTextLine* line = lineNode->GetData();
4974 wxRichTextRange lineRange = line->GetAbsoluteRange();
4975
4976 // Loop through objects until we get to the one within range
4977 wxRichTextObjectList::compatibility_iterator node2 = m_children.GetFirst();
4978
4979 while (node2)
4980 {
4981 wxRichTextObject* child = node2->GetData();
4982
4983 if (child->GetRange().GetLength() > 0 && !child->GetRange().IsOutside(lineRange))
4984 {
4985 wxRichTextRange rangeToUse = lineRange;
4986 rangeToUse.LimitTo(child->GetRange());
4987
4988 // Find the size of the child from the text extents, and store in an array
4989 // for drawing later
4990 int left = 0;
4991 if (rangeToUse.GetStart() > GetRange().GetStart())
4992 left = partialExtents[(rangeToUse.GetStart()-1) - GetRange().GetStart()];
4993 int right = partialExtents[rangeToUse.GetEnd() - GetRange().GetStart()];
4994 int sz = right - left;
4995 line->GetObjectSizes().Add(sz);
4996 }
4997 else if (child->GetRange().GetStart() > lineRange.GetEnd())
4998 // Can break out of inner loop now since we've passed this line's range
4999 break;
5000
5001 node2 = node2->GetNext();
5002 }
5003
5004 lineNode = lineNode->GetNext();
5005 }
5006 #endif
5007 #endif
5008
5009 return true;
5010 }
5011
5012 /// Apply paragraph styles, such as centering, to wrapped lines
5013 /// TODO: take into account box attributes, possibly
5014 void wxRichTextParagraph::ApplyParagraphStyle(wxRichTextLine* line, const wxRichTextAttr& attr, const wxRect& rect, wxDC& dc)
5015 {
5016 if (!attr.HasAlignment())
5017 return;
5018
5019 wxPoint pos = line->GetPosition();
5020 wxSize size = line->GetSize();
5021
5022 // centering, right-justification
5023 if (attr.HasAlignment() && attr.GetAlignment() == wxTEXT_ALIGNMENT_CENTRE)
5024 {
5025 int rightIndent = ConvertTenthsMMToPixels(dc, attr.GetRightIndent());
5026 pos.x = (rect.GetWidth() - rightIndent - size.x)/2 + pos.x;
5027 line->SetPosition(pos);
5028 }
5029 else if (attr.HasAlignment() && attr.GetAlignment() == wxTEXT_ALIGNMENT_RIGHT)
5030 {
5031 int rightIndent = ConvertTenthsMMToPixels(dc, attr.GetRightIndent());
5032 pos.x = pos.x + rect.GetWidth() - size.x - rightIndent;
5033 line->SetPosition(pos);
5034 }
5035 }
5036
5037 /// Insert text at the given position
5038 bool wxRichTextParagraph::InsertText(long pos, const wxString& text)
5039 {
5040 wxRichTextObject* childToUse = NULL;
5041 wxRichTextObjectList::compatibility_iterator nodeToUse = wxRichTextObjectList::compatibility_iterator();
5042
5043 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
5044 while (node)
5045 {
5046 wxRichTextObject* child = node->GetData();
5047 if (child->GetRange().Contains(pos) && child->GetRange().GetLength() > 0)
5048 {
5049 childToUse = child;
5050 nodeToUse = node;
5051 break;
5052 }
5053
5054 node = node->GetNext();
5055 }
5056
5057 if (childToUse)
5058 {
5059 wxRichTextPlainText* textObject = wxDynamicCast(childToUse, wxRichTextPlainText);
5060 if (textObject)
5061 {
5062 int posInString = pos - textObject->GetRange().GetStart();
5063
5064 wxString newText = textObject->GetText().Mid(0, posInString) +
5065 text + textObject->GetText().Mid(posInString);
5066 textObject->SetText(newText);
5067
5068 int textLength = text.length();
5069
5070 textObject->SetRange(wxRichTextRange(textObject->GetRange().GetStart(),
5071 textObject->GetRange().GetEnd() + textLength));
5072
5073 // Increment the end range of subsequent fragments in this paragraph.
5074 // We'll set the paragraph range itself at a higher level.
5075
5076 wxRichTextObjectList::compatibility_iterator node = nodeToUse->GetNext();
5077 while (node)
5078 {
5079 wxRichTextObject* child = node->GetData();
5080 child->SetRange(wxRichTextRange(textObject->GetRange().GetStart() + textLength,
5081 textObject->GetRange().GetEnd() + textLength));
5082
5083 node = node->GetNext();
5084 }
5085
5086 return true;
5087 }
5088 else
5089 {
5090 // TODO: if not a text object, insert at closest position, e.g. in front of it
5091 }
5092 }
5093 else
5094 {
5095 // Add at end.
5096 // Don't pass parent initially to suppress auto-setting of parent range.
5097 // We'll do that at a higher level.
5098 wxRichTextPlainText* textObject = new wxRichTextPlainText(text, this);
5099
5100 AppendChild(textObject);
5101 return true;
5102 }
5103
5104 return false;
5105 }
5106
5107 void wxRichTextParagraph::Copy(const wxRichTextParagraph& obj)
5108 {
5109 wxRichTextCompositeObject::Copy(obj);
5110 }
5111
5112 /// Clear the cached lines
5113 void wxRichTextParagraph::ClearLines()
5114 {
5115 WX_CLEAR_LIST(wxRichTextLineList, m_cachedLines);
5116 }
5117
5118 /// Get/set the object size for the given range. Returns false if the range
5119 /// is invalid for this object.
5120 bool wxRichTextParagraph::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, wxPoint position, wxArrayInt* partialExtents) const
5121 {
5122 if (!range.IsWithin(GetRange()))
5123 return false;
5124
5125 if (flags & wxRICHTEXT_UNFORMATTED)
5126 {
5127 // Just use unformatted data, assume no line breaks
5128 // TODO: take into account line breaks
5129
5130 wxSize sz;
5131
5132 wxArrayInt childExtents;
5133 wxArrayInt* p;
5134 if (partialExtents)
5135 p = & childExtents;
5136 else
5137 p = NULL;
5138
5139 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
5140 while (node)
5141 {
5142
5143 wxRichTextObject* child = node->GetData();
5144 if (!child->GetRange().IsOutside(range))
5145 {
5146 // Floating objects have a zero size within the paragraph.
5147 if (child->IsFloating())
5148 {
5149 if (partialExtents)
5150 {
5151 int lastSize;
5152 if (partialExtents->GetCount() > 0)
5153 lastSize = (*partialExtents)[partialExtents->GetCount()-1];
5154 else
5155 lastSize = 0;
5156
5157 partialExtents->Add(0 /* zero size */ + lastSize);
5158 }
5159 }
5160 else
5161 {
5162 wxSize childSize;
5163
5164 wxRichTextRange rangeToUse = range;
5165 rangeToUse.LimitTo(child->GetRange());
5166 #if 0
5167 if (child->IsTopLevel())
5168 rangeToUse = child->GetOwnRange();
5169 #endif
5170 int childDescent = 0;
5171
5172 // At present wxRICHTEXT_HEIGHT_ONLY is only fast if we're already cached the size,
5173 // but it's only going to be used after caching has taken place.
5174 if ((flags & wxRICHTEXT_HEIGHT_ONLY) && child->GetCachedSize().y != 0)
5175 {
5176 childDescent = child->GetDescent();
5177 childSize = child->GetCachedSize();
5178
5179 sz.y = wxMax(sz.y, childSize.y);
5180 sz.x += childSize.x;
5181 descent = wxMax(descent, childDescent);
5182 }
5183 else if (child->IsTopLevel())
5184 {
5185 childDescent = child->GetDescent();
5186 childSize = child->GetCachedSize();
5187
5188 sz.y = wxMax(sz.y, childSize.y);
5189 sz.x += childSize.x;
5190 descent = wxMax(descent, childDescent);
5191 if ((flags & wxRICHTEXT_CACHE_SIZE) && (rangeToUse == child->GetRange()))
5192 {
5193 child->SetCachedSize(childSize);
5194 child->SetDescent(childDescent);
5195 }
5196
5197 if (partialExtents)
5198 {
5199 int lastSize;
5200 if (partialExtents->GetCount() > 0)
5201 lastSize = (*partialExtents)[partialExtents->GetCount()-1];
5202 else
5203 lastSize = 0;
5204
5205 partialExtents->Add(childSize.x + lastSize);
5206 }
5207 }
5208 else if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, context, flags, wxPoint(position.x + sz.x, position.y), p))
5209 {
5210 sz.y = wxMax(sz.y, childSize.y);
5211 sz.x += childSize.x;
5212 descent = wxMax(descent, childDescent);
5213
5214 if ((flags & wxRICHTEXT_CACHE_SIZE) && (rangeToUse == child->GetRange()))
5215 {
5216 child->SetCachedSize(childSize);
5217 child->SetDescent(childDescent);
5218 }
5219
5220 if (partialExtents)
5221 {
5222 int lastSize;
5223 if (partialExtents->GetCount() > 0)
5224 lastSize = (*partialExtents)[partialExtents->GetCount()-1];
5225 else
5226 lastSize = 0;
5227
5228 size_t i;
5229 for (i = 0; i < childExtents.GetCount(); i++)
5230 {
5231 partialExtents->Add(childExtents[i] + lastSize);
5232 }
5233 }
5234 }
5235 }
5236
5237 if (p)
5238 p->Clear();
5239 }
5240
5241 node = node->GetNext();
5242 }
5243 size = sz;
5244 }
5245 else
5246 {
5247 // Use formatted data, with line breaks
5248 wxSize sz;
5249
5250 // We're going to loop through each line, and then for each line,
5251 // call GetRangeSize for the fragment that comprises that line.
5252 // Only we have to do that multiple times within the line, because
5253 // the line may be broken into pieces. For now ignore line break commands
5254 // (so we can assume that getting the unformatted size for a fragment
5255 // within a line is the actual size)
5256
5257 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
5258 while (node)
5259 {
5260 wxRichTextLine* line = node->GetData();
5261 wxRichTextRange lineRange = line->GetAbsoluteRange();
5262 if (!lineRange.IsOutside(range))
5263 {
5264 wxSize lineSize;
5265
5266 wxRichTextObjectList::compatibility_iterator node2 = m_children.GetFirst();
5267 while (node2)
5268 {
5269 wxRichTextObject* child = node2->GetData();
5270
5271 if (!child->IsFloating() && !child->GetRange().IsOutside(lineRange))
5272 {
5273 wxRichTextRange rangeToUse = lineRange;
5274 rangeToUse.LimitTo(child->GetRange());
5275 if (child->IsTopLevel())
5276 rangeToUse = child->GetOwnRange();
5277
5278 wxSize childSize;
5279 int childDescent = 0;
5280 if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, context, flags, wxPoint(position.x + sz.x, position.y)))
5281 {
5282 lineSize.y = wxMax(lineSize.y, childSize.y);
5283 lineSize.x += childSize.x;
5284 }
5285 descent = wxMax(descent, childDescent);
5286 }
5287
5288 node2 = node2->GetNext();
5289 }
5290
5291 // Increase size by a line (TODO: paragraph spacing)
5292 sz.y += lineSize.y;
5293 sz.x = wxMax(sz.x, lineSize.x);
5294 }
5295 node = node->GetNext();
5296 }
5297 size = sz;
5298 }
5299 return true;
5300 }
5301
5302 /// Finds the absolute position and row height for the given character position
5303 bool wxRichTextParagraph::FindPosition(wxDC& dc, wxRichTextDrawingContext& context, long index, wxPoint& pt, int* height, bool forceLineStart)
5304 {
5305 if (index == -1)
5306 {
5307 wxRichTextLine* line = ((wxRichTextParagraphLayoutBox*)GetParent())->GetLineAtPosition(0);
5308 if (line)
5309 *height = line->GetSize().y;
5310 else
5311 *height = dc.GetCharHeight();
5312
5313 // -1 means 'the start of the buffer'.
5314 pt = GetPosition();
5315 if (line)
5316 pt = pt + line->GetPosition();
5317
5318 return true;
5319 }
5320
5321 // The final position in a paragraph is taken to mean the position
5322 // at the start of the next paragraph.
5323 if (index == GetRange().GetEnd())
5324 {
5325 wxRichTextParagraphLayoutBox* parent = wxDynamicCast(GetParent(), wxRichTextParagraphLayoutBox);
5326 wxASSERT( parent != NULL );
5327
5328 // Find the height at the next paragraph, if any
5329 wxRichTextLine* line = parent->GetLineAtPosition(index + 1);
5330 if (line)
5331 {
5332 *height = line->GetSize().y;
5333 pt = line->GetAbsolutePosition();
5334 }
5335 else
5336 {
5337 *height = dc.GetCharHeight();
5338 int indent = ConvertTenthsMMToPixels(dc, m_attributes.GetLeftIndent());
5339 pt = wxPoint(indent, GetCachedSize().y);
5340 }
5341
5342 return true;
5343 }
5344
5345 if (index < GetRange().GetStart() || index > GetRange().GetEnd())
5346 return false;
5347
5348 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
5349 while (node)
5350 {
5351 wxRichTextLine* line = node->GetData();
5352 wxRichTextRange lineRange = line->GetAbsoluteRange();
5353 if (index >= lineRange.GetStart() && index <= lineRange.GetEnd())
5354 {
5355 // If this is the last point in the line, and we're forcing the
5356 // returned value to be the start of the next line, do the required
5357 // thing.
5358 if (index == lineRange.GetEnd() && forceLineStart)
5359 {
5360 if (node->GetNext())
5361 {
5362 wxRichTextLine* nextLine = node->GetNext()->GetData();
5363 *height = nextLine->GetSize().y;
5364 pt = nextLine->GetAbsolutePosition();
5365 return true;
5366 }
5367 }
5368
5369 pt.y = line->GetPosition().y + GetPosition().y;
5370
5371 wxRichTextRange r(lineRange.GetStart(), index);
5372 wxSize rangeSize;
5373 int descent = 0;
5374
5375 // We find the size of the line up to this point,
5376 // then we can add this size to the line start position and
5377 // paragraph start position to find the actual position.
5378
5379 if (GetRangeSize(r, rangeSize, descent, dc, context, wxRICHTEXT_UNFORMATTED, line->GetPosition()+ GetPosition()))
5380 {
5381 pt.x = line->GetPosition().x + GetPosition().x + rangeSize.x;
5382 *height = line->GetSize().y;
5383
5384 return true;
5385 }
5386
5387 }
5388
5389 node = node->GetNext();
5390 }
5391
5392 return false;
5393 }
5394
5395 /// Hit-testing: returns a flag indicating hit test details, plus
5396 /// information about position
5397 int wxRichTextParagraph::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int flags)
5398 {
5399 if (!IsShown())
5400 return wxRICHTEXT_HITTEST_NONE;
5401
5402 // If we're in the top-level container, then we can return
5403 // a suitable hit test code even if the point is outside the container area,
5404 // so that we can position the caret sensibly even if we don't
5405 // click on valid content. If we're not at the top-level, and the point
5406 // is not within this paragraph object, then we don't want to stop more
5407 // precise hit-testing from working prematurely, so return immediately.
5408 // NEW STRATEGY: use the parent boundary to test whether we're in the
5409 // right region, not the paragraph, since the paragraph may be positioned
5410 // some way in from where the user clicks.
5411 {
5412 long tmpPos;
5413 wxRichTextObject* tempObj, *tempContextObj;
5414 if (GetParent() && GetParent()->wxRichTextObject::HitTest(dc, context, pt, tmpPos, & tempObj, & tempContextObj, flags) == wxRICHTEXT_HITTEST_NONE)
5415 return wxRICHTEXT_HITTEST_NONE;
5416 }
5417
5418 wxRichTextObjectList::compatibility_iterator objNode = m_children.GetFirst();
5419 while (objNode)
5420 {
5421 wxRichTextObject* child = objNode->GetData();
5422 if (child->IsTopLevel() && ((flags & wxRICHTEXT_HITTEST_NO_NESTED_OBJECTS) == 0))
5423 {
5424 {
5425 int hitTest = child->HitTest(dc, context, pt, textPosition, obj, contextObj);
5426 if (hitTest != wxRICHTEXT_HITTEST_NONE)
5427 return hitTest;
5428 }
5429 }
5430
5431 objNode = objNode->GetNext();
5432 }
5433
5434 wxPoint paraPos = GetPosition();
5435
5436 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
5437 while (node)
5438 {
5439 wxRichTextLine* line = node->GetData();
5440 wxPoint linePos = paraPos + line->GetPosition();
5441 wxSize lineSize = line->GetSize();
5442 wxRichTextRange lineRange = line->GetAbsoluteRange();
5443
5444 if (pt.y <= linePos.y + lineSize.y)
5445 {
5446 if (pt.x < linePos.x)
5447 {
5448 textPosition = lineRange.GetStart();
5449 *obj = FindObjectAtPosition(textPosition);
5450 *contextObj = GetContainer();
5451 return wxRICHTEXT_HITTEST_BEFORE|wxRICHTEXT_HITTEST_OUTSIDE;
5452 }
5453 else if (pt.x >= (linePos.x + lineSize.x))
5454 {
5455 textPosition = lineRange.GetEnd();
5456 *obj = FindObjectAtPosition(textPosition);
5457 *contextObj = GetContainer();
5458 return wxRICHTEXT_HITTEST_AFTER|wxRICHTEXT_HITTEST_OUTSIDE;
5459 }
5460 else
5461 {
5462 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
5463 wxArrayInt partialExtents;
5464
5465 wxSize paraSize;
5466 int paraDescent;
5467
5468 // This calculates the partial text extents
5469 GetRangeSize(lineRange, paraSize, paraDescent, dc, context, wxRICHTEXT_UNFORMATTED, linePos, & partialExtents);
5470
5471 int lastX = linePos.x;
5472 size_t i;
5473 for (i = 0; i < partialExtents.GetCount(); i++)
5474 {
5475 int nextX = partialExtents[i] + linePos.x;
5476
5477 if (pt.x >= lastX && pt.x <= nextX)
5478 {
5479 textPosition = i + lineRange.GetStart(); // minus 1?
5480
5481 *obj = FindObjectAtPosition(textPosition);
5482 *contextObj = GetContainer();
5483
5484 // So now we know it's between i-1 and i.
5485 // Let's see if we can be more precise about
5486 // which side of the position it's on.
5487
5488 int midPoint = (nextX + lastX)/2;
5489 if (pt.x >= midPoint)
5490 return wxRICHTEXT_HITTEST_AFTER;
5491 else
5492 return wxRICHTEXT_HITTEST_BEFORE;
5493 }
5494
5495 lastX = nextX;
5496 }
5497 #else
5498 long i;
5499 int lastX = linePos.x;
5500 for (i = lineRange.GetStart(); i <= lineRange.GetEnd(); i++)
5501 {
5502 wxSize childSize;
5503 int descent = 0;
5504
5505 wxRichTextRange rangeToUse(lineRange.GetStart(), i);
5506
5507 GetRangeSize(rangeToUse, childSize, descent, dc, context, wxRICHTEXT_UNFORMATTED, linePos);
5508
5509 int nextX = childSize.x + linePos.x;
5510
5511 if (pt.x >= lastX && pt.x <= nextX)
5512 {
5513 textPosition = i;
5514
5515 *obj = FindObjectAtPosition(textPosition);
5516 *contextObj = GetContainer();
5517
5518 // So now we know it's between i-1 and i.
5519 // Let's see if we can be more precise about
5520 // which side of the position it's on.
5521
5522 int midPoint = (nextX + lastX)/2;
5523 if (pt.x >= midPoint)
5524 return wxRICHTEXT_HITTEST_AFTER;
5525 else
5526 return wxRICHTEXT_HITTEST_BEFORE;
5527 }
5528 else
5529 {
5530 lastX = nextX;
5531 }
5532 }
5533 #endif
5534 }
5535 }
5536
5537 node = node->GetNext();
5538 }
5539
5540 return wxRICHTEXT_HITTEST_NONE;
5541 }
5542
5543 /// Split an object at this position if necessary, and return
5544 /// the previous object, or NULL if inserting at beginning.
5545 wxRichTextObject* wxRichTextParagraph::SplitAt(long pos, wxRichTextObject** previousObject)
5546 {
5547 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
5548 while (node)
5549 {
5550 wxRichTextObject* child = node->GetData();
5551
5552 if (pos == child->GetRange().GetStart())
5553 {
5554 if (previousObject)
5555 {
5556 if (node->GetPrevious())
5557 *previousObject = node->GetPrevious()->GetData();
5558 else
5559 *previousObject = NULL;
5560 }
5561
5562 return child;
5563 }
5564
5565 if (child->GetRange().Contains(pos))
5566 {
5567 // This should create a new object, transferring part of
5568 // the content to the old object and the rest to the new object.
5569 wxRichTextObject* newObject = child->DoSplit(pos);
5570
5571 // If we couldn't split this object, just insert in front of it.
5572 if (!newObject)
5573 {
5574 // Maybe this is an empty string, try the next one
5575 // return child;
5576 }
5577 else
5578 {
5579 // Insert the new object after 'child'
5580 if (node->GetNext())
5581 m_children.Insert(node->GetNext(), newObject);
5582 else
5583 m_children.Append(newObject);
5584 newObject->SetParent(this);
5585
5586 if (previousObject)
5587 *previousObject = child;
5588
5589 return newObject;
5590 }
5591 }
5592
5593 node = node->GetNext();
5594 }
5595 if (previousObject)
5596 *previousObject = NULL;
5597 return NULL;
5598 }
5599
5600 /// Move content to a list from obj on
5601 void wxRichTextParagraph::MoveToList(wxRichTextObject* obj, wxList& list)
5602 {
5603 wxRichTextObjectList::compatibility_iterator node = m_children.Find(obj);
5604 while (node)
5605 {
5606 wxRichTextObject* child = node->GetData();
5607 list.Append(child);
5608
5609 wxRichTextObjectList::compatibility_iterator oldNode = node;
5610
5611 node = node->GetNext();
5612
5613 m_children.DeleteNode(oldNode);
5614 }
5615 }
5616
5617 /// Add content back from list
5618 void wxRichTextParagraph::MoveFromList(wxList& list)
5619 {
5620 for (wxList::compatibility_iterator node = list.GetFirst(); node; node = node->GetNext())
5621 {
5622 AppendChild((wxRichTextObject*) node->GetData());
5623 }
5624 }
5625
5626 /// Calculate range
5627 void wxRichTextParagraph::CalculateRange(long start, long& end)
5628 {
5629 wxRichTextCompositeObject::CalculateRange(start, end);
5630
5631 // Add one for end of paragraph
5632 end ++;
5633
5634 m_range.SetRange(start, end);
5635 }
5636
5637 /// Find the object at the given position
5638 wxRichTextObject* wxRichTextParagraph::FindObjectAtPosition(long position)
5639 {
5640 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
5641 while (node)
5642 {
5643 wxRichTextObject* obj = node->GetData();
5644 if (obj->GetRange().Contains(position) ||
5645 obj->GetRange().GetStart() == position ||
5646 obj->GetRange().GetEnd() == position)
5647 return obj;
5648
5649 node = node->GetNext();
5650 }
5651 return NULL;
5652 }
5653
5654 /// Get the plain text searching from the start or end of the range.
5655 /// The resulting string may be shorter than the range given.
5656 bool wxRichTextParagraph::GetContiguousPlainText(wxString& text, const wxRichTextRange& range, bool fromStart)
5657 {
5658 text = wxEmptyString;
5659
5660 if (fromStart)
5661 {
5662 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
5663 while (node)
5664 {
5665 wxRichTextObject* obj = node->GetData();
5666 if (!obj->GetRange().IsOutside(range))
5667 {
5668 wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText);
5669 if (textObj)
5670 {
5671 text += textObj->GetTextForRange(range);
5672 }
5673 else
5674 {
5675 text += wxT(" ");
5676 }
5677 }
5678
5679 node = node->GetNext();
5680 }
5681 }
5682 else
5683 {
5684 wxRichTextObjectList::compatibility_iterator node = m_children.GetLast();
5685 while (node)
5686 {
5687 wxRichTextObject* obj = node->GetData();
5688 if (!obj->GetRange().IsOutside(range))
5689 {
5690 wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText);
5691 if (textObj)
5692 {
5693 text = textObj->GetTextForRange(range) + text;
5694 }
5695 else
5696 {
5697 text = wxT(" ") + text;
5698 }
5699 }
5700
5701 node = node->GetPrevious();
5702 }
5703 }
5704
5705 return true;
5706 }
5707
5708 /// Find a suitable wrap position.
5709 bool wxRichTextParagraph::FindWrapPosition(const wxRichTextRange& range, wxDC& dc, wxRichTextDrawingContext& context, int availableSpace, long& wrapPosition, wxArrayInt* partialExtents)
5710 {
5711 if (range.GetLength() <= 0)
5712 return false;
5713
5714 // Find the first position where the line exceeds the available space.
5715 wxSize sz;
5716 long breakPosition = range.GetEnd();
5717
5718 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
5719 if (partialExtents && partialExtents->GetCount() >= (size_t) (GetRange().GetLength()-1)) // the final position in a paragraph is the newline
5720 {
5721 int widthBefore;
5722
5723 if (range.GetStart() > GetRange().GetStart())
5724 widthBefore = (*partialExtents)[range.GetStart() - GetRange().GetStart() - 1];
5725 else
5726 widthBefore = 0;
5727
5728 size_t i;
5729 for (i = (size_t) range.GetStart(); i <= (size_t) range.GetEnd(); i++)
5730 {
5731 int widthFromStartOfThisRange = (*partialExtents)[i - GetRange().GetStart()] - widthBefore;
5732
5733 if (widthFromStartOfThisRange > availableSpace)
5734 {
5735 breakPosition = i-1;
5736 break;
5737 }
5738 }
5739 }
5740 else
5741 #endif
5742 {
5743 // Binary chop for speed
5744 long minPos = range.GetStart();
5745 long maxPos = range.GetEnd();
5746 while (true)
5747 {
5748 if (minPos == maxPos)
5749 {
5750 int descent = 0;
5751 GetRangeSize(wxRichTextRange(range.GetStart(), minPos), sz, descent, dc, context, wxRICHTEXT_UNFORMATTED);
5752
5753 if (sz.x > availableSpace)
5754 breakPosition = minPos - 1;
5755 break;
5756 }
5757 else if ((maxPos - minPos) == 1)
5758 {
5759 int descent = 0;
5760 GetRangeSize(wxRichTextRange(range.GetStart(), minPos), sz, descent, dc, context, wxRICHTEXT_UNFORMATTED);
5761
5762 if (sz.x > availableSpace)
5763 breakPosition = minPos - 1;
5764 else
5765 {
5766 GetRangeSize(wxRichTextRange(range.GetStart(), maxPos), sz, descent, dc, context, wxRICHTEXT_UNFORMATTED);
5767 if (sz.x > availableSpace)
5768 breakPosition = maxPos-1;
5769 }
5770 break;
5771 }
5772 else
5773 {
5774 long nextPos = minPos + ((maxPos - minPos) / 2);
5775
5776 int descent = 0;
5777 GetRangeSize(wxRichTextRange(range.GetStart(), nextPos), sz, descent, dc, context, wxRICHTEXT_UNFORMATTED);
5778
5779 if (sz.x > availableSpace)
5780 {
5781 maxPos = nextPos;
5782 }
5783 else
5784 {
5785 minPos = nextPos;
5786 }
5787 }
5788 }
5789 }
5790
5791 // Now we know the last position on the line.
5792 // Let's try to find a word break.
5793
5794 wxString plainText;
5795 if (GetContiguousPlainText(plainText, wxRichTextRange(range.GetStart(), breakPosition), false))
5796 {
5797 int newLinePos = plainText.Find(wxRichTextLineBreakChar);
5798 if (newLinePos != wxNOT_FOUND)
5799 {
5800 breakPosition = wxMax(0, range.GetStart() + newLinePos);
5801 }
5802 else
5803 {
5804 int spacePos = plainText.Find(wxT(' '), true);
5805 int tabPos = plainText.Find(wxT('\t'), true);
5806 int pos = wxMax(spacePos, tabPos);
5807 if (pos != wxNOT_FOUND)
5808 {
5809 int positionsFromEndOfString = plainText.length() - pos - 1;
5810 breakPosition = breakPosition - positionsFromEndOfString;
5811 }
5812 }
5813 }
5814
5815 wrapPosition = breakPosition;
5816
5817 return true;
5818 }
5819
5820 /// Get the bullet text for this paragraph.
5821 wxString wxRichTextParagraph::GetBulletText()
5822 {
5823 if (GetAttributes().GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_NONE ||
5824 (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_BITMAP))
5825 return wxEmptyString;
5826
5827 int number = GetAttributes().GetBulletNumber();
5828
5829 wxString text;
5830 if ((GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ARABIC) || (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE))
5831 {
5832 text.Printf(wxT("%d"), number);
5833 }
5834 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_LETTERS_UPPER)
5835 {
5836 // TODO: Unicode, and also check if number > 26
5837 text.Printf(wxT("%c"), (wxChar) (number+64));
5838 }
5839 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_LETTERS_LOWER)
5840 {
5841 // TODO: Unicode, and also check if number > 26
5842 text.Printf(wxT("%c"), (wxChar) (number+96));
5843 }
5844 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ROMAN_UPPER)
5845 {
5846 text = wxRichTextDecimalToRoman(number);
5847 }
5848 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ROMAN_LOWER)
5849 {
5850 text = wxRichTextDecimalToRoman(number);
5851 text.MakeLower();
5852 }
5853 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_SYMBOL)
5854 {
5855 text = GetAttributes().GetBulletText();
5856 }
5857
5858 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE)
5859 {
5860 // The outline style relies on the text being computed statically,
5861 // since it depends on other levels points (e.g. 1.2.1.1). So normally the bullet text
5862 // should be stored in the attributes; if not, just use the number for this
5863 // level, as previously computed.
5864 if (!GetAttributes().GetBulletText().IsEmpty())
5865 text = GetAttributes().GetBulletText();
5866 }
5867
5868 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_PARENTHESES)
5869 {
5870 text = wxT("(") + text + wxT(")");
5871 }
5872 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_RIGHT_PARENTHESIS)
5873 {
5874 text = text + wxT(")");
5875 }
5876
5877 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_PERIOD)
5878 {
5879 text += wxT(".");
5880 }
5881
5882 return text;
5883 }
5884
5885 /// Allocate or reuse a line object
5886 wxRichTextLine* wxRichTextParagraph::AllocateLine(int pos)
5887 {
5888 if (pos < (int) m_cachedLines.GetCount())
5889 {
5890 wxRichTextLine* line = m_cachedLines.Item(pos)->GetData();
5891 line->Init(this);
5892 return line;
5893 }
5894 else
5895 {
5896 wxRichTextLine* line = new wxRichTextLine(this);
5897 m_cachedLines.Append(line);
5898 return line;
5899 }
5900 }
5901
5902 /// Clear remaining unused line objects, if any
5903 bool wxRichTextParagraph::ClearUnusedLines(int lineCount)
5904 {
5905 int cachedLineCount = m_cachedLines.GetCount();
5906 if ((int) cachedLineCount > lineCount)
5907 {
5908 for (int i = 0; i < (int) (cachedLineCount - lineCount); i ++)
5909 {
5910 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetLast();
5911 wxRichTextLine* line = node->GetData();
5912 m_cachedLines.Erase(node);
5913 delete line;
5914 }
5915 }
5916 return true;
5917 }
5918
5919 /// Get combined attributes of the base style, paragraph style and character style. We use this to dynamically
5920 /// retrieve the actual style.
5921 wxRichTextAttr wxRichTextParagraph::GetCombinedAttributes(const wxRichTextAttr& contentStyle, bool includingBoxAttr) const
5922 {
5923 wxRichTextAttr attr;
5924 wxRichTextParagraphLayoutBox* buf = wxDynamicCast(GetParent(), wxRichTextParagraphLayoutBox);
5925 if (buf)
5926 {
5927 attr = buf->GetBasicStyle();
5928 if (!includingBoxAttr)
5929 {
5930 attr.GetTextBoxAttr().Reset();
5931 // The background colour will be painted by the container, and we don't
5932 // want to unnecessarily overwrite the background when we're drawing text
5933 // because this may erase the guideline (which appears just under the text
5934 // if there's no padding).
5935 attr.SetFlags(attr.GetFlags() & ~wxTEXT_ATTR_BACKGROUND_COLOUR);
5936 }
5937 wxRichTextApplyStyle(attr, GetAttributes());
5938 }
5939 else
5940 attr = GetAttributes();
5941
5942 wxRichTextApplyStyle(attr, contentStyle);
5943 return attr;
5944 }
5945
5946 /// Get combined attributes of the base style and paragraph style.
5947 wxRichTextAttr wxRichTextParagraph::GetCombinedAttributes(bool includingBoxAttr) const
5948 {
5949 wxRichTextAttr attr;
5950 wxRichTextParagraphLayoutBox* buf = wxDynamicCast(GetParent(), wxRichTextParagraphLayoutBox);
5951 if (buf)
5952 {
5953 attr = buf->GetBasicStyle();
5954 if (!includingBoxAttr)
5955 attr.GetTextBoxAttr().Reset();
5956 wxRichTextApplyStyle(attr, GetAttributes());
5957 }
5958 else
5959 attr = GetAttributes();
5960
5961 return attr;
5962 }
5963
5964 // Create default tabstop array
5965 void wxRichTextParagraph::InitDefaultTabs()
5966 {
5967 // create a default tab list at 10 mm each.
5968 for (int i = 0; i < 20; ++i)
5969 {
5970 sm_defaultTabs.Add(i*100);
5971 }
5972 }
5973
5974 // Clear default tabstop array
5975 void wxRichTextParagraph::ClearDefaultTabs()
5976 {
5977 sm_defaultTabs.Clear();
5978 }
5979
5980 void wxRichTextParagraph::LayoutFloat(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, int style, wxRichTextFloatCollector* floatCollector)
5981 {
5982 wxRichTextObjectList::compatibility_iterator node = GetChildren().GetFirst();
5983 while (node)
5984 {
5985 wxRichTextObject* anchored = node->GetData();
5986 if (anchored && anchored->IsFloating() && !floatCollector->HasFloat(anchored))
5987 {
5988 wxSize size;
5989 int descent, x = 0;
5990 anchored->GetRangeSize(anchored->GetRange(), size, descent, dc, context, style);
5991
5992 int offsetY = 0;
5993 if (anchored->GetAttributes().GetTextBoxAttr().GetTop().IsValid())
5994 {
5995 offsetY = anchored->GetAttributes().GetTextBoxAttr().GetTop().GetValue();
5996 if (anchored->GetAttributes().GetTextBoxAttr().GetTop().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
5997 {
5998 offsetY = ConvertTenthsMMToPixels(dc, offsetY);
5999 }
6000 }
6001
6002 int pos = floatCollector->GetFitPosition(anchored->GetAttributes().GetTextBoxAttr().GetFloatMode(), rect.y + offsetY, size.y);
6003
6004 /* Update the offset */
6005 int newOffsetY = pos - rect.y;
6006 if (newOffsetY != offsetY)
6007 {
6008 if (anchored->GetAttributes().GetTextBoxAttr().GetTop().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
6009 newOffsetY = ConvertPixelsToTenthsMM(dc, newOffsetY);
6010 anchored->GetAttributes().GetTextBoxAttr().GetTop().SetValue(newOffsetY);
6011 }
6012
6013 if (anchored->GetAttributes().GetTextBoxAttr().GetFloatMode() == wxTEXT_BOX_ATTR_FLOAT_LEFT)
6014 x = rect.x;
6015 else if (anchored->GetAttributes().GetTextBoxAttr().GetFloatMode() == wxTEXT_BOX_ATTR_FLOAT_RIGHT)
6016 x = rect.x + rect.width - size.x;
6017
6018 anchored->SetPosition(wxPoint(x, pos));
6019 anchored->SetCachedSize(size);
6020 floatCollector->CollectFloat(this, anchored);
6021 }
6022
6023 node = node->GetNext();
6024 }
6025 }
6026
6027 // Get the first position from pos that has a line break character.
6028 long wxRichTextParagraph::GetFirstLineBreakPosition(long pos)
6029 {
6030 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
6031 while (node)
6032 {
6033 wxRichTextObject* obj = node->GetData();
6034 if (pos >= obj->GetRange().GetStart() && pos <= obj->GetRange().GetEnd())
6035 {
6036 wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText);
6037 if (textObj)
6038 {
6039 long breakPos = textObj->GetFirstLineBreakPosition(pos);
6040 if (breakPos > -1)
6041 return breakPos;
6042 }
6043 }
6044 node = node->GetNext();
6045 }
6046 return -1;
6047 }
6048
6049 /*!
6050 * wxRichTextLine
6051 * This object represents a line in a paragraph, and stores
6052 * offsets from the start of the paragraph representing the
6053 * start and end positions of the line.
6054 */
6055
6056 wxRichTextLine::wxRichTextLine(wxRichTextParagraph* parent)
6057 {
6058 Init(parent);
6059 }
6060
6061 /// Initialisation
6062 void wxRichTextLine::Init(wxRichTextParagraph* parent)
6063 {
6064 m_parent = parent;
6065 m_range.SetRange(-1, -1);
6066 m_pos = wxPoint(0, 0);
6067 m_size = wxSize(0, 0);
6068 m_descent = 0;
6069 #if wxRICHTEXT_USE_OPTIMIZED_LINE_DRAWING
6070 m_objectSizes.Clear();
6071 #endif
6072 }
6073
6074 /// Copy
6075 void wxRichTextLine::Copy(const wxRichTextLine& obj)
6076 {
6077 m_range = obj.m_range;
6078 #if wxRICHTEXT_USE_OPTIMIZED_LINE_DRAWING
6079 m_objectSizes = obj.m_objectSizes;
6080 #endif
6081 }
6082
6083 /// Get the absolute object position
6084 wxPoint wxRichTextLine::GetAbsolutePosition() const
6085 {
6086 return m_parent->GetPosition() + m_pos;
6087 }
6088
6089 /// Get the absolute range
6090 wxRichTextRange wxRichTextLine::GetAbsoluteRange() const
6091 {
6092 wxRichTextRange range(m_range.GetStart() + m_parent->GetRange().GetStart(), 0);
6093 range.SetEnd(range.GetStart() + m_range.GetLength()-1);
6094 return range;
6095 }
6096
6097 /*!
6098 * wxRichTextPlainText
6099 * This object represents a single piece of text.
6100 */
6101
6102 IMPLEMENT_DYNAMIC_CLASS(wxRichTextPlainText, wxRichTextObject)
6103
6104 wxRichTextPlainText::wxRichTextPlainText(const wxString& text, wxRichTextObject* parent, wxRichTextAttr* style):
6105 wxRichTextObject(parent)
6106 {
6107 if (style)
6108 SetAttributes(*style);
6109
6110 m_text = text;
6111 }
6112
6113 #define USE_KERNING_FIX 1
6114
6115 // If insufficient tabs are defined, this is the tab width used
6116 #define WIDTH_FOR_DEFAULT_TABS 50
6117
6118 /// Draw the item
6119 bool wxRichTextPlainText::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int WXUNUSED(style))
6120 {
6121 wxRichTextParagraph* para = wxDynamicCast(GetParent(), wxRichTextParagraph);
6122 wxASSERT (para != NULL);
6123
6124 wxRichTextAttr textAttr(para ? para->GetCombinedAttributes(GetAttributes(), false /* no box attributes */) : GetAttributes());
6125 context.ApplyVirtualAttributes(textAttr, this);
6126
6127 // Let's make the assumption for now that for content in a paragraph, including
6128 // text, we never have a discontinuous selection. So we only deal with a
6129 // single range.
6130 wxRichTextRange selectionRange;
6131 if (selection.IsValid())
6132 {
6133 wxRichTextRangeArray selectionRanges = selection.GetSelectionForObject(this);
6134 if (selectionRanges.GetCount() > 0)
6135 selectionRange = selectionRanges[0];
6136 else
6137 selectionRange = wxRICHTEXT_NO_SELECTION;
6138 }
6139 else
6140 selectionRange = wxRICHTEXT_NO_SELECTION;
6141
6142 int offset = GetRange().GetStart();
6143
6144 // Replace line break characters with spaces
6145 wxString str = m_text;
6146 wxString toRemove = wxRichTextLineBreakChar;
6147 str.Replace(toRemove, wxT(" "));
6148 if (textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_CAPITALS))
6149 str.MakeUpper();
6150
6151 long len = range.GetLength();
6152 wxString stringChunk = str.Mid(range.GetStart() - offset, (size_t) len);
6153
6154 // Test for the optimized situations where all is selected, or none
6155 // is selected.
6156
6157 wxFont textFont(GetBuffer()->GetFontTable().FindFont(textAttr));
6158 wxCheckSetFont(dc, textFont);
6159 int charHeight = dc.GetCharHeight();
6160
6161 int x, y;
6162 if ( textFont.IsOk() )
6163 {
6164 if ( textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUPERSCRIPT) )
6165 {
6166 double size = static_cast<double>(textFont.GetPointSize()) / wxSCRIPT_MUL_FACTOR;
6167 textFont.SetPointSize( static_cast<int>(size) );
6168 x = rect.x;
6169 y = rect.y;
6170 wxCheckSetFont(dc, textFont);
6171 }
6172 else if ( textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUBSCRIPT) )
6173 {
6174 double size = static_cast<double>(textFont.GetPointSize()) / wxSCRIPT_MUL_FACTOR;
6175 textFont.SetPointSize( static_cast<int>(size) );
6176 x = rect.x;
6177 int sub_height = static_cast<int>( static_cast<double>(charHeight) / wxSCRIPT_MUL_FACTOR);
6178 y = rect.y + (rect.height - sub_height + (descent - m_descent));
6179 wxCheckSetFont(dc, textFont);
6180 }
6181 else
6182 {
6183 x = rect.x;
6184 y = rect.y + (rect.height - charHeight - (descent - m_descent));
6185 }
6186 }
6187 else
6188 {
6189 x = rect.x;
6190 y = rect.y + (rect.height - charHeight - (descent - m_descent));
6191 }
6192
6193 // TODO: new selection code
6194
6195 // (a) All selected.
6196 if (selectionRange.GetStart() <= range.GetStart() && selectionRange.GetEnd() >= range.GetEnd())
6197 {
6198 DrawTabbedString(dc, textAttr, rect, stringChunk, x, y, true);
6199 }
6200 // (b) None selected.
6201 else if (selectionRange.GetEnd() < range.GetStart() || selectionRange.GetStart() > range.GetEnd())
6202 {
6203 // Draw all unselected
6204 DrawTabbedString(dc, textAttr, rect, stringChunk, x, y, false);
6205 }
6206 else
6207 {
6208 // (c) Part selected, part not
6209 // Let's draw unselected chunk, selected chunk, then unselected chunk.
6210
6211 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
6212
6213 // 1. Initial unselected chunk, if any, up until start of selection.
6214 if (selectionRange.GetStart() > range.GetStart() && selectionRange.GetStart() <= range.GetEnd())
6215 {
6216 int r1 = range.GetStart();
6217 int s1 = selectionRange.GetStart()-1;
6218 int fragmentLen = s1 - r1 + 1;
6219 if (fragmentLen < 0)
6220 {
6221 wxLogDebug(wxT("Mid(%d, %d"), (int)(r1 - offset), (int)fragmentLen);
6222 }
6223 wxString stringFragment = str.Mid(r1 - offset, fragmentLen);
6224
6225 DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, false);
6226
6227 #if USE_KERNING_FIX
6228 if (stringChunk.Find(wxT("\t")) == wxNOT_FOUND)
6229 {
6230 // Compensate for kerning difference
6231 wxString stringFragment2(str.Mid(r1 - offset, fragmentLen+1));
6232 wxString stringFragment3(str.Mid(r1 - offset + fragmentLen, 1));
6233
6234 wxCoord w1, h1, w2, h2, w3, h3;
6235 dc.GetTextExtent(stringFragment, & w1, & h1);
6236 dc.GetTextExtent(stringFragment2, & w2, & h2);
6237 dc.GetTextExtent(stringFragment3, & w3, & h3);
6238
6239 int kerningDiff = (w1 + w3) - w2;
6240 x = x - kerningDiff;
6241 }
6242 #endif
6243 }
6244
6245 // 2. Selected chunk, if any.
6246 if (selectionRange.GetEnd() >= range.GetStart())
6247 {
6248 int s1 = wxMax(selectionRange.GetStart(), range.GetStart());
6249 int s2 = wxMin(selectionRange.GetEnd(), range.GetEnd());
6250
6251 int fragmentLen = s2 - s1 + 1;
6252 if (fragmentLen < 0)
6253 {
6254 wxLogDebug(wxT("Mid(%d, %d"), (int)(s1 - offset), (int)fragmentLen);
6255 }
6256 wxString stringFragment = str.Mid(s1 - offset, fragmentLen);
6257
6258 DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, true);
6259
6260 #if USE_KERNING_FIX
6261 if (stringChunk.Find(wxT("\t")) == wxNOT_FOUND)
6262 {
6263 // Compensate for kerning difference
6264 wxString stringFragment2(str.Mid(s1 - offset, fragmentLen+1));
6265 wxString stringFragment3(str.Mid(s1 - offset + fragmentLen, 1));
6266
6267 wxCoord w1, h1, w2, h2, w3, h3;
6268 dc.GetTextExtent(stringFragment, & w1, & h1);
6269 dc.GetTextExtent(stringFragment2, & w2, & h2);
6270 dc.GetTextExtent(stringFragment3, & w3, & h3);
6271
6272 int kerningDiff = (w1 + w3) - w2;
6273 x = x - kerningDiff;
6274 }
6275 #endif
6276 }
6277
6278 // 3. Remaining unselected chunk, if any
6279 if (selectionRange.GetEnd() < range.GetEnd())
6280 {
6281 int s2 = wxMin(selectionRange.GetEnd()+1, range.GetEnd());
6282 int r2 = range.GetEnd();
6283
6284 int fragmentLen = r2 - s2 + 1;
6285 if (fragmentLen < 0)
6286 {
6287 wxLogDebug(wxT("Mid(%d, %d"), (int)(s2 - offset), (int)fragmentLen);
6288 }
6289 wxString stringFragment = str.Mid(s2 - offset, fragmentLen);
6290
6291 DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, false);
6292 }
6293 }
6294
6295 return true;
6296 }
6297
6298 bool wxRichTextPlainText::DrawTabbedString(wxDC& dc, const wxRichTextAttr& attr, const wxRect& rect,wxString& str, wxCoord& x, wxCoord& y, bool selected)
6299 {
6300 bool hasTabs = (str.Find(wxT('\t')) != wxNOT_FOUND);
6301
6302 wxArrayInt tabArray;
6303 int tabCount;
6304 if (hasTabs)
6305 {
6306 if (attr.GetTabs().IsEmpty())
6307 tabArray = wxRichTextParagraph::GetDefaultTabs();
6308 else
6309 tabArray = attr.GetTabs();
6310 tabCount = tabArray.GetCount();
6311
6312 for (int i = 0; i < tabCount; ++i)
6313 {
6314 int pos = tabArray[i];
6315 pos = ConvertTenthsMMToPixels(dc, pos);
6316 tabArray[i] = pos;
6317 }
6318 }
6319 else
6320 tabCount = 0;
6321
6322 int nextTabPos = -1;
6323 int tabPos = -1;
6324 wxCoord w, h;
6325
6326 if (selected)
6327 {
6328 wxColour highlightColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT));
6329 wxColour highlightTextColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT));
6330
6331 wxCheckSetBrush(dc, wxBrush(highlightColour));
6332 wxCheckSetPen(dc, wxPen(highlightColour));
6333 dc.SetTextForeground(highlightTextColour);
6334 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
6335 }
6336 else
6337 {
6338 dc.SetTextForeground(attr.GetTextColour());
6339
6340 if (attr.HasFlag(wxTEXT_ATTR_BACKGROUND_COLOUR) && attr.GetBackgroundColour().IsOk())
6341 {
6342 dc.SetBackgroundMode(wxBRUSHSTYLE_SOLID);
6343 dc.SetTextBackground(attr.GetBackgroundColour());
6344 }
6345 else
6346 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
6347 }
6348
6349 wxCoord x_orig = GetParent()->GetPosition().x;
6350 while (hasTabs)
6351 {
6352 // the string has a tab
6353 // break up the string at the Tab
6354 wxString stringChunk = str.BeforeFirst(wxT('\t'));
6355 str = str.AfterFirst(wxT('\t'));
6356 dc.GetTextExtent(stringChunk, & w, & h);
6357 tabPos = x + w;
6358 bool not_found = true;
6359 for (int i = 0; i < tabCount && not_found; ++i)
6360 {
6361 nextTabPos = tabArray.Item(i) + x_orig;
6362
6363 // Find the next tab position.
6364 // Even if we're at the end of the tab array, we must still draw the chunk.
6365
6366 if (nextTabPos > tabPos || (i == (tabCount - 1)))
6367 {
6368 if (nextTabPos <= tabPos)
6369 {
6370 int defaultTabWidth = ConvertTenthsMMToPixels(dc, WIDTH_FOR_DEFAULT_TABS);
6371 nextTabPos = tabPos + defaultTabWidth;
6372 }
6373
6374 not_found = false;
6375 if (selected)
6376 {
6377 w = nextTabPos - x;
6378 wxRect selRect(x, rect.y, w, rect.GetHeight());
6379 dc.DrawRectangle(selRect);
6380 }
6381 dc.DrawText(stringChunk, x, y);
6382
6383 if (attr.HasTextEffects() && (attr.GetTextEffects() & wxTEXT_ATTR_EFFECT_STRIKETHROUGH))
6384 {
6385 wxPen oldPen = dc.GetPen();
6386 wxCheckSetPen(dc, wxPen(attr.GetTextColour(), 1));
6387 dc.DrawLine(x, (int) (y+(h/2)+0.5), x+w, (int) (y+(h/2)+0.5));
6388 wxCheckSetPen(dc, oldPen);
6389 }
6390
6391 x = nextTabPos;
6392 }
6393 }
6394 hasTabs = (str.Find(wxT('\t')) != wxNOT_FOUND);
6395 }
6396
6397 if (!str.IsEmpty())
6398 {
6399 dc.GetTextExtent(str, & w, & h);
6400 if (selected)
6401 {
6402 wxRect selRect(x, rect.y, w, rect.GetHeight());
6403 dc.DrawRectangle(selRect);
6404 }
6405 dc.DrawText(str, x, y);
6406
6407 if (attr.HasTextEffects() && (attr.GetTextEffects() & wxTEXT_ATTR_EFFECT_STRIKETHROUGH))
6408 {
6409 wxPen oldPen = dc.GetPen();
6410 wxCheckSetPen(dc, wxPen(attr.GetTextColour(), 1));
6411 dc.DrawLine(x, (int) (y+(h/2)+0.5), x+w, (int) (y+(h/2)+0.5));
6412 wxCheckSetPen(dc, oldPen);
6413 }
6414
6415 x += w;
6416 }
6417 return true;
6418
6419 }
6420
6421 /// Lay the item out
6422 bool wxRichTextPlainText::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& WXUNUSED(rect), const wxRect& WXUNUSED(parentRect), int WXUNUSED(style))
6423 {
6424 // Only lay out if we haven't already cached the size
6425 if (m_size.x == -1)
6426 GetRangeSize(GetRange(), m_size, m_descent, dc, context, 0, wxPoint(0, 0));
6427 m_maxSize = m_size;
6428 // Eventually we want to have a reasonable estimate of minimum size.
6429 m_minSize = wxSize(0, 0);
6430 return true;
6431 }
6432
6433 /// Copy
6434 void wxRichTextPlainText::Copy(const wxRichTextPlainText& obj)
6435 {
6436 wxRichTextObject::Copy(obj);
6437
6438 m_text = obj.m_text;
6439 }
6440
6441 /// Get/set the object size for the given range. Returns false if the range
6442 /// is invalid for this object.
6443 bool wxRichTextPlainText::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int WXUNUSED(flags), wxPoint position, wxArrayInt* partialExtents) const
6444 {
6445 if (!range.IsWithin(GetRange()))
6446 return false;
6447
6448 wxRichTextParagraph* para = wxDynamicCast(GetParent(), wxRichTextParagraph);
6449 wxASSERT (para != NULL);
6450
6451 int relativeX = position.x - GetParent()->GetPosition().x;
6452
6453 wxRichTextAttr textAttr(para ? para->GetCombinedAttributes(GetAttributes()) : GetAttributes());
6454 context.ApplyVirtualAttributes(textAttr, (wxRichTextObject*) this);
6455
6456 // Always assume unformatted text, since at this level we have no knowledge
6457 // of line breaks - and we don't need it, since we'll calculate size within
6458 // formatted text by doing it in chunks according to the line ranges
6459
6460 bool bScript(false);
6461 wxFont font(GetBuffer()->GetFontTable().FindFont(textAttr));
6462 if (font.IsOk())
6463 {
6464 if ( textAttr.HasTextEffects() && ( (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUPERSCRIPT)
6465 || (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUBSCRIPT) ) )
6466 {
6467 wxFont textFont = font;
6468 double size = static_cast<double>(textFont.GetPointSize()) / wxSCRIPT_MUL_FACTOR;
6469 textFont.SetPointSize( static_cast<int>(size) );
6470 wxCheckSetFont(dc, textFont);
6471 bScript = true;
6472 }
6473 else
6474 {
6475 wxCheckSetFont(dc, font);
6476 }
6477 }
6478
6479 bool haveDescent = false;
6480 int startPos = range.GetStart() - GetRange().GetStart();
6481 long len = range.GetLength();
6482
6483 wxString str(m_text);
6484 wxString toReplace = wxRichTextLineBreakChar;
6485 str.Replace(toReplace, wxT(" "));
6486
6487 wxString stringChunk = str.Mid(startPos, (size_t) len);
6488
6489 if (textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_CAPITALS))
6490 stringChunk.MakeUpper();
6491
6492 wxCoord w, h;
6493 int width = 0;
6494 if (stringChunk.Find(wxT('\t')) != wxNOT_FOUND)
6495 {
6496 // the string has a tab
6497 wxArrayInt tabArray;
6498 if (textAttr.GetTabs().IsEmpty())
6499 tabArray = wxRichTextParagraph::GetDefaultTabs();
6500 else
6501 tabArray = textAttr.GetTabs();
6502
6503 int tabCount = tabArray.GetCount();
6504
6505 for (int i = 0; i < tabCount; ++i)
6506 {
6507 int pos = tabArray[i];
6508 pos = ((wxRichTextPlainText*) this)->ConvertTenthsMMToPixels(dc, pos);
6509 tabArray[i] = pos;
6510 }
6511
6512 int nextTabPos = -1;
6513
6514 while (stringChunk.Find(wxT('\t')) >= 0)
6515 {
6516 int absoluteWidth = 0;
6517
6518 // the string has a tab
6519 // break up the string at the Tab
6520 wxString stringFragment = stringChunk.BeforeFirst(wxT('\t'));
6521 stringChunk = stringChunk.AfterFirst(wxT('\t'));
6522
6523 if (partialExtents)
6524 {
6525 int oldWidth;
6526 if (partialExtents->GetCount() > 0)
6527 oldWidth = (*partialExtents)[partialExtents->GetCount()-1];
6528 else
6529 oldWidth = 0;
6530
6531 // Add these partial extents
6532 wxArrayInt p;
6533 dc.GetPartialTextExtents(stringFragment, p);
6534 size_t j;
6535 for (j = 0; j < p.GetCount(); j++)
6536 partialExtents->Add(oldWidth + p[j]);
6537
6538 if (partialExtents->GetCount() > 0)
6539 absoluteWidth = (*partialExtents)[(*partialExtents).GetCount()-1] + relativeX;
6540 else
6541 absoluteWidth = relativeX;
6542 }
6543 else
6544 {
6545 dc.GetTextExtent(stringFragment, & w, & h);
6546 width += w;
6547 absoluteWidth = width + relativeX;
6548 haveDescent = true;
6549 }
6550
6551 bool notFound = true;
6552 for (int i = 0; i < tabCount && notFound; ++i)
6553 {
6554 nextTabPos = tabArray.Item(i);
6555
6556 // Find the next tab position.
6557 // Even if we're at the end of the tab array, we must still process the chunk.
6558
6559 if (nextTabPos > absoluteWidth || (i == (tabCount - 1)))
6560 {
6561 if (nextTabPos <= absoluteWidth)
6562 {
6563 int defaultTabWidth = ((wxRichTextPlainText*) this)->ConvertTenthsMMToPixels(dc, WIDTH_FOR_DEFAULT_TABS);
6564 nextTabPos = absoluteWidth + defaultTabWidth;
6565 }
6566
6567 notFound = false;
6568 width = nextTabPos - relativeX;
6569
6570 if (partialExtents)
6571 partialExtents->Add(width);
6572 }
6573 }
6574 }
6575 }
6576
6577 if (!stringChunk.IsEmpty())
6578 {
6579 if (partialExtents)
6580 {
6581 int oldWidth;
6582 if (partialExtents->GetCount() > 0)
6583 oldWidth = (*partialExtents)[partialExtents->GetCount()-1];
6584 else
6585 oldWidth = 0;
6586
6587 // Add these partial extents
6588 wxArrayInt p;
6589 dc.GetPartialTextExtents(stringChunk, p);
6590 size_t j;
6591 for (j = 0; j < p.GetCount(); j++)
6592 partialExtents->Add(oldWidth + p[j]);
6593 }
6594 else
6595 {
6596 dc.GetTextExtent(stringChunk, & w, & h, & descent);
6597 width += w;
6598 haveDescent = true;
6599 }
6600 }
6601
6602 if (partialExtents)
6603 {
6604 int charHeight = dc.GetCharHeight();
6605 if ((*partialExtents).GetCount() > 0)
6606 w = (*partialExtents)[partialExtents->GetCount()-1];
6607 else
6608 w = 0;
6609 size = wxSize(w, charHeight);
6610 }
6611 else
6612 {
6613 size = wxSize(width, dc.GetCharHeight());
6614 }
6615
6616 if (!haveDescent)
6617 dc.GetTextExtent(wxT("X"), & w, & h, & descent);
6618
6619 if ( bScript )
6620 dc.SetFont(font);
6621
6622 return true;
6623 }
6624
6625 /// Do a split, returning an object containing the second part, and setting
6626 /// the first part in 'this'.
6627 wxRichTextObject* wxRichTextPlainText::DoSplit(long pos)
6628 {
6629 long index = pos - GetRange().GetStart();
6630
6631 if (index < 0 || index >= (int) m_text.length())
6632 return NULL;
6633
6634 wxString firstPart = m_text.Mid(0, index);
6635 wxString secondPart = m_text.Mid(index);
6636
6637 m_text = firstPart;
6638
6639 wxRichTextPlainText* newObject = new wxRichTextPlainText(secondPart);
6640 newObject->SetAttributes(GetAttributes());
6641 newObject->SetProperties(GetProperties());
6642
6643 newObject->SetRange(wxRichTextRange(pos, GetRange().GetEnd()));
6644 GetRange().SetEnd(pos-1);
6645
6646 return newObject;
6647 }
6648
6649 /// Calculate range
6650 void wxRichTextPlainText::CalculateRange(long start, long& end)
6651 {
6652 end = start + m_text.length() - 1;
6653 m_range.SetRange(start, end);
6654 }
6655
6656 /// Delete range
6657 bool wxRichTextPlainText::DeleteRange(const wxRichTextRange& range)
6658 {
6659 wxRichTextRange r = range;
6660
6661 r.LimitTo(GetRange());
6662
6663 if (r.GetStart() == GetRange().GetStart() && r.GetEnd() == GetRange().GetEnd())
6664 {
6665 m_text.Empty();
6666 return true;
6667 }
6668
6669 long startIndex = r.GetStart() - GetRange().GetStart();
6670 long len = r.GetLength();
6671
6672 m_text = m_text.Mid(0, startIndex) + m_text.Mid(startIndex+len);
6673 return true;
6674 }
6675
6676 /// Get text for the given range.
6677 wxString wxRichTextPlainText::GetTextForRange(const wxRichTextRange& range) const
6678 {
6679 wxRichTextRange r = range;
6680
6681 r.LimitTo(GetRange());
6682
6683 long startIndex = r.GetStart() - GetRange().GetStart();
6684 long len = r.GetLength();
6685
6686 return m_text.Mid(startIndex, len);
6687 }
6688
6689 /// Returns true if this object can merge itself with the given one.
6690 bool wxRichTextPlainText::CanMerge(wxRichTextObject* object) const
6691 {
6692 return object->GetClassInfo() == CLASSINFO(wxRichTextPlainText) &&
6693 (m_text.empty() || (wxTextAttrEq(GetAttributes(), object->GetAttributes()) && m_properties == object->GetProperties()));
6694 }
6695
6696 /// Returns true if this object merged itself with the given one.
6697 /// The calling code will then delete the given object.
6698 bool wxRichTextPlainText::Merge(wxRichTextObject* object)
6699 {
6700 wxRichTextPlainText* textObject = wxDynamicCast(object, wxRichTextPlainText);
6701 wxASSERT( textObject != NULL );
6702
6703 if (textObject)
6704 {
6705 m_text += textObject->GetText();
6706 wxRichTextApplyStyle(m_attributes, textObject->GetAttributes());
6707 return true;
6708 }
6709 else
6710 return false;
6711 }
6712
6713 /// Dump to output stream for debugging
6714 void wxRichTextPlainText::Dump(wxTextOutputStream& stream)
6715 {
6716 wxRichTextObject::Dump(stream);
6717 stream << m_text << wxT("\n");
6718 }
6719
6720 /// Get the first position from pos that has a line break character.
6721 long wxRichTextPlainText::GetFirstLineBreakPosition(long pos)
6722 {
6723 int i;
6724 int len = m_text.length();
6725 int startPos = pos - m_range.GetStart();
6726 for (i = startPos; i < len; i++)
6727 {
6728 wxChar ch = m_text[i];
6729 if (ch == wxRichTextLineBreakChar)
6730 {
6731 return i + m_range.GetStart();
6732 }
6733 }
6734 return -1;
6735 }
6736
6737 /*!
6738 * wxRichTextBuffer
6739 * This is a kind of box, used to represent the whole buffer
6740 */
6741
6742 IMPLEMENT_DYNAMIC_CLASS(wxRichTextBuffer, wxRichTextParagraphLayoutBox)
6743
6744 wxList wxRichTextBuffer::sm_handlers;
6745 wxList wxRichTextBuffer::sm_drawingHandlers;
6746 wxRichTextRenderer* wxRichTextBuffer::sm_renderer = NULL;
6747 int wxRichTextBuffer::sm_bulletRightMargin = 20;
6748 float wxRichTextBuffer::sm_bulletProportion = (float) 0.3;
6749
6750 /// Initialisation
6751 void wxRichTextBuffer::Init()
6752 {
6753 m_commandProcessor = new wxCommandProcessor;
6754 m_styleSheet = NULL;
6755 m_modified = false;
6756 m_batchedCommandDepth = 0;
6757 m_batchedCommand = NULL;
6758 m_suppressUndo = 0;
6759 m_handlerFlags = 0;
6760 m_scale = 1.0;
6761 }
6762
6763 /// Initialisation
6764 wxRichTextBuffer::~wxRichTextBuffer()
6765 {
6766 delete m_commandProcessor;
6767 delete m_batchedCommand;
6768
6769 ClearStyleStack();
6770 ClearEventHandlers();
6771 }
6772
6773 void wxRichTextBuffer::ResetAndClearCommands()
6774 {
6775 Reset();
6776
6777 GetCommandProcessor()->ClearCommands();
6778
6779 Modify(false);
6780 Invalidate(wxRICHTEXT_ALL);
6781 }
6782
6783 void wxRichTextBuffer::Copy(const wxRichTextBuffer& obj)
6784 {
6785 wxRichTextParagraphLayoutBox::Copy(obj);
6786
6787 m_styleSheet = obj.m_styleSheet;
6788 m_modified = obj.m_modified;
6789 m_batchedCommandDepth = 0;
6790 if (m_batchedCommand)
6791 delete m_batchedCommand;
6792 m_batchedCommand = NULL;
6793 m_suppressUndo = obj.m_suppressUndo;
6794 m_invalidRange = obj.m_invalidRange;
6795 }
6796
6797 /// Push style sheet to top of stack
6798 bool wxRichTextBuffer::PushStyleSheet(wxRichTextStyleSheet* styleSheet)
6799 {
6800 if (m_styleSheet)
6801 styleSheet->InsertSheet(m_styleSheet);
6802
6803 SetStyleSheet(styleSheet);
6804
6805 return true;
6806 }
6807
6808 /// Pop style sheet from top of stack
6809 wxRichTextStyleSheet* wxRichTextBuffer::PopStyleSheet()
6810 {
6811 if (m_styleSheet)
6812 {
6813 wxRichTextStyleSheet* oldSheet = m_styleSheet;
6814 m_styleSheet = oldSheet->GetNextSheet();
6815 oldSheet->Unlink();
6816
6817 return oldSheet;
6818 }
6819 else
6820 return NULL;
6821 }
6822
6823 /// Submit command to insert paragraphs
6824 bool wxRichTextBuffer::InsertParagraphsWithUndo(long pos, const wxRichTextParagraphLayoutBox& paragraphs, wxRichTextCtrl* ctrl, int flags)
6825 {
6826 return ctrl->GetFocusObject()->InsertParagraphsWithUndo(this, pos, paragraphs, ctrl, flags);
6827 }
6828
6829 /// Submit command to insert paragraphs
6830 bool wxRichTextParagraphLayoutBox::InsertParagraphsWithUndo(wxRichTextBuffer* buffer, long pos, const wxRichTextParagraphLayoutBox& paragraphs, wxRichTextCtrl* ctrl, int WXUNUSED(flags))
6831 {
6832 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
6833
6834 action->GetNewParagraphs() = paragraphs;
6835
6836 action->SetPosition(pos);
6837
6838 wxRichTextRange range = wxRichTextRange(pos, pos + paragraphs.GetOwnRange().GetEnd() - 1);
6839 if (!paragraphs.GetPartialParagraph())
6840 range.SetEnd(range.GetEnd()+1);
6841
6842 // Set the range we'll need to delete in Undo
6843 action->SetRange(range);
6844
6845 buffer->SubmitAction(action);
6846
6847 return true;
6848 }
6849
6850 /// Submit command to insert the given text
6851 bool wxRichTextBuffer::InsertTextWithUndo(long pos, const wxString& text, wxRichTextCtrl* ctrl, int flags)
6852 {
6853 return ctrl->GetFocusObject()->InsertTextWithUndo(this, pos, text, ctrl, flags);
6854 }
6855
6856 /// Submit command to insert the given text
6857 bool wxRichTextParagraphLayoutBox::InsertTextWithUndo(wxRichTextBuffer* buffer, long pos, const wxString& text, wxRichTextCtrl* ctrl, int flags)
6858 {
6859 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
6860
6861 wxRichTextAttr* p = NULL;
6862 wxRichTextAttr paraAttr;
6863 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
6864 {
6865 // Get appropriate paragraph style
6866 paraAttr = GetStyleForNewParagraph(buffer, pos, false, false);
6867 if (!paraAttr.IsDefault())
6868 p = & paraAttr;
6869 }
6870
6871 action->GetNewParagraphs().AddParagraphs(text, p);
6872
6873 int length = action->GetNewParagraphs().GetOwnRange().GetLength();
6874
6875 if (!text.empty() && text.Last() != wxT('\n'))
6876 {
6877 // Don't count the newline when undoing
6878 length --;
6879 action->GetNewParagraphs().SetPartialParagraph(true);
6880 }
6881 else if (!text.empty() && text.Last() == wxT('\n'))
6882 length --;
6883
6884 action->SetPosition(pos);
6885
6886 // Set the range we'll need to delete in Undo
6887 action->SetRange(wxRichTextRange(pos, pos + length - 1));
6888
6889 buffer->SubmitAction(action);
6890
6891 return true;
6892 }
6893
6894 /// Submit command to insert the given text
6895 bool wxRichTextBuffer::InsertNewlineWithUndo(long pos, wxRichTextCtrl* ctrl, int flags)
6896 {
6897 return ctrl->GetFocusObject()->InsertNewlineWithUndo(this, pos, ctrl, flags);
6898 }
6899
6900 /// Submit command to insert the given text
6901 bool wxRichTextParagraphLayoutBox::InsertNewlineWithUndo(wxRichTextBuffer* buffer, long pos, wxRichTextCtrl* ctrl, int flags)
6902 {
6903 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
6904
6905 wxRichTextAttr* p = NULL;
6906 wxRichTextAttr paraAttr;
6907 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
6908 {
6909 paraAttr = GetStyleForNewParagraph(buffer, pos, false, true /* look for next paragraph style */);
6910 if (!paraAttr.IsDefault())
6911 p = & paraAttr;
6912 }
6913
6914 wxRichTextAttr attr(buffer->GetDefaultStyle());
6915
6916 wxRichTextParagraph* newPara = new wxRichTextParagraph(wxEmptyString, this, & attr);
6917 action->GetNewParagraphs().AppendChild(newPara);
6918 action->GetNewParagraphs().UpdateRanges();
6919 action->GetNewParagraphs().SetPartialParagraph(false);
6920 wxRichTextParagraph* para = GetParagraphAtPosition(pos, false);
6921 long pos1 = pos;
6922
6923 if (p)
6924 newPara->SetAttributes(*p);
6925
6926 if (flags & wxRICHTEXT_INSERT_INTERACTIVE)
6927 {
6928 if (para && para->GetRange().GetEnd() == pos)
6929 pos1 ++;
6930
6931 // Now see if we need to number the paragraph.
6932 if (newPara->GetAttributes().HasBulletNumber())
6933 {
6934 wxRichTextAttr numberingAttr;
6935 if (FindNextParagraphNumber(para, numberingAttr))
6936 wxRichTextApplyStyle(newPara->GetAttributes(), (const wxRichTextAttr&) numberingAttr);
6937 }
6938 }
6939
6940 action->SetPosition(pos);
6941
6942 // Use the default character style
6943 // Use the default character style
6944 if (!buffer->GetDefaultStyle().IsDefault() && newPara->GetChildren().GetFirst())
6945 {
6946 // Check whether the default style merely reflects the paragraph/basic style,
6947 // in which case don't apply it.
6948 wxRichTextAttr defaultStyle(buffer->GetDefaultStyle());
6949 wxRichTextAttr toApply;
6950 if (para)
6951 {
6952 wxRichTextAttr combinedAttr = para->GetCombinedAttributes(true /* include box attributes */);
6953 wxRichTextAttr newAttr;
6954 // This filters out attributes that are accounted for by the current
6955 // paragraph/basic style
6956 wxRichTextApplyStyle(toApply, defaultStyle, & combinedAttr);
6957 }
6958 else
6959 toApply = defaultStyle;
6960
6961 if (!toApply.IsDefault())
6962 newPara->GetChildren().GetFirst()->GetData()->SetAttributes(toApply);
6963 }
6964
6965 // Set the range we'll need to delete in Undo
6966 action->SetRange(wxRichTextRange(pos1, pos1));
6967
6968 buffer->SubmitAction(action);
6969
6970 return true;
6971 }
6972
6973 /// Submit command to insert the given image
6974 bool wxRichTextBuffer::InsertImageWithUndo(long pos, const wxRichTextImageBlock& imageBlock, wxRichTextCtrl* ctrl, int flags,
6975 const wxRichTextAttr& textAttr)
6976 {
6977 return ctrl->GetFocusObject()->InsertImageWithUndo(this, pos, imageBlock, ctrl, flags, textAttr);
6978 }
6979
6980 /// Submit command to insert the given image
6981 bool wxRichTextParagraphLayoutBox::InsertImageWithUndo(wxRichTextBuffer* buffer, long pos, const wxRichTextImageBlock& imageBlock,
6982 wxRichTextCtrl* ctrl, int flags,
6983 const wxRichTextAttr& textAttr)
6984 {
6985 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Image"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
6986
6987 wxRichTextAttr* p = NULL;
6988 wxRichTextAttr paraAttr;
6989 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
6990 {
6991 paraAttr = GetStyleForNewParagraph(buffer, pos);
6992 if (!paraAttr.IsDefault())
6993 p = & paraAttr;
6994 }
6995
6996 wxRichTextAttr attr(buffer->GetDefaultStyle());
6997
6998 wxRichTextParagraph* newPara = new wxRichTextParagraph(this, & attr);
6999 if (p)
7000 newPara->SetAttributes(*p);
7001
7002 wxRichTextImage* imageObject = new wxRichTextImage(imageBlock, newPara);
7003 newPara->AppendChild(imageObject);
7004 imageObject->SetAttributes(textAttr);
7005 action->GetNewParagraphs().AppendChild(newPara);
7006 action->GetNewParagraphs().UpdateRanges();
7007
7008 action->GetNewParagraphs().SetPartialParagraph(true);
7009
7010 action->SetPosition(pos);
7011
7012 // Set the range we'll need to delete in Undo
7013 action->SetRange(wxRichTextRange(pos, pos));
7014
7015 buffer->SubmitAction(action);
7016
7017 return true;
7018 }
7019
7020 // Insert an object with no change of it
7021 wxRichTextObject* wxRichTextBuffer::InsertObjectWithUndo(long pos, wxRichTextObject *object, wxRichTextCtrl* ctrl, int flags)
7022 {
7023 return ctrl->GetFocusObject()->InsertObjectWithUndo(this, pos, object, ctrl, flags);
7024 }
7025
7026 // Insert an object with no change of it
7027 wxRichTextObject* wxRichTextParagraphLayoutBox::InsertObjectWithUndo(wxRichTextBuffer* buffer, long pos, wxRichTextObject *object, wxRichTextCtrl* ctrl, int flags)
7028 {
7029 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Object"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
7030
7031 wxRichTextAttr* p = NULL;
7032 wxRichTextAttr paraAttr;
7033 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
7034 {
7035 paraAttr = GetStyleForNewParagraph(buffer, pos);
7036 if (!paraAttr.IsDefault())
7037 p = & paraAttr;
7038 }
7039
7040 wxRichTextAttr attr(buffer->GetDefaultStyle());
7041
7042 wxRichTextParagraph* newPara = new wxRichTextParagraph(this, & attr);
7043 if (p)
7044 newPara->SetAttributes(*p);
7045
7046 newPara->AppendChild(object);
7047 action->GetNewParagraphs().AppendChild(newPara);
7048 action->GetNewParagraphs().UpdateRanges();
7049
7050 action->GetNewParagraphs().SetPartialParagraph(true);
7051
7052 action->SetPosition(pos);
7053
7054 // Set the range we'll need to delete in Undo
7055 action->SetRange(wxRichTextRange(pos, pos));
7056
7057 buffer->SubmitAction(action);
7058
7059 wxRichTextObject* obj = GetLeafObjectAtPosition(pos);
7060 return obj;
7061 }
7062
7063 /// Get the style that is appropriate for a new paragraph at this position.
7064 /// If the previous paragraph has a paragraph style name, look up the next-paragraph
7065 /// style.
7066 wxRichTextAttr wxRichTextParagraphLayoutBox::GetStyleForNewParagraph(wxRichTextBuffer* buffer, long pos, bool caretPosition, bool lookUpNewParaStyle) const
7067 {
7068 wxRichTextParagraph* para = GetParagraphAtPosition(pos, caretPosition);
7069 if (para)
7070 {
7071 wxRichTextAttr attr;
7072 bool foundAttributes = false;
7073
7074 // Look for a matching paragraph style
7075 if (lookUpNewParaStyle && !para->GetAttributes().GetParagraphStyleName().IsEmpty() && buffer->GetStyleSheet())
7076 {
7077 wxRichTextParagraphStyleDefinition* paraDef = buffer->GetStyleSheet()->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName());
7078 if (paraDef)
7079 {
7080 // If we're not at the end of the paragraph, then we apply THIS style, and not the designated next style.
7081 if (para->GetRange().GetEnd() == pos && !paraDef->GetNextStyle().IsEmpty())
7082 {
7083 wxRichTextParagraphStyleDefinition* nextParaDef = buffer->GetStyleSheet()->FindParagraphStyle(paraDef->GetNextStyle());
7084 if (nextParaDef)
7085 {
7086 foundAttributes = true;
7087 attr = nextParaDef->GetStyleMergedWithBase(buffer->GetStyleSheet());
7088 }
7089 }
7090
7091 // If we didn't find the 'next style', use this style instead.
7092 if (!foundAttributes)
7093 {
7094 foundAttributes = true;
7095 attr = paraDef->GetStyleMergedWithBase(buffer->GetStyleSheet());
7096 }
7097 }
7098 }
7099
7100 // Also apply list style if present
7101 if (lookUpNewParaStyle && !para->GetAttributes().GetListStyleName().IsEmpty() && buffer->GetStyleSheet())
7102 {
7103 wxRichTextListStyleDefinition* listDef = buffer->GetStyleSheet()->FindListStyle(para->GetAttributes().GetListStyleName());
7104 if (listDef)
7105 {
7106 int thisIndent = para->GetAttributes().GetLeftIndent();
7107 int thisLevel = para->GetAttributes().HasOutlineLevel() ? para->GetAttributes().GetOutlineLevel() : listDef->FindLevelForIndent(thisIndent);
7108
7109 // Apply the overall list style, and item style for this level
7110 wxRichTextAttr listStyle(listDef->GetCombinedStyleForLevel(thisLevel, buffer->GetStyleSheet()));
7111 wxRichTextApplyStyle(attr, listStyle);
7112 attr.SetOutlineLevel(thisLevel);
7113 if (para->GetAttributes().HasBulletNumber())
7114 attr.SetBulletNumber(para->GetAttributes().GetBulletNumber());
7115 }
7116 }
7117
7118 if (!foundAttributes)
7119 {
7120 attr = para->GetAttributes();
7121 int flags = attr.GetFlags();
7122
7123 // Eliminate character styles
7124 flags &= ( (~ wxTEXT_ATTR_FONT) |
7125 (~ wxTEXT_ATTR_TEXT_COLOUR) |
7126 (~ wxTEXT_ATTR_BACKGROUND_COLOUR) );
7127 attr.SetFlags(flags);
7128 }
7129
7130 return attr;
7131 }
7132 else
7133 return wxRichTextAttr();
7134 }
7135
7136 /// Submit command to delete this range
7137 bool wxRichTextBuffer::DeleteRangeWithUndo(const wxRichTextRange& range, wxRichTextCtrl* ctrl)
7138 {
7139 return ctrl->GetFocusObject()->DeleteRangeWithUndo(range, ctrl, this);
7140 }
7141
7142 /// Submit command to delete this range
7143 bool wxRichTextParagraphLayoutBox::DeleteRangeWithUndo(const wxRichTextRange& range, wxRichTextCtrl* ctrl, wxRichTextBuffer* buffer)
7144 {
7145 wxRichTextAction* action = new wxRichTextAction(NULL, _("Delete"), wxRICHTEXT_DELETE, buffer, this, ctrl);
7146
7147 action->SetPosition(ctrl->GetCaretPosition());
7148
7149 // Set the range to delete
7150 action->SetRange(range);
7151
7152 // Copy the fragment that we'll need to restore in Undo
7153 CopyFragment(range, action->GetOldParagraphs());
7154
7155 // See if we're deleting a paragraph marker, in which case we need to
7156 // make a note not to copy the attributes from the 2nd paragraph to the 1st.
7157 if (range.GetStart() == range.GetEnd())
7158 {
7159 wxRichTextParagraph* para = GetParagraphAtPosition(range.GetStart());
7160 if (para && para->GetRange().GetEnd() == range.GetEnd())
7161 {
7162 wxRichTextParagraph* nextPara = GetParagraphAtPosition(range.GetStart()+1);
7163 if (nextPara && nextPara != para)
7164 {
7165 action->GetOldParagraphs().GetChildren().GetFirst()->GetData()->SetAttributes(nextPara->GetAttributes());
7166 action->GetOldParagraphs().GetAttributes().SetFlags(action->GetOldParagraphs().GetAttributes().GetFlags() | wxTEXT_ATTR_KEEP_FIRST_PARA_STYLE);
7167 }
7168 }
7169 }
7170
7171 buffer->SubmitAction(action);
7172
7173 return true;
7174 }
7175
7176 /// Collapse undo/redo commands
7177 bool wxRichTextBuffer::BeginBatchUndo(const wxString& cmdName)
7178 {
7179 if (m_batchedCommandDepth == 0)
7180 {
7181 wxASSERT(m_batchedCommand == NULL);
7182 if (m_batchedCommand)
7183 {
7184 GetCommandProcessor()->Store(m_batchedCommand);
7185 }
7186 m_batchedCommand = new wxRichTextCommand(cmdName);
7187 }
7188
7189 m_batchedCommandDepth ++;
7190
7191 return true;
7192 }
7193
7194 /// Collapse undo/redo commands
7195 bool wxRichTextBuffer::EndBatchUndo()
7196 {
7197 m_batchedCommandDepth --;
7198
7199 wxASSERT(m_batchedCommandDepth >= 0);
7200 wxASSERT(m_batchedCommand != NULL);
7201
7202 if (m_batchedCommandDepth == 0)
7203 {
7204 GetCommandProcessor()->Store(m_batchedCommand);
7205 m_batchedCommand = NULL;
7206 }
7207
7208 return true;
7209 }
7210
7211 /// Submit immediately, or delay according to whether collapsing is on
7212 bool wxRichTextBuffer::SubmitAction(wxRichTextAction* action)
7213 {
7214 if (action && !action->GetNewParagraphs().IsEmpty())
7215 PrepareContent(action->GetNewParagraphs());
7216
7217 if (BatchingUndo() && m_batchedCommand && !SuppressingUndo())
7218 {
7219 wxRichTextCommand* cmd = new wxRichTextCommand(action->GetName());
7220 cmd->AddAction(action);
7221 cmd->Do();
7222 cmd->GetActions().Clear();
7223 delete cmd;
7224
7225 m_batchedCommand->AddAction(action);
7226 }
7227 else
7228 {
7229 wxRichTextCommand* cmd = new wxRichTextCommand(action->GetName());
7230 cmd->AddAction(action);
7231
7232 // Only store it if we're not suppressing undo.
7233 return GetCommandProcessor()->Submit(cmd, !SuppressingUndo());
7234 }
7235
7236 return true;
7237 }
7238
7239 /// Begin suppressing undo/redo commands.
7240 bool wxRichTextBuffer::BeginSuppressUndo()
7241 {
7242 m_suppressUndo ++;
7243
7244 return true;
7245 }
7246
7247 /// End suppressing undo/redo commands.
7248 bool wxRichTextBuffer::EndSuppressUndo()
7249 {
7250 m_suppressUndo --;
7251
7252 return true;
7253 }
7254
7255 /// Begin using a style
7256 bool wxRichTextBuffer::BeginStyle(const wxRichTextAttr& style)
7257 {
7258 wxRichTextAttr newStyle(GetDefaultStyle());
7259
7260 // Save the old default style
7261 m_attributeStack.Append((wxObject*) new wxRichTextAttr(GetDefaultStyle()));
7262
7263 wxRichTextApplyStyle(newStyle, style);
7264 newStyle.SetFlags(style.GetFlags()|newStyle.GetFlags());
7265
7266 SetDefaultStyle(newStyle);
7267
7268 return true;
7269 }
7270
7271 /// End the style
7272 bool wxRichTextBuffer::EndStyle()
7273 {
7274 if (!m_attributeStack.GetFirst())
7275 {
7276 wxLogDebug(_("Too many EndStyle calls!"));
7277 return false;
7278 }
7279
7280 wxList::compatibility_iterator node = m_attributeStack.GetLast();
7281 wxRichTextAttr* attr = (wxRichTextAttr*)node->GetData();
7282 m_attributeStack.Erase(node);
7283
7284 SetDefaultStyle(*attr);
7285
7286 delete attr;
7287 return true;
7288 }
7289
7290 /// End all styles
7291 bool wxRichTextBuffer::EndAllStyles()
7292 {
7293 while (m_attributeStack.GetCount() != 0)
7294 EndStyle();
7295 return true;
7296 }
7297
7298 /// Clear the style stack
7299 void wxRichTextBuffer::ClearStyleStack()
7300 {
7301 for (wxList::compatibility_iterator node = m_attributeStack.GetFirst(); node; node = node->GetNext())
7302 delete (wxRichTextAttr*) node->GetData();
7303 m_attributeStack.Clear();
7304 }
7305
7306 /// Begin using bold
7307 bool wxRichTextBuffer::BeginBold()
7308 {
7309 wxRichTextAttr attr;
7310 attr.SetFontWeight(wxFONTWEIGHT_BOLD);
7311
7312 return BeginStyle(attr);
7313 }
7314
7315 /// Begin using italic
7316 bool wxRichTextBuffer::BeginItalic()
7317 {
7318 wxRichTextAttr attr;
7319 attr.SetFontStyle(wxFONTSTYLE_ITALIC);
7320
7321 return BeginStyle(attr);
7322 }
7323
7324 /// Begin using underline
7325 bool wxRichTextBuffer::BeginUnderline()
7326 {
7327 wxRichTextAttr attr;
7328 attr.SetFontUnderlined(true);
7329
7330 return BeginStyle(attr);
7331 }
7332
7333 /// Begin using point size
7334 bool wxRichTextBuffer::BeginFontSize(int pointSize)
7335 {
7336 wxRichTextAttr attr;
7337 attr.SetFontSize(pointSize);
7338
7339 return BeginStyle(attr);
7340 }
7341
7342 /// Begin using this font
7343 bool wxRichTextBuffer::BeginFont(const wxFont& font)
7344 {
7345 wxRichTextAttr attr;
7346 attr.SetFont(font);
7347
7348 return BeginStyle(attr);
7349 }
7350
7351 /// Begin using this colour
7352 bool wxRichTextBuffer::BeginTextColour(const wxColour& colour)
7353 {
7354 wxRichTextAttr attr;
7355 attr.SetFlags(wxTEXT_ATTR_TEXT_COLOUR);
7356 attr.SetTextColour(colour);
7357
7358 return BeginStyle(attr);
7359 }
7360
7361 /// Begin using alignment
7362 bool wxRichTextBuffer::BeginAlignment(wxTextAttrAlignment alignment)
7363 {
7364 wxRichTextAttr attr;
7365 attr.SetFlags(wxTEXT_ATTR_ALIGNMENT);
7366 attr.SetAlignment(alignment);
7367
7368 return BeginStyle(attr);
7369 }
7370
7371 /// Begin left indent
7372 bool wxRichTextBuffer::BeginLeftIndent(int leftIndent, int leftSubIndent)
7373 {
7374 wxRichTextAttr attr;
7375 attr.SetFlags(wxTEXT_ATTR_LEFT_INDENT);
7376 attr.SetLeftIndent(leftIndent, leftSubIndent);
7377
7378 return BeginStyle(attr);
7379 }
7380
7381 /// Begin right indent
7382 bool wxRichTextBuffer::BeginRightIndent(int rightIndent)
7383 {
7384 wxRichTextAttr attr;
7385 attr.SetFlags(wxTEXT_ATTR_RIGHT_INDENT);
7386 attr.SetRightIndent(rightIndent);
7387
7388 return BeginStyle(attr);
7389 }
7390
7391 /// Begin paragraph spacing
7392 bool wxRichTextBuffer::BeginParagraphSpacing(int before, int after)
7393 {
7394 long flags = 0;
7395 if (before != 0)
7396 flags |= wxTEXT_ATTR_PARA_SPACING_BEFORE;
7397 if (after != 0)
7398 flags |= wxTEXT_ATTR_PARA_SPACING_AFTER;
7399
7400 wxRichTextAttr attr;
7401 attr.SetFlags(flags);
7402 attr.SetParagraphSpacingBefore(before);
7403 attr.SetParagraphSpacingAfter(after);
7404
7405 return BeginStyle(attr);
7406 }
7407
7408 /// Begin line spacing
7409 bool wxRichTextBuffer::BeginLineSpacing(int lineSpacing)
7410 {
7411 wxRichTextAttr attr;
7412 attr.SetFlags(wxTEXT_ATTR_LINE_SPACING);
7413 attr.SetLineSpacing(lineSpacing);
7414
7415 return BeginStyle(attr);
7416 }
7417
7418 /// Begin numbered bullet
7419 bool wxRichTextBuffer::BeginNumberedBullet(int bulletNumber, int leftIndent, int leftSubIndent, int bulletStyle)
7420 {
7421 wxRichTextAttr attr;
7422 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT);
7423 attr.SetBulletStyle(bulletStyle);
7424 attr.SetBulletNumber(bulletNumber);
7425 attr.SetLeftIndent(leftIndent, leftSubIndent);
7426
7427 return BeginStyle(attr);
7428 }
7429
7430 /// Begin symbol bullet
7431 bool wxRichTextBuffer::BeginSymbolBullet(const wxString& symbol, int leftIndent, int leftSubIndent, int bulletStyle)
7432 {
7433 wxRichTextAttr attr;
7434 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT);
7435 attr.SetBulletStyle(bulletStyle);
7436 attr.SetLeftIndent(leftIndent, leftSubIndent);
7437 attr.SetBulletText(symbol);
7438
7439 return BeginStyle(attr);
7440 }
7441
7442 /// Begin standard bullet
7443 bool wxRichTextBuffer::BeginStandardBullet(const wxString& bulletName, int leftIndent, int leftSubIndent, int bulletStyle)
7444 {
7445 wxRichTextAttr attr;
7446 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT);
7447 attr.SetBulletStyle(bulletStyle);
7448 attr.SetLeftIndent(leftIndent, leftSubIndent);
7449 attr.SetBulletName(bulletName);
7450
7451 return BeginStyle(attr);
7452 }
7453
7454 /// Begin named character style
7455 bool wxRichTextBuffer::BeginCharacterStyle(const wxString& characterStyle)
7456 {
7457 if (GetStyleSheet())
7458 {
7459 wxRichTextCharacterStyleDefinition* def = GetStyleSheet()->FindCharacterStyle(characterStyle);
7460 if (def)
7461 {
7462 wxRichTextAttr attr = def->GetStyleMergedWithBase(GetStyleSheet());
7463 return BeginStyle(attr);
7464 }
7465 }
7466 return false;
7467 }
7468
7469 /// Begin named paragraph style
7470 bool wxRichTextBuffer::BeginParagraphStyle(const wxString& paragraphStyle)
7471 {
7472 if (GetStyleSheet())
7473 {
7474 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(paragraphStyle);
7475 if (def)
7476 {
7477 wxRichTextAttr attr = def->GetStyleMergedWithBase(GetStyleSheet());
7478 return BeginStyle(attr);
7479 }
7480 }
7481 return false;
7482 }
7483
7484 /// Begin named list style
7485 bool wxRichTextBuffer::BeginListStyle(const wxString& listStyle, int level, int number)
7486 {
7487 if (GetStyleSheet())
7488 {
7489 wxRichTextListStyleDefinition* def = GetStyleSheet()->FindListStyle(listStyle);
7490 if (def)
7491 {
7492 wxRichTextAttr attr(def->GetCombinedStyleForLevel(level));
7493
7494 attr.SetBulletNumber(number);
7495
7496 return BeginStyle(attr);
7497 }
7498 }
7499 return false;
7500 }
7501
7502 /// Begin URL
7503 bool wxRichTextBuffer::BeginURL(const wxString& url, const wxString& characterStyle)
7504 {
7505 wxRichTextAttr attr;
7506
7507 if (!characterStyle.IsEmpty() && GetStyleSheet())
7508 {
7509 wxRichTextCharacterStyleDefinition* def = GetStyleSheet()->FindCharacterStyle(characterStyle);
7510 if (def)
7511 {
7512 attr = def->GetStyleMergedWithBase(GetStyleSheet());
7513 }
7514 }
7515 attr.SetURL(url);
7516
7517 return BeginStyle(attr);
7518 }
7519
7520 /// Adds a handler to the end
7521 void wxRichTextBuffer::AddHandler(wxRichTextFileHandler *handler)
7522 {
7523 sm_handlers.Append(handler);
7524 }
7525
7526 /// Inserts a handler at the front
7527 void wxRichTextBuffer::InsertHandler(wxRichTextFileHandler *handler)
7528 {
7529 sm_handlers.Insert( handler );
7530 }
7531
7532 /// Removes a handler
7533 bool wxRichTextBuffer::RemoveHandler(const wxString& name)
7534 {
7535 wxRichTextFileHandler *handler = FindHandler(name);
7536 if (handler)
7537 {
7538 sm_handlers.DeleteObject(handler);
7539 delete handler;
7540 return true;
7541 }
7542 else
7543 return false;
7544 }
7545
7546 /// Finds a handler by filename or, if supplied, type
7547 wxRichTextFileHandler *wxRichTextBuffer::FindHandlerFilenameOrType(const wxString& filename,
7548 wxRichTextFileType imageType)
7549 {
7550 if (imageType != wxRICHTEXT_TYPE_ANY)
7551 return FindHandler(imageType);
7552 else if (!filename.IsEmpty())
7553 {
7554 wxString path, file, ext;
7555 wxFileName::SplitPath(filename, & path, & file, & ext);
7556 return FindHandler(ext, imageType);
7557 }
7558 else
7559 return NULL;
7560 }
7561
7562
7563 /// Finds a handler by name
7564 wxRichTextFileHandler* wxRichTextBuffer::FindHandler(const wxString& name)
7565 {
7566 wxList::compatibility_iterator node = sm_handlers.GetFirst();
7567 while (node)
7568 {
7569 wxRichTextFileHandler *handler = (wxRichTextFileHandler*)node->GetData();
7570 if (handler->GetName().Lower() == name.Lower()) return handler;
7571
7572 node = node->GetNext();
7573 }
7574 return NULL;
7575 }
7576
7577 /// Finds a handler by extension and type
7578 wxRichTextFileHandler* wxRichTextBuffer::FindHandler(const wxString& extension, wxRichTextFileType type)
7579 {
7580 wxList::compatibility_iterator node = sm_handlers.GetFirst();
7581 while (node)
7582 {
7583 wxRichTextFileHandler *handler = (wxRichTextFileHandler*)node->GetData();
7584 if ( handler->GetExtension().Lower() == extension.Lower() &&
7585 (type == wxRICHTEXT_TYPE_ANY || handler->GetType() == type) )
7586 return handler;
7587 node = node->GetNext();
7588 }
7589 return 0;
7590 }
7591
7592 /// Finds a handler by type
7593 wxRichTextFileHandler* wxRichTextBuffer::FindHandler(wxRichTextFileType type)
7594 {
7595 wxList::compatibility_iterator node = sm_handlers.GetFirst();
7596 while (node)
7597 {
7598 wxRichTextFileHandler *handler = (wxRichTextFileHandler *)node->GetData();
7599 if (handler->GetType() == type) return handler;
7600 node = node->GetNext();
7601 }
7602 return NULL;
7603 }
7604
7605 void wxRichTextBuffer::InitStandardHandlers()
7606 {
7607 if (!FindHandler(wxRICHTEXT_TYPE_TEXT))
7608 AddHandler(new wxRichTextPlainTextHandler);
7609 }
7610
7611 void wxRichTextBuffer::CleanUpHandlers()
7612 {
7613 wxList::compatibility_iterator node = sm_handlers.GetFirst();
7614 while (node)
7615 {
7616 wxRichTextFileHandler* handler = (wxRichTextFileHandler*)node->GetData();
7617 wxList::compatibility_iterator next = node->GetNext();
7618 delete handler;
7619 node = next;
7620 }
7621
7622 sm_handlers.Clear();
7623 }
7624
7625 wxString wxRichTextBuffer::GetExtWildcard(bool combine, bool save, wxArrayInt* types)
7626 {
7627 if (types)
7628 types->Clear();
7629
7630 wxString wildcard;
7631
7632 wxList::compatibility_iterator node = GetHandlers().GetFirst();
7633 int count = 0;
7634 while (node)
7635 {
7636 wxRichTextFileHandler* handler = (wxRichTextFileHandler*) node->GetData();
7637 if (handler->IsVisible() && ((save && handler->CanSave()) || (!save && handler->CanLoad())))
7638 {
7639 if (combine)
7640 {
7641 if (count > 0)
7642 wildcard += wxT(";");
7643 wildcard += wxT("*.") + handler->GetExtension();
7644 }
7645 else
7646 {
7647 if (count > 0)
7648 wildcard += wxT("|");
7649 wildcard += handler->GetName();
7650 wildcard += wxT(" ");
7651 wildcard += _("files");
7652 wildcard += wxT(" (*.");
7653 wildcard += handler->GetExtension();
7654 wildcard += wxT(")|*.");
7655 wildcard += handler->GetExtension();
7656 if (types)
7657 types->Add(handler->GetType());
7658 }
7659 count ++;
7660 }
7661
7662 node = node->GetNext();
7663 }
7664
7665 if (combine)
7666 wildcard = wxT("(") + wildcard + wxT(")|") + wildcard;
7667 return wildcard;
7668 }
7669
7670 /// Load a file
7671 bool wxRichTextBuffer::LoadFile(const wxString& filename, wxRichTextFileType type)
7672 {
7673 wxRichTextFileHandler* handler = FindHandlerFilenameOrType(filename, type);
7674 if (handler)
7675 {
7676 SetDefaultStyle(wxRichTextAttr());
7677 handler->SetFlags(GetHandlerFlags());
7678 bool success = handler->LoadFile(this, filename);
7679 Invalidate(wxRICHTEXT_ALL);
7680 return success;
7681 }
7682 else
7683 return false;
7684 }
7685
7686 /// Save a file
7687 bool wxRichTextBuffer::SaveFile(const wxString& filename, wxRichTextFileType type)
7688 {
7689 wxRichTextFileHandler* handler = FindHandlerFilenameOrType(filename, type);
7690 if (handler)
7691 {
7692 handler->SetFlags(GetHandlerFlags());
7693 return handler->SaveFile(this, filename);
7694 }
7695 else
7696 return false;
7697 }
7698
7699 /// Load from a stream
7700 bool wxRichTextBuffer::LoadFile(wxInputStream& stream, wxRichTextFileType type)
7701 {
7702 wxRichTextFileHandler* handler = FindHandler(type);
7703 if (handler)
7704 {
7705 SetDefaultStyle(wxRichTextAttr());
7706 handler->SetFlags(GetHandlerFlags());
7707 bool success = handler->LoadFile(this, stream);
7708 Invalidate(wxRICHTEXT_ALL);
7709 return success;
7710 }
7711 else
7712 return false;
7713 }
7714
7715 /// Save to a stream
7716 bool wxRichTextBuffer::SaveFile(wxOutputStream& stream, wxRichTextFileType type)
7717 {
7718 wxRichTextFileHandler* handler = FindHandler(type);
7719 if (handler)
7720 {
7721 handler->SetFlags(GetHandlerFlags());
7722 return handler->SaveFile(this, stream);
7723 }
7724 else
7725 return false;
7726 }
7727
7728 /// Copy the range to the clipboard
7729 bool wxRichTextBuffer::CopyToClipboard(const wxRichTextRange& range)
7730 {
7731 bool success = false;
7732 wxRichTextParagraphLayoutBox* container = this;
7733 if (GetRichTextCtrl())
7734 container = GetRichTextCtrl()->GetFocusObject();
7735
7736 #if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
7737
7738 if (!wxTheClipboard->IsOpened() && wxTheClipboard->Open())
7739 {
7740 wxTheClipboard->Clear();
7741
7742 // Add composite object
7743
7744 wxDataObjectComposite* compositeObject = new wxDataObjectComposite();
7745
7746 {
7747 wxString text = container->GetTextForRange(range);
7748
7749 #ifdef __WXMSW__
7750 text = wxTextFile::Translate(text, wxTextFileType_Dos);
7751 #endif
7752
7753 compositeObject->Add(new wxTextDataObject(text), false /* not preferred */);
7754 }
7755
7756 // Add rich text buffer data object. This needs the XML handler to be present.
7757
7758 if (FindHandler(wxRICHTEXT_TYPE_XML))
7759 {
7760 wxRichTextBuffer* richTextBuf = new wxRichTextBuffer;
7761 container->CopyFragment(range, *richTextBuf);
7762
7763 compositeObject->Add(new wxRichTextBufferDataObject(richTextBuf), true /* preferred */);
7764 }
7765
7766 if (wxTheClipboard->SetData(compositeObject))
7767 success = true;
7768
7769 wxTheClipboard->Close();
7770 }
7771
7772 #else
7773 wxUnusedVar(range);
7774 #endif
7775 return success;
7776 }
7777
7778 /// Paste the clipboard content to the buffer
7779 bool wxRichTextBuffer::PasteFromClipboard(long position)
7780 {
7781 bool success = false;
7782 wxRichTextParagraphLayoutBox* container = this;
7783 if (GetRichTextCtrl())
7784 container = GetRichTextCtrl()->GetFocusObject();
7785
7786 #if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
7787 if (CanPasteFromClipboard())
7788 {
7789 if (wxTheClipboard->Open())
7790 {
7791 if (wxTheClipboard->IsSupported(wxDataFormat(wxRichTextBufferDataObject::GetRichTextBufferFormatId())))
7792 {
7793 wxRichTextBufferDataObject data;
7794 wxTheClipboard->GetData(data);
7795 wxRichTextBuffer* richTextBuffer = data.GetRichTextBuffer();
7796 if (richTextBuffer)
7797 {
7798 container->InsertParagraphsWithUndo(this, position+1, *richTextBuffer, GetRichTextCtrl(), 0);
7799 if (GetRichTextCtrl())
7800 GetRichTextCtrl()->ShowPosition(position + richTextBuffer->GetOwnRange().GetEnd());
7801 delete richTextBuffer;
7802 }
7803 }
7804 else if (wxTheClipboard->IsSupported(wxDF_TEXT)
7805 #if wxUSE_UNICODE
7806 || wxTheClipboard->IsSupported(wxDF_UNICODETEXT)
7807 #endif
7808 )
7809 {
7810 wxTextDataObject data;
7811 wxTheClipboard->GetData(data);
7812 wxString text(data.GetText());
7813 #ifdef __WXMSW__
7814 wxString text2;
7815 text2.Alloc(text.Length()+1);
7816 size_t i;
7817 for (i = 0; i < text.Length(); i++)
7818 {
7819 wxChar ch = text[i];
7820 if (ch != wxT('\r'))
7821 text2 += ch;
7822 }
7823 #else
7824 wxString text2 = text;
7825 #endif
7826 container->InsertTextWithUndo(this, position+1, text2, GetRichTextCtrl(), wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE);
7827
7828 if (GetRichTextCtrl())
7829 GetRichTextCtrl()->ShowPosition(position + text2.Length());
7830
7831 success = true;
7832 }
7833 else if (wxTheClipboard->IsSupported(wxDF_BITMAP))
7834 {
7835 wxBitmapDataObject data;
7836 wxTheClipboard->GetData(data);
7837 wxBitmap bitmap(data.GetBitmap());
7838 wxImage image(bitmap.ConvertToImage());
7839
7840 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Image"), wxRICHTEXT_INSERT, this, container, GetRichTextCtrl(), false);
7841
7842 action->GetNewParagraphs().AddImage(image);
7843
7844 if (action->GetNewParagraphs().GetChildCount() == 1)
7845 action->GetNewParagraphs().SetPartialParagraph(true);
7846
7847 action->SetPosition(position+1);
7848
7849 // Set the range we'll need to delete in Undo
7850 action->SetRange(wxRichTextRange(position+1, position+1));
7851
7852 SubmitAction(action);
7853
7854 success = true;
7855 }
7856 wxTheClipboard->Close();
7857 }
7858 }
7859 #else
7860 wxUnusedVar(position);
7861 #endif
7862 return success;
7863 }
7864
7865 /// Can we paste from the clipboard?
7866 bool wxRichTextBuffer::CanPasteFromClipboard() const
7867 {
7868 bool canPaste = false;
7869 #if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
7870 if (!wxTheClipboard->IsOpened() && wxTheClipboard->Open())
7871 {
7872 if (wxTheClipboard->IsSupported(wxDF_TEXT)
7873 #if wxUSE_UNICODE
7874 || wxTheClipboard->IsSupported(wxDF_UNICODETEXT)
7875 #endif
7876 || wxTheClipboard->IsSupported(wxDataFormat(wxRichTextBufferDataObject::GetRichTextBufferFormatId())) ||
7877 wxTheClipboard->IsSupported(wxDF_BITMAP))
7878 {
7879 canPaste = true;
7880 }
7881 wxTheClipboard->Close();
7882 }
7883 #endif
7884 return canPaste;
7885 }
7886
7887 /// Dumps contents of buffer for debugging purposes
7888 void wxRichTextBuffer::Dump()
7889 {
7890 wxString text;
7891 {
7892 wxStringOutputStream stream(& text);
7893 wxTextOutputStream textStream(stream);
7894 Dump(textStream);
7895 }
7896
7897 wxLogDebug(text);
7898 }
7899
7900 /// Add an event handler
7901 bool wxRichTextBuffer::AddEventHandler(wxEvtHandler* handler)
7902 {
7903 m_eventHandlers.Append(handler);
7904 return true;
7905 }
7906
7907 /// Remove an event handler
7908 bool wxRichTextBuffer::RemoveEventHandler(wxEvtHandler* handler, bool deleteHandler)
7909 {
7910 wxList::compatibility_iterator node = m_eventHandlers.Find(handler);
7911 if (node)
7912 {
7913 m_eventHandlers.Erase(node);
7914 if (deleteHandler)
7915 delete handler;
7916
7917 return true;
7918 }
7919 else
7920 return false;
7921 }
7922
7923 /// Clear event handlers
7924 void wxRichTextBuffer::ClearEventHandlers()
7925 {
7926 m_eventHandlers.Clear();
7927 }
7928
7929 /// Send event to event handlers. If sendToAll is true, will send to all event handlers,
7930 /// otherwise will stop at the first successful one.
7931 bool wxRichTextBuffer::SendEvent(wxEvent& event, bool sendToAll)
7932 {
7933 bool success = false;
7934 for (wxList::compatibility_iterator node = m_eventHandlers.GetFirst(); node; node = node->GetNext())
7935 {
7936 wxEvtHandler* handler = (wxEvtHandler*) node->GetData();
7937 if (handler->ProcessEvent(event))
7938 {
7939 success = true;
7940 if (!sendToAll)
7941 return true;
7942 }
7943 }
7944 return success;
7945 }
7946
7947 /// Set style sheet and notify of the change
7948 bool wxRichTextBuffer::SetStyleSheetAndNotify(wxRichTextStyleSheet* sheet)
7949 {
7950 wxRichTextStyleSheet* oldSheet = GetStyleSheet();
7951
7952 wxWindowID winid = wxID_ANY;
7953 if (GetRichTextCtrl())
7954 winid = GetRichTextCtrl()->GetId();
7955
7956 wxRichTextEvent event(wxEVT_COMMAND_RICHTEXT_STYLESHEET_REPLACING, winid);
7957 event.SetEventObject(GetRichTextCtrl());
7958 event.SetContainer(GetRichTextCtrl()->GetFocusObject());
7959 event.SetOldStyleSheet(oldSheet);
7960 event.SetNewStyleSheet(sheet);
7961 event.Allow();
7962
7963 if (SendEvent(event) && !event.IsAllowed())
7964 {
7965 if (sheet != oldSheet)
7966 delete sheet;
7967
7968 return false;
7969 }
7970
7971 if (oldSheet && oldSheet != sheet)
7972 delete oldSheet;
7973
7974 SetStyleSheet(sheet);
7975
7976 event.SetEventType(wxEVT_COMMAND_RICHTEXT_STYLESHEET_REPLACED);
7977 event.SetOldStyleSheet(NULL);
7978 event.Allow();
7979
7980 return SendEvent(event);
7981 }
7982
7983 /// Set renderer, deleting old one
7984 void wxRichTextBuffer::SetRenderer(wxRichTextRenderer* renderer)
7985 {
7986 if (sm_renderer)
7987 delete sm_renderer;
7988 sm_renderer = renderer;
7989 }
7990
7991 /// Hit-testing: returns a flag indicating hit test details, plus
7992 /// information about position
7993 int wxRichTextBuffer::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int flags)
7994 {
7995 int ret = wxRichTextParagraphLayoutBox::HitTest(dc, context, pt, textPosition, obj, contextObj, flags);
7996 if (ret != wxRICHTEXT_HITTEST_NONE)
7997 {
7998 return ret;
7999 }
8000 else
8001 {
8002 textPosition = m_ownRange.GetEnd()-1;
8003 *obj = this;
8004 *contextObj = this;
8005 return wxRICHTEXT_HITTEST_AFTER|wxRICHTEXT_HITTEST_OUTSIDE;
8006 }
8007 }
8008
8009 bool wxRichTextStdRenderer::DrawStandardBullet(wxRichTextParagraph* paragraph, wxDC& dc, const wxRichTextAttr& bulletAttr, const wxRect& rect)
8010 {
8011 if (bulletAttr.GetTextColour().IsOk())
8012 {
8013 wxCheckSetPen(dc, wxPen(bulletAttr.GetTextColour()));
8014 wxCheckSetBrush(dc, wxBrush(bulletAttr.GetTextColour()));
8015 }
8016 else
8017 {
8018 wxCheckSetPen(dc, *wxBLACK_PEN);
8019 wxCheckSetBrush(dc, *wxBLACK_BRUSH);
8020 }
8021
8022 wxFont font;
8023 if (bulletAttr.HasFont())
8024 {
8025 font = paragraph->GetBuffer()->GetFontTable().FindFont(bulletAttr);
8026 }
8027 else
8028 font = (*wxNORMAL_FONT);
8029
8030 wxCheckSetFont(dc, font);
8031
8032 int charHeight = dc.GetCharHeight();
8033
8034 int bulletWidth = (int) (((float) charHeight) * wxRichTextBuffer::GetBulletProportion());
8035 int bulletHeight = bulletWidth;
8036
8037 int x = rect.x;
8038
8039 // Calculate the top position of the character (as opposed to the whole line height)
8040 int y = rect.y + (rect.height - charHeight);
8041
8042 // Calculate where the bullet should be positioned
8043 y = y + (charHeight+1)/2 - (bulletHeight+1)/2;
8044
8045 // The margin between a bullet and text.
8046 int margin = paragraph->ConvertTenthsMMToPixels(dc, wxRichTextBuffer::GetBulletRightMargin());
8047
8048 if (bulletAttr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_RIGHT)
8049 x = rect.x + rect.width - bulletWidth - margin;
8050 else if (bulletAttr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_CENTRE)
8051 x = x + (rect.width)/2 - bulletWidth/2;
8052
8053 if (bulletAttr.GetBulletName() == wxT("standard/square"))
8054 {
8055 dc.DrawRectangle(x, y, bulletWidth, bulletHeight);
8056 }
8057 else if (bulletAttr.GetBulletName() == wxT("standard/diamond"))
8058 {
8059 wxPoint pts[5];
8060 pts[0].x = x; pts[0].y = y + bulletHeight/2;
8061 pts[1].x = x + bulletWidth/2; pts[1].y = y;
8062 pts[2].x = x + bulletWidth; pts[2].y = y + bulletHeight/2;
8063 pts[3].x = x + bulletWidth/2; pts[3].y = y + bulletHeight;
8064
8065 dc.DrawPolygon(4, pts);
8066 }
8067 else if (bulletAttr.GetBulletName() == wxT("standard/triangle"))
8068 {
8069 wxPoint pts[3];
8070 pts[0].x = x; pts[0].y = y;
8071 pts[1].x = x + bulletWidth; pts[1].y = y + bulletHeight/2;
8072 pts[2].x = x; pts[2].y = y + bulletHeight;
8073
8074 dc.DrawPolygon(3, pts);
8075 }
8076 else if (bulletAttr.GetBulletName() == wxT("standard/circle-outline"))
8077 {
8078 wxCheckSetBrush(dc, *wxTRANSPARENT_BRUSH);
8079 dc.DrawEllipse(x, y, bulletWidth, bulletHeight);
8080 }
8081 else // "standard/circle", and catch-all
8082 {
8083 dc.DrawEllipse(x, y, bulletWidth, bulletHeight);
8084 }
8085
8086 return true;
8087 }
8088
8089 bool wxRichTextStdRenderer::DrawTextBullet(wxRichTextParagraph* paragraph, wxDC& dc, const wxRichTextAttr& attr, const wxRect& rect, const wxString& text)
8090 {
8091 if (!text.empty())
8092 {
8093 wxFont font;
8094 if ((attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_SYMBOL) && !attr.GetBulletFont().IsEmpty() && attr.HasFont())
8095 {
8096 wxRichTextAttr fontAttr;
8097 fontAttr.SetFontSize(attr.GetFontSize());
8098 fontAttr.SetFontStyle(attr.GetFontStyle());
8099 fontAttr.SetFontWeight(attr.GetFontWeight());
8100 fontAttr.SetFontUnderlined(attr.GetFontUnderlined());
8101 fontAttr.SetFontFaceName(attr.GetBulletFont());
8102 font = paragraph->GetBuffer()->GetFontTable().FindFont(fontAttr);
8103 }
8104 else if (attr.HasFont())
8105 font = paragraph->GetBuffer()->GetFontTable().FindFont(attr);
8106 else
8107 font = (*wxNORMAL_FONT);
8108
8109 wxCheckSetFont(dc, font);
8110
8111 if (attr.GetTextColour().IsOk())
8112 dc.SetTextForeground(attr.GetTextColour());
8113
8114 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
8115
8116 int charHeight = dc.GetCharHeight();
8117 wxCoord tw, th;
8118 dc.GetTextExtent(text, & tw, & th);
8119
8120 int x = rect.x;
8121
8122 // Calculate the top position of the character (as opposed to the whole line height)
8123 int y = rect.y + (rect.height - charHeight);
8124
8125 // The margin between a bullet and text.
8126 int margin = paragraph->ConvertTenthsMMToPixels(dc, wxRichTextBuffer::GetBulletRightMargin());
8127
8128 if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_RIGHT)
8129 x = (rect.x + rect.width) - tw - margin;
8130 else if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_CENTRE)
8131 x = x + (rect.width)/2 - tw/2;
8132
8133 dc.DrawText(text, x, y);
8134
8135 return true;
8136 }
8137 else
8138 return false;
8139 }
8140
8141 bool wxRichTextStdRenderer::DrawBitmapBullet(wxRichTextParagraph* WXUNUSED(paragraph), wxDC& WXUNUSED(dc), const wxRichTextAttr& WXUNUSED(attr), const wxRect& WXUNUSED(rect))
8142 {
8143 // Currently unimplemented. The intention is to store bitmaps by name in a media store associated
8144 // with the buffer. The store will allow retrieval from memory, disk or other means.
8145 return false;
8146 }
8147
8148 /// Enumerate the standard bullet names currently supported
8149 bool wxRichTextStdRenderer::EnumerateStandardBulletNames(wxArrayString& bulletNames)
8150 {
8151 bulletNames.Add(wxTRANSLATE("standard/circle"));
8152 bulletNames.Add(wxTRANSLATE("standard/circle-outline"));
8153 bulletNames.Add(wxTRANSLATE("standard/square"));
8154 bulletNames.Add(wxTRANSLATE("standard/diamond"));
8155 bulletNames.Add(wxTRANSLATE("standard/triangle"));
8156
8157 return true;
8158 }
8159
8160 /*!
8161 * wxRichTextBox
8162 */
8163
8164 IMPLEMENT_DYNAMIC_CLASS(wxRichTextBox, wxRichTextParagraphLayoutBox)
8165
8166 wxRichTextBox::wxRichTextBox(wxRichTextObject* parent):
8167 wxRichTextParagraphLayoutBox(parent)
8168 {
8169 }
8170
8171 /// Draw the item
8172 bool wxRichTextBox::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
8173 {
8174 if (!IsShown())
8175 return true;
8176
8177 // TODO: if the active object in the control, draw an indication.
8178 // We need to add the concept of active object, and not just focus object,
8179 // so we can apply commands (properties, delete, ...) to objects such as text boxes and images.
8180 // Ultimately we would like to be able to interactively resize an active object
8181 // using drag handles.
8182 return wxRichTextParagraphLayoutBox::Draw(dc, context, range, selection, rect, descent, style);
8183 }
8184
8185 /// Copy
8186 void wxRichTextBox::Copy(const wxRichTextBox& obj)
8187 {
8188 wxRichTextParagraphLayoutBox::Copy(obj);
8189 }
8190
8191 // Edit properties via a GUI
8192 bool wxRichTextBox::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer)
8193 {
8194 wxRichTextObjectPropertiesDialog boxDlg(this, wxGetTopLevelParent(parent), wxID_ANY, _("Box Properties"));
8195 boxDlg.SetAttributes(GetAttributes());
8196
8197 if (boxDlg.ShowModal() == wxID_OK)
8198 {
8199 // By passing wxRICHTEXT_SETSTYLE_RESET, indeterminate attributes set by the user will be set as
8200 // indeterminate in the object.
8201 boxDlg.ApplyStyle(buffer->GetRichTextCtrl(), wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_RESET);
8202 return true;
8203 }
8204 else
8205 return false;
8206 }
8207
8208 IMPLEMENT_DYNAMIC_CLASS(wxRichTextCell, wxRichTextBox)
8209
8210 wxRichTextCell::wxRichTextCell(wxRichTextObject* parent):
8211 wxRichTextBox(parent)
8212 {
8213 }
8214
8215 /// Draw the item
8216 bool wxRichTextCell::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
8217 {
8218 return wxRichTextBox::Draw(dc, context, range, selection, rect, descent, style);
8219 }
8220
8221 /// Copy
8222 void wxRichTextCell::Copy(const wxRichTextCell& obj)
8223 {
8224 wxRichTextBox::Copy(obj);
8225 }
8226
8227 // Edit properties via a GUI
8228 bool wxRichTextCell::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer)
8229 {
8230 // We need to gather common attributes for all selected cells.
8231
8232 wxRichTextTable* table = wxDynamicCast(GetParent(), wxRichTextTable);
8233 bool multipleCells = false;
8234 wxRichTextAttr attr;
8235
8236 if (table && buffer && buffer->GetRichTextCtrl() && buffer->GetRichTextCtrl()->GetSelection().IsValid() &&
8237 buffer->GetRichTextCtrl()->GetSelection().GetContainer() == GetParent())
8238 {
8239 wxRichTextAttr clashingAttr, absentAttr;
8240 const wxRichTextSelection& sel = buffer->GetRichTextCtrl()->GetSelection();
8241 size_t i;
8242 int selectedCellCount = 0;
8243 for (i = 0; i < sel.GetCount(); i++)
8244 {
8245 const wxRichTextRange& range = sel[i];
8246 wxRichTextCell* cell = table->GetCell(range.GetStart());
8247 if (cell)
8248 {
8249 wxRichTextAttr cellStyle = cell->GetAttributes();
8250
8251 CollectStyle(attr, cellStyle, clashingAttr, absentAttr);
8252
8253 selectedCellCount ++;
8254 }
8255 }
8256 multipleCells = selectedCellCount > 1;
8257 }
8258 else
8259 {
8260 attr = GetAttributes();
8261 }
8262
8263 wxString caption;
8264 if (multipleCells)
8265 caption = _("Multiple Cell Properties");
8266 else
8267 caption = _("Cell Properties");
8268
8269 wxRichTextObjectPropertiesDialog cellDlg(this, wxGetTopLevelParent(parent), wxID_ANY, caption);
8270 cellDlg.SetAttributes(attr);
8271
8272 wxRichTextSizePage* sizePage = wxDynamicCast(cellDlg.FindPage(CLASSINFO(wxRichTextSizePage)), wxRichTextSizePage);
8273 if (sizePage)
8274 {
8275 // We don't want position and floating controls for a cell.
8276 sizePage->ShowPositionControls(false);
8277 sizePage->ShowFloatingControls(false);
8278 }
8279
8280 if (cellDlg.ShowModal() == wxID_OK)
8281 {
8282 if (multipleCells)
8283 {
8284 const wxRichTextSelection& sel = buffer->GetRichTextCtrl()->GetSelection();
8285 // Apply the style; we interpret indeterminate attributes as 'don't touch this attribute'
8286 // since it may represent clashing attributes across multiple objects.
8287 table->SetCellStyle(sel, attr);
8288 }
8289 else
8290 // For a single object, indeterminate attributes set by the user should be reflected in the
8291 // actual object style, so pass the wxRICHTEXT_SETSTYLE_RESET flag to assign
8292 // the style directly instead of applying (which ignores indeterminate attributes,
8293 // leaving them as they were).
8294 cellDlg.ApplyStyle(buffer->GetRichTextCtrl(), wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_RESET);
8295 return true;
8296 }
8297 else
8298 return false;
8299 }
8300
8301 WX_DEFINE_OBJARRAY(wxRichTextObjectPtrArrayArray)
8302
8303 IMPLEMENT_DYNAMIC_CLASS(wxRichTextTable, wxRichTextBox)
8304
8305 wxRichTextTable::wxRichTextTable(wxRichTextObject* parent): wxRichTextBox(parent)
8306 {
8307 m_rowCount = 0;
8308 m_colCount = 0;
8309 }
8310
8311 // Draws the object.
8312 bool wxRichTextTable::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
8313 {
8314 return wxRichTextBox::Draw(dc, context, range, selection, rect, descent, style);
8315 }
8316
8317 WX_DECLARE_OBJARRAY(wxRect, wxRichTextRectArray);
8318 WX_DEFINE_OBJARRAY(wxRichTextRectArray);
8319
8320 // Lays the object out. rect is the space available for layout. Often it will
8321 // be the specified overall space for this object, if trying to constrain
8322 // layout to a particular size, or it could be the total space available in the
8323 // parent. rect is the overall size, so we must subtract margins and padding.
8324 // to get the actual available space.
8325 bool wxRichTextTable::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& WXUNUSED(parentRect), int style)
8326 {
8327 SetPosition(rect.GetPosition());
8328
8329 // TODO: the meaty bit. Calculate sizes of all cells and rows. Try to use
8330 // minimum size if within alloted size, then divide up remaining size
8331 // between rows/cols.
8332
8333 double scale = 1.0;
8334 wxRichTextBuffer* buffer = GetBuffer();
8335 if (buffer) scale = buffer->GetScale();
8336
8337 wxRect availableSpace = GetAvailableContentArea(dc, context, rect);
8338 wxTextAttrDimensionConverter converter(dc, scale, availableSpace.GetSize());
8339
8340 wxRichTextAttr attr(GetAttributes());
8341 context.ApplyVirtualAttributes(attr, this);
8342
8343 // If we have no fixed table size, and assuming we're not pushed for
8344 // space, then we don't have to try to stretch the table to fit the contents.
8345 bool stretchToFitTableWidth = false;
8346
8347 int tableWidth = rect.width;
8348 if (attr.GetTextBoxAttr().GetWidth().IsValid())
8349 {
8350 tableWidth = converter.GetPixels(attr.GetTextBoxAttr().GetWidth());
8351
8352 // Fixed table width, so we do want to stretch columns out if necessary.
8353 stretchToFitTableWidth = true;
8354
8355 // Shouldn't be able to exceed the size passed to this function
8356 tableWidth = wxMin(rect.width, tableWidth);
8357 }
8358
8359 // Get internal padding
8360 int paddingLeft = 0, paddingTop = 0;
8361 if (attr.GetTextBoxAttr().GetPadding().GetLeft().IsValid())
8362 paddingLeft = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetLeft());
8363 if (attr.GetTextBoxAttr().GetPadding().GetTop().IsValid())
8364 paddingTop = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetTop());
8365
8366 // Assume that left and top padding are also used for inter-cell padding.
8367 int paddingX = paddingLeft;
8368 int paddingY = paddingTop;
8369
8370 int totalLeftMargin = 0, totalRightMargin = 0, totalTopMargin = 0, totalBottomMargin = 0;
8371 GetTotalMargin(dc, buffer, attr, totalLeftMargin, totalRightMargin, totalTopMargin, totalBottomMargin);
8372
8373 // Internal table width - the area for content
8374 int internalTableWidth = tableWidth - totalLeftMargin - totalRightMargin;
8375
8376 int rowCount = m_cells.GetCount();
8377 if (m_colCount == 0 || rowCount == 0)
8378 {
8379 wxRect overallRect(rect.x, rect.y, totalLeftMargin + totalRightMargin, totalTopMargin + totalBottomMargin);
8380 SetCachedSize(overallRect.GetSize());
8381
8382 // Zero content size
8383 SetMinSize(overallRect.GetSize());
8384 SetMaxSize(GetMinSize());
8385 return true;
8386 }
8387
8388 // The final calculated widths
8389 wxArrayInt colWidths;
8390 colWidths.Add(0, m_colCount);
8391
8392 wxArrayInt absoluteColWidths;
8393 absoluteColWidths.Add(0, m_colCount);
8394 // wxArrayInt absoluteColWidthsSpanning(m_colCount);
8395 wxArrayInt percentageColWidths;
8396 percentageColWidths.Add(0, m_colCount);
8397 // wxArrayInt percentageColWidthsSpanning(m_colCount);
8398 // These are only relevant when the first column contains spanning information.
8399 // wxArrayInt columnSpans(m_colCount); // Each contains 1 for non-spanning cell, > 1 for spanning cell.
8400 wxArrayInt maxColWidths;
8401 maxColWidths.Add(0, m_colCount);
8402 wxArrayInt minColWidths;
8403 minColWidths.Add(0, m_colCount);
8404
8405 wxSize tableSize(tableWidth, 0);
8406
8407 int i, j, k;
8408
8409 for (i = 0; i < m_colCount; i++)
8410 {
8411 absoluteColWidths[i] = 0;
8412 // absoluteColWidthsSpanning[i] = 0;
8413 percentageColWidths[i] = -1;
8414 // percentageColWidthsSpanning[i] = -1;
8415 colWidths[i] = 0;
8416 maxColWidths[i] = 0;
8417 minColWidths[i] = 0;
8418 // columnSpans[i] = 1;
8419 }
8420
8421 // (0) Determine which cells are visible according to spans
8422 // 1 2 3 4 5
8423 // __________________
8424 // | | | | | 1
8425 // |------| |----|
8426 // |------| | | 2
8427 // |------| | | 3
8428 // |------------------|
8429 // |__________________| 4
8430
8431 // To calculate cell visibility:
8432 // First find all spanning cells. Build an array of span records with start x, y and end x, y.
8433 // Then for each cell, test whether we're within one of those cells, and unless we're at the start of
8434 // that cell, hide the cell.
8435
8436 // We can also use this array to match the size of spanning cells to the grid. Or just do
8437 // this when we iterate through all cells.
8438
8439 // 0.1: add spanning cells to an array
8440 wxRichTextRectArray rectArray;
8441 for (j = 0; j < m_rowCount; j++)
8442 {
8443 for (i = 0; i < m_colCount; i++)
8444 {
8445 wxRichTextBox* cell = GetCell(j, i);
8446 int colSpan = 1, rowSpan = 1;
8447 if (cell->GetProperties().HasProperty(wxT("colspan")))
8448 colSpan = cell->GetProperties().GetPropertyLong(wxT("colspan"));
8449 if (cell->GetProperties().HasProperty(wxT("rowspan")))
8450 rowSpan = cell->GetProperties().GetPropertyLong(wxT("rowspan"));
8451 if (colSpan > 1 || rowSpan > 1)
8452 {
8453 rectArray.Add(wxRect(i, j, colSpan, rowSpan));
8454 }
8455 }
8456 }
8457 // 0.2: find which cells are subsumed by a spanning cell
8458 for (j = 0; j < m_rowCount; j++)
8459 {
8460 for (i = 0; i < m_colCount; i++)
8461 {
8462 wxRichTextBox* cell = GetCell(j, i);
8463 if (rectArray.GetCount() == 0)
8464 {
8465 cell->Show(true);
8466 }
8467 else
8468 {
8469 int colSpan = 1, rowSpan = 1;
8470 if (cell->GetProperties().HasProperty(wxT("colspan")))
8471 colSpan = cell->GetProperties().GetPropertyLong(wxT("colspan"));
8472 if (cell->GetProperties().HasProperty(wxT("rowspan")))
8473 rowSpan = cell->GetProperties().GetPropertyLong(wxT("rowspan"));
8474 if (colSpan > 1 || rowSpan > 1)
8475 {
8476 // Assume all spanning cells are shown
8477 cell->Show(true);
8478 }
8479 else
8480 {
8481 bool shown = true;
8482 for (k = 0; k < (int) rectArray.GetCount(); k++)
8483 {
8484 if (rectArray[k].Contains(wxPoint(i, j)))
8485 {
8486 shown = false;
8487 break;
8488 }
8489 }
8490 cell->Show(shown);
8491 }
8492 }
8493 }
8494 }
8495
8496 // TODO: find the first spanned cell in each row that spans the most columns and doesn't
8497 // overlap with a spanned cell starting at a previous column position.
8498 // This means we need to keep an array of rects so we can check. However
8499 // it does also mean that some spans simply may not be taken into account
8500 // where there are different spans happening on different rows. In these cases,
8501 // they will simply be as wide as their constituent columns.
8502
8503 // (1) Do an initial layout for all cells to get minimum and maximum size, and get
8504 // the absolute or percentage width of each column.
8505
8506 for (j = 0; j < m_rowCount; j++)
8507 {
8508 // First get the overall margins so we can calculate percentage widths based on
8509 // the available content space for all cells on the row
8510
8511 int overallRowContentMargin = 0;
8512 int visibleCellCount = 0;
8513
8514 for (i = 0; i < m_colCount; i++)
8515 {
8516 wxRichTextBox* cell = GetCell(j, i);
8517 if (cell->IsShown())
8518 {
8519 int cellTotalLeftMargin = 0, cellTotalRightMargin = 0, cellTotalTopMargin = 0, cellTotalBottomMargin = 0;
8520 GetTotalMargin(dc, buffer, cell->GetAttributes(), cellTotalLeftMargin, cellTotalRightMargin, cellTotalTopMargin, cellTotalBottomMargin);
8521
8522 overallRowContentMargin += (cellTotalLeftMargin + cellTotalRightMargin);
8523 visibleCellCount ++;
8524 }
8525 }
8526
8527 // Add in inter-cell padding
8528 overallRowContentMargin += ((visibleCellCount-1) * paddingX);
8529
8530 int rowContentWidth = internalTableWidth - overallRowContentMargin;
8531 wxSize rowTableSize(rowContentWidth, 0);
8532 wxTextAttrDimensionConverter converter(dc, scale, rowTableSize);
8533
8534 for (i = 0; i < m_colCount; i++)
8535 {
8536 wxRichTextBox* cell = GetCell(j, i);
8537 if (cell->IsShown())
8538 {
8539 int colSpan = 1;
8540 if (cell->GetProperties().HasProperty(wxT("colspan")))
8541 colSpan = cell->GetProperties().GetPropertyLong(wxT("colspan"));
8542
8543 // Lay out cell to find min/max widths
8544 cell->Invalidate(wxRICHTEXT_ALL);
8545 cell->Layout(dc, context, availableSpace, availableSpace, style);
8546
8547 if (colSpan == 1)
8548 {
8549 int absoluteCellWidth = -1;
8550 int percentageCellWidth = -1;
8551
8552 // I think we need to calculate percentages from the internal table size,
8553 // minus the padding between cells which we'll need to calculate from the
8554 // (number of VISIBLE cells - 1)*paddingX. Then percentages that add up to 100%
8555 // will add up to 100%. In CSS, the width specifies the cell's content rect width,
8556 // so if we want to conform to that we'll need to add in the overall cell margins.
8557 // However, this will make it difficult to specify percentages that add up to
8558 // 100% and still fit within the table width.
8559 // Let's say two cells have 50% width. They have 10 pixels of overall margin each.
8560 // The table content rect is 500 pixels and the inter-cell padding is 20 pixels.
8561 // If we're using internal content size for the width, we would calculate the
8562 // the overall cell width for n cells as:
8563 // (500 - 20*(n-1) - overallCellMargin1 - overallCellMargin2 - ...) * percentage / 100
8564 // + thisOverallCellMargin
8565 // = 500 - 20 - 10 - 10) * 0.5 + 10 = 240 pixels overall cell width.
8566 // Adding this back, we get 240 + 240 + 20 = 500 pixels.
8567
8568 if (cell->GetAttributes().GetTextBoxAttr().GetWidth().IsValid())
8569 {
8570 int w = converter.GetPixels(cell->GetAttributes().GetTextBoxAttr().GetWidth());
8571 if (cell->GetAttributes().GetTextBoxAttr().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE)
8572 {
8573 percentageCellWidth = w;
8574 }
8575 else
8576 {
8577 absoluteCellWidth = w;
8578 }
8579 // Override absolute width with minimum width if necessary
8580 if (cell->GetMinSize().x > 0 && absoluteCellWidth !=1 && cell->GetMinSize().x > absoluteCellWidth)
8581 absoluteCellWidth = cell->GetMinSize().x;
8582 }
8583
8584 if (absoluteCellWidth != -1)
8585 {
8586 if (absoluteCellWidth > absoluteColWidths[i])
8587 absoluteColWidths[i] = absoluteCellWidth;
8588 }
8589
8590 if (percentageCellWidth != -1)
8591 {
8592 if (percentageCellWidth > percentageColWidths[i])
8593 percentageColWidths[i] = percentageCellWidth;
8594 }
8595
8596 if (colSpan == 1 && cell->GetMinSize().x && cell->GetMinSize().x > minColWidths[i])
8597 minColWidths[i] = cell->GetMinSize().x;
8598 if (colSpan == 1 && cell->GetMaxSize().x && cell->GetMaxSize().x > maxColWidths[i])
8599 maxColWidths[i] = cell->GetMaxSize().x;
8600 }
8601 }
8602 }
8603 }
8604
8605 // (2) Allocate initial column widths from minimum widths, absolute values and proportions
8606 // TODO: simply merge this into (1).
8607 for (i = 0; i < m_colCount; i++)
8608 {
8609 if (absoluteColWidths[i] > 0)
8610 {
8611 colWidths[i] = absoluteColWidths[i];
8612 }
8613 else if (percentageColWidths[i] > 0)
8614 {
8615 colWidths[i] = percentageColWidths[i];
8616
8617 // This is rubbish - we calculated the absolute widths from percentages, so
8618 // we can't do it again here.
8619 //colWidths[i] = (int) (double(percentageColWidths[i]) * double(tableWidth) / 100.0 + 0.5);
8620 }
8621 }
8622
8623 // (3) Process absolute or proportional widths of spanning columns,
8624 // now that we know what our fixed column widths are going to be.
8625 // Spanned cells will try to adjust columns so the span will fit.
8626 // Even existing fixed column widths can be expanded if necessary.
8627 // Actually, currently fixed columns widths aren't adjusted; instead,
8628 // the algorithm favours earlier rows and adjusts unspecified column widths
8629 // the first time only. After that, we can't know whether the column has been
8630 // specified explicitly or not. (We could make a note if necessary.)
8631 for (j = 0; j < m_rowCount; j++)
8632 {
8633 // First get the overall margins so we can calculate percentage widths based on
8634 // the available content space for all cells on the row
8635
8636 int overallRowContentMargin = 0;
8637 int visibleCellCount = 0;
8638
8639 for (i = 0; i < m_colCount; i++)
8640 {
8641 wxRichTextBox* cell = GetCell(j, i);
8642 if (cell->IsShown())
8643 {
8644 int cellTotalLeftMargin = 0, cellTotalRightMargin = 0, cellTotalTopMargin = 0, cellTotalBottomMargin = 0;
8645 GetTotalMargin(dc, buffer, cell->GetAttributes(), cellTotalLeftMargin, cellTotalRightMargin, cellTotalTopMargin, cellTotalBottomMargin);
8646
8647 overallRowContentMargin += (cellTotalLeftMargin + cellTotalRightMargin);
8648 visibleCellCount ++;
8649 }
8650 }
8651
8652 // Add in inter-cell padding
8653 overallRowContentMargin += ((visibleCellCount-1) * paddingX);
8654
8655 int rowContentWidth = internalTableWidth - overallRowContentMargin;
8656 wxSize rowTableSize(rowContentWidth, 0);
8657 wxTextAttrDimensionConverter converter(dc, scale, rowTableSize);
8658
8659 for (i = 0; i < m_colCount; i++)
8660 {
8661 wxRichTextBox* cell = GetCell(j, i);
8662 if (cell->IsShown())
8663 {
8664 int colSpan = 1;
8665 if (cell->GetProperties().HasProperty(wxT("colspan")))
8666 colSpan = cell->GetProperties().GetPropertyLong(wxT("colspan"));
8667
8668 if (colSpan > 1)
8669 {
8670 int spans = wxMin(colSpan, m_colCount - i);
8671 int cellWidth = 0;
8672 if (spans > 0)
8673 {
8674 if (cell->GetAttributes().GetTextBoxAttr().GetWidth().IsValid())
8675 {
8676 cellWidth = converter.GetPixels(cell->GetAttributes().GetTextBoxAttr().GetWidth());
8677 // Override absolute width with minimum width if necessary
8678 if (cell->GetMinSize().x > 0 && cellWidth !=1 && cell->GetMinSize().x > cellWidth)
8679 cellWidth = cell->GetMinSize().x;
8680 }
8681 else
8682 {
8683 // Do we want to do this? It's the only chance we get to
8684 // use the cell's min/max sizes, so we need to work out
8685 // how we're going to balance the unspecified spanning cell
8686 // width with the possibility more-constrained constituent cell widths.
8687 // Say there's a tiny bitmap giving it a max width of 10 pixels. We
8688 // don't want to constraint all the spanned columns to fit into this cell.
8689 // OK, let's say that if any of the constituent columns don't fit,
8690 // then we simply stop constraining the columns; instead, we'll just fit the spanning
8691 // cells to the columns later.
8692 cellWidth = cell->GetMinSize().x;
8693 if (cell->GetMaxSize().x > cellWidth)
8694 cellWidth = cell->GetMaxSize().x;
8695 }
8696
8697 // Subtract the padding between cells
8698 int spanningWidth = cellWidth;
8699 spanningWidth -= paddingX * (spans-1);
8700
8701 if (spanningWidth > 0)
8702 {
8703 // Now share the spanning width between columns within that span
8704 // TODO: take into account min widths of columns within the span
8705 int spanningWidthLeft = spanningWidth;
8706 int stretchColCount = 0;
8707 for (k = i; k < (i+spans); k++)
8708 {
8709 if (colWidths[k] > 0) // absolute or proportional width has been specified
8710 spanningWidthLeft -= colWidths[k];
8711 else
8712 stretchColCount ++;
8713 }
8714 // Now divide what's left between the remaining columns
8715 int colShare = 0;
8716 if (stretchColCount > 0)
8717 colShare = spanningWidthLeft / stretchColCount;
8718 int colShareRemainder = spanningWidthLeft - (colShare * stretchColCount);
8719
8720 // If fixed-width columns are currently too big, then we'll later
8721 // stretch the spanned cell to fit.
8722
8723 if (spanningWidthLeft > 0)
8724 {
8725 for (k = i; k < (i+spans); k++)
8726 {
8727 if (colWidths[k] <= 0) // absolute or proportional width has not been specified
8728 {
8729 int newWidth = colShare;
8730 if (k == (i+spans-1))
8731 newWidth += colShareRemainder; // ensure all pixels are filled
8732 colWidths[k] = newWidth;
8733 }
8734 }
8735 }
8736 }
8737 }
8738 }
8739 }
8740 }
8741 }
8742
8743 // (4) Next, share any remaining space out between columns that have not yet been calculated.
8744 // TODO: take into account min widths of columns within the span
8745 int tableWidthMinusPadding = internalTableWidth - (m_colCount-1)*paddingX;
8746 int widthLeft = tableWidthMinusPadding;
8747 int stretchColCount = 0;
8748 for (i = 0; i < m_colCount; i++)
8749 {
8750 // TODO: we need to take into account min widths.
8751 // Subtract min width from width left, then
8752 // add the colShare to the min width
8753 if (colWidths[i] > 0) // absolute or proportional width has been specified
8754 widthLeft -= colWidths[i];
8755 else
8756 {
8757 if (minColWidths[i] > 0)
8758 widthLeft -= minColWidths[i];
8759
8760 stretchColCount ++;
8761 }
8762 }
8763
8764 // Now divide what's left between the remaining columns
8765 int colShare = 0;
8766 if (stretchColCount > 0)
8767 colShare = widthLeft / stretchColCount;
8768 int colShareRemainder = widthLeft - (colShare * stretchColCount);
8769
8770 // Check we don't have enough space, in which case shrink all columns, overriding
8771 // any absolute/proportional widths
8772 // TODO: actually we would like to divide up the shrinkage according to size.
8773 // How do we calculate the proportions that will achieve this?
8774 // Could first choose an arbitrary value for stretching cells, and then calculate
8775 // factors to multiply each width by.
8776 // TODO: want to record this fact and pass to an iteration that tries e.g. min widths
8777 if (widthLeft < 0 || (stretchToFitTableWidth && (stretchColCount == 0)))
8778 {
8779 colShare = tableWidthMinusPadding / m_colCount;
8780 colShareRemainder = tableWidthMinusPadding - (colShare * m_colCount);
8781 for (i = 0; i < m_colCount; i++)
8782 {
8783 colWidths[i] = 0;
8784 minColWidths[i] = 0;
8785 }
8786 }
8787
8788 // We have to adjust the columns if either we need to shrink the
8789 // table to fit the parent/table width, or we explicitly set the
8790 // table width and need to stretch out the table.
8791 if (widthLeft < 0 || stretchToFitTableWidth)
8792 {
8793 for (i = 0; i < m_colCount; i++)
8794 {
8795 if (colWidths[i] <= 0) // absolute or proportional width has not been specified
8796 {
8797 if (minColWidths[i] > 0)
8798 colWidths[i] = minColWidths[i] + colShare;
8799 else
8800 colWidths[i] = colShare;
8801 if (i == (m_colCount-1))
8802 colWidths[i] += colShareRemainder; // ensure all pixels are filled
8803 }
8804 }
8805 }
8806
8807 // TODO: if spanned cells have no specified or max width, make them the
8808 // as big as the columns they span. Do this for all spanned cells in all
8809 // rows, of course. Size any spanned cells left over at the end - even if they
8810 // have width > 0, make sure they're limited to the appropriate column edge.
8811
8812
8813 /*
8814 Sort out confusion between content width
8815 and overall width later. For now, assume we specify overall width.
8816
8817 So, now we've laid out the table to fit into the given space
8818 and have used specified widths and minimum widths.
8819
8820 Now we need to consider how we will try to take maximum width into account.
8821
8822 */
8823
8824 // (??) TODO: take max width into account
8825
8826 // (6) Lay out all cells again with the current values
8827
8828 int maxRight = 0;
8829 int y = availableSpace.y;
8830 for (j = 0; j < m_rowCount; j++)
8831 {
8832 int x = availableSpace.x; // TODO: take into account centering etc.
8833 int maxCellHeight = 0;
8834 int maxSpecifiedCellHeight = 0;
8835
8836 wxArrayInt actualWidths;
8837 actualWidths.Add(0, m_colCount);
8838
8839 wxTextAttrDimensionConverter converter(dc, scale);
8840 for (i = 0; i < m_colCount; i++)
8841 {
8842 wxRichTextCell* cell = GetCell(j, i);
8843 if (cell->IsShown())
8844 {
8845 // Get max specified cell height
8846 // Don't handle percentages for height
8847 if (cell->GetAttributes().GetTextBoxAttr().GetHeight().IsValid() && cell->GetAttributes().GetTextBoxAttr().GetHeight().GetUnits() != wxTEXT_ATTR_UNITS_PERCENTAGE)
8848 {
8849 int h = converter.GetPixels(cell->GetAttributes().GetTextBoxAttr().GetHeight());
8850 if (h > maxSpecifiedCellHeight)
8851 maxSpecifiedCellHeight = h;
8852 }
8853
8854 if (colWidths[i] > 0) // absolute or proportional width has been specified
8855 {
8856 int colSpan = 1;
8857 if (cell->GetProperties().HasProperty(wxT("colspan")))
8858 colSpan = cell->GetProperties().GetPropertyLong(wxT("colspan"));
8859
8860 wxRect availableCellSpace;
8861
8862 // TODO: take into acount spans
8863 if (colSpan > 1)
8864 {
8865 // Calculate the size of this spanning cell from its constituent columns
8866 int xx = x;
8867 int spans = wxMin(colSpan, m_colCount - i);
8868 for (k = i; k < spans; k++)
8869 {
8870 if (k != i)
8871 xx += paddingX;
8872 xx += colWidths[k];
8873 }
8874 availableCellSpace = wxRect(x, y, xx, -1);
8875 }
8876 else
8877 availableCellSpace = wxRect(x, y, colWidths[i], -1);
8878
8879 // Store actual width so we can force cell to be the appropriate width on the final loop
8880 actualWidths[i] = availableCellSpace.GetWidth();
8881
8882 // Lay out cell
8883 cell->Invalidate(wxRICHTEXT_ALL);
8884 cell->Layout(dc, context, availableCellSpace, availableSpace, style);
8885
8886 // TODO: use GetCachedSize().x to compute 'natural' size
8887
8888 x += (availableCellSpace.GetWidth() + paddingX);
8889 if (cell->GetCachedSize().y > maxCellHeight)
8890 maxCellHeight = cell->GetCachedSize().y;
8891 }
8892 }
8893 }
8894
8895 maxCellHeight = wxMax(maxCellHeight, maxSpecifiedCellHeight);
8896
8897 for (i = 0; i < m_colCount; i++)
8898 {
8899 wxRichTextCell* cell = GetCell(j, i);
8900 if (cell->IsShown())
8901 {
8902 wxRect availableCellSpace = wxRect(cell->GetPosition(), wxSize(actualWidths[i], maxCellHeight));
8903 // Lay out cell with new height
8904 cell->Invalidate(wxRICHTEXT_ALL);
8905 cell->Layout(dc, context, availableCellSpace, availableSpace, style);
8906
8907 // Make sure the cell size really is the appropriate size,
8908 // not the calculated box size
8909 cell->SetCachedSize(wxSize(actualWidths[i], maxCellHeight));
8910
8911 maxRight = wxMax(maxRight, cell->GetPosition().x + cell->GetCachedSize().x);
8912 }
8913 }
8914
8915 y += maxCellHeight;
8916 if (j < (m_rowCount-1))
8917 y += paddingY;
8918 }
8919
8920 // We need to add back the margins etc.
8921 {
8922 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
8923 contentRect = wxRect(wxPoint(0, 0), wxSize(maxRight - availableSpace.x, y - availableSpace.y));
8924 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
8925 SetCachedSize(marginRect.GetSize());
8926 }
8927
8928 // TODO: calculate max size
8929 {
8930 SetMaxSize(GetCachedSize());
8931 }
8932
8933 // TODO: calculate min size
8934 {
8935 SetMinSize(GetCachedSize());
8936 }
8937
8938 // TODO: currently we use either a fixed table width or the parent's size.
8939 // We also want to be able to calculate the table width from its content,
8940 // whether using fixed column widths or cell content min/max width.
8941 // Probably need a boolean flag to say whether we need to stretch cells
8942 // to fit the table width, or to simply use min/max cell widths. The
8943 // trouble with this is that if cell widths are not specified, they
8944 // will be tiny; we could use arbitrary defaults but this seems unsatisfactory.
8945 // Anyway, ignoring that problem, we probably need to factor layout into a function
8946 // that can can calculate the maximum unconstrained layout in case table size is
8947 // not specified. Then LayoutToBestSize() can choose to use either parent size to
8948 // constrain Layout(), or the previously-calculated max size to constraint layout.
8949
8950 return true;
8951 }
8952
8953 // Finds the absolute position and row height for the given character position
8954 bool wxRichTextTable::FindPosition(wxDC& dc, wxRichTextDrawingContext& context, long index, wxPoint& pt, int* height, bool forceLineStart)
8955 {
8956 wxRichTextCell* child = GetCell(index+1);
8957 if (child)
8958 {
8959 // Find the position at the start of the child cell, since the table doesn't
8960 // have any caret position of its own.
8961 return child->FindPosition(dc, context, -1, pt, height, forceLineStart);
8962 }
8963 else
8964 return false;
8965 }
8966
8967 // Get the cell at the given character position (in the range of the table).
8968 wxRichTextCell* wxRichTextTable::GetCell(long pos) const
8969 {
8970 int row = 0, col = 0;
8971 if (GetCellRowColumnPosition(pos, row, col))
8972 {
8973 return GetCell(row, col);
8974 }
8975 else
8976 return NULL;
8977 }
8978
8979 // Get the row/column for a given character position
8980 bool wxRichTextTable::GetCellRowColumnPosition(long pos, int& row, int& col) const
8981 {
8982 if (m_colCount == 0 || m_rowCount == 0)
8983 return false;
8984
8985 row = (int) (pos / m_colCount);
8986 col = pos - (row * m_colCount);
8987
8988 wxASSERT(row < m_rowCount && col < m_colCount);
8989
8990 if (row < m_rowCount && col < m_colCount)
8991 return true;
8992 else
8993 return false;
8994 }
8995
8996 // Calculate range, taking row/cell ordering into account instead of relying
8997 // on list ordering.
8998 void wxRichTextTable::CalculateRange(long start, long& end)
8999 {
9000 long current = start;
9001 long lastEnd = current;
9002
9003 if (IsTopLevel())
9004 {
9005 current = 0;
9006 lastEnd = 0;
9007 }
9008
9009 int i, j;
9010 for (i = 0; i < m_rowCount; i++)
9011 {
9012 for (j = 0; j < m_colCount; j++)
9013 {
9014 wxRichTextCell* child = GetCell(i, j);
9015 if (child)
9016 {
9017 long childEnd = 0;
9018
9019 child->CalculateRange(current, childEnd);
9020
9021 lastEnd = childEnd;
9022 current = childEnd + 1;
9023 }
9024 }
9025 }
9026
9027 // A top-level object always has a range of size 1,
9028 // because its children don't count at this level.
9029 end = start;
9030 m_range.SetRange(start, start);
9031
9032 // An object with no children has zero length
9033 if (m_children.GetCount() == 0)
9034 lastEnd --;
9035 m_ownRange.SetRange(0, lastEnd);
9036 }
9037
9038 // Gets the range size.
9039 bool wxRichTextTable::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, wxPoint position, wxArrayInt* partialExtents) const
9040 {
9041 return wxRichTextBox::GetRangeSize(range, size, descent, dc, context, flags, position, partialExtents);
9042 }
9043
9044 // Deletes content in the given range.
9045 bool wxRichTextTable::DeleteRange(const wxRichTextRange& WXUNUSED(range))
9046 {
9047 // TODO: implement deletion of cells
9048 return true;
9049 }
9050
9051 // Gets any text in this object for the given range.
9052 wxString wxRichTextTable::GetTextForRange(const wxRichTextRange& range) const
9053 {
9054 return wxRichTextBox::GetTextForRange(range);
9055 }
9056
9057 // Copies this object.
9058 void wxRichTextTable::Copy(const wxRichTextTable& obj)
9059 {
9060 wxRichTextBox::Copy(obj);
9061
9062 ClearTable();
9063
9064 m_rowCount = obj.m_rowCount;
9065 m_colCount = obj.m_colCount;
9066
9067 m_cells.Add(wxRichTextObjectPtrArray(), m_rowCount);
9068
9069 int i, j;
9070 for (i = 0; i < m_rowCount; i++)
9071 {
9072 wxRichTextObjectPtrArray& colArray = m_cells[i];
9073 for (j = 0; j < m_colCount; j++)
9074 {
9075 wxRichTextCell* cell = wxDynamicCast(obj.GetCell(i, j)->Clone(), wxRichTextCell);
9076 AppendChild(cell);
9077
9078 colArray.Add(cell);
9079 }
9080 }
9081 }
9082
9083 void wxRichTextTable::ClearTable()
9084 {
9085 m_cells.Clear();
9086 DeleteChildren();
9087 }
9088
9089 bool wxRichTextTable::CreateTable(int rows, int cols)
9090 {
9091 ClearTable();
9092
9093 m_rowCount = rows;
9094 m_colCount = cols;
9095
9096 m_cells.Add(wxRichTextObjectPtrArray(), rows);
9097
9098 int i, j;
9099 for (i = 0; i < rows; i++)
9100 {
9101 wxRichTextObjectPtrArray& colArray = m_cells[i];
9102 for (j = 0; j < cols; j++)
9103 {
9104 wxRichTextCell* cell = new wxRichTextCell;
9105 AppendChild(cell);
9106 cell->AddParagraph(wxEmptyString);
9107
9108 colArray.Add(cell);
9109 }
9110 }
9111
9112 return true;
9113 }
9114
9115 wxRichTextCell* wxRichTextTable::GetCell(int row, int col) const
9116 {
9117 wxASSERT(row < m_rowCount);
9118 wxASSERT(col < m_colCount);
9119
9120 if (row < m_rowCount && col < m_colCount)
9121 {
9122 wxRichTextObjectPtrArray& colArray = m_cells[row];
9123 wxRichTextObject* obj = colArray[col];
9124 return wxDynamicCast(obj, wxRichTextCell);
9125 }
9126 else
9127 return NULL;
9128 }
9129
9130 // Returns a selection object specifying the selections between start and end character positions.
9131 // For example, a table would deduce what cells (of range length 1) are selected when dragging across the table.
9132 wxRichTextSelection wxRichTextTable::GetSelection(long start, long end) const
9133 {
9134 wxRichTextSelection selection;
9135 selection.SetContainer((wxRichTextTable*) this);
9136
9137 if (start > end)
9138 {
9139 long tmp = end;
9140 end = start;
9141 start = tmp;
9142 }
9143
9144 wxASSERT( start >= 0 && end < (m_colCount * m_rowCount));
9145
9146 if (end >= (m_colCount * m_rowCount))
9147 return selection;
9148
9149 // We need to find the rectangle of cells that is described by the rectangle
9150 // with start, end as the diagonal. Make sure we don't add cells that are
9151 // not currenty visible because they are overlapped by spanning cells.
9152 /*
9153 --------------------------
9154 | 0 | 1 | 2 | 3 | 4 |
9155 --------------------------
9156 | 5 | 6 | 7 | 8 | 9 |
9157 --------------------------
9158 | 10 | 11 | 12 | 13 | 14 |
9159 --------------------------
9160 | 15 | 16 | 17 | 18 | 19 |
9161 --------------------------
9162
9163 Let's say we select 6 -> 18.
9164
9165 Left and right edge cols of rectangle are 1 and 3 inclusive. Find least/greatest to find
9166 which is left and which is right.
9167
9168 Top and bottom edge rows are 1 and 3 inclusive. Again, find least/greatest to find top and bottom.
9169
9170 Now go through rows from 1 to 3 and only add cells that are (a) within above column range
9171 and (b) shown.
9172
9173
9174 */
9175
9176 int leftCol = start - m_colCount * int(start/m_colCount);
9177 int rightCol = end - m_colCount * int(end/m_colCount);
9178
9179 int topRow = int(start/m_colCount);
9180 int bottomRow = int(end/m_colCount);
9181
9182 if (leftCol > rightCol)
9183 {
9184 int tmp = rightCol;
9185 rightCol = leftCol;
9186 leftCol = tmp;
9187 }
9188
9189 if (topRow > bottomRow)
9190 {
9191 int tmp = bottomRow;
9192 bottomRow = topRow;
9193 topRow = tmp;
9194 }
9195
9196 int i, j;
9197 for (i = topRow; i <= bottomRow; i++)
9198 {
9199 for (j = leftCol; j <= rightCol; j++)
9200 {
9201 wxRichTextCell* cell = GetCell(i, j);
9202 if (cell && cell->IsShown())
9203 selection.Add(cell->GetRange());
9204 }
9205 }
9206
9207 return selection;
9208 }
9209
9210 // Sets the attributes for the cells specified by the selection.
9211 bool wxRichTextTable::SetCellStyle(const wxRichTextSelection& selection, const wxRichTextAttr& style, int flags)
9212 {
9213 if (selection.GetContainer() != this)
9214 return false;
9215
9216 wxRichTextBuffer* buffer = GetBuffer();
9217 bool haveControl = (buffer && buffer->GetRichTextCtrl() != NULL);
9218 bool withUndo = haveControl && ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0);
9219
9220 if (withUndo)
9221 buffer->BeginBatchUndo(_("Set Cell Style"));
9222
9223 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
9224 while (node)
9225 {
9226 wxRichTextCell* cell = wxDynamicCast(node->GetData(), wxRichTextCell);
9227 if (cell && selection.WithinSelection(cell->GetRange().GetStart()))
9228 SetStyle(cell, style, flags);
9229 node = node->GetNext();
9230 }
9231
9232 // Do action, or delay it until end of batch.
9233 if (withUndo)
9234 buffer->EndBatchUndo();
9235
9236 return true;
9237 }
9238
9239 bool wxRichTextTable::DeleteRows(int startRow, int noRows)
9240 {
9241 wxASSERT((startRow + noRows) < m_rowCount);
9242 if ((startRow + noRows) >= m_rowCount)
9243 return false;
9244
9245 int i, j;
9246 for (i = startRow; i < (startRow+noRows); i++)
9247 {
9248 wxRichTextObjectPtrArray& colArray = m_cells[startRow];
9249 for (j = 0; j < (int) colArray.GetCount(); j++)
9250 {
9251 wxRichTextObject* cell = colArray[j];
9252 RemoveChild(cell, true);
9253 }
9254
9255 // Keep deleting at the same position, since we move all
9256 // the others up
9257 m_cells.RemoveAt(startRow);
9258 }
9259
9260 m_rowCount = m_rowCount - noRows;
9261
9262 return true;
9263 }
9264
9265 bool wxRichTextTable::DeleteColumns(int startCol, int noCols)
9266 {
9267 wxASSERT((startCol + noCols) < m_colCount);
9268 if ((startCol + noCols) >= m_colCount)
9269 return false;
9270
9271 bool deleteRows = (noCols == m_colCount);
9272
9273 int i, j;
9274 for (i = 0; i < m_rowCount; i++)
9275 {
9276 wxRichTextObjectPtrArray& colArray = m_cells[deleteRows ? 0 : i];
9277 for (j = startCol; j < (startCol+noCols); j++)
9278 {
9279 wxRichTextObject* cell = colArray[j];
9280 RemoveChild(cell, true);
9281 }
9282
9283 if (deleteRows)
9284 m_cells.RemoveAt(0);
9285 }
9286
9287 if (deleteRows)
9288 m_rowCount = 0;
9289 m_colCount = m_colCount - noCols;
9290
9291 return true;
9292 }
9293
9294 bool wxRichTextTable::AddRows(int startRow, int noRows, const wxRichTextAttr& attr)
9295 {
9296 wxASSERT(startRow <= m_rowCount);
9297 if (startRow > m_rowCount)
9298 return false;
9299
9300 int i, j;
9301 for (i = 0; i < noRows; i++)
9302 {
9303 int idx;
9304 if (startRow == m_rowCount)
9305 {
9306 m_cells.Add(wxRichTextObjectPtrArray());
9307 idx = m_cells.GetCount() - 1;
9308 }
9309 else
9310 {
9311 m_cells.Insert(wxRichTextObjectPtrArray(), startRow+i);
9312 idx = startRow+i;
9313 }
9314
9315 wxRichTextObjectPtrArray& colArray = m_cells[idx];
9316 for (j = 0; j < m_colCount; j++)
9317 {
9318 wxRichTextCell* cell = new wxRichTextCell;
9319 cell->GetAttributes() = attr;
9320
9321 AppendChild(cell);
9322 colArray.Add(cell);
9323 }
9324 }
9325
9326 m_rowCount = m_rowCount + noRows;
9327 return true;
9328 }
9329
9330 bool wxRichTextTable::AddColumns(int startCol, int noCols, const wxRichTextAttr& attr)
9331 {
9332 wxASSERT(startCol <= m_colCount);
9333 if (startCol > m_colCount)
9334 return false;
9335
9336 int i, j;
9337 for (i = 0; i < m_rowCount; i++)
9338 {
9339 wxRichTextObjectPtrArray& colArray = m_cells[i];
9340 for (j = 0; j < noCols; j++)
9341 {
9342 wxRichTextCell* cell = new wxRichTextCell;
9343 cell->GetAttributes() = attr;
9344
9345 AppendChild(cell);
9346
9347 if (startCol == m_colCount)
9348 colArray.Add(cell);
9349 else
9350 colArray.Insert(cell, startCol+j);
9351 }
9352 }
9353
9354 m_colCount = m_colCount + noCols;
9355
9356 return true;
9357 }
9358
9359 // Edit properties via a GUI
9360 bool wxRichTextTable::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer)
9361 {
9362 wxRichTextObjectPropertiesDialog boxDlg(this, wxGetTopLevelParent(parent), wxID_ANY, _("Table Properties"));
9363 boxDlg.SetAttributes(GetAttributes());
9364
9365 if (boxDlg.ShowModal() == wxID_OK)
9366 {
9367 boxDlg.ApplyStyle(buffer->GetRichTextCtrl(), wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_RESET);
9368 return true;
9369 }
9370 else
9371 return false;
9372 }
9373
9374 /*
9375 * Module to initialise and clean up handlers
9376 */
9377
9378 class wxRichTextModule: public wxModule
9379 {
9380 DECLARE_DYNAMIC_CLASS(wxRichTextModule)
9381 public:
9382 wxRichTextModule() {}
9383 bool OnInit()
9384 {
9385 wxRichTextBuffer::SetRenderer(new wxRichTextStdRenderer);
9386 wxRichTextBuffer::InitStandardHandlers();
9387 wxRichTextParagraph::InitDefaultTabs();
9388 return true;
9389 }
9390 void OnExit()
9391 {
9392 wxRichTextBuffer::CleanUpHandlers();
9393 wxRichTextBuffer::CleanUpDrawingHandlers();
9394 wxRichTextDecimalToRoman(-1);
9395 wxRichTextParagraph::ClearDefaultTabs();
9396 wxRichTextCtrl::ClearAvailableFontNames();
9397 wxRichTextBuffer::SetRenderer(NULL);
9398 }
9399 };
9400
9401 IMPLEMENT_DYNAMIC_CLASS(wxRichTextModule, wxModule)
9402
9403
9404 // If the richtext lib is dynamically loaded after the app has already started
9405 // (such as from wxPython) then the built-in module system will not init this
9406 // module. Provide this function to do it manually.
9407 void wxRichTextModuleInit()
9408 {
9409 wxModule* module = new wxRichTextModule;
9410 module->Init();
9411 wxModule::RegisterModule(module);
9412 }
9413
9414
9415 /*!
9416 * Commands for undo/redo
9417 *
9418 */
9419
9420 wxRichTextCommand::wxRichTextCommand(const wxString& name, wxRichTextCommandId id, wxRichTextBuffer* buffer,
9421 wxRichTextParagraphLayoutBox* container, wxRichTextCtrl* ctrl, bool ignoreFirstTime): wxCommand(true, name)
9422 {
9423 /* wxRichTextAction* action = */ new wxRichTextAction(this, name, id, buffer, container, ctrl, ignoreFirstTime);
9424 }
9425
9426 wxRichTextCommand::wxRichTextCommand(const wxString& name): wxCommand(true, name)
9427 {
9428 }
9429
9430 wxRichTextCommand::~wxRichTextCommand()
9431 {
9432 ClearActions();
9433 }
9434
9435 void wxRichTextCommand::AddAction(wxRichTextAction* action)
9436 {
9437 if (!m_actions.Member(action))
9438 m_actions.Append(action);
9439 }
9440
9441 bool wxRichTextCommand::Do()
9442 {
9443 for (wxList::compatibility_iterator node = m_actions.GetFirst(); node; node = node->GetNext())
9444 {
9445 wxRichTextAction* action = (wxRichTextAction*) node->GetData();
9446 action->Do();
9447 }
9448
9449 return true;
9450 }
9451
9452 bool wxRichTextCommand::Undo()
9453 {
9454 for (wxList::compatibility_iterator node = m_actions.GetLast(); node; node = node->GetPrevious())
9455 {
9456 wxRichTextAction* action = (wxRichTextAction*) node->GetData();
9457 action->Undo();
9458 }
9459
9460 return true;
9461 }
9462
9463 void wxRichTextCommand::ClearActions()
9464 {
9465 WX_CLEAR_LIST(wxList, m_actions);
9466 }
9467
9468 /*!
9469 * Individual action
9470 *
9471 */
9472
9473 wxRichTextAction::wxRichTextAction(wxRichTextCommand* cmd, const wxString& name, wxRichTextCommandId id,
9474 wxRichTextBuffer* buffer, wxRichTextParagraphLayoutBox* container,
9475 wxRichTextCtrl* ctrl, bool ignoreFirstTime)
9476 {
9477 m_buffer = buffer;
9478 m_object = NULL;
9479 m_containerAddress.Create(buffer, container);
9480 m_ignoreThis = ignoreFirstTime;
9481 m_cmdId = id;
9482 m_position = -1;
9483 m_ctrl = ctrl;
9484 m_name = name;
9485 m_newParagraphs.SetDefaultStyle(buffer->GetDefaultStyle());
9486 m_newParagraphs.SetBasicStyle(buffer->GetBasicStyle());
9487 if (cmd)
9488 cmd->AddAction(this);
9489 }
9490
9491 wxRichTextAction::~wxRichTextAction()
9492 {
9493 if (m_object)
9494 delete m_object;
9495 }
9496
9497 // Returns the container that this action refers to, using the container address and top-level buffer.
9498 wxRichTextParagraphLayoutBox* wxRichTextAction::GetContainer() const
9499 {
9500 wxRichTextParagraphLayoutBox* container = wxDynamicCast(GetContainerAddress().GetObject(m_buffer), wxRichTextParagraphLayoutBox);
9501 return container;
9502 }
9503
9504
9505 void wxRichTextAction::CalculateRefreshOptimizations(wxArrayInt& optimizationLineCharPositions, wxArrayInt& optimizationLineYPositions)
9506 {
9507 // Store a list of line start character and y positions so we can figure out which area
9508 // we need to refresh
9509
9510 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
9511 wxRichTextParagraphLayoutBox* container = GetContainer();
9512 wxASSERT(container != NULL);
9513 if (!container)
9514 return;
9515
9516 // NOTE: we're assuming that the buffer is laid out correctly at this point.
9517 // If we had several actions, which only invalidate and leave layout until the
9518 // paint handler is called, then this might not be true. So we may need to switch
9519 // optimisation on only when we're simply adding text and not simultaneously
9520 // deleting a selection, for example. Or, we make sure the buffer is laid out correctly
9521 // first, but of course this means we'll be doing it twice.
9522 if (!m_buffer->IsDirty() && m_ctrl) // can only do optimisation if the buffer is already laid out correctly
9523 {
9524 wxSize clientSize = m_ctrl->GetClientSize();
9525 wxPoint firstVisiblePt = m_ctrl->GetFirstVisiblePoint();
9526 int lastY = firstVisiblePt.y + clientSize.y;
9527
9528 wxRichTextParagraph* para = container->GetParagraphAtPosition(GetRange().GetStart());
9529 wxRichTextObjectList::compatibility_iterator node = container->GetChildren().Find(para);
9530 while (node)
9531 {
9532 wxRichTextParagraph* child = (wxRichTextParagraph*) node->GetData();
9533 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
9534 while (node2)
9535 {
9536 wxRichTextLine* line = node2->GetData();
9537 wxPoint pt = line->GetAbsolutePosition();
9538 wxRichTextRange range = line->GetAbsoluteRange();
9539
9540 if (pt.y > lastY)
9541 {
9542 node2 = wxRichTextLineList::compatibility_iterator();
9543 node = wxRichTextObjectList::compatibility_iterator();
9544 }
9545 else if (range.GetStart() > GetPosition() && pt.y >= firstVisiblePt.y)
9546 {
9547 optimizationLineCharPositions.Add(range.GetStart());
9548 optimizationLineYPositions.Add(pt.y);
9549 }
9550
9551 if (node2)
9552 node2 = node2->GetNext();
9553 }
9554
9555 if (node)
9556 node = node->GetNext();
9557 }
9558 }
9559 #endif
9560 }
9561
9562 bool wxRichTextAction::Do()
9563 {
9564 m_buffer->Modify(true);
9565
9566 wxRichTextParagraphLayoutBox* container = GetContainer();
9567 wxASSERT(container != NULL);
9568 if (!container)
9569 return false;
9570
9571 switch (m_cmdId)
9572 {
9573 case wxRICHTEXT_INSERT:
9574 {
9575 // Store a list of line start character and y positions so we can figure out which area
9576 // we need to refresh
9577 wxArrayInt optimizationLineCharPositions;
9578 wxArrayInt optimizationLineYPositions;
9579
9580 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
9581 CalculateRefreshOptimizations(optimizationLineCharPositions, optimizationLineYPositions);
9582 #endif
9583
9584 container->InsertFragment(GetRange().GetStart(), m_newParagraphs);
9585 container->UpdateRanges();
9586
9587 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
9588 // Layout() would stop prematurely at the top level.
9589 container->InvalidateHierarchy(wxRichTextRange(wxMax(0, GetRange().GetStart()-1), GetRange().GetEnd()));
9590
9591 long newCaretPosition = GetPosition() + m_newParagraphs.GetOwnRange().GetLength();
9592
9593 // Character position to caret position
9594 newCaretPosition --;
9595
9596 // Don't take into account the last newline
9597 if (m_newParagraphs.GetPartialParagraph())
9598 newCaretPosition --;
9599 else
9600 if (m_newParagraphs.GetChildren().GetCount() > 1)
9601 {
9602 wxRichTextObject* p = (wxRichTextObject*) m_newParagraphs.GetChildren().GetLast()->GetData();
9603 if (p->GetRange().GetLength() == 1)
9604 newCaretPosition --;
9605 }
9606
9607 newCaretPosition = wxMin(newCaretPosition, (container->GetOwnRange().GetEnd()-1));
9608
9609 UpdateAppearance(newCaretPosition, true /* send update event */, & optimizationLineCharPositions, & optimizationLineYPositions, true /* do */);
9610
9611 wxRichTextEvent cmdEvent(
9612 wxEVT_COMMAND_RICHTEXT_CONTENT_INSERTED,
9613 m_ctrl ? m_ctrl->GetId() : -1);
9614 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
9615 cmdEvent.SetRange(GetRange());
9616 cmdEvent.SetPosition(GetRange().GetStart());
9617 cmdEvent.SetContainer(container);
9618
9619 m_buffer->SendEvent(cmdEvent);
9620
9621 break;
9622 }
9623 case wxRICHTEXT_DELETE:
9624 {
9625 wxArrayInt optimizationLineCharPositions;
9626 wxArrayInt optimizationLineYPositions;
9627
9628 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
9629 CalculateRefreshOptimizations(optimizationLineCharPositions, optimizationLineYPositions);
9630 #endif
9631
9632 container->DeleteRange(GetRange());
9633 container->UpdateRanges();
9634 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
9635 // Layout() would stop prematurely at the top level.
9636 container->InvalidateHierarchy(wxRichTextRange(GetRange().GetStart(), GetRange().GetStart()));
9637
9638 long caretPos = GetRange().GetStart()-1;
9639 if (caretPos >= container->GetOwnRange().GetEnd())
9640 caretPos --;
9641
9642 UpdateAppearance(caretPos, true /* send update event */, & optimizationLineCharPositions, & optimizationLineYPositions, true /* do */);
9643
9644 wxRichTextEvent cmdEvent(
9645 wxEVT_COMMAND_RICHTEXT_CONTENT_DELETED,
9646 m_ctrl ? m_ctrl->GetId() : -1);
9647 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
9648 cmdEvent.SetRange(GetRange());
9649 cmdEvent.SetPosition(GetRange().GetStart());
9650 cmdEvent.SetContainer(container);
9651
9652 m_buffer->SendEvent(cmdEvent);
9653
9654 break;
9655 }
9656 case wxRICHTEXT_CHANGE_STYLE:
9657 case wxRICHTEXT_CHANGE_PROPERTIES:
9658 {
9659 ApplyParagraphs(GetNewParagraphs());
9660
9661 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
9662 // Layout() would stop prematurely at the top level.
9663 container->InvalidateHierarchy(GetRange());
9664
9665 UpdateAppearance(GetPosition());
9666
9667 wxRichTextEvent cmdEvent(
9668 m_cmdId == wxRICHTEXT_CHANGE_STYLE ? wxEVT_COMMAND_RICHTEXT_STYLE_CHANGED : wxEVT_COMMAND_RICHTEXT_PROPERTIES_CHANGED,
9669 m_ctrl ? m_ctrl->GetId() : -1);
9670 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
9671 cmdEvent.SetRange(GetRange());
9672 cmdEvent.SetPosition(GetRange().GetStart());
9673 cmdEvent.SetContainer(container);
9674
9675 m_buffer->SendEvent(cmdEvent);
9676
9677 break;
9678 }
9679 case wxRICHTEXT_CHANGE_ATTRIBUTES:
9680 {
9681 wxRichTextObject* obj = m_objectAddress.GetObject(m_buffer); // container->GetChildAtPosition(GetRange().GetStart());
9682 if (obj)
9683 {
9684 wxRichTextAttr oldAttr = obj->GetAttributes();
9685 obj->GetAttributes() = m_attributes;
9686 m_attributes = oldAttr;
9687 }
9688
9689 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
9690 // Layout() would stop prematurely at the top level.
9691 container->InvalidateHierarchy(GetRange());
9692
9693 UpdateAppearance(GetPosition());
9694
9695 wxRichTextEvent cmdEvent(
9696 wxEVT_COMMAND_RICHTEXT_STYLE_CHANGED,
9697 m_ctrl ? m_ctrl->GetId() : -1);
9698 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
9699 cmdEvent.SetRange(GetRange());
9700 cmdEvent.SetPosition(GetRange().GetStart());
9701 cmdEvent.SetContainer(container);
9702
9703 m_buffer->SendEvent(cmdEvent);
9704
9705 break;
9706 }
9707 case wxRICHTEXT_CHANGE_OBJECT:
9708 {
9709 wxRichTextObject* obj = m_objectAddress.GetObject(m_buffer);
9710 // wxRichTextObject* obj = container->GetChildAtPosition(GetRange().GetStart());
9711 if (obj && m_object)
9712 {
9713 wxRichTextObjectList::compatibility_iterator node = container->GetChildren().Find(obj);
9714 if (node)
9715 {
9716 wxRichTextObject* obj = node->GetData();
9717 node->SetData(m_object);
9718 m_object = obj;
9719 }
9720 }
9721
9722 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
9723 // Layout() would stop prematurely at the top level.
9724 container->InvalidateHierarchy(GetRange());
9725
9726 UpdateAppearance(GetPosition());
9727
9728 // TODO: send new kind of modification event
9729
9730 break;
9731 }
9732 default:
9733 break;
9734 }
9735
9736 return true;
9737 }
9738
9739 bool wxRichTextAction::Undo()
9740 {
9741 m_buffer->Modify(true);
9742
9743 wxRichTextParagraphLayoutBox* container = GetContainer();
9744 wxASSERT(container != NULL);
9745 if (!container)
9746 return false;
9747
9748 switch (m_cmdId)
9749 {
9750 case wxRICHTEXT_INSERT:
9751 {
9752 wxArrayInt optimizationLineCharPositions;
9753 wxArrayInt optimizationLineYPositions;
9754
9755 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
9756 CalculateRefreshOptimizations(optimizationLineCharPositions, optimizationLineYPositions);
9757 #endif
9758
9759 container->DeleteRange(GetRange());
9760 container->UpdateRanges();
9761 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
9762 // Layout() would stop prematurely at the top level.
9763 container->InvalidateHierarchy(wxRichTextRange(GetRange().GetStart(), GetRange().GetStart()));
9764
9765 long newCaretPosition = GetPosition() - 1;
9766
9767 UpdateAppearance(newCaretPosition, true, /* send update event */ & optimizationLineCharPositions, & optimizationLineYPositions, false /* undo */);
9768
9769 wxRichTextEvent cmdEvent(
9770 wxEVT_COMMAND_RICHTEXT_CONTENT_DELETED,
9771 m_ctrl ? m_ctrl->GetId() : -1);
9772 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
9773 cmdEvent.SetRange(GetRange());
9774 cmdEvent.SetPosition(GetRange().GetStart());
9775 cmdEvent.SetContainer(container);
9776
9777 m_buffer->SendEvent(cmdEvent);
9778
9779 break;
9780 }
9781 case wxRICHTEXT_DELETE:
9782 {
9783 wxArrayInt optimizationLineCharPositions;
9784 wxArrayInt optimizationLineYPositions;
9785
9786 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
9787 CalculateRefreshOptimizations(optimizationLineCharPositions, optimizationLineYPositions);
9788 #endif
9789
9790 container->InsertFragment(GetRange().GetStart(), m_oldParagraphs);
9791 container->UpdateRanges();
9792 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
9793 // Layout() would stop prematurely at the top level.
9794 container->InvalidateHierarchy(GetRange());
9795
9796 UpdateAppearance(GetPosition(), true, /* send update event */ & optimizationLineCharPositions, & optimizationLineYPositions, false /* undo */);
9797
9798 wxRichTextEvent cmdEvent(
9799 wxEVT_COMMAND_RICHTEXT_CONTENT_INSERTED,
9800 m_ctrl ? m_ctrl->GetId() : -1);
9801 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
9802 cmdEvent.SetRange(GetRange());
9803 cmdEvent.SetPosition(GetRange().GetStart());
9804 cmdEvent.SetContainer(container);
9805
9806 m_buffer->SendEvent(cmdEvent);
9807
9808 break;
9809 }
9810 case wxRICHTEXT_CHANGE_STYLE:
9811 case wxRICHTEXT_CHANGE_PROPERTIES:
9812 {
9813 ApplyParagraphs(GetOldParagraphs());
9814 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
9815 // Layout() would stop prematurely at the top level.
9816 container->InvalidateHierarchy(GetRange());
9817
9818 UpdateAppearance(GetPosition());
9819
9820 wxRichTextEvent cmdEvent(
9821 m_cmdId == wxRICHTEXT_CHANGE_STYLE ? wxEVT_COMMAND_RICHTEXT_STYLE_CHANGED : wxEVT_COMMAND_RICHTEXT_PROPERTIES_CHANGED,
9822 m_ctrl ? m_ctrl->GetId() : -1);
9823 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
9824 cmdEvent.SetRange(GetRange());
9825 cmdEvent.SetPosition(GetRange().GetStart());
9826 cmdEvent.SetContainer(container);
9827
9828 m_buffer->SendEvent(cmdEvent);
9829
9830 break;
9831 }
9832 case wxRICHTEXT_CHANGE_ATTRIBUTES:
9833 case wxRICHTEXT_CHANGE_OBJECT:
9834 {
9835 return Do();
9836 }
9837 default:
9838 break;
9839 }
9840
9841 return true;
9842 }
9843
9844 /// Update the control appearance
9845 void wxRichTextAction::UpdateAppearance(long caretPosition, bool sendUpdateEvent, wxArrayInt* optimizationLineCharPositions, wxArrayInt* optimizationLineYPositions, bool isDoCmd)
9846 {
9847 wxRichTextParagraphLayoutBox* container = GetContainer();
9848 wxASSERT(container != NULL);
9849 if (!container)
9850 return;
9851
9852 if (m_ctrl)
9853 {
9854 m_ctrl->SetFocusObject(container);
9855 m_ctrl->SetCaretPosition(caretPosition);
9856
9857 if (!m_ctrl->IsFrozen())
9858 {
9859 wxRect containerRect = container->GetRect();
9860
9861 m_ctrl->LayoutContent();
9862
9863 // Refresh everything if there were floating objects or the container changed size
9864 // (we can't yet optimize in these cases, since more complex interaction with other content occurs)
9865 if (container->GetFloatingObjectCount() > 0 || (container->GetParent() && containerRect != container->GetRect()))
9866 {
9867 m_ctrl->Refresh(false);
9868 }
9869 else
9870
9871 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
9872 // Find refresh rectangle if we are in a position to optimise refresh
9873 if ((m_cmdId == wxRICHTEXT_INSERT || m_cmdId == wxRICHTEXT_DELETE) && optimizationLineCharPositions)
9874 {
9875 size_t i;
9876
9877 wxSize clientSize = m_ctrl->GetClientSize();
9878 wxPoint firstVisiblePt = m_ctrl->GetFirstVisiblePoint();
9879
9880 // Start/end positions
9881 int firstY = 0;
9882 int lastY = firstVisiblePt.y + clientSize.y;
9883
9884 bool foundEnd = false;
9885
9886 // position offset - how many characters were inserted
9887 int positionOffset = GetRange().GetLength();
9888
9889 // Determine whether this is Do or Undo, and adjust positionOffset accordingly
9890 if ((m_cmdId == wxRICHTEXT_DELETE && isDoCmd) || (m_cmdId == wxRICHTEXT_INSERT && !isDoCmd))
9891 positionOffset = - positionOffset;
9892
9893 // find the first line which is being drawn at the same position as it was
9894 // before. Since we're talking about a simple insertion, we can assume
9895 // that the rest of the window does not need to be redrawn.
9896
9897 wxRichTextParagraph* para = container->GetParagraphAtPosition(GetPosition());
9898 // Since we support floating layout, we should redraw the whole para instead of just
9899 // the first line touching the invalid range.
9900 if (para)
9901 {
9902 firstY = para->GetPosition().y;
9903 }
9904
9905 wxRichTextObjectList::compatibility_iterator node = container->GetChildren().Find(para);
9906 while (node)
9907 {
9908 wxRichTextParagraph* child = (wxRichTextParagraph*) node->GetData();
9909 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
9910 while (node2)
9911 {
9912 wxRichTextLine* line = node2->GetData();
9913 wxPoint pt = line->GetAbsolutePosition();
9914 wxRichTextRange range = line->GetAbsoluteRange();
9915
9916 // we want to find the first line that is in the same position
9917 // as before. This will mean we're at the end of the changed text.
9918
9919 if (pt.y > lastY) // going past the end of the window, no more info
9920 {
9921 node2 = wxRichTextLineList::compatibility_iterator();
9922 node = wxRichTextObjectList::compatibility_iterator();
9923 }
9924 // Detect last line in the buffer
9925 else if (!node2->GetNext() && para->GetRange().Contains(container->GetOwnRange().GetEnd()))
9926 {
9927 // If deleting text, make sure we refresh below as well as above
9928 if (positionOffset >= 0)
9929 {
9930 foundEnd = true;
9931 lastY = pt.y + line->GetSize().y;
9932 }
9933
9934 node2 = wxRichTextLineList::compatibility_iterator();
9935 node = wxRichTextObjectList::compatibility_iterator();
9936
9937 break;
9938 }
9939 else
9940 {
9941 // search for this line being at the same position as before
9942 for (i = 0; i < optimizationLineCharPositions->GetCount(); i++)
9943 {
9944 if (((*optimizationLineCharPositions)[i] + positionOffset == range.GetStart()) &&
9945 ((*optimizationLineYPositions)[i] == pt.y))
9946 {
9947 // Stop, we're now the same as we were
9948 foundEnd = true;
9949
9950 lastY = pt.y;
9951
9952 node2 = wxRichTextLineList::compatibility_iterator();
9953 node = wxRichTextObjectList::compatibility_iterator();
9954
9955 break;
9956 }
9957 }
9958 }
9959
9960 if (node2)
9961 node2 = node2->GetNext();
9962 }
9963
9964 if (node)
9965 node = node->GetNext();
9966 }
9967
9968 firstY = wxMax(firstVisiblePt.y, firstY);
9969 if (!foundEnd)
9970 lastY = firstVisiblePt.y + clientSize.y;
9971
9972 // Convert to device coordinates
9973 wxRect rect(m_ctrl->GetPhysicalPoint(wxPoint(firstVisiblePt.x, firstY)), wxSize(clientSize.x, lastY - firstY));
9974 m_ctrl->RefreshRect(rect);
9975 }
9976 else
9977 #endif
9978 m_ctrl->Refresh(false);
9979
9980 m_ctrl->PositionCaret();
9981
9982 // This causes styles to persist when doing programmatic
9983 // content creation except when Freeze/Thaw is used, so
9984 // disable this and check for the consequences.
9985 // m_ctrl->SetDefaultStyleToCursorStyle();
9986
9987 if (sendUpdateEvent)
9988 wxTextCtrl::SendTextUpdatedEvent(m_ctrl);
9989 }
9990 }
9991 }
9992
9993 /// Replace the buffer paragraphs with the new ones.
9994 void wxRichTextAction::ApplyParagraphs(const wxRichTextParagraphLayoutBox& fragment)
9995 {
9996 wxRichTextParagraphLayoutBox* container = GetContainer();
9997 wxASSERT(container != NULL);
9998 if (!container)
9999 return;
10000
10001 wxRichTextObjectList::compatibility_iterator node = fragment.GetChildren().GetFirst();
10002 while (node)
10003 {
10004 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
10005 wxASSERT (para != NULL);
10006
10007 // We'll replace the existing paragraph by finding the paragraph at this position,
10008 // delete its node data, and setting a copy as the new node data.
10009 // TODO: make more efficient by simply swapping old and new paragraph objects.
10010
10011 wxRichTextParagraph* existingPara = container->GetParagraphAtPosition(para->GetRange().GetStart());
10012 if (existingPara)
10013 {
10014 wxRichTextObjectList::compatibility_iterator bufferParaNode = container->GetChildren().Find(existingPara);
10015 if (bufferParaNode)
10016 {
10017 wxRichTextParagraph* newPara = new wxRichTextParagraph(*para);
10018 newPara->SetParent(container);
10019
10020 bufferParaNode->SetData(newPara);
10021
10022 delete existingPara;
10023 }
10024 }
10025
10026 node = node->GetNext();
10027 }
10028 }
10029
10030
10031 /*!
10032 * wxRichTextRange
10033 * This stores beginning and end positions for a range of data.
10034 */
10035
10036 WX_DEFINE_OBJARRAY(wxRichTextRangeArray);
10037
10038 /// Limit this range to be within 'range'
10039 bool wxRichTextRange::LimitTo(const wxRichTextRange& range)
10040 {
10041 if (m_start < range.m_start)
10042 m_start = range.m_start;
10043
10044 if (m_end > range.m_end)
10045 m_end = range.m_end;
10046
10047 return true;
10048 }
10049
10050 /*!
10051 * wxRichTextImage implementation
10052 * This object represents an image.
10053 */
10054
10055 IMPLEMENT_DYNAMIC_CLASS(wxRichTextImage, wxRichTextObject)
10056
10057 wxRichTextImage::wxRichTextImage(const wxImage& image, wxRichTextObject* parent, wxRichTextAttr* charStyle):
10058 wxRichTextObject(parent)
10059 {
10060 m_imageBlock.MakeImageBlockDefaultQuality(image, wxBITMAP_TYPE_PNG);
10061 if (charStyle)
10062 SetAttributes(*charStyle);
10063 }
10064
10065 wxRichTextImage::wxRichTextImage(const wxRichTextImageBlock& imageBlock, wxRichTextObject* parent, wxRichTextAttr* charStyle):
10066 wxRichTextObject(parent)
10067 {
10068 m_imageBlock = imageBlock;
10069 if (charStyle)
10070 SetAttributes(*charStyle);
10071 }
10072
10073 /// Create a cached image at the required size
10074 bool wxRichTextImage::LoadImageCache(wxDC& dc, bool resetCache)
10075 {
10076 if (resetCache || !m_imageCache.IsOk() /* || m_imageCache.GetWidth() != size.x || m_imageCache.GetHeight() != size.y */)
10077 {
10078 if (!m_imageBlock.IsOk())
10079 return false;
10080
10081 wxImage image;
10082 m_imageBlock.Load(image);
10083 if (!image.IsOk())
10084 return false;
10085
10086 int width = image.GetWidth();
10087 int height = image.GetHeight();
10088
10089 if (GetAttributes().GetTextBoxAttr().GetWidth().IsValid() && GetAttributes().GetTextBoxAttr().GetWidth().GetValue() > 0)
10090 {
10091 if (GetAttributes().GetTextBoxAttr().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
10092 width = ConvertTenthsMMToPixels(dc, GetAttributes().GetTextBoxAttr().GetWidth().GetValue());
10093 else
10094 width = GetAttributes().GetTextBoxAttr().GetWidth().GetValue();
10095 }
10096 if (GetAttributes().GetTextBoxAttr().GetHeight().IsValid() && GetAttributes().GetTextBoxAttr().GetHeight().GetValue() > 0)
10097 {
10098 if (GetAttributes().GetTextBoxAttr().GetHeight().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
10099 height = ConvertTenthsMMToPixels(dc, GetAttributes().GetTextBoxAttr().GetHeight().GetValue());
10100 else
10101 height = GetAttributes().GetTextBoxAttr().GetHeight().GetValue();
10102 }
10103
10104 if (image.GetWidth() == width && image.GetHeight() == height)
10105 m_imageCache = wxBitmap(image);
10106 else
10107 {
10108 // If the original width and height is small, e.g. 400 or below,
10109 // scale up and then down to improve image quality. This can make
10110 // a big difference, with not much performance hit.
10111 int upscaleThreshold = 400;
10112 wxImage img;
10113 if (image.GetWidth() <= upscaleThreshold || image.GetHeight() <= upscaleThreshold)
10114 {
10115 img = image.Scale(image.GetWidth()*2, image.GetHeight()*2);
10116 img.Rescale(width, height, wxIMAGE_QUALITY_HIGH);
10117 }
10118 else
10119 img = image.Scale(width, height, wxIMAGE_QUALITY_HIGH);
10120 m_imageCache = wxBitmap(img);
10121 }
10122 }
10123
10124 return m_imageCache.IsOk();
10125 }
10126
10127 /// Draw the item
10128 bool wxRichTextImage::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int WXUNUSED(descent), int WXUNUSED(style))
10129 {
10130 if (!IsShown())
10131 return true;
10132
10133 // Don't need cached size AFAIK
10134 // wxSize size = GetCachedSize();
10135 if (!LoadImageCache(dc))
10136 return false;
10137
10138 wxRichTextAttr attr(GetAttributes());
10139 context.ApplyVirtualAttributes(attr, this);
10140
10141 DrawBoxAttributes(dc, GetBuffer(), attr, wxRect(rect.GetPosition(), GetCachedSize()));
10142
10143 #if 0
10144 int y = rect.y + (rect.height - m_imageCache.GetHeight());
10145
10146 dc.DrawBitmap(m_imageCache, rect.x, y, true);
10147 #endif
10148
10149 wxSize imageSize(m_imageCache.GetWidth(), m_imageCache.GetHeight());
10150 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
10151 marginRect = rect; // outer rectangle, will calculate contentRect
10152 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
10153
10154 dc.DrawBitmap(m_imageCache, contentRect.x, contentRect.y, true);
10155
10156 if (selection.WithinSelection(range.GetStart(), this))
10157 {
10158 wxCheckSetBrush(dc, *wxBLACK_BRUSH);
10159 wxCheckSetPen(dc, *wxBLACK_PEN);
10160 dc.SetLogicalFunction(wxINVERT);
10161 dc.DrawRectangle(contentRect);
10162 dc.SetLogicalFunction(wxCOPY);
10163 }
10164
10165 return true;
10166 }
10167
10168 /// Lay the item out
10169 bool wxRichTextImage::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& WXUNUSED(parentRect), int WXUNUSED(style))
10170 {
10171 if (!LoadImageCache(dc))
10172 return false;
10173
10174 wxSize imageSize(m_imageCache.GetWidth(), m_imageCache.GetHeight());
10175 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
10176 contentRect = wxRect(wxPoint(0,0), imageSize);
10177
10178 wxRichTextAttr attr(GetAttributes());
10179 context.ApplyVirtualAttributes(attr, this);
10180
10181 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
10182
10183 wxSize overallSize = marginRect.GetSize();
10184
10185 SetCachedSize(overallSize);
10186 SetMaxSize(overallSize);
10187 SetMinSize(overallSize);
10188 SetPosition(rect.GetPosition());
10189
10190 return true;
10191 }
10192
10193 /// Get/set the object size for the given range. Returns false if the range
10194 /// is invalid for this object.
10195 bool wxRichTextImage::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& WXUNUSED(descent), wxDC& dc, wxRichTextDrawingContext& context, int WXUNUSED(flags), wxPoint WXUNUSED(position), wxArrayInt* partialExtents) const
10196 {
10197 if (!range.IsWithin(GetRange()))
10198 return false;
10199
10200 if (!((wxRichTextImage*)this)->LoadImageCache(dc))
10201 {
10202 size.x = 0; size.y = 0;
10203 if (partialExtents)
10204 partialExtents->Add(0);
10205 return false;
10206 }
10207
10208 wxRichTextAttr attr(GetAttributes());
10209 context.ApplyVirtualAttributes(attr, (wxRichTextObject*) this);
10210
10211 wxSize imageSize(m_imageCache.GetWidth(), m_imageCache.GetHeight());
10212 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
10213 contentRect = wxRect(wxPoint(0,0), imageSize);
10214 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
10215
10216 wxSize overallSize = marginRect.GetSize();
10217
10218 if (partialExtents)
10219 partialExtents->Add(overallSize.x);
10220
10221 size = overallSize;
10222
10223 return true;
10224 }
10225
10226 // Get the 'natural' size for an object. For an image, it would be the
10227 // image size.
10228 wxTextAttrSize wxRichTextImage::GetNaturalSize() const
10229 {
10230 wxTextAttrSize size;
10231 if (GetImageCache().IsOk())
10232 {
10233 size.SetWidth(GetImageCache().GetWidth(), wxTEXT_ATTR_UNITS_PIXELS);
10234 size.SetHeight(GetImageCache().GetHeight(), wxTEXT_ATTR_UNITS_PIXELS);
10235 }
10236 return size;
10237 }
10238
10239
10240 /// Copy
10241 void wxRichTextImage::Copy(const wxRichTextImage& obj)
10242 {
10243 wxRichTextObject::Copy(obj);
10244
10245 m_imageBlock = obj.m_imageBlock;
10246 }
10247
10248 /// Edit properties via a GUI
10249 bool wxRichTextImage::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer)
10250 {
10251 wxRichTextObjectPropertiesDialog imageDlg(this, wxGetTopLevelParent(parent), wxID_ANY, _("Picture Properties"));
10252 imageDlg.SetAttributes(GetAttributes());
10253
10254 if (imageDlg.ShowModal() == wxID_OK)
10255 {
10256 // By passing wxRICHTEXT_SETSTYLE_RESET, indeterminate attributes set by the user will be set as
10257 // indeterminate in the object.
10258 imageDlg.ApplyStyle(buffer->GetRichTextCtrl(), wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_RESET);
10259 return true;
10260 }
10261 else
10262 return false;
10263 }
10264
10265 /*!
10266 * Utilities
10267 *
10268 */
10269
10270 /// Compare two attribute objects
10271 bool wxTextAttrEq(const wxRichTextAttr& attr1, const wxRichTextAttr& attr2)
10272 {
10273 return (attr1 == attr2);
10274 }
10275
10276 // Partial equality test taking flags into account
10277 bool wxTextAttrEqPartial(const wxRichTextAttr& attr1, const wxRichTextAttr& attr2)
10278 {
10279 return attr1.EqPartial(attr2);
10280 }
10281
10282 /// Compare tabs
10283 bool wxRichTextTabsEq(const wxArrayInt& tabs1, const wxArrayInt& tabs2)
10284 {
10285 if (tabs1.GetCount() != tabs2.GetCount())
10286 return false;
10287
10288 size_t i;
10289 for (i = 0; i < tabs1.GetCount(); i++)
10290 {
10291 if (tabs1[i] != tabs2[i])
10292 return false;
10293 }
10294 return true;
10295 }
10296
10297 bool wxRichTextApplyStyle(wxRichTextAttr& destStyle, const wxRichTextAttr& style, wxRichTextAttr* compareWith)
10298 {
10299 return destStyle.Apply(style, compareWith);
10300 }
10301
10302 // Remove attributes
10303 bool wxRichTextRemoveStyle(wxRichTextAttr& destStyle, const wxRichTextAttr& style)
10304 {
10305 return destStyle.RemoveStyle(style);
10306 }
10307
10308 /// Combine two bitlists, specifying the bits of interest with separate flags.
10309 bool wxRichTextCombineBitlists(int& valueA, int valueB, int& flagsA, int flagsB)
10310 {
10311 return wxRichTextAttr::CombineBitlists(valueA, valueB, flagsA, flagsB);
10312 }
10313
10314 /// Compare two bitlists
10315 bool wxRichTextBitlistsEqPartial(int valueA, int valueB, int flags)
10316 {
10317 return wxRichTextAttr::BitlistsEqPartial(valueA, valueB, flags);
10318 }
10319
10320 /// Split into paragraph and character styles
10321 bool wxRichTextSplitParaCharStyles(const wxRichTextAttr& style, wxRichTextAttr& parStyle, wxRichTextAttr& charStyle)
10322 {
10323 return wxRichTextAttr::SplitParaCharStyles(style, parStyle, charStyle);
10324 }
10325
10326 /// Convert a decimal to Roman numerals
10327 wxString wxRichTextDecimalToRoman(long n)
10328 {
10329 static wxArrayInt decimalNumbers;
10330 static wxArrayString romanNumbers;
10331
10332 // Clean up arrays
10333 if (n == -1)
10334 {
10335 decimalNumbers.Clear();
10336 romanNumbers.Clear();
10337 return wxEmptyString;
10338 }
10339
10340 if (decimalNumbers.GetCount() == 0)
10341 {
10342 #define wxRichTextAddDecRom(n, r) decimalNumbers.Add(n); romanNumbers.Add(r);
10343
10344 wxRichTextAddDecRom(1000, wxT("M"));
10345 wxRichTextAddDecRom(900, wxT("CM"));
10346 wxRichTextAddDecRom(500, wxT("D"));
10347 wxRichTextAddDecRom(400, wxT("CD"));
10348 wxRichTextAddDecRom(100, wxT("C"));
10349 wxRichTextAddDecRom(90, wxT("XC"));
10350 wxRichTextAddDecRom(50, wxT("L"));
10351 wxRichTextAddDecRom(40, wxT("XL"));
10352 wxRichTextAddDecRom(10, wxT("X"));
10353 wxRichTextAddDecRom(9, wxT("IX"));
10354 wxRichTextAddDecRom(5, wxT("V"));
10355 wxRichTextAddDecRom(4, wxT("IV"));
10356 wxRichTextAddDecRom(1, wxT("I"));
10357 }
10358
10359 int i = 0;
10360 wxString roman;
10361
10362 while (n > 0 && i < 13)
10363 {
10364 if (n >= decimalNumbers[i])
10365 {
10366 n -= decimalNumbers[i];
10367 roman += romanNumbers[i];
10368 }
10369 else
10370 {
10371 i ++;
10372 }
10373 }
10374 if (roman.IsEmpty())
10375 roman = wxT("0");
10376 return roman;
10377 }
10378
10379 /*!
10380 * wxRichTextFileHandler
10381 * Base class for file handlers
10382 */
10383
10384 IMPLEMENT_CLASS(wxRichTextFileHandler, wxObject)
10385
10386 #if wxUSE_FFILE && wxUSE_STREAMS
10387 bool wxRichTextFileHandler::LoadFile(wxRichTextBuffer *buffer, const wxString& filename)
10388 {
10389 wxFFileInputStream stream(filename);
10390 if (stream.IsOk())
10391 return LoadFile(buffer, stream);
10392
10393 return false;
10394 }
10395
10396 bool wxRichTextFileHandler::SaveFile(wxRichTextBuffer *buffer, const wxString& filename)
10397 {
10398 wxFFileOutputStream stream(filename);
10399 if (stream.IsOk())
10400 return SaveFile(buffer, stream);
10401
10402 return false;
10403 }
10404 #endif // wxUSE_FFILE && wxUSE_STREAMS
10405
10406 /// Can we handle this filename (if using files)? By default, checks the extension.
10407 bool wxRichTextFileHandler::CanHandle(const wxString& filename) const
10408 {
10409 wxString path, file, ext;
10410 wxFileName::SplitPath(filename, & path, & file, & ext);
10411
10412 return (ext.Lower() == GetExtension());
10413 }
10414
10415 /*!
10416 * wxRichTextTextHandler
10417 * Plain text handler
10418 */
10419
10420 IMPLEMENT_CLASS(wxRichTextPlainTextHandler, wxRichTextFileHandler)
10421
10422 #if wxUSE_STREAMS
10423 bool wxRichTextPlainTextHandler::DoLoadFile(wxRichTextBuffer *buffer, wxInputStream& stream)
10424 {
10425 if (!stream.IsOk())
10426 return false;
10427
10428 wxString str;
10429 int lastCh = 0;
10430
10431 while (!stream.Eof())
10432 {
10433 int ch = stream.GetC();
10434
10435 if (!stream.Eof())
10436 {
10437 if (ch == 10 && lastCh != 13)
10438 str += wxT('\n');
10439
10440 if (ch > 0 && ch != 10)
10441 str += wxChar(ch);
10442
10443 lastCh = ch;
10444 }
10445 }
10446
10447 buffer->ResetAndClearCommands();
10448 buffer->Clear();
10449 buffer->AddParagraphs(str);
10450 buffer->UpdateRanges();
10451
10452 return true;
10453 }
10454
10455 bool wxRichTextPlainTextHandler::DoSaveFile(wxRichTextBuffer *buffer, wxOutputStream& stream)
10456 {
10457 if (!stream.IsOk())
10458 return false;
10459
10460 wxString text = buffer->GetText();
10461
10462 wxString newLine = wxRichTextLineBreakChar;
10463 text.Replace(newLine, wxT("\n"));
10464
10465 wxCharBuffer buf = text.ToAscii();
10466
10467 stream.Write((const char*) buf, text.length());
10468 return true;
10469 }
10470 #endif // wxUSE_STREAMS
10471
10472 /*
10473 * Stores information about an image, in binary in-memory form
10474 */
10475
10476 wxRichTextImageBlock::wxRichTextImageBlock()
10477 {
10478 Init();
10479 }
10480
10481 wxRichTextImageBlock::wxRichTextImageBlock(const wxRichTextImageBlock& block):wxObject()
10482 {
10483 Init();
10484 Copy(block);
10485 }
10486
10487 wxRichTextImageBlock::~wxRichTextImageBlock()
10488 {
10489 wxDELETEA(m_data);
10490 }
10491
10492 void wxRichTextImageBlock::Init()
10493 {
10494 m_data = NULL;
10495 m_dataSize = 0;
10496 m_imageType = wxBITMAP_TYPE_INVALID;
10497 }
10498
10499 void wxRichTextImageBlock::Clear()
10500 {
10501 wxDELETEA(m_data);
10502 m_dataSize = 0;
10503 m_imageType = wxBITMAP_TYPE_INVALID;
10504 }
10505
10506
10507 // Load the original image into a memory block.
10508 // If the image is not a JPEG, we must convert it into a JPEG
10509 // to conserve space.
10510 // If it's not a JPEG we can make use of 'image', already scaled, so we don't have to
10511 // load the image a 2nd time.
10512
10513 bool wxRichTextImageBlock::MakeImageBlock(const wxString& filename, wxBitmapType imageType,
10514 wxImage& image, bool convertToJPEG)
10515 {
10516 m_imageType = imageType;
10517
10518 wxString filenameToRead(filename);
10519 bool removeFile = false;
10520
10521 if (imageType == wxBITMAP_TYPE_INVALID)
10522 return false; // Could not determine image type
10523
10524 if ((imageType != wxBITMAP_TYPE_JPEG) && convertToJPEG)
10525 {
10526 wxString tempFile =
10527 wxFileName::CreateTempFileName(_("image"));
10528
10529 wxASSERT(!tempFile.IsEmpty());
10530
10531 image.SaveFile(tempFile, wxBITMAP_TYPE_JPEG);
10532 filenameToRead = tempFile;
10533 removeFile = true;
10534
10535 m_imageType = wxBITMAP_TYPE_JPEG;
10536 }
10537 wxFile file;
10538 if (!file.Open(filenameToRead))
10539 return false;
10540
10541 m_dataSize = (size_t) file.Length();
10542 file.Close();
10543
10544 if (m_data)
10545 delete[] m_data;
10546 m_data = ReadBlock(filenameToRead, m_dataSize);
10547
10548 if (removeFile)
10549 wxRemoveFile(filenameToRead);
10550
10551 return (m_data != NULL);
10552 }
10553
10554 // Make an image block from the wxImage in the given
10555 // format.
10556 bool wxRichTextImageBlock::MakeImageBlock(wxImage& image, wxBitmapType imageType, int quality)
10557 {
10558 image.SetOption(wxT("quality"), quality);
10559
10560 if (imageType == wxBITMAP_TYPE_INVALID)
10561 return false; // Could not determine image type
10562
10563 return DoMakeImageBlock(image, imageType);
10564 }
10565
10566 // Uses a const wxImage for efficiency, but can't set quality (only relevant for JPEG)
10567 bool wxRichTextImageBlock::MakeImageBlockDefaultQuality(const wxImage& image, wxBitmapType imageType)
10568 {
10569 if (imageType == wxBITMAP_TYPE_INVALID)
10570 return false; // Could not determine image type
10571
10572 return DoMakeImageBlock(image, imageType);
10573 }
10574
10575 // Makes the image block
10576 bool wxRichTextImageBlock::DoMakeImageBlock(const wxImage& image, wxBitmapType imageType)
10577 {
10578 wxMemoryOutputStream memStream;
10579 if (!image.SaveFile(memStream, imageType))
10580 {
10581 return false;
10582 }
10583
10584 unsigned char* block = new unsigned char[memStream.GetSize()];
10585 if (!block)
10586 return false;
10587
10588 if (m_data)
10589 delete[] m_data;
10590 m_data = block;
10591
10592 m_imageType = imageType;
10593 m_dataSize = memStream.GetSize();
10594
10595 memStream.CopyTo(m_data, m_dataSize);
10596
10597 return (m_data != NULL);
10598 }
10599
10600 // Write to a file
10601 bool wxRichTextImageBlock::Write(const wxString& filename)
10602 {
10603 return WriteBlock(filename, m_data, m_dataSize);
10604 }
10605
10606 void wxRichTextImageBlock::Copy(const wxRichTextImageBlock& block)
10607 {
10608 m_imageType = block.m_imageType;
10609 wxDELETEA(m_data);
10610 m_dataSize = block.m_dataSize;
10611 if (m_dataSize == 0)
10612 return;
10613
10614 m_data = new unsigned char[m_dataSize];
10615 unsigned int i;
10616 for (i = 0; i < m_dataSize; i++)
10617 m_data[i] = block.m_data[i];
10618 }
10619
10620 //// Operators
10621 void wxRichTextImageBlock::operator=(const wxRichTextImageBlock& block)
10622 {
10623 Copy(block);
10624 }
10625
10626 // Load a wxImage from the block
10627 bool wxRichTextImageBlock::Load(wxImage& image)
10628 {
10629 if (!m_data)
10630 return false;
10631
10632 // Read in the image.
10633 #if wxUSE_STREAMS
10634 wxMemoryInputStream mstream(m_data, m_dataSize);
10635 bool success = image.LoadFile(mstream, GetImageType());
10636 #else
10637 wxString tempFile = wxFileName::CreateTempFileName(_("image"));
10638 wxASSERT(!tempFile.IsEmpty());
10639
10640 if (!WriteBlock(tempFile, m_data, m_dataSize))
10641 {
10642 return false;
10643 }
10644 success = image.LoadFile(tempFile, GetImageType());
10645 wxRemoveFile(tempFile);
10646 #endif
10647
10648 return success;
10649 }
10650
10651 // Write data in hex to a stream
10652 bool wxRichTextImageBlock::WriteHex(wxOutputStream& stream)
10653 {
10654 if (m_dataSize == 0)
10655 return true;
10656
10657 int bufSize = 100000;
10658 if (int(2*m_dataSize) < bufSize)
10659 bufSize = 2*m_dataSize;
10660 char* buf = new char[bufSize+1];
10661
10662 int left = m_dataSize;
10663 int n, i, j;
10664 j = 0;
10665 while (left > 0)
10666 {
10667 if (left*2 > bufSize)
10668 {
10669 n = bufSize; left -= (bufSize/2);
10670 }
10671 else
10672 {
10673 n = left*2; left = 0;
10674 }
10675
10676 char* b = buf;
10677 for (i = 0; i < (n/2); i++)
10678 {
10679 wxDecToHex(m_data[j], b, b+1);
10680 b += 2; j ++;
10681 }
10682
10683 buf[n] = 0;
10684 stream.Write((const char*) buf, n);
10685 }
10686 delete[] buf;
10687 return true;
10688 }
10689
10690 // Read data in hex from a stream
10691 bool wxRichTextImageBlock::ReadHex(wxInputStream& stream, int length, wxBitmapType imageType)
10692 {
10693 int dataSize = length/2;
10694
10695 if (m_data)
10696 delete[] m_data;
10697
10698 // create a null terminated temporary string:
10699 char str[3];
10700 str[2] = '\0';
10701
10702 m_data = new unsigned char[dataSize];
10703 int i;
10704 for (i = 0; i < dataSize; i ++)
10705 {
10706 str[0] = (char)stream.GetC();
10707 str[1] = (char)stream.GetC();
10708
10709 m_data[i] = (unsigned char)wxHexToDec(str);
10710 }
10711
10712 m_dataSize = dataSize;
10713 m_imageType = imageType;
10714
10715 return true;
10716 }
10717
10718 // Allocate and read from stream as a block of memory
10719 unsigned char* wxRichTextImageBlock::ReadBlock(wxInputStream& stream, size_t size)
10720 {
10721 unsigned char* block = new unsigned char[size];
10722 if (!block)
10723 return NULL;
10724
10725 stream.Read(block, size);
10726
10727 return block;
10728 }
10729
10730 unsigned char* wxRichTextImageBlock::ReadBlock(const wxString& filename, size_t size)
10731 {
10732 wxFileInputStream stream(filename);
10733 if (!stream.IsOk())
10734 return NULL;
10735
10736 return ReadBlock(stream, size);
10737 }
10738
10739 // Write memory block to stream
10740 bool wxRichTextImageBlock::WriteBlock(wxOutputStream& stream, unsigned char* block, size_t size)
10741 {
10742 stream.Write((void*) block, size);
10743 return stream.IsOk();
10744
10745 }
10746
10747 // Write memory block to file
10748 bool wxRichTextImageBlock::WriteBlock(const wxString& filename, unsigned char* block, size_t size)
10749 {
10750 wxFileOutputStream outStream(filename);
10751 if (!outStream.IsOk())
10752 return false;
10753
10754 return WriteBlock(outStream, block, size);
10755 }
10756
10757 // Gets the extension for the block's type
10758 wxString wxRichTextImageBlock::GetExtension() const
10759 {
10760 wxImageHandler* handler = wxImage::FindHandler(GetImageType());
10761 if (handler)
10762 return handler->GetExtension();
10763 else
10764 return wxEmptyString;
10765 }
10766
10767 #if wxUSE_DATAOBJ
10768
10769 /*!
10770 * The data object for a wxRichTextBuffer
10771 */
10772
10773 const wxChar *wxRichTextBufferDataObject::ms_richTextBufferFormatId = wxT("wxShape");
10774
10775 wxRichTextBufferDataObject::wxRichTextBufferDataObject(wxRichTextBuffer* richTextBuffer)
10776 {
10777 m_richTextBuffer = richTextBuffer;
10778
10779 // this string should uniquely identify our format, but is otherwise
10780 // arbitrary
10781 m_formatRichTextBuffer.SetId(GetRichTextBufferFormatId());
10782
10783 SetFormat(m_formatRichTextBuffer);
10784 }
10785
10786 wxRichTextBufferDataObject::~wxRichTextBufferDataObject()
10787 {
10788 delete m_richTextBuffer;
10789 }
10790
10791 // after a call to this function, the richTextBuffer is owned by the caller and it
10792 // is responsible for deleting it!
10793 wxRichTextBuffer* wxRichTextBufferDataObject::GetRichTextBuffer()
10794 {
10795 wxRichTextBuffer* richTextBuffer = m_richTextBuffer;
10796 m_richTextBuffer = NULL;
10797
10798 return richTextBuffer;
10799 }
10800
10801 wxDataFormat wxRichTextBufferDataObject::GetPreferredFormat(Direction WXUNUSED(dir)) const
10802 {
10803 return m_formatRichTextBuffer;
10804 }
10805
10806 size_t wxRichTextBufferDataObject::GetDataSize() const
10807 {
10808 if (!m_richTextBuffer)
10809 return 0;
10810
10811 wxString bufXML;
10812
10813 {
10814 wxStringOutputStream stream(& bufXML);
10815 if (!m_richTextBuffer->SaveFile(stream, wxRICHTEXT_TYPE_XML))
10816 {
10817 wxLogError(wxT("Could not write the buffer to an XML stream.\nYou may have forgotten to add the XML file handler."));
10818 return 0;
10819 }
10820 }
10821
10822 #if wxUSE_UNICODE
10823 wxCharBuffer buffer = bufXML.mb_str(wxConvUTF8);
10824 return strlen(buffer) + 1;
10825 #else
10826 return bufXML.Length()+1;
10827 #endif
10828 }
10829
10830 bool wxRichTextBufferDataObject::GetDataHere(void *pBuf) const
10831 {
10832 if (!pBuf || !m_richTextBuffer)
10833 return false;
10834
10835 wxString bufXML;
10836
10837 {
10838 wxStringOutputStream stream(& bufXML);
10839 if (!m_richTextBuffer->SaveFile(stream, wxRICHTEXT_TYPE_XML))
10840 {
10841 wxLogError(wxT("Could not write the buffer to an XML stream.\nYou may have forgotten to add the XML file handler."));
10842 return 0;
10843 }
10844 }
10845
10846 #if wxUSE_UNICODE
10847 wxCharBuffer buffer = bufXML.mb_str(wxConvUTF8);
10848 size_t len = strlen(buffer);
10849 memcpy((char*) pBuf, (const char*) buffer, len);
10850 ((char*) pBuf)[len] = 0;
10851 #else
10852 size_t len = bufXML.Length();
10853 memcpy((char*) pBuf, (const char*) bufXML.c_str(), len);
10854 ((char*) pBuf)[len] = 0;
10855 #endif
10856
10857 return true;
10858 }
10859
10860 bool wxRichTextBufferDataObject::SetData(size_t WXUNUSED(len), const void *buf)
10861 {
10862 wxDELETE(m_richTextBuffer);
10863
10864 wxString bufXML((const char*) buf, wxConvUTF8);
10865
10866 m_richTextBuffer = new wxRichTextBuffer;
10867
10868 wxStringInputStream stream(bufXML);
10869 if (!m_richTextBuffer->LoadFile(stream, wxRICHTEXT_TYPE_XML))
10870 {
10871 wxLogError(wxT("Could not read the buffer from an XML stream.\nYou may have forgotten to add the XML file handler."));
10872
10873 wxDELETE(m_richTextBuffer);
10874
10875 return false;
10876 }
10877 return true;
10878 }
10879
10880 #endif
10881 // wxUSE_DATAOBJ
10882
10883
10884 /*
10885 * wxRichTextFontTable
10886 * Manages quick access to a pool of fonts for rendering rich text
10887 */
10888
10889 WX_DECLARE_STRING_HASH_MAP_WITH_DECL(wxFont, wxRichTextFontTableHashMap, class WXDLLIMPEXP_RICHTEXT);
10890
10891 class wxRichTextFontTableData: public wxObjectRefData
10892 {
10893 public:
10894 wxRichTextFontTableData() {}
10895
10896 wxFont FindFont(const wxRichTextAttr& fontSpec);
10897
10898 wxRichTextFontTableHashMap m_hashMap;
10899 };
10900
10901 wxFont wxRichTextFontTableData::FindFont(const wxRichTextAttr& fontSpec)
10902 {
10903 wxString facename(fontSpec.GetFontFaceName());
10904 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()));
10905 wxRichTextFontTableHashMap::iterator entry = m_hashMap.find(spec);
10906
10907 if ( entry == m_hashMap.end() )
10908 {
10909 wxFont font(fontSpec.GetFontSize(), wxDEFAULT, fontSpec.GetFontStyle(), fontSpec.GetFontWeight(), fontSpec.GetFontUnderlined(), facename.c_str());
10910 m_hashMap[spec] = font;
10911 return font;
10912 }
10913 else
10914 {
10915 return entry->second;
10916 }
10917 }
10918
10919 IMPLEMENT_DYNAMIC_CLASS(wxRichTextFontTable, wxObject)
10920
10921 wxRichTextFontTable::wxRichTextFontTable()
10922 {
10923 m_refData = new wxRichTextFontTableData;
10924 }
10925
10926 wxRichTextFontTable::wxRichTextFontTable(const wxRichTextFontTable& table)
10927 : wxObject()
10928 {
10929 (*this) = table;
10930 }
10931
10932 wxRichTextFontTable::~wxRichTextFontTable()
10933 {
10934 UnRef();
10935 }
10936
10937 bool wxRichTextFontTable::operator == (const wxRichTextFontTable& table) const
10938 {
10939 return (m_refData == table.m_refData);
10940 }
10941
10942 void wxRichTextFontTable::operator= (const wxRichTextFontTable& table)
10943 {
10944 Ref(table);
10945 }
10946
10947 wxFont wxRichTextFontTable::FindFont(const wxRichTextAttr& fontSpec)
10948 {
10949 wxRichTextFontTableData* data = (wxRichTextFontTableData*) m_refData;
10950 if (data)
10951 return data->FindFont(fontSpec);
10952 else
10953 return wxFont();
10954 }
10955
10956 void wxRichTextFontTable::Clear()
10957 {
10958 wxRichTextFontTableData* data = (wxRichTextFontTableData*) m_refData;
10959 if (data)
10960 data->m_hashMap.clear();
10961 }
10962
10963 // wxTextBoxAttr
10964
10965
10966 void wxTextBoxAttr::Reset()
10967 {
10968 m_flags = 0;
10969 m_floatMode = wxTEXT_BOX_ATTR_FLOAT_NONE;
10970 m_clearMode = wxTEXT_BOX_ATTR_CLEAR_NONE;
10971 m_collapseMode = wxTEXT_BOX_ATTR_COLLAPSE_NONE;
10972 m_verticalAlignment = wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT_NONE;
10973 m_boxStyleName = wxEmptyString;
10974
10975 m_margins.Reset();
10976 m_padding.Reset();
10977 m_position.Reset();
10978
10979 m_size.Reset();
10980 m_minSize.Reset();
10981 m_maxSize.Reset();
10982
10983 m_border.Reset();
10984 m_outline.Reset();
10985 }
10986
10987 // Equality test
10988 bool wxTextBoxAttr::operator== (const wxTextBoxAttr& attr) const
10989 {
10990 return (
10991 m_flags == attr.m_flags &&
10992 m_floatMode == attr.m_floatMode &&
10993 m_clearMode == attr.m_clearMode &&
10994 m_collapseMode == attr.m_collapseMode &&
10995 m_verticalAlignment == attr.m_verticalAlignment &&
10996
10997 m_margins == attr.m_margins &&
10998 m_padding == attr.m_padding &&
10999 m_position == attr.m_position &&
11000
11001 m_size == attr.m_size &&
11002 m_minSize == attr.m_minSize &&
11003 m_maxSize == attr.m_maxSize &&
11004
11005 m_border == attr.m_border &&
11006 m_outline == attr.m_outline &&
11007
11008 m_boxStyleName == attr.m_boxStyleName
11009 );
11010 }
11011
11012 // Partial equality test
11013 bool wxTextBoxAttr::EqPartial(const wxTextBoxAttr& attr) const
11014 {
11015 if (attr.HasFloatMode() && HasFloatMode() && (GetFloatMode() != attr.GetFloatMode()))
11016 return false;
11017
11018 if (attr.HasClearMode() && HasClearMode() && (GetClearMode() != attr.GetClearMode()))
11019 return false;
11020
11021 if (attr.HasCollapseBorders() && HasCollapseBorders() && (attr.GetCollapseBorders() != GetCollapseBorders()))
11022 return false;
11023
11024 if (attr.HasVerticalAlignment() && HasVerticalAlignment() && (attr.GetVerticalAlignment() != GetVerticalAlignment()))
11025 return false;
11026
11027 if (attr.HasBoxStyleName() && HasBoxStyleName() && (attr.GetBoxStyleName() != GetBoxStyleName()))
11028 return false;
11029
11030 // Position
11031
11032 if (!m_position.EqPartial(attr.m_position))
11033 return false;
11034
11035 // Size
11036
11037 if (!m_size.EqPartial(attr.m_size))
11038 return false;
11039 if (!m_minSize.EqPartial(attr.m_minSize))
11040 return false;
11041 if (!m_maxSize.EqPartial(attr.m_maxSize))
11042 return false;
11043
11044 // Margins
11045
11046 if (!m_margins.EqPartial(attr.m_margins))
11047 return false;
11048
11049 // Padding
11050
11051 if (!m_padding.EqPartial(attr.m_padding))
11052 return false;
11053
11054 // Border
11055
11056 if (!GetBorder().EqPartial(attr.GetBorder()))
11057 return false;
11058
11059 // Outline
11060
11061 if (!GetOutline().EqPartial(attr.GetOutline()))
11062 return false;
11063
11064 return true;
11065 }
11066
11067 // Merges the given attributes. If compareWith
11068 // is non-NULL, then it will be used to mask out those attributes that are the same in style
11069 // and compareWith, for situations where we don't want to explicitly set inherited attributes.
11070 bool wxTextBoxAttr::Apply(const wxTextBoxAttr& attr, const wxTextBoxAttr* compareWith)
11071 {
11072 if (attr.HasFloatMode())
11073 {
11074 if (!(compareWith && compareWith->HasFloatMode() && compareWith->GetFloatMode() == attr.GetFloatMode()))
11075 SetFloatMode(attr.GetFloatMode());
11076 }
11077
11078 if (attr.HasClearMode())
11079 {
11080 if (!(compareWith && compareWith->HasClearMode() && compareWith->GetClearMode() == attr.GetClearMode()))
11081 SetClearMode(attr.GetClearMode());
11082 }
11083
11084 if (attr.HasCollapseBorders())
11085 {
11086 if (!(compareWith && compareWith->HasCollapseBorders() && compareWith->GetCollapseBorders() == attr.GetCollapseBorders()))
11087 SetCollapseBorders(attr.GetCollapseBorders());
11088 }
11089
11090 if (attr.HasVerticalAlignment())
11091 {
11092 if (!(compareWith && compareWith->HasVerticalAlignment() && compareWith->GetVerticalAlignment() == attr.GetVerticalAlignment()))
11093 SetVerticalAlignment(attr.GetVerticalAlignment());
11094 }
11095
11096 if (attr.HasBoxStyleName())
11097 {
11098 if (!(compareWith && compareWith->HasBoxStyleName() && compareWith->GetBoxStyleName() == attr.GetBoxStyleName()))
11099 SetBoxStyleName(attr.GetBoxStyleName());
11100 }
11101
11102 m_margins.Apply(attr.m_margins, compareWith ? (& attr.m_margins) : (const wxTextAttrDimensions*) NULL);
11103 m_padding.Apply(attr.m_padding, compareWith ? (& attr.m_padding) : (const wxTextAttrDimensions*) NULL);
11104 m_position.Apply(attr.m_position, compareWith ? (& attr.m_position) : (const wxTextAttrDimensions*) NULL);
11105
11106 m_size.Apply(attr.m_size, compareWith ? (& attr.m_size) : (const wxTextAttrSize*) NULL);
11107 m_minSize.Apply(attr.m_minSize, compareWith ? (& attr.m_minSize) : (const wxTextAttrSize*) NULL);
11108 m_maxSize.Apply(attr.m_maxSize, compareWith ? (& attr.m_maxSize) : (const wxTextAttrSize*) NULL);
11109
11110 m_border.Apply(attr.m_border, compareWith ? (& attr.m_border) : (const wxTextAttrBorders*) NULL);
11111 m_outline.Apply(attr.m_outline, compareWith ? (& attr.m_outline) : (const wxTextAttrBorders*) NULL);
11112
11113 return true;
11114 }
11115
11116 // Remove specified attributes from this object
11117 bool wxTextBoxAttr::RemoveStyle(const wxTextBoxAttr& attr)
11118 {
11119 if (attr.HasFloatMode())
11120 RemoveFlag(wxTEXT_BOX_ATTR_FLOAT);
11121
11122 if (attr.HasClearMode())
11123 RemoveFlag(wxTEXT_BOX_ATTR_CLEAR);
11124
11125 if (attr.HasCollapseBorders())
11126 RemoveFlag(wxTEXT_BOX_ATTR_COLLAPSE_BORDERS);
11127
11128 if (attr.HasVerticalAlignment())
11129 RemoveFlag(wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT);
11130
11131 if (attr.HasBoxStyleName())
11132 {
11133 SetBoxStyleName(wxEmptyString);
11134 RemoveFlag(wxTEXT_BOX_ATTR_BOX_STYLE_NAME);
11135 }
11136
11137 m_margins.RemoveStyle(attr.m_margins);
11138 m_padding.RemoveStyle(attr.m_padding);
11139 m_position.RemoveStyle(attr.m_position);
11140
11141 m_size.RemoveStyle(attr.m_size);
11142 m_minSize.RemoveStyle(attr.m_minSize);
11143 m_maxSize.RemoveStyle(attr.m_maxSize);
11144
11145 m_border.RemoveStyle(attr.m_border);
11146 m_outline.RemoveStyle(attr.m_outline);
11147
11148 return true;
11149 }
11150
11151 // Collects the attributes that are common to a range of content, building up a note of
11152 // which attributes are absent in some objects and which clash in some objects.
11153 void wxTextBoxAttr::CollectCommonAttributes(const wxTextBoxAttr& attr, wxTextBoxAttr& clashingAttr, wxTextBoxAttr& absentAttr)
11154 {
11155 if (attr.HasFloatMode())
11156 {
11157 if (!clashingAttr.HasFloatMode() && !absentAttr.HasFloatMode())
11158 {
11159 if (HasFloatMode())
11160 {
11161 if (GetFloatMode() != attr.GetFloatMode())
11162 {
11163 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_FLOAT);
11164 RemoveFlag(wxTEXT_BOX_ATTR_FLOAT);
11165 }
11166 }
11167 else
11168 SetFloatMode(attr.GetFloatMode());
11169 }
11170 }
11171 else
11172 absentAttr.AddFlag(wxTEXT_BOX_ATTR_FLOAT);
11173
11174 if (attr.HasClearMode())
11175 {
11176 if (!clashingAttr.HasClearMode() && !absentAttr.HasClearMode())
11177 {
11178 if (HasClearMode())
11179 {
11180 if (GetClearMode() != attr.GetClearMode())
11181 {
11182 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_CLEAR);
11183 RemoveFlag(wxTEXT_BOX_ATTR_CLEAR);
11184 }
11185 }
11186 else
11187 SetClearMode(attr.GetClearMode());
11188 }
11189 }
11190 else
11191 absentAttr.AddFlag(wxTEXT_BOX_ATTR_CLEAR);
11192
11193 if (attr.HasCollapseBorders())
11194 {
11195 if (!clashingAttr.HasCollapseBorders() && !absentAttr.HasCollapseBorders())
11196 {
11197 if (HasCollapseBorders())
11198 {
11199 if (GetCollapseBorders() != attr.GetCollapseBorders())
11200 {
11201 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_COLLAPSE_BORDERS);
11202 RemoveFlag(wxTEXT_BOX_ATTR_COLLAPSE_BORDERS);
11203 }
11204 }
11205 else
11206 SetCollapseBorders(attr.GetCollapseBorders());
11207 }
11208 }
11209 else
11210 absentAttr.AddFlag(wxTEXT_BOX_ATTR_COLLAPSE_BORDERS);
11211
11212 if (attr.HasVerticalAlignment())
11213 {
11214 if (!clashingAttr.HasVerticalAlignment() && !absentAttr.HasVerticalAlignment())
11215 {
11216 if (HasVerticalAlignment())
11217 {
11218 if (GetVerticalAlignment() != attr.GetVerticalAlignment())
11219 {
11220 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT);
11221 RemoveFlag(wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT);
11222 }
11223 }
11224 else
11225 SetVerticalAlignment(attr.GetVerticalAlignment());
11226 }
11227 }
11228 else
11229 absentAttr.AddFlag(wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT);
11230
11231 if (attr.HasBoxStyleName())
11232 {
11233 if (!clashingAttr.HasBoxStyleName() && !absentAttr.HasBoxStyleName())
11234 {
11235 if (HasBoxStyleName())
11236 {
11237 if (GetBoxStyleName() != attr.GetBoxStyleName())
11238 {
11239 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_BOX_STYLE_NAME);
11240 RemoveFlag(wxTEXT_BOX_ATTR_BOX_STYLE_NAME);
11241 }
11242 }
11243 else
11244 SetBoxStyleName(attr.GetBoxStyleName());
11245 }
11246 }
11247 else
11248 absentAttr.AddFlag(wxTEXT_BOX_ATTR_BOX_STYLE_NAME);
11249
11250 m_margins.CollectCommonAttributes(attr.m_margins, clashingAttr.m_margins, absentAttr.m_margins);
11251 m_padding.CollectCommonAttributes(attr.m_padding, clashingAttr.m_padding, absentAttr.m_padding);
11252 m_position.CollectCommonAttributes(attr.m_position, clashingAttr.m_position, absentAttr.m_position);
11253
11254 m_size.CollectCommonAttributes(attr.m_size, clashingAttr.m_size, absentAttr.m_size);
11255 m_minSize.CollectCommonAttributes(attr.m_minSize, clashingAttr.m_minSize, absentAttr.m_minSize);
11256 m_maxSize.CollectCommonAttributes(attr.m_maxSize, clashingAttr.m_maxSize, absentAttr.m_maxSize);
11257
11258 m_border.CollectCommonAttributes(attr.m_border, clashingAttr.m_border, absentAttr.m_border);
11259 m_outline.CollectCommonAttributes(attr.m_outline, clashingAttr.m_outline, absentAttr.m_outline);
11260 }
11261
11262 bool wxTextBoxAttr::IsDefault() const
11263 {
11264 return GetFlags() == 0 && !m_border.IsValid() && !m_outline.IsValid() &&
11265 !m_size.IsValid() && !m_minSize.IsValid() && !m_maxSize.IsValid() &&
11266 !m_position.IsValid() && !m_padding.IsValid() && !m_margins.IsValid();
11267 }
11268
11269 // wxRichTextAttr
11270
11271 void wxRichTextAttr::Copy(const wxRichTextAttr& attr)
11272 {
11273 wxTextAttr::Copy(attr);
11274
11275 m_textBoxAttr = attr.m_textBoxAttr;
11276 }
11277
11278 bool wxRichTextAttr::operator==(const wxRichTextAttr& attr) const
11279 {
11280 if (!(wxTextAttr::operator==(attr)))
11281 return false;
11282
11283 return (m_textBoxAttr == attr.m_textBoxAttr);
11284 }
11285
11286 // Partial equality test taking comparison object into account
11287 bool wxRichTextAttr::EqPartial(const wxRichTextAttr& attr) const
11288 {
11289 if (!(wxTextAttr::EqPartial(attr)))
11290 return false;
11291
11292 return m_textBoxAttr.EqPartial(attr.m_textBoxAttr);
11293 }
11294
11295 // Merges the given attributes. If compareWith
11296 // is non-NULL, then it will be used to mask out those attributes that are the same in style
11297 // and compareWith, for situations where we don't want to explicitly set inherited attributes.
11298 bool wxRichTextAttr::Apply(const wxRichTextAttr& style, const wxRichTextAttr* compareWith)
11299 {
11300 wxTextAttr::Apply(style, compareWith);
11301
11302 return m_textBoxAttr.Apply(style.m_textBoxAttr, compareWith ? (& compareWith->m_textBoxAttr) : (const wxTextBoxAttr*) NULL);
11303 }
11304
11305 // Remove specified attributes from this object
11306 bool wxRichTextAttr::RemoveStyle(const wxRichTextAttr& attr)
11307 {
11308 wxTextAttr::RemoveStyle(*this, attr);
11309
11310 return m_textBoxAttr.RemoveStyle(attr.m_textBoxAttr);
11311 }
11312
11313 // Collects the attributes that are common to a range of content, building up a note of
11314 // which attributes are absent in some objects and which clash in some objects.
11315 void wxRichTextAttr::CollectCommonAttributes(const wxRichTextAttr& attr, wxRichTextAttr& clashingAttr, wxRichTextAttr& absentAttr)
11316 {
11317 wxTextAttrCollectCommonAttributes(*this, attr, clashingAttr, absentAttr);
11318
11319 m_textBoxAttr.CollectCommonAttributes(attr.m_textBoxAttr, clashingAttr.m_textBoxAttr, absentAttr.m_textBoxAttr);
11320 }
11321
11322 // Partial equality test
11323 bool wxTextAttrBorder::EqPartial(const wxTextAttrBorder& border) const
11324 {
11325 if (border.HasStyle() && !HasStyle() && (border.GetStyle() != GetStyle()))
11326 return false;
11327
11328 if (border.HasColour() && !HasColour() && (border.GetColourLong() != GetColourLong()))
11329 return false;
11330
11331 if (border.HasWidth() && !HasWidth() && !(border.GetWidth() == GetWidth()))
11332 return false;
11333
11334 return true;
11335 }
11336
11337 // Apply border to 'this', but not if the same as compareWith
11338 bool wxTextAttrBorder::Apply(const wxTextAttrBorder& border, const wxTextAttrBorder* compareWith)
11339 {
11340 if (border.HasStyle())
11341 {
11342 if (!(compareWith && (border.GetStyle() == compareWith->GetStyle())))
11343 SetStyle(border.GetStyle());
11344 }
11345 if (border.HasColour())
11346 {
11347 if (!(compareWith && (border.GetColourLong() == compareWith->GetColourLong())))
11348 SetColour(border.GetColourLong());
11349 }
11350 if (border.HasWidth())
11351 {
11352 if (!(compareWith && (border.GetWidth() == compareWith->GetWidth())))
11353 SetWidth(border.GetWidth());
11354 }
11355
11356 return true;
11357 }
11358
11359 // Remove specified attributes from this object
11360 bool wxTextAttrBorder::RemoveStyle(const wxTextAttrBorder& attr)
11361 {
11362 if (attr.HasStyle() && HasStyle())
11363 SetFlags(GetFlags() & ~wxTEXT_BOX_ATTR_BORDER_STYLE);
11364 if (attr.HasColour() && HasColour())
11365 SetFlags(GetFlags() & ~wxTEXT_BOX_ATTR_BORDER_COLOUR);
11366 if (attr.HasWidth() && HasWidth())
11367 m_borderWidth.Reset();
11368
11369 return true;
11370 }
11371
11372 // Collects the attributes that are common to a range of content, building up a note of
11373 // which attributes are absent in some objects and which clash in some objects.
11374 void wxTextAttrBorder::CollectCommonAttributes(const wxTextAttrBorder& attr, wxTextAttrBorder& clashingAttr, wxTextAttrBorder& absentAttr)
11375 {
11376 if (attr.HasStyle())
11377 {
11378 if (!clashingAttr.HasStyle() && !absentAttr.HasStyle())
11379 {
11380 if (HasStyle())
11381 {
11382 if (GetStyle() != attr.GetStyle())
11383 {
11384 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_BORDER_STYLE);
11385 RemoveFlag(wxTEXT_BOX_ATTR_BORDER_STYLE);
11386 }
11387 }
11388 else
11389 SetStyle(attr.GetStyle());
11390 }
11391 }
11392 else
11393 absentAttr.AddFlag(wxTEXT_BOX_ATTR_BORDER_STYLE);
11394
11395 if (attr.HasColour())
11396 {
11397 if (!clashingAttr.HasColour() && !absentAttr.HasColour())
11398 {
11399 if (HasColour())
11400 {
11401 if (GetColour() != attr.GetColour())
11402 {
11403 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_BORDER_COLOUR);
11404 RemoveFlag(wxTEXT_BOX_ATTR_BORDER_COLOUR);
11405 }
11406 }
11407 else
11408 SetColour(attr.GetColourLong());
11409 }
11410 }
11411 else
11412 absentAttr.AddFlag(wxTEXT_BOX_ATTR_BORDER_COLOUR);
11413
11414 m_borderWidth.CollectCommonAttributes(attr.m_borderWidth, clashingAttr.m_borderWidth, absentAttr.m_borderWidth);
11415 }
11416
11417 // Partial equality test
11418 bool wxTextAttrBorders::EqPartial(const wxTextAttrBorders& borders) const
11419 {
11420 return m_left.EqPartial(borders.m_left) && m_right.EqPartial(borders.m_right) &&
11421 m_top.EqPartial(borders.m_top) && m_bottom.EqPartial(borders.m_bottom);
11422 }
11423
11424 // Apply border to 'this', but not if the same as compareWith
11425 bool wxTextAttrBorders::Apply(const wxTextAttrBorders& borders, const wxTextAttrBorders* compareWith)
11426 {
11427 m_left.Apply(borders.m_left, compareWith ? (& compareWith->m_left) : (const wxTextAttrBorder*) NULL);
11428 m_right.Apply(borders.m_right, compareWith ? (& compareWith->m_right) : (const wxTextAttrBorder*) NULL);
11429 m_top.Apply(borders.m_top, compareWith ? (& compareWith->m_top) : (const wxTextAttrBorder*) NULL);
11430 m_bottom.Apply(borders.m_bottom, compareWith ? (& compareWith->m_bottom) : (const wxTextAttrBorder*) NULL);
11431 return true;
11432 }
11433
11434 // Remove specified attributes from this object
11435 bool wxTextAttrBorders::RemoveStyle(const wxTextAttrBorders& attr)
11436 {
11437 m_left.RemoveStyle(attr.m_left);
11438 m_right.RemoveStyle(attr.m_right);
11439 m_top.RemoveStyle(attr.m_top);
11440 m_bottom.RemoveStyle(attr.m_bottom);
11441 return true;
11442 }
11443
11444 // Collects the attributes that are common to a range of content, building up a note of
11445 // which attributes are absent in some objects and which clash in some objects.
11446 void wxTextAttrBorders::CollectCommonAttributes(const wxTextAttrBorders& attr, wxTextAttrBorders& clashingAttr, wxTextAttrBorders& absentAttr)
11447 {
11448 m_left.CollectCommonAttributes(attr.m_left, clashingAttr.m_left, absentAttr.m_left);
11449 m_right.CollectCommonAttributes(attr.m_right, clashingAttr.m_right, absentAttr.m_right);
11450 m_top.CollectCommonAttributes(attr.m_top, clashingAttr.m_top, absentAttr.m_top);
11451 m_bottom.CollectCommonAttributes(attr.m_bottom, clashingAttr.m_bottom, absentAttr.m_bottom);
11452 }
11453
11454 // Set style of all borders
11455 void wxTextAttrBorders::SetStyle(int style)
11456 {
11457 m_left.SetStyle(style);
11458 m_right.SetStyle(style);
11459 m_top.SetStyle(style);
11460 m_bottom.SetStyle(style);
11461 }
11462
11463 // Set colour of all borders
11464 void wxTextAttrBorders::SetColour(unsigned long colour)
11465 {
11466 m_left.SetColour(colour);
11467 m_right.SetColour(colour);
11468 m_top.SetColour(colour);
11469 m_bottom.SetColour(colour);
11470 }
11471
11472 void wxTextAttrBorders::SetColour(const wxColour& colour)
11473 {
11474 m_left.SetColour(colour);
11475 m_right.SetColour(colour);
11476 m_top.SetColour(colour);
11477 m_bottom.SetColour(colour);
11478 }
11479
11480 // Set width of all borders
11481 void wxTextAttrBorders::SetWidth(const wxTextAttrDimension& width)
11482 {
11483 m_left.SetWidth(width);
11484 m_right.SetWidth(width);
11485 m_top.SetWidth(width);
11486 m_bottom.SetWidth(width);
11487 }
11488
11489 // Partial equality test
11490 bool wxTextAttrDimension::EqPartial(const wxTextAttrDimension& dim) const
11491 {
11492 if (dim.IsValid() && IsValid() && !((*this) == dim))
11493 return false;
11494 else
11495 return true;
11496 }
11497
11498 bool wxTextAttrDimension::Apply(const wxTextAttrDimension& dim, const wxTextAttrDimension* compareWith)
11499 {
11500 if (dim.IsValid())
11501 {
11502 if (!(compareWith && dim == (*compareWith)))
11503 (*this) = dim;
11504 }
11505
11506 return true;
11507 }
11508
11509 // Collects the attributes that are common to a range of content, building up a note of
11510 // which attributes are absent in some objects and which clash in some objects.
11511 void wxTextAttrDimension::CollectCommonAttributes(const wxTextAttrDimension& attr, wxTextAttrDimension& clashingAttr, wxTextAttrDimension& absentAttr)
11512 {
11513 if (attr.IsValid())
11514 {
11515 if (!clashingAttr.IsValid() && !absentAttr.IsValid())
11516 {
11517 if (IsValid())
11518 {
11519 if (!((*this) == attr))
11520 {
11521 clashingAttr.SetValid(true);
11522 SetValid(false);
11523 }
11524 }
11525 else
11526 (*this) = attr;
11527 }
11528 }
11529 else
11530 absentAttr.SetValid(true);
11531 }
11532
11533 wxTextAttrDimensionConverter::wxTextAttrDimensionConverter(wxDC& dc, double scale, const wxSize& parentSize)
11534 {
11535 m_ppi = dc.GetPPI().x; m_scale = scale; m_parentSize = parentSize;
11536 }
11537
11538 wxTextAttrDimensionConverter::wxTextAttrDimensionConverter(int ppi, double scale, const wxSize& parentSize)
11539 {
11540 m_ppi = ppi; m_scale = scale; m_parentSize = parentSize;
11541 }
11542
11543 int wxTextAttrDimensionConverter::ConvertTenthsMMToPixels(int units) const
11544 {
11545 return wxRichTextObject::ConvertTenthsMMToPixels(m_ppi, units, m_scale);
11546 }
11547
11548 int wxTextAttrDimensionConverter::ConvertPixelsToTenthsMM(int pixels) const
11549 {
11550 return wxRichTextObject::ConvertPixelsToTenthsMM(m_ppi, pixels, m_scale);
11551 }
11552
11553 int wxTextAttrDimensionConverter::GetPixels(const wxTextAttrDimension& dim, int direction) const
11554 {
11555 if (dim.GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
11556 return ConvertTenthsMMToPixels(dim.GetValue());
11557 else if (dim.GetUnits() == wxTEXT_ATTR_UNITS_PIXELS)
11558 return dim.GetValue();
11559 else if (dim.GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE)
11560 {
11561 wxASSERT(m_parentSize != wxDefaultSize);
11562 if (direction == wxHORIZONTAL)
11563 return (int) (double(m_parentSize.x) * double(dim.GetValue()) / 100.0);
11564 else
11565 return (int) (double(m_parentSize.y) * double(dim.GetValue()) / 100.0);
11566 }
11567 else
11568 {
11569 wxASSERT(false);
11570 return 0;
11571 }
11572 }
11573
11574 int wxTextAttrDimensionConverter::GetTenthsMM(const wxTextAttrDimension& dim) const
11575 {
11576 if (dim.GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
11577 return dim.GetValue();
11578 else if (dim.GetUnits() == wxTEXT_ATTR_UNITS_PIXELS)
11579 return ConvertPixelsToTenthsMM(dim.GetValue());
11580 else
11581 {
11582 wxASSERT(false);
11583 return 0;
11584 }
11585 }
11586
11587 // Partial equality test
11588 bool wxTextAttrDimensions::EqPartial(const wxTextAttrDimensions& dims) const
11589 {
11590 if (!m_left.EqPartial(dims.m_left))
11591 return false;
11592
11593 if (!m_right.EqPartial(dims.m_right))
11594 return false;
11595
11596 if (!m_top.EqPartial(dims.m_top))
11597 return false;
11598
11599 if (!m_bottom.EqPartial(dims.m_bottom))
11600 return false;
11601
11602 return true;
11603 }
11604
11605 // Apply border to 'this', but not if the same as compareWith
11606 bool wxTextAttrDimensions::Apply(const wxTextAttrDimensions& dims, const wxTextAttrDimensions* compareWith)
11607 {
11608 m_left.Apply(dims.m_left, compareWith ? (& compareWith->m_left) : (const wxTextAttrDimension*) NULL);
11609 m_right.Apply(dims.m_right, compareWith ? (& compareWith->m_right): (const wxTextAttrDimension*) NULL);
11610 m_top.Apply(dims.m_top, compareWith ? (& compareWith->m_top): (const wxTextAttrDimension*) NULL);
11611 m_bottom.Apply(dims.m_bottom, compareWith ? (& compareWith->m_bottom): (const wxTextAttrDimension*) NULL);
11612
11613 return true;
11614 }
11615
11616 // Remove specified attributes from this object
11617 bool wxTextAttrDimensions::RemoveStyle(const wxTextAttrDimensions& attr)
11618 {
11619 if (attr.m_left.IsValid())
11620 m_left.Reset();
11621 if (attr.m_right.IsValid())
11622 m_right.Reset();
11623 if (attr.m_top.IsValid())
11624 m_top.Reset();
11625 if (attr.m_bottom.IsValid())
11626 m_bottom.Reset();
11627
11628 return true;
11629 }
11630
11631 // Collects the attributes that are common to a range of content, building up a note of
11632 // which attributes are absent in some objects and which clash in some objects.
11633 void wxTextAttrDimensions::CollectCommonAttributes(const wxTextAttrDimensions& attr, wxTextAttrDimensions& clashingAttr, wxTextAttrDimensions& absentAttr)
11634 {
11635 m_left.CollectCommonAttributes(attr.m_left, clashingAttr.m_left, absentAttr.m_left);
11636 m_right.CollectCommonAttributes(attr.m_right, clashingAttr.m_right, absentAttr.m_right);
11637 m_top.CollectCommonAttributes(attr.m_top, clashingAttr.m_top, absentAttr.m_top);
11638 m_bottom.CollectCommonAttributes(attr.m_bottom, clashingAttr.m_bottom, absentAttr.m_bottom);
11639 }
11640
11641 // Partial equality test
11642 bool wxTextAttrSize::EqPartial(const wxTextAttrSize& size) const
11643 {
11644 if (!m_width.EqPartial(size.m_width))
11645 return false;
11646
11647 if (!m_height.EqPartial(size.m_height))
11648 return false;
11649
11650 return true;
11651 }
11652
11653 // Apply border to 'this', but not if the same as compareWith
11654 bool wxTextAttrSize::Apply(const wxTextAttrSize& size, const wxTextAttrSize* compareWith)
11655 {
11656 m_width.Apply(size.m_width, compareWith ? (& compareWith->m_width) : (const wxTextAttrDimension*) NULL);
11657 m_height.Apply(size.m_height, compareWith ? (& compareWith->m_height): (const wxTextAttrDimension*) NULL);
11658
11659 return true;
11660 }
11661
11662 // Remove specified attributes from this object
11663 bool wxTextAttrSize::RemoveStyle(const wxTextAttrSize& attr)
11664 {
11665 if (attr.m_width.IsValid())
11666 m_width.Reset();
11667 if (attr.m_height.IsValid())
11668 m_height.Reset();
11669
11670 return true;
11671 }
11672
11673 // Collects the attributes that are common to a range of content, building up a note of
11674 // which attributes are absent in some objects and which clash in some objects.
11675 void wxTextAttrSize::CollectCommonAttributes(const wxTextAttrSize& attr, wxTextAttrSize& clashingAttr, wxTextAttrSize& absentAttr)
11676 {
11677 m_width.CollectCommonAttributes(attr.m_width, clashingAttr.m_width, absentAttr.m_width);
11678 m_height.CollectCommonAttributes(attr.m_height, clashingAttr.m_height, absentAttr.m_height);
11679 }
11680
11681 // Collects the attributes that are common to a range of content, building up a note of
11682 // which attributes are absent in some objects and which clash in some objects.
11683 void wxTextAttrCollectCommonAttributes(wxTextAttr& currentStyle, const wxTextAttr& attr, wxTextAttr& clashingAttr, wxTextAttr& absentAttr)
11684 {
11685 absentAttr.SetFlags(absentAttr.GetFlags() | (~attr.GetFlags() & wxTEXT_ATTR_ALL));
11686 absentAttr.SetTextEffectFlags(absentAttr.GetTextEffectFlags() | (~attr.GetTextEffectFlags() & 0xFFFF));
11687
11688 long forbiddenFlags = clashingAttr.GetFlags()|absentAttr.GetFlags();
11689
11690 if (attr.HasFont())
11691 {
11692 if (attr.HasFontSize() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_SIZE))
11693 {
11694 if (currentStyle.HasFontSize())
11695 {
11696 if (currentStyle.GetFontSize() != attr.GetFontSize())
11697 {
11698 // Clash of attr - mark as such
11699 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_SIZE);
11700 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_SIZE);
11701 }
11702 }
11703 else
11704 currentStyle.SetFontSize(attr.GetFontSize());
11705 }
11706
11707 if (attr.HasFontItalic() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_ITALIC))
11708 {
11709 if (currentStyle.HasFontItalic())
11710 {
11711 if (currentStyle.GetFontStyle() != attr.GetFontStyle())
11712 {
11713 // Clash of attr - mark as such
11714 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_ITALIC);
11715 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_ITALIC);
11716 }
11717 }
11718 else
11719 currentStyle.SetFontStyle(attr.GetFontStyle());
11720 }
11721
11722 if (attr.HasFontFamily() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_FAMILY))
11723 {
11724 if (currentStyle.HasFontFamily())
11725 {
11726 if (currentStyle.GetFontFamily() != attr.GetFontFamily())
11727 {
11728 // Clash of attr - mark as such
11729 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_FAMILY);
11730 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_FAMILY);
11731 }
11732 }
11733 else
11734 currentStyle.SetFontFamily(attr.GetFontFamily());
11735 }
11736
11737 if (attr.HasFontWeight() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_WEIGHT))
11738 {
11739 if (currentStyle.HasFontWeight())
11740 {
11741 if (currentStyle.GetFontWeight() != attr.GetFontWeight())
11742 {
11743 // Clash of attr - mark as such
11744 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_WEIGHT);
11745 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_WEIGHT);
11746 }
11747 }
11748 else
11749 currentStyle.SetFontWeight(attr.GetFontWeight());
11750 }
11751
11752 if (attr.HasFontFaceName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_FACE))
11753 {
11754 if (currentStyle.HasFontFaceName())
11755 {
11756 wxString faceName1(currentStyle.GetFontFaceName());
11757 wxString faceName2(attr.GetFontFaceName());
11758
11759 if (faceName1 != faceName2)
11760 {
11761 // Clash of attr - mark as such
11762 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_FACE);
11763 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_FACE);
11764 }
11765 }
11766 else
11767 currentStyle.SetFontFaceName(attr.GetFontFaceName());
11768 }
11769
11770 if (attr.HasFontUnderlined() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_UNDERLINE))
11771 {
11772 if (currentStyle.HasFontUnderlined())
11773 {
11774 if (currentStyle.GetFontUnderlined() != attr.GetFontUnderlined())
11775 {
11776 // Clash of attr - mark as such
11777 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_UNDERLINE);
11778 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_UNDERLINE);
11779 }
11780 }
11781 else
11782 currentStyle.SetFontUnderlined(attr.GetFontUnderlined());
11783 }
11784 }
11785
11786 if (attr.HasTextColour() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_TEXT_COLOUR))
11787 {
11788 if (currentStyle.HasTextColour())
11789 {
11790 if (currentStyle.GetTextColour() != attr.GetTextColour())
11791 {
11792 // Clash of attr - mark as such
11793 clashingAttr.AddFlag(wxTEXT_ATTR_TEXT_COLOUR);
11794 currentStyle.RemoveFlag(wxTEXT_ATTR_TEXT_COLOUR);
11795 }
11796 }
11797 else
11798 currentStyle.SetTextColour(attr.GetTextColour());
11799 }
11800
11801 if (attr.HasBackgroundColour() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BACKGROUND_COLOUR))
11802 {
11803 if (currentStyle.HasBackgroundColour())
11804 {
11805 if (currentStyle.GetBackgroundColour() != attr.GetBackgroundColour())
11806 {
11807 // Clash of attr - mark as such
11808 clashingAttr.AddFlag(wxTEXT_ATTR_BACKGROUND_COLOUR);
11809 currentStyle.RemoveFlag(wxTEXT_ATTR_BACKGROUND_COLOUR);
11810 }
11811 }
11812 else
11813 currentStyle.SetBackgroundColour(attr.GetBackgroundColour());
11814 }
11815
11816 if (attr.HasAlignment() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_ALIGNMENT))
11817 {
11818 if (currentStyle.HasAlignment())
11819 {
11820 if (currentStyle.GetAlignment() != attr.GetAlignment())
11821 {
11822 // Clash of attr - mark as such
11823 clashingAttr.AddFlag(wxTEXT_ATTR_ALIGNMENT);
11824 currentStyle.RemoveFlag(wxTEXT_ATTR_ALIGNMENT);
11825 }
11826 }
11827 else
11828 currentStyle.SetAlignment(attr.GetAlignment());
11829 }
11830
11831 if (attr.HasTabs() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_TABS))
11832 {
11833 if (currentStyle.HasTabs())
11834 {
11835 if (!wxRichTextTabsEq(currentStyle.GetTabs(), attr.GetTabs()))
11836 {
11837 // Clash of attr - mark as such
11838 clashingAttr.AddFlag(wxTEXT_ATTR_TABS);
11839 currentStyle.RemoveFlag(wxTEXT_ATTR_TABS);
11840 }
11841 }
11842 else
11843 currentStyle.SetTabs(attr.GetTabs());
11844 }
11845
11846 if (attr.HasLeftIndent() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_LEFT_INDENT))
11847 {
11848 if (currentStyle.HasLeftIndent())
11849 {
11850 if (currentStyle.GetLeftIndent() != attr.GetLeftIndent() || currentStyle.GetLeftSubIndent() != attr.GetLeftSubIndent())
11851 {
11852 // Clash of attr - mark as such
11853 clashingAttr.AddFlag(wxTEXT_ATTR_LEFT_INDENT);
11854 currentStyle.RemoveFlag(wxTEXT_ATTR_LEFT_INDENT);
11855 }
11856 }
11857 else
11858 currentStyle.SetLeftIndent(attr.GetLeftIndent(), attr.GetLeftSubIndent());
11859 }
11860
11861 if (attr.HasRightIndent() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_RIGHT_INDENT))
11862 {
11863 if (currentStyle.HasRightIndent())
11864 {
11865 if (currentStyle.GetRightIndent() != attr.GetRightIndent())
11866 {
11867 // Clash of attr - mark as such
11868 clashingAttr.AddFlag(wxTEXT_ATTR_RIGHT_INDENT);
11869 currentStyle.RemoveFlag(wxTEXT_ATTR_RIGHT_INDENT);
11870 }
11871 }
11872 else
11873 currentStyle.SetRightIndent(attr.GetRightIndent());
11874 }
11875
11876 if (attr.HasParagraphSpacingAfter() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_PARA_SPACING_AFTER))
11877 {
11878 if (currentStyle.HasParagraphSpacingAfter())
11879 {
11880 if (currentStyle.GetParagraphSpacingAfter() != attr.GetParagraphSpacingAfter())
11881 {
11882 // Clash of attr - mark as such
11883 clashingAttr.AddFlag(wxTEXT_ATTR_PARA_SPACING_AFTER);
11884 currentStyle.RemoveFlag(wxTEXT_ATTR_PARA_SPACING_AFTER);
11885 }
11886 }
11887 else
11888 currentStyle.SetParagraphSpacingAfter(attr.GetParagraphSpacingAfter());
11889 }
11890
11891 if (attr.HasParagraphSpacingBefore() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_PARA_SPACING_BEFORE))
11892 {
11893 if (currentStyle.HasParagraphSpacingBefore())
11894 {
11895 if (currentStyle.GetParagraphSpacingBefore() != attr.GetParagraphSpacingBefore())
11896 {
11897 // Clash of attr - mark as such
11898 clashingAttr.AddFlag(wxTEXT_ATTR_PARA_SPACING_BEFORE);
11899 currentStyle.RemoveFlag(wxTEXT_ATTR_PARA_SPACING_BEFORE);
11900 }
11901 }
11902 else
11903 currentStyle.SetParagraphSpacingBefore(attr.GetParagraphSpacingBefore());
11904 }
11905
11906 if (attr.HasLineSpacing() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_LINE_SPACING))
11907 {
11908 if (currentStyle.HasLineSpacing())
11909 {
11910 if (currentStyle.GetLineSpacing() != attr.GetLineSpacing())
11911 {
11912 // Clash of attr - mark as such
11913 clashingAttr.AddFlag(wxTEXT_ATTR_LINE_SPACING);
11914 currentStyle.RemoveFlag(wxTEXT_ATTR_LINE_SPACING);
11915 }
11916 }
11917 else
11918 currentStyle.SetLineSpacing(attr.GetLineSpacing());
11919 }
11920
11921 if (attr.HasCharacterStyleName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_CHARACTER_STYLE_NAME))
11922 {
11923 if (currentStyle.HasCharacterStyleName())
11924 {
11925 if (currentStyle.GetCharacterStyleName() != attr.GetCharacterStyleName())
11926 {
11927 // Clash of attr - mark as such
11928 clashingAttr.AddFlag(wxTEXT_ATTR_CHARACTER_STYLE_NAME);
11929 currentStyle.RemoveFlag(wxTEXT_ATTR_CHARACTER_STYLE_NAME);
11930 }
11931 }
11932 else
11933 currentStyle.SetCharacterStyleName(attr.GetCharacterStyleName());
11934 }
11935
11936 if (attr.HasParagraphStyleName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_PARAGRAPH_STYLE_NAME))
11937 {
11938 if (currentStyle.HasParagraphStyleName())
11939 {
11940 if (currentStyle.GetParagraphStyleName() != attr.GetParagraphStyleName())
11941 {
11942 // Clash of attr - mark as such
11943 clashingAttr.AddFlag(wxTEXT_ATTR_PARAGRAPH_STYLE_NAME);
11944 currentStyle.RemoveFlag(wxTEXT_ATTR_PARAGRAPH_STYLE_NAME);
11945 }
11946 }
11947 else
11948 currentStyle.SetParagraphStyleName(attr.GetParagraphStyleName());
11949 }
11950
11951 if (attr.HasListStyleName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_LIST_STYLE_NAME))
11952 {
11953 if (currentStyle.HasListStyleName())
11954 {
11955 if (currentStyle.GetListStyleName() != attr.GetListStyleName())
11956 {
11957 // Clash of attr - mark as such
11958 clashingAttr.AddFlag(wxTEXT_ATTR_LIST_STYLE_NAME);
11959 currentStyle.RemoveFlag(wxTEXT_ATTR_LIST_STYLE_NAME);
11960 }
11961 }
11962 else
11963 currentStyle.SetListStyleName(attr.GetListStyleName());
11964 }
11965
11966 if (attr.HasBulletStyle() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BULLET_STYLE))
11967 {
11968 if (currentStyle.HasBulletStyle())
11969 {
11970 if (currentStyle.GetBulletStyle() != attr.GetBulletStyle())
11971 {
11972 // Clash of attr - mark as such
11973 clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_STYLE);
11974 currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_STYLE);
11975 }
11976 }
11977 else
11978 currentStyle.SetBulletStyle(attr.GetBulletStyle());
11979 }
11980
11981 if (attr.HasBulletNumber() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BULLET_NUMBER))
11982 {
11983 if (currentStyle.HasBulletNumber())
11984 {
11985 if (currentStyle.GetBulletNumber() != attr.GetBulletNumber())
11986 {
11987 // Clash of attr - mark as such
11988 clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_NUMBER);
11989 currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_NUMBER);
11990 }
11991 }
11992 else
11993 currentStyle.SetBulletNumber(attr.GetBulletNumber());
11994 }
11995
11996 if (attr.HasBulletText() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BULLET_TEXT))
11997 {
11998 if (currentStyle.HasBulletText())
11999 {
12000 if (currentStyle.GetBulletText() != attr.GetBulletText())
12001 {
12002 // Clash of attr - mark as such
12003 clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_TEXT);
12004 currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_TEXT);
12005 }
12006 }
12007 else
12008 {
12009 currentStyle.SetBulletText(attr.GetBulletText());
12010 currentStyle.SetBulletFont(attr.GetBulletFont());
12011 }
12012 }
12013
12014 if (attr.HasBulletName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BULLET_NAME))
12015 {
12016 if (currentStyle.HasBulletName())
12017 {
12018 if (currentStyle.GetBulletName() != attr.GetBulletName())
12019 {
12020 // Clash of attr - mark as such
12021 clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_NAME);
12022 currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_NAME);
12023 }
12024 }
12025 else
12026 {
12027 currentStyle.SetBulletName(attr.GetBulletName());
12028 }
12029 }
12030
12031 if (attr.HasURL() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_URL))
12032 {
12033 if (currentStyle.HasURL())
12034 {
12035 if (currentStyle.GetURL() != attr.GetURL())
12036 {
12037 // Clash of attr - mark as such
12038 clashingAttr.AddFlag(wxTEXT_ATTR_URL);
12039 currentStyle.RemoveFlag(wxTEXT_ATTR_URL);
12040 }
12041 }
12042 else
12043 {
12044 currentStyle.SetURL(attr.GetURL());
12045 }
12046 }
12047
12048 if (attr.HasTextEffects() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_EFFECTS))
12049 {
12050 if (currentStyle.HasTextEffects())
12051 {
12052 // We need to find the bits in the new attr that are different:
12053 // just look at those bits that are specified by the new attr.
12054
12055 // We need to remove the bits and flags that are not common between current attr
12056 // and new attr. In so doing we need to take account of the styles absent from one or more of the
12057 // previous styles.
12058
12059 int currentRelevantTextEffects = currentStyle.GetTextEffects() & attr.GetTextEffectFlags();
12060 int newRelevantTextEffects = attr.GetTextEffects() & attr.GetTextEffectFlags();
12061
12062 if (currentRelevantTextEffects != newRelevantTextEffects)
12063 {
12064 // Find the text effects that were different, using XOR
12065 int differentEffects = currentRelevantTextEffects ^ newRelevantTextEffects;
12066
12067 // Clash of attr - mark as such
12068 clashingAttr.SetTextEffectFlags(clashingAttr.GetTextEffectFlags() | differentEffects);
12069 currentStyle.SetTextEffectFlags(currentStyle.GetTextEffectFlags() & ~differentEffects);
12070 }
12071 }
12072 else
12073 {
12074 currentStyle.SetTextEffects(attr.GetTextEffects());
12075 currentStyle.SetTextEffectFlags(attr.GetTextEffectFlags());
12076 }
12077
12078 // Mask out the flags and values that cannot be common because they were absent in one or more objecrs
12079 // that we've looked at so far
12080 currentStyle.SetTextEffects(currentStyle.GetTextEffects() & ~absentAttr.GetTextEffectFlags());
12081 currentStyle.SetTextEffectFlags(currentStyle.GetTextEffectFlags() & ~absentAttr.GetTextEffectFlags());
12082
12083 if (currentStyle.GetTextEffectFlags() == 0)
12084 currentStyle.RemoveFlag(wxTEXT_ATTR_EFFECTS);
12085 }
12086
12087 if (attr.HasOutlineLevel() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_OUTLINE_LEVEL))
12088 {
12089 if (currentStyle.HasOutlineLevel())
12090 {
12091 if (currentStyle.GetOutlineLevel() != attr.GetOutlineLevel())
12092 {
12093 // Clash of attr - mark as such
12094 clashingAttr.AddFlag(wxTEXT_ATTR_OUTLINE_LEVEL);
12095 currentStyle.RemoveFlag(wxTEXT_ATTR_OUTLINE_LEVEL);
12096 }
12097 }
12098 else
12099 currentStyle.SetOutlineLevel(attr.GetOutlineLevel());
12100 }
12101 }
12102
12103 WX_DEFINE_OBJARRAY(wxRichTextVariantArray);
12104
12105 IMPLEMENT_DYNAMIC_CLASS(wxRichTextProperties, wxObject)
12106
12107 bool wxRichTextProperties::operator==(const wxRichTextProperties& props) const
12108 {
12109 if (m_properties.GetCount() != props.GetCount())
12110 return false;
12111
12112 size_t i;
12113 for (i = 0; i < m_properties.GetCount(); i++)
12114 {
12115 const wxVariant& var1 = m_properties[i];
12116 int idx = props.Find(var1.GetName());
12117 if (idx == -1)
12118 return false;
12119 const wxVariant& var2 = props.m_properties[idx];
12120 if (!(var1 == var2))
12121 return false;
12122 }
12123
12124 return true;
12125 }
12126
12127 wxArrayString wxRichTextProperties::GetPropertyNames() const
12128 {
12129 wxArrayString arr;
12130 size_t i;
12131 for (i = 0; i < m_properties.GetCount(); i++)
12132 {
12133 arr.Add(m_properties[i].GetName());
12134 }
12135 return arr;
12136 }
12137
12138 int wxRichTextProperties::Find(const wxString& name) const
12139 {
12140 size_t i;
12141 for (i = 0; i < m_properties.GetCount(); i++)
12142 {
12143 if (m_properties[i].GetName() == name)
12144 return (int) i;
12145 }
12146 return -1;
12147 }
12148
12149 bool wxRichTextProperties::Remove(const wxString& name)
12150 {
12151 int idx = Find(name);
12152 if (idx != -1)
12153 {
12154 m_properties.RemoveAt(idx);
12155 return true;
12156 }
12157 else
12158 return false;
12159 }
12160
12161 wxVariant* wxRichTextProperties::FindOrCreateProperty(const wxString& name)
12162 {
12163 int idx = Find(name);
12164 if (idx == wxNOT_FOUND)
12165 SetProperty(name, wxString());
12166 idx = Find(name);
12167 if (idx != wxNOT_FOUND)
12168 {
12169 return & (*this)[idx];
12170 }
12171 else
12172 return NULL;
12173 }
12174
12175 const wxVariant& wxRichTextProperties::GetProperty(const wxString& name) const
12176 {
12177 static const wxVariant nullVariant;
12178 int idx = Find(name);
12179 if (idx != -1)
12180 return m_properties[idx];
12181 else
12182 return nullVariant;
12183 }
12184
12185 wxString wxRichTextProperties::GetPropertyString(const wxString& name) const
12186 {
12187 return GetProperty(name).GetString();
12188 }
12189
12190 long wxRichTextProperties::GetPropertyLong(const wxString& name) const
12191 {
12192 return GetProperty(name).GetLong();
12193 }
12194
12195 bool wxRichTextProperties::GetPropertyBool(const wxString& name) const
12196 {
12197 return GetProperty(name).GetBool();
12198 }
12199
12200 double wxRichTextProperties::GetPropertyDouble(const wxString& name) const
12201 {
12202 return GetProperty(name).GetDouble();
12203 }
12204
12205 void wxRichTextProperties::SetProperty(const wxVariant& variant)
12206 {
12207 wxASSERT(!variant.GetName().IsEmpty());
12208
12209 int idx = Find(variant.GetName());
12210
12211 if (idx == -1)
12212 m_properties.Add(variant);
12213 else
12214 m_properties[idx] = variant;
12215 }
12216
12217 void wxRichTextProperties::SetProperty(const wxString& name, const wxVariant& variant)
12218 {
12219 int idx = Find(name);
12220 wxVariant var(variant);
12221 var.SetName(name);
12222
12223 if (idx == -1)
12224 m_properties.Add(var);
12225 else
12226 m_properties[idx] = var;
12227 }
12228
12229 void wxRichTextProperties::SetProperty(const wxString& name, const wxString& value)
12230 {
12231 SetProperty(name, wxVariant(value, name));
12232 }
12233
12234 void wxRichTextProperties::SetProperty(const wxString& name, long value)
12235 {
12236 SetProperty(name, wxVariant(value, name));
12237 }
12238
12239 void wxRichTextProperties::SetProperty(const wxString& name, double value)
12240 {
12241 SetProperty(name, wxVariant(value, name));
12242 }
12243
12244 void wxRichTextProperties::SetProperty(const wxString& name, bool value)
12245 {
12246 SetProperty(name, wxVariant(value, name));
12247 }
12248
12249 void wxRichTextProperties::RemoveProperties(const wxRichTextProperties& properties)
12250 {
12251 size_t i;
12252 for (i = 0; i < properties.GetCount(); i++)
12253 {
12254 wxString name = properties.GetProperties()[i].GetName();
12255 if (HasProperty(name))
12256 Remove(name);
12257 }
12258 }
12259
12260 void wxRichTextProperties::MergeProperties(const wxRichTextProperties& properties)
12261 {
12262 size_t i;
12263 for (i = 0; i < properties.GetCount(); i++)
12264 {
12265 SetProperty(properties.GetProperties()[i]);
12266 }
12267 }
12268
12269 wxRichTextObject* wxRichTextObjectAddress::GetObject(wxRichTextParagraphLayoutBox* topLevelContainer) const
12270 {
12271 if (m_address.GetCount() == 0)
12272 return topLevelContainer;
12273
12274 wxRichTextCompositeObject* p = topLevelContainer;
12275 size_t i = 0;
12276 while (p && i < m_address.GetCount())
12277 {
12278 int pos = m_address[i];
12279 wxASSERT(pos >= 0 && pos < (int) p->GetChildren().GetCount());
12280 if (pos < 0 || pos >= (int) p->GetChildren().GetCount())
12281 return NULL;
12282
12283 wxRichTextObject* p1 = p->GetChild(pos);
12284 if (i == (m_address.GetCount()-1))
12285 return p1;
12286
12287 p = wxDynamicCast(p1, wxRichTextCompositeObject);
12288 i ++;
12289 }
12290 return NULL;
12291 }
12292
12293 bool wxRichTextObjectAddress::Create(wxRichTextParagraphLayoutBox* topLevelContainer, wxRichTextObject* obj)
12294 {
12295 m_address.Clear();
12296
12297 if (topLevelContainer == obj)
12298 return true;
12299
12300 wxRichTextObject* o = obj;
12301 while (o)
12302 {
12303 wxRichTextCompositeObject* p = wxDynamicCast(o->GetParent(), wxRichTextCompositeObject);
12304 if (!p)
12305 return false;
12306
12307 int pos = p->GetChildren().IndexOf(o);
12308 if (pos == -1)
12309 return false;
12310
12311 m_address.Insert(pos, 0);
12312
12313 if (p == topLevelContainer)
12314 return true;
12315
12316 o = p;
12317 }
12318 return false;
12319 }
12320
12321 // Equality test
12322 bool wxRichTextSelection::operator==(const wxRichTextSelection& sel) const
12323 {
12324 if (m_container != sel.m_container)
12325 return false;
12326 if (m_ranges.GetCount() != sel.m_ranges.GetCount())
12327 return false;
12328 size_t i;
12329 for (i = 0; i < m_ranges.GetCount(); i++)
12330 if (!(m_ranges[i] == sel.m_ranges[i]))
12331 return false;
12332 return true;
12333 }
12334
12335 // Get the selections appropriate to the specified object, if any; returns wxRICHTEXT_NO_SELECTION if none
12336 // or none at the level of the object's container.
12337 wxRichTextRangeArray wxRichTextSelection::GetSelectionForObject(wxRichTextObject* obj) const
12338 {
12339 if (IsValid())
12340 {
12341 wxRichTextParagraphLayoutBox* container = obj->GetParentContainer();
12342
12343 if (container == m_container)
12344 return m_ranges;
12345
12346 container = obj->GetContainer();
12347 while (container)
12348 {
12349 if (container->GetParent())
12350 {
12351 // If we found that our object's container is within the range of
12352 // a selection higher up, then assume the whole original object
12353 // is also selected.
12354 wxRichTextParagraphLayoutBox* parentContainer = container->GetParentContainer();
12355 if (parentContainer == m_container)
12356 {
12357 if (WithinSelection(container->GetRange().GetStart(), m_ranges))
12358 {
12359 wxRichTextRangeArray ranges;
12360 ranges.Add(obj->GetRange());
12361 return ranges;
12362 }
12363 }
12364
12365 container = parentContainer;
12366 }
12367 else
12368 {
12369 container = NULL;
12370 break;
12371 }
12372 }
12373 }
12374 return wxRichTextRangeArray();
12375 }
12376
12377 // Is the given position within the selection?
12378 bool wxRichTextSelection::WithinSelection(long pos, wxRichTextObject* obj) const
12379 {
12380 if (!IsValid())
12381 return false;
12382 else
12383 {
12384 wxRichTextRangeArray selectionRanges = GetSelectionForObject(obj);
12385 return WithinSelection(pos, selectionRanges);
12386 }
12387 }
12388
12389 // Is the given position within the selection range?
12390 bool wxRichTextSelection::WithinSelection(long pos, const wxRichTextRangeArray& ranges)
12391 {
12392 size_t i;
12393 for (i = 0; i < ranges.GetCount(); i++)
12394 {
12395 const wxRichTextRange& range = ranges[i];
12396 if (pos >= range.GetStart() && pos <= range.GetEnd())
12397 return true;
12398 }
12399 return false;
12400 }
12401
12402 // Is the given range completely within the selection range?
12403 bool wxRichTextSelection::WithinSelection(const wxRichTextRange& range, const wxRichTextRangeArray& ranges)
12404 {
12405 size_t i;
12406 for (i = 0; i < ranges.GetCount(); i++)
12407 {
12408 const wxRichTextRange& eachRange = ranges[i];
12409 if (range.IsWithin(eachRange))
12410 return true;
12411 }
12412 return false;
12413 }
12414
12415 IMPLEMENT_CLASS(wxRichTextDrawingHandler, wxObject)
12416 IMPLEMENT_CLASS(wxRichTextDrawingContext, wxObject)
12417
12418 bool wxRichTextDrawingContext::HasVirtualAttributes(wxRichTextObject* obj) const
12419 {
12420 wxList::compatibility_iterator node = m_buffer->GetDrawingHandlers().GetFirst();
12421 while (node)
12422 {
12423 wxRichTextDrawingHandler *handler = (wxRichTextDrawingHandler*)node->GetData();
12424 if (handler->HasVirtualAttributes(obj))
12425 return true;
12426
12427 node = node->GetNext();
12428 }
12429 return false;
12430 }
12431
12432 wxRichTextAttr wxRichTextDrawingContext::GetVirtualAttributes(wxRichTextObject* obj) const
12433 {
12434 wxRichTextAttr attr;
12435 // We apply all handlers, so we can may combine several different attributes
12436 wxList::compatibility_iterator node = m_buffer->GetDrawingHandlers().GetFirst();
12437 while (node)
12438 {
12439 wxRichTextDrawingHandler *handler = (wxRichTextDrawingHandler*)node->GetData();
12440 if (handler->HasVirtualAttributes(obj))
12441 {
12442 bool success = handler->GetVirtualAttributes(attr, obj);
12443 wxASSERT(success);
12444 }
12445
12446 node = node->GetNext();
12447 }
12448 return attr;
12449 }
12450
12451 bool wxRichTextDrawingContext::ApplyVirtualAttributes(wxRichTextAttr& attr, wxRichTextObject* obj) const
12452 {
12453 if (HasVirtualAttributes(obj))
12454 {
12455 wxRichTextAttr a(GetVirtualAttributes(obj));
12456 attr.Apply(a);
12457 return true;
12458 }
12459 else
12460 return false;
12461 }
12462
12463 /// Adds a handler to the end
12464 void wxRichTextBuffer::AddDrawingHandler(wxRichTextDrawingHandler *handler)
12465 {
12466 sm_drawingHandlers.Append(handler);
12467 }
12468
12469 /// Inserts a handler at the front
12470 void wxRichTextBuffer::InsertDrawingHandler(wxRichTextDrawingHandler *handler)
12471 {
12472 sm_drawingHandlers.Insert( handler );
12473 }
12474
12475 /// Removes a handler
12476 bool wxRichTextBuffer::RemoveDrawingHandler(const wxString& name)
12477 {
12478 wxRichTextDrawingHandler *handler = FindDrawingHandler(name);
12479 if (handler)
12480 {
12481 sm_drawingHandlers.DeleteObject(handler);
12482 delete handler;
12483 return true;
12484 }
12485 else
12486 return false;
12487 }
12488
12489 wxRichTextDrawingHandler* wxRichTextBuffer::FindDrawingHandler(const wxString& name)
12490 {
12491 wxList::compatibility_iterator node = sm_drawingHandlers.GetFirst();
12492 while (node)
12493 {
12494 wxRichTextDrawingHandler *handler = (wxRichTextDrawingHandler*)node->GetData();
12495 if (handler->GetName().Lower() == name.Lower()) return handler;
12496
12497 node = node->GetNext();
12498 }
12499 return NULL;
12500 }
12501
12502 void wxRichTextBuffer::CleanUpDrawingHandlers()
12503 {
12504 wxList::compatibility_iterator node = sm_drawingHandlers.GetFirst();
12505 while (node)
12506 {
12507 wxRichTextDrawingHandler* handler = (wxRichTextDrawingHandler*)node->GetData();
12508 wxList::compatibility_iterator next = node->GetNext();
12509 delete handler;
12510 node = next;
12511 }
12512
12513 sm_drawingHandlers.Clear();
12514 }
12515
12516 #endif
12517 // wxUSE_RICHTEXT