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