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