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