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