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