Added continuation bullet style for supporting multiple paragraphs in a list item
[wxWidgets.git] / src / richtext / richtextbuffer.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/richtext/richtextbuffer.cpp
3 // Purpose: Buffer for wxRichTextCtrl
4 // Author: Julian Smart
5 // Modified by:
6 // Created: 2005-09-30
7 // RCS-ID: $Id$
8 // Copyright: (c) Julian Smart
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 // For compilers that support precompilation, includes "wx.h".
13 #include "wx/wxprec.h"
14
15 #ifdef __BORLANDC__
16 #pragma hdrstop
17 #endif
18
19 #if wxUSE_RICHTEXT
20
21 #include "wx/richtext/richtextbuffer.h"
22
23 #ifndef WX_PRECOMP
24 #include "wx/dc.h"
25 #include "wx/intl.h"
26 #include "wx/log.h"
27 #include "wx/dataobj.h"
28 #include "wx/module.h"
29 #endif
30
31 #include "wx/settings.h"
32 #include "wx/filename.h"
33 #include "wx/clipbrd.h"
34 #include "wx/wfstream.h"
35 #include "wx/mstream.h"
36 #include "wx/sstream.h"
37 #include "wx/textfile.h"
38 #include "wx/hashmap.h"
39 #include "wx/dynarray.h"
40
41 #include "wx/richtext/richtextctrl.h"
42 #include "wx/richtext/richtextstyles.h"
43 #include "wx/richtext/richtextimagedlg.h"
44 #include "wx/richtext/richtextsizepage.h"
45 #include "wx/richtext/richtextxml.h"
46
47 #include "wx/listimpl.cpp"
48 #include "wx/arrimpl.cpp"
49
50 WX_DEFINE_LIST(wxRichTextObjectList)
51 WX_DEFINE_LIST(wxRichTextLineList)
52
53 // Switch off if the platform doesn't like it for some reason
54 #define wxRICHTEXT_USE_OPTIMIZED_DRAWING 1
55
56 // Use GetPartialTextExtents for platforms that support it natively
57 #define wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS 1
58
59 const wxChar wxRichTextLineBreakChar = (wxChar) 29;
60
61 // Helper classes for floating layout
62 struct wxRichTextFloatRectMap
63 {
64 wxRichTextFloatRectMap(int sY, int eY, int w, wxRichTextObject* obj)
65 {
66 startY = sY;
67 endY = eY;
68 width = w;
69 anchor = obj;
70 }
71
72 int startY, endY;
73 int width;
74 wxRichTextObject* anchor;
75 };
76
77 WX_DEFINE_SORTED_ARRAY(wxRichTextFloatRectMap*, wxRichTextFloatRectMapArray);
78
79 int wxRichTextFloatRectMapCmp(wxRichTextFloatRectMap* r1, wxRichTextFloatRectMap* r2)
80 {
81 return r1->startY - r2->startY;
82 }
83
84 class wxRichTextFloatCollector
85 {
86 public:
87 wxRichTextFloatCollector(const wxRect& availableRect);
88 ~wxRichTextFloatCollector();
89
90 // Collect the floating objects info in the given paragraph
91 void CollectFloat(wxRichTextParagraph* para);
92 void CollectFloat(wxRichTextParagraph* para, wxRichTextObject* floating);
93
94 // Return the last paragraph we collected
95 wxRichTextParagraph* LastParagraph();
96
97 // Given the start y position and the height of the line,
98 // find out how wide the line can be
99 wxRect GetAvailableRect(int startY, int endY);
100
101 // Given a floating box, find its fit position
102 int GetFitPosition(int direction, int start, int height) const;
103 int GetFitPosition(const wxRichTextFloatRectMapArray& array, int start, int height) const;
104
105 // Find the last y position
106 int GetLastRectBottom();
107
108 // Draw the floats inside a rect
109 void Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style);
110
111 // HitTest the floats
112 int HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, int flags);
113
114 // Get floating object count
115 int GetFloatingObjectCount() const { return m_left.GetCount() + m_right.GetCount(); }
116
117 // Get floating objects
118 bool GetFloatingObjects(wxRichTextObjectList& objects) const;
119
120 // Delete a float
121 bool DeleteFloat(wxRichTextObject* obj);
122
123 // Do we have this float already?
124 bool HasFloat(wxRichTextObject* obj);
125
126 bool HasFloats() const { return m_left.GetCount() >0 || m_right.GetCount() > 0; }
127
128 static int SearchAdjacentRect(const wxRichTextFloatRectMapArray& array, int point);
129
130 static int GetWidthFromFloatRect(const wxRichTextFloatRectMapArray& array, int index, int startY, int endY);
131
132 static void FreeFloatRectMapArray(wxRichTextFloatRectMapArray& array);
133
134 static void DrawFloat(const wxRichTextFloatRectMapArray& array, wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style);
135
136 static int HitTestFloat(const wxRichTextFloatRectMapArray& array, wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, int flags);
137
138 private:
139 wxRichTextFloatRectMapArray m_left;
140 wxRichTextFloatRectMapArray m_right;
141 //int m_width;
142 wxRect m_availableRect;
143 wxRichTextParagraph* m_para;
144 };
145
146 // Delete a float
147 bool wxRichTextFloatCollector::DeleteFloat(wxRichTextObject* obj)
148 {
149 size_t i;
150 for (i = 0; i < m_left.GetCount(); i++)
151 {
152 if (m_left[i]->anchor == obj)
153 {
154 m_left.RemoveAt(i);
155 return true;
156 }
157 }
158 for (i = 0; i < m_right.GetCount(); i++)
159 {
160 if (m_right[i]->anchor == obj)
161 {
162 m_right.RemoveAt(i);
163 return true;
164 }
165 }
166 return false;
167 }
168
169 // Do we have this float already?
170 bool wxRichTextFloatCollector::HasFloat(wxRichTextObject* obj)
171 {
172 size_t i;
173 for (i = 0; i < m_left.GetCount(); i++)
174 {
175 if (m_left[i]->anchor == obj)
176 {
177 return true;
178 }
179 }
180 for (i = 0; i < m_right.GetCount(); i++)
181 {
182 if (m_right[i]->anchor == obj)
183 {
184 return true;
185 }
186 }
187 return false;
188 }
189
190 // Get floating objects
191 bool wxRichTextFloatCollector::GetFloatingObjects(wxRichTextObjectList& objects) const
192 {
193 size_t i;
194 for (i = 0; i < m_left.GetCount(); i++)
195 objects.Append(m_left[i]->anchor);
196 for (i = 0; i < m_right.GetCount(); i++)
197 objects.Append(m_right[i]->anchor);
198 return true;
199 }
200
201
202 /*
203 * Binary search helper function
204 * The argument point is the Y coordinate, and this fuction
205 * always return the floating rect that contain this coordinate
206 * or under this coordinate.
207 */
208 int wxRichTextFloatCollector::SearchAdjacentRect(const wxRichTextFloatRectMapArray& array, int point)
209 {
210 int end = array.GetCount() - 1;
211 int start = 0;
212 int ret = 0;
213
214 wxASSERT(end >= 0);
215
216 while (true)
217 {
218 if (start > end)
219 {
220 break;
221 }
222
223 int mid = (start + end) / 2;
224 if (array[mid]->startY <= point && array[mid]->endY >= point)
225 return mid;
226 else if (array[mid]->startY > point)
227 {
228 end = mid - 1;
229 ret = mid;
230 }
231 else if (array[mid]->endY < point)
232 {
233 start = mid + 1;
234 ret = start;
235 }
236 }
237
238 return ret;
239 }
240
241 int wxRichTextFloatCollector::GetWidthFromFloatRect(const wxRichTextFloatRectMapArray& array, int index, int startY, int endY)
242 {
243 int ret = 0;
244 int len = array.GetCount();
245
246 wxASSERT(index >= 0 && index < len);
247
248 if (array[index]->startY < startY && array[index]->endY > startY)
249 ret = ret < array[index]->width ? array[index]->width : ret;
250 while (index < len && array[index]->startY <= endY)
251 {
252 ret = ret < array[index]->width ? array[index]->width : ret;
253 index++;
254 }
255
256 return ret;
257 }
258
259 wxRichTextFloatCollector::wxRichTextFloatCollector(const wxRect& rect) : m_left(wxRichTextFloatRectMapCmp), m_right(wxRichTextFloatRectMapCmp)
260 {
261 m_availableRect = rect;
262 m_para = NULL;
263 }
264
265 void wxRichTextFloatCollector::FreeFloatRectMapArray(wxRichTextFloatRectMapArray& array)
266 {
267 int len = array.GetCount();
268 for (int i = 0; i < len; i++)
269 delete array[i];
270 }
271
272 wxRichTextFloatCollector::~wxRichTextFloatCollector()
273 {
274 FreeFloatRectMapArray(m_left);
275 FreeFloatRectMapArray(m_right);
276 }
277
278 int wxRichTextFloatCollector::GetFitPosition(const wxRichTextFloatRectMapArray& array, int start, int height) const
279 {
280 if (array.GetCount() == 0)
281 return start;
282
283 int i = SearchAdjacentRect(array, start);
284 int last = start;
285 while (i < (int) array.GetCount())
286 {
287 if (array[i]->startY - last >= height)
288 return last + 1;
289 last = array[i]->endY;
290 i++;
291 }
292
293 return last + 1;
294 }
295
296 int wxRichTextFloatCollector::GetFitPosition(int direction, int start, int height) const
297 {
298 if (direction == wxTEXT_BOX_ATTR_FLOAT_LEFT)
299 return GetFitPosition(m_left, start, height);
300 else if (direction == wxTEXT_BOX_ATTR_FLOAT_RIGHT)
301 return GetFitPosition(m_right, start, height);
302 else
303 {
304 wxASSERT("Never should be here");
305 return start;
306 }
307 }
308
309 // Adds a floating image to the float collector.
310 // The actual positioning is done by wxRichTextParagraph::LayoutFloat.
311 void wxRichTextFloatCollector::CollectFloat(wxRichTextParagraph* para, wxRichTextObject* floating)
312 {
313 int direction = floating->GetFloatDirection();
314
315 wxPoint pos = floating->GetPosition();
316 wxSize size = floating->GetCachedSize();
317 wxRichTextFloatRectMap *map = new wxRichTextFloatRectMap(pos.y, pos.y + size.y, size.x, floating);
318 switch (direction)
319 {
320 case wxTEXT_BOX_ATTR_FLOAT_NONE:
321 delete map;
322 break;
323 case wxTEXT_BOX_ATTR_FLOAT_LEFT:
324 // Just a not-enough simple assertion
325 wxASSERT (m_left.Index(map) == wxNOT_FOUND);
326 m_left.Add(map);
327 break;
328 case wxTEXT_BOX_ATTR_FLOAT_RIGHT:
329 wxASSERT (m_right.Index(map) == wxNOT_FOUND);
330 m_right.Add(map);
331 break;
332 default:
333 delete map;
334 wxASSERT("Unrecognised float attribute.");
335 }
336
337 m_para = para;
338 }
339
340 void wxRichTextFloatCollector::CollectFloat(wxRichTextParagraph* para)
341 {
342 wxRichTextObjectList::compatibility_iterator node = para->GetChildren().GetFirst();
343 while (node)
344 {
345 wxRichTextObject* floating = node->GetData();
346
347 if (floating->IsFloating())
348 {
349 CollectFloat(para, floating);
350 }
351
352 node = node->GetNext();
353 }
354
355 m_para = para;
356 }
357
358 wxRichTextParagraph* wxRichTextFloatCollector::LastParagraph()
359 {
360 return m_para;
361 }
362
363 wxRect wxRichTextFloatCollector::GetAvailableRect(int startY, int endY)
364 {
365 int widthLeft = 0, widthRight = 0;
366 if (m_left.GetCount() != 0)
367 {
368 int i = SearchAdjacentRect(m_left, startY);
369 if (i < (int) m_left.GetCount())
370 widthLeft = GetWidthFromFloatRect(m_left, i, startY, endY);
371 }
372 if (m_right.GetCount() != 0)
373 {
374 int j = SearchAdjacentRect(m_right, startY);
375 if (j < (int) m_right.GetCount())
376 widthRight = GetWidthFromFloatRect(m_right, j, startY, endY);
377 }
378
379 // TODO: actually we want to use the actual image positions to find the
380 // available remaining space, since the image might not be right up against
381 // the left or right edge of the container.
382 return wxRect(widthLeft + m_availableRect.x, 0, m_availableRect.width - widthLeft - widthRight, 0);
383 }
384
385 int wxRichTextFloatCollector::GetLastRectBottom()
386 {
387 int ret = 0;
388 int len = m_left.GetCount();
389 if (len) {
390 ret = ret > m_left[len-1]->endY ? ret : m_left[len-1]->endY;
391 }
392 len = m_right.GetCount();
393 if (len) {
394 ret = ret > m_right[len-1]->endY ? ret : m_right[len-1]->endY;
395 }
396
397 return ret;
398 }
399
400 void wxRichTextFloatCollector::DrawFloat(const wxRichTextFloatRectMapArray& array, wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& WXUNUSED(range), const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
401 {
402 int start = rect.y;
403 int end = rect.y + rect.height;
404 int i, j;
405 i = SearchAdjacentRect(array, start);
406 if (i < 0 || i >= (int) array.GetCount())
407 return;
408 j = SearchAdjacentRect(array, end);
409 if (j < 0 || j >= (int) array.GetCount())
410 j = array.GetCount() - 1;
411 while (i <= j)
412 {
413 wxRichTextObject* obj = array[i]->anchor;
414 wxRichTextRange r = obj->GetRange();
415 obj->Draw(dc, context, r, selection, wxRect(obj->GetPosition(), obj->GetCachedSize()), descent, style);
416 i++;
417 }
418 }
419
420 void wxRichTextFloatCollector::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
421 {
422 if (m_left.GetCount() > 0)
423 DrawFloat(m_left, dc, context, range, selection, rect, descent, style);
424 if (m_right.GetCount() > 0)
425 DrawFloat(m_right, dc, context, range, selection, rect, descent, style);
426 }
427
428 int wxRichTextFloatCollector::HitTestFloat(const wxRichTextFloatRectMapArray& array, wxDC& WXUNUSED(dc), wxRichTextDrawingContext& WXUNUSED(context), const wxPoint& pt, long& textPosition, wxRichTextObject** obj, int WXUNUSED(flags))
429 {
430 int i;
431 if (array.GetCount() == 0)
432 return wxRICHTEXT_HITTEST_NONE;
433 i = SearchAdjacentRect(array, pt.y);
434 if (i < 0 || i >= (int) array.GetCount())
435 return wxRICHTEXT_HITTEST_NONE;
436 if (!array[i]->anchor->IsShown())
437 return wxRICHTEXT_HITTEST_NONE;
438
439 wxPoint point = array[i]->anchor->GetPosition();
440 wxSize size = array[i]->anchor->GetCachedSize();
441 if (point.x <= pt.x && point.x + size.x >= pt.x
442 && point.y <= pt.y && point.y + size.y >= pt.y)
443 {
444 textPosition = array[i]->anchor->GetRange().GetStart();
445 * obj = array[i]->anchor;
446 if (pt.x > (pt.x + pt.x + size.x) / 2)
447 return wxRICHTEXT_HITTEST_BEFORE;
448 else
449 return wxRICHTEXT_HITTEST_AFTER;
450 }
451
452 return wxRICHTEXT_HITTEST_NONE;
453 }
454
455 int wxRichTextFloatCollector::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, int flags)
456 {
457 int ret = HitTestFloat(m_left, dc, context, pt, textPosition, obj, flags);
458 if (ret == wxRICHTEXT_HITTEST_NONE)
459 {
460 ret = HitTestFloat(m_right, dc, context, pt, textPosition, obj, flags);
461 }
462 return ret;
463 }
464
465 // Helpers for efficiency
466 inline void wxCheckSetFont(wxDC& dc, const wxFont& font)
467 {
468 dc.SetFont(font);
469 }
470
471 inline void wxCheckSetPen(wxDC& dc, const wxPen& pen)
472 {
473 const wxPen& pen1 = dc.GetPen();
474 if (pen1.IsOk() && pen.IsOk())
475 {
476 if (pen1.GetWidth() == pen.GetWidth() &&
477 pen1.GetStyle() == pen.GetStyle() &&
478 pen1.GetColour() == pen.GetColour())
479 return;
480 }
481 dc.SetPen(pen);
482 }
483
484 inline void wxCheckSetBrush(wxDC& dc, const wxBrush& brush)
485 {
486 const wxBrush& brush1 = dc.GetBrush();
487 if (brush1.IsOk() && brush.IsOk())
488 {
489 if (brush1.GetStyle() == brush.GetStyle() &&
490 brush1.GetColour() == brush.GetColour())
491 return;
492 }
493 dc.SetBrush(brush);
494 }
495
496 /*!
497 * wxRichTextObject
498 * This is the base for drawable objects.
499 */
500
501 IMPLEMENT_CLASS(wxRichTextObject, wxObject)
502
503 wxRichTextObject::wxRichTextObject(wxRichTextObject* parent)
504 {
505 m_refCount = 1;
506 m_parent = parent;
507 m_descent = 0;
508 m_show = true;
509 }
510
511 wxRichTextObject::~wxRichTextObject()
512 {
513 }
514
515 void wxRichTextObject::Dereference()
516 {
517 m_refCount --;
518 if (m_refCount <= 0)
519 delete this;
520 }
521
522 /// Copy
523 void wxRichTextObject::Copy(const wxRichTextObject& obj)
524 {
525 m_size = obj.m_size;
526 m_maxSize = obj.m_maxSize;
527 m_minSize = obj.m_minSize;
528 m_pos = obj.m_pos;
529 m_range = obj.m_range;
530 m_ownRange = obj.m_ownRange;
531 m_attributes = obj.m_attributes;
532 m_properties = obj.m_properties;
533 m_descent = obj.m_descent;
534 m_show = obj.m_show;
535 }
536
537 // Get/set the top-level container of this object.
538 wxRichTextParagraphLayoutBox* wxRichTextObject::GetContainer() const
539 {
540 const wxRichTextObject* p = this;
541 while (p)
542 {
543 if (p->IsTopLevel())
544 {
545 return wxDynamicCast(p, wxRichTextParagraphLayoutBox);
546 }
547 p = p->GetParent();
548 }
549 return NULL;
550 }
551
552 void wxRichTextObject::SetMargins(int margin)
553 {
554 SetMargins(margin, margin, margin, margin);
555 }
556
557 void wxRichTextObject::SetMargins(int leftMargin, int rightMargin, int topMargin, int bottomMargin)
558 {
559 GetAttributes().GetTextBoxAttr().GetMargins().GetLeft().SetValue(leftMargin, wxTEXT_ATTR_UNITS_PIXELS);
560 GetAttributes().GetTextBoxAttr().GetMargins().GetRight().SetValue(rightMargin, wxTEXT_ATTR_UNITS_PIXELS);
561 GetAttributes().GetTextBoxAttr().GetMargins().GetTop().SetValue(topMargin, wxTEXT_ATTR_UNITS_PIXELS);
562 GetAttributes().GetTextBoxAttr().GetMargins().GetBottom().SetValue(bottomMargin, wxTEXT_ATTR_UNITS_PIXELS);
563 }
564
565 int wxRichTextObject::GetLeftMargin() const
566 {
567 return GetAttributes().GetTextBoxAttr().GetMargins().GetLeft().GetValue();
568 }
569
570 int wxRichTextObject::GetRightMargin() const
571 {
572 return GetAttributes().GetTextBoxAttr().GetMargins().GetRight().GetValue();
573 }
574
575 int wxRichTextObject::GetTopMargin() const
576 {
577 return GetAttributes().GetTextBoxAttr().GetMargins().GetTop().GetValue();
578 }
579
580 int wxRichTextObject::GetBottomMargin() const
581 {
582 return GetAttributes().GetTextBoxAttr().GetMargins().GetBottom().GetValue();
583 }
584
585 // Calculate the available content space in the given rectangle, given the
586 // margins, border and padding specified in the object's attributes.
587 wxRect wxRichTextObject::GetAvailableContentArea(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& outerRect) const
588 {
589 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
590 marginRect = outerRect;
591 wxRichTextAttr attr(GetAttributes());
592 context.ApplyVirtualAttributes(attr, (wxRichTextObject*) this);
593 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
594 return contentRect;
595 }
596
597 // Invalidate the buffer. With no argument, invalidates whole buffer.
598 void wxRichTextObject::Invalidate(const wxRichTextRange& invalidRange)
599 {
600 if (invalidRange != wxRICHTEXT_NONE)
601 {
602 // If this is a floating object, size may not be recalculated
603 // after floats have been collected in an early stage of Layout.
604 // So avoid resetting the cache for floating objects during layout.
605 if (!IsFloating())
606 SetCachedSize(wxDefaultSize);
607 SetMaxSize(wxDefaultSize);
608 SetMinSize(wxDefaultSize);
609 }
610 }
611
612 // Convert units in tenths of a millimetre to device units
613 int wxRichTextObject::ConvertTenthsMMToPixels(wxDC& dc, int units) const
614 {
615 // Unscale
616 double scale = 1.0;
617 if (GetBuffer())
618 scale = GetBuffer()->GetScale() / GetBuffer()->GetDimensionScale();
619 int p = ConvertTenthsMMToPixels(dc.GetPPI().x, units, scale);
620
621 return p;
622 }
623
624 // Convert units in tenths of a millimetre to device units
625 int wxRichTextObject::ConvertTenthsMMToPixels(int ppi, int units, double scale)
626 {
627 // There are ppi pixels in 254.1 "1/10 mm"
628
629 double pixels = ((double) units * (double)ppi) / 254.1;
630 if (scale != 1.0)
631 pixels /= scale;
632
633 // If the result is very small, make it at least one pixel in size.
634 if (pixels == 0 && units > 0)
635 pixels = 1;
636
637 return (int) pixels;
638 }
639
640 // Convert units in pixels to tenths of a millimetre
641 int wxRichTextObject::ConvertPixelsToTenthsMM(wxDC& dc, int pixels) const
642 {
643 int p = pixels;
644 double scale = 1.0;
645 if (GetBuffer())
646 scale = GetBuffer()->GetScale();
647
648 return ConvertPixelsToTenthsMM(dc.GetPPI().x, p, scale);
649 }
650
651 int wxRichTextObject::ConvertPixelsToTenthsMM(int ppi, int pixels, double scale)
652 {
653 // There are ppi pixels in 254.1 "1/10 mm"
654
655 double p = double(pixels);
656
657 if (scale != 1.0)
658 p *= scale;
659
660 int units = int( p * 254.1 / (double) ppi );
661 return units;
662 }
663
664 // Draw the borders and background for the given rectangle and attributes.
665 // Width and height are taken to be the outer margin size, not the content.
666 bool wxRichTextObject::DrawBoxAttributes(wxDC& dc, wxRichTextBuffer* buffer, const wxRichTextAttr& attr, const wxRect& boxRect, int flags)
667 {
668 // Assume boxRect is the area around the content
669 wxRect marginRect = boxRect;
670 wxRect contentRect, borderRect, paddingRect, outlineRect;
671
672 GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
673
674 // Margin is transparent. Draw background from margin.
675 if (attr.HasBackgroundColour() || (flags & wxRICHTEXT_DRAW_SELECTED))
676 {
677 wxColour colour;
678 if (flags & wxRICHTEXT_DRAW_SELECTED)
679 {
680 // TODO: get selection colour from control?
681 colour = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT);
682 }
683 else
684 colour = attr.GetBackgroundColour();
685
686 wxPen pen(colour);
687 wxBrush brush(colour);
688
689 dc.SetPen(pen);
690 dc.SetBrush(brush);
691 dc.DrawRectangle(borderRect);
692 }
693
694 if (flags & wxRICHTEXT_DRAW_GUIDELINES)
695 {
696 wxRichTextAttr editBorderAttr = attr;
697 // TODO: make guideline colour configurable
698 editBorderAttr.GetTextBoxAttr().GetBorder().SetColour(*wxLIGHT_GREY);
699 editBorderAttr.GetTextBoxAttr().GetBorder().SetWidth(1, wxTEXT_ATTR_UNITS_PIXELS);
700 editBorderAttr.GetTextBoxAttr().GetBorder().SetStyle(wxTEXT_BOX_ATTR_BORDER_SOLID);
701
702 DrawBorder(dc, buffer, editBorderAttr.GetTextBoxAttr().GetBorder(), borderRect, flags);
703 }
704
705 if (attr.GetTextBoxAttr().GetBorder().IsValid())
706 DrawBorder(dc, buffer, attr.GetTextBoxAttr().GetBorder(), borderRect);
707
708 if (attr.GetTextBoxAttr().GetOutline().IsValid())
709 DrawBorder(dc, buffer, attr.GetTextBoxAttr().GetOutline(), outlineRect);
710
711 return true;
712 }
713
714 // Draw a border
715 bool wxRichTextObject::DrawBorder(wxDC& dc, wxRichTextBuffer* buffer, const wxTextAttrBorders& attr, const wxRect& rect, int WXUNUSED(flags))
716 {
717 int borderLeft = 0, borderRight = 0, borderTop = 0, borderBottom = 0;
718 wxTextAttrDimensionConverter converter(dc, buffer ? buffer->GetScale() : 1.0);
719
720 if (attr.GetLeft().IsValid() && attr.GetLeft().GetStyle() != wxTEXT_BOX_ATTR_BORDER_NONE)
721 {
722 borderLeft = converter.GetPixels(attr.GetLeft().GetWidth());
723 wxColour col(attr.GetLeft().GetColour());
724
725 // If pen width is > 1, resorts to a solid rectangle.
726 if (borderLeft == 1)
727 {
728 int penStyle = wxSOLID;
729 if (attr.GetLeft().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DOTTED)
730 penStyle = wxDOT;
731 else if (attr.GetLeft().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DASHED)
732 penStyle = wxLONG_DASH;
733 wxPen pen(col, 1, penStyle);
734 dc.SetPen(pen);
735 dc.DrawLine(rect.x, rect.y, rect.x, rect.y + rect.height);
736
737 }
738 else if (borderLeft > 1)
739 {
740 wxPen pen(col);
741 wxBrush brush(col);
742 dc.SetPen(pen);
743 dc.SetBrush(brush);
744 dc.DrawRectangle(rect.x, rect.y, borderLeft, rect.height);
745 }
746 }
747
748 if (attr.GetRight().IsValid() && attr.GetRight().GetStyle() != wxTEXT_BOX_ATTR_BORDER_NONE)
749 {
750 borderRight = converter.GetPixels(attr.GetRight().GetWidth());
751
752 wxColour col(attr.GetRight().GetColour());
753
754 // If pen width is > 1, resorts to a solid rectangle.
755 if (borderRight == 1)
756 {
757 int penStyle = wxSOLID;
758 if (attr.GetRight().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DOTTED)
759 penStyle = wxDOT;
760 else if (attr.GetRight().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DASHED)
761 penStyle = wxLONG_DASH;
762 wxPen pen(col, 1, penStyle);
763 dc.SetPen(pen);
764 dc.DrawLine(rect.x + rect.width, rect.y, rect.x + rect.width, rect.y + rect.height + 1);
765
766 }
767 else if (borderRight > 1)
768 {
769 wxPen pen(col);
770 wxBrush brush(col);
771 dc.SetPen(pen);
772 dc.SetBrush(brush);
773 dc.DrawRectangle(rect.x + rect.width - borderRight, rect.y, borderRight, rect.height);
774 }
775 }
776
777 if (attr.GetTop().IsValid() && attr.GetTop().GetStyle() != wxTEXT_BOX_ATTR_BORDER_NONE)
778 {
779 borderTop = converter.GetPixels(attr.GetTop().GetWidth());
780
781 wxColour col(attr.GetTop().GetColour());
782
783 // If pen width is > 1, resorts to a solid rectangle.
784 if (borderTop == 1)
785 {
786 int penStyle = wxSOLID;
787 if (attr.GetTop().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DOTTED)
788 penStyle = wxDOT;
789 else if (attr.GetTop().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DASHED)
790 penStyle = wxLONG_DASH;
791 wxPen pen(col, 1, penStyle);
792 dc.SetPen(pen);
793 dc.DrawLine(rect.x, rect.y, rect.x + rect.width, rect.y);
794
795 }
796 else if (borderTop > 1)
797 {
798 wxPen pen(col);
799 wxBrush brush(col);
800 dc.SetPen(pen);
801 dc.SetBrush(brush);
802 dc.DrawRectangle(rect.x, rect.y, rect.width, borderTop);
803 }
804 }
805
806 if (attr.GetBottom().IsValid() && attr.GetBottom().GetStyle() != wxTEXT_BOX_ATTR_BORDER_NONE)
807 {
808 borderBottom = converter.GetPixels(attr.GetBottom().GetWidth());
809 wxColour col(attr.GetTop().GetColour());
810
811 // If pen width is > 1, resorts to a solid rectangle.
812 if (borderBottom == 1)
813 {
814 int penStyle = wxSOLID;
815 if (attr.GetBottom().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DOTTED)
816 penStyle = wxDOT;
817 else if (attr.GetBottom().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DASHED)
818 penStyle = wxLONG_DASH;
819 wxPen pen(col, 1, penStyle);
820 dc.SetPen(pen);
821 dc.DrawLine(rect.x, rect.y + rect.height, rect.x + rect.width, rect.y + rect.height);
822
823 }
824 else if (borderBottom > 1)
825 {
826 wxPen pen(col);
827 wxBrush brush(col);
828 dc.SetPen(pen);
829 dc.SetBrush(brush);
830 dc.DrawRectangle(rect.x, rect.y + rect.height - borderBottom, rect.width, borderBottom);
831 }
832 }
833
834 return true;
835 }
836
837 // Get the various rectangles of the box model in pixels. You can either specify contentRect (inner)
838 // or marginRect (outer), and the other must be the default rectangle (no width or height).
839 // Note that the outline doesn't affect the position of the rectangle, it's drawn in whatever space
840 // is available.
841 //
842 // | Margin | Border | Padding | CONTENT | Padding | Border | Margin |
843
844 bool wxRichTextObject::GetBoxRects(wxDC& dc, wxRichTextBuffer* buffer, const wxRichTextAttr& attr, wxRect& marginRect, wxRect& borderRect, wxRect& contentRect, wxRect& paddingRect, wxRect& outlineRect)
845 {
846 int borderLeft = 0, borderRight = 0, borderTop = 0, borderBottom = 0;
847 int outlineLeft = 0, outlineRight = 0, outlineTop = 0, outlineBottom = 0;
848 int paddingLeft = 0, paddingRight = 0, paddingTop = 0, paddingBottom = 0;
849 int marginLeft = 0, marginRight = 0, marginTop = 0, marginBottom = 0;
850
851 wxTextAttrDimensionConverter converter(dc, buffer ? buffer->GetScale() : 1.0);
852
853 if (attr.GetTextBoxAttr().GetMargins().GetLeft().IsValid())
854 marginLeft = converter.GetPixels(attr.GetTextBoxAttr().GetMargins().GetLeft());
855 if (attr.GetTextBoxAttr().GetMargins().GetRight().IsValid())
856 marginRight = converter.GetPixels(attr.GetTextBoxAttr().GetMargins().GetRight());
857 if (attr.GetTextBoxAttr().GetMargins().GetTop().IsValid())
858 marginTop = converter.GetPixels(attr.GetTextBoxAttr().GetMargins().GetTop());
859 if (attr.GetTextBoxAttr().GetMargins().GetBottom().IsValid())
860 marginBottom = converter.GetPixels(attr.GetTextBoxAttr().GetMargins().GetBottom());
861
862 if (attr.GetTextBoxAttr().GetBorder().GetLeft().GetWidth().IsValid())
863 borderLeft = converter.GetPixels(attr.GetTextBoxAttr().GetBorder().GetLeft().GetWidth());
864 if (attr.GetTextBoxAttr().GetBorder().GetRight().GetWidth().IsValid())
865 borderRight = converter.GetPixels(attr.GetTextBoxAttr().GetBorder().GetRight().GetWidth());
866 if (attr.GetTextBoxAttr().GetBorder().GetTop().GetWidth().IsValid())
867 borderTop = converter.GetPixels(attr.GetTextBoxAttr().GetBorder().GetTop().GetWidth());
868 if (attr.GetTextBoxAttr().GetBorder().GetBottom().GetWidth().IsValid())
869 borderBottom = converter.GetPixels(attr.GetTextBoxAttr().GetBorder().GetBottom().GetWidth());
870
871 if (attr.GetTextBoxAttr().GetPadding().GetLeft().IsValid())
872 paddingLeft = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetLeft());
873 if (attr.GetTextBoxAttr().GetPadding().GetRight().IsValid())
874 paddingRight = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetRight());
875 if (attr.GetTextBoxAttr().GetPadding().GetTop().IsValid())
876 paddingTop = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetTop());
877 if (attr.GetTextBoxAttr().GetPadding().GetBottom().IsValid())
878 paddingBottom = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetBottom());
879
880 if (attr.GetTextBoxAttr().GetOutline().GetLeft().GetWidth().IsValid())
881 outlineLeft = converter.GetPixels(attr.GetTextBoxAttr().GetOutline().GetLeft().GetWidth());
882 if (attr.GetTextBoxAttr().GetOutline().GetRight().GetWidth().IsValid())
883 outlineRight = converter.GetPixels(attr.GetTextBoxAttr().GetOutline().GetRight().GetWidth());
884 if (attr.GetTextBoxAttr().GetOutline().GetTop().GetWidth().IsValid())
885 outlineTop = converter.GetPixels(attr.GetTextBoxAttr().GetOutline().GetTop().GetWidth());
886 if (attr.GetTextBoxAttr().GetOutline().GetBottom().GetWidth().IsValid())
887 outlineBottom = converter.GetPixels(attr.GetTextBoxAttr().GetOutline().GetBottom().GetWidth());
888
889 int leftTotal = marginLeft + borderLeft + paddingLeft;
890 int rightTotal = marginRight + borderRight + paddingRight;
891 int topTotal = marginTop + borderTop + paddingTop;
892 int bottomTotal = marginBottom + borderBottom + paddingBottom;
893
894 if (marginRect != wxRect())
895 {
896 contentRect.x = marginRect.x + leftTotal;
897 contentRect.y = marginRect.y + topTotal;
898 contentRect.width = marginRect.width - (leftTotal + rightTotal);
899 contentRect.height = marginRect.height - (topTotal + bottomTotal);
900 }
901 else
902 {
903 marginRect.x = contentRect.x - leftTotal;
904 marginRect.y = contentRect.y - topTotal;
905 marginRect.width = contentRect.width + (leftTotal + rightTotal);
906 marginRect.height = contentRect.height + (topTotal + bottomTotal);
907 }
908
909 borderRect.x = marginRect.x + marginLeft;
910 borderRect.y = marginRect.y + marginTop;
911 borderRect.width = marginRect.width - (marginLeft + marginRight);
912 borderRect.height = marginRect.height - (marginTop + marginBottom);
913
914 paddingRect.x = marginRect.x + marginLeft + borderLeft;
915 paddingRect.y = marginRect.y + marginTop + borderTop;
916 paddingRect.width = marginRect.width - (marginLeft + marginRight + borderLeft + borderRight);
917 paddingRect.height = marginRect.height - (marginTop + marginBottom + borderTop + borderBottom);
918
919 // The outline is outside the margin and doesn't influence the overall box position or content size.
920 outlineRect.x = marginRect.x - outlineLeft;
921 outlineRect.y = marginRect.y - outlineTop;
922 outlineRect.width = marginRect.width + (outlineLeft + outlineRight);
923 outlineRect.height = marginRect.height + (outlineTop + outlineBottom);
924
925 return true;
926 }
927
928 // Get the total margin for the object in pixels, taking into account margin, padding and border size
929 bool wxRichTextObject::GetTotalMargin(wxDC& dc, wxRichTextBuffer* buffer, const wxRichTextAttr& attr, int& leftMargin, int& rightMargin,
930 int& topMargin, int& bottomMargin)
931 {
932 // Assume boxRect is the area around the content
933 wxRect contentRect, marginRect, borderRect, paddingRect, outlineRect;
934 marginRect = wxRect(0, 0, 1000, 1000);
935
936 GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
937
938 leftMargin = contentRect.GetLeft() - marginRect.GetLeft();
939 rightMargin = marginRect.GetRight() - contentRect.GetRight();
940 topMargin = contentRect.GetTop() - marginRect.GetTop();
941 bottomMargin = marginRect.GetBottom() - contentRect.GetBottom();
942
943 return true;
944 }
945
946 // Returns the rectangle which the child has available to it given restrictions specified in the
947 // child attribute, e.g. 50% width of the parent, 400 pixels, x position 20% of the parent, etc.
948 // availableContainerSpace might be a parent that the cell has to compute its width relative to.
949 // E.g. a cell that's 50% of its parent.
950 wxRect wxRichTextObject::AdjustAvailableSpace(wxDC& dc, wxRichTextBuffer* buffer, const wxRichTextAttr& WXUNUSED(parentAttr), const wxRichTextAttr& childAttr, const wxRect& availableParentSpace, const wxRect& availableContainerSpace)
951 {
952 wxRect rect = availableParentSpace;
953 double scale = 1.0;
954 if (buffer)
955 scale = buffer->GetScale();
956
957 wxTextAttrDimensionConverter converter(dc, scale, availableContainerSpace.GetSize());
958
959 if (childAttr.GetTextBoxAttr().GetWidth().IsValid())
960 rect.width = converter.GetPixels(childAttr.GetTextBoxAttr().GetWidth());
961
962 if (childAttr.GetTextBoxAttr().GetHeight().IsValid())
963 rect.height = converter.GetPixels(childAttr.GetTextBoxAttr().GetHeight());
964
965 // Can specify either left or right for the position (we're assuming we can't
966 // set the left and right edges to effectively set the size. Would we want to do that?)
967 if (childAttr.GetTextBoxAttr().GetPosition().GetLeft().IsValid())
968 {
969 rect.x = rect.x + converter.GetPixels(childAttr.GetTextBoxAttr().GetPosition().GetLeft());
970 }
971 else if (childAttr.GetTextBoxAttr().GetPosition().GetRight().IsValid())
972 {
973 int x = converter.GetPixels(childAttr.GetTextBoxAttr().GetPosition().GetRight());
974 if (childAttr.GetTextBoxAttr().GetPosition().GetRight().GetPosition() == wxTEXT_BOX_ATTR_POSITION_RELATIVE)
975 rect.x = availableContainerSpace.x + availableContainerSpace.width - rect.width;
976 else
977 rect.x += x;
978 }
979
980 if (childAttr.GetTextBoxAttr().GetPosition().GetTop().IsValid())
981 {
982 rect.y = rect.y + converter.GetPixels(childAttr.GetTextBoxAttr().GetPosition().GetTop());
983 }
984 else if (childAttr.GetTextBoxAttr().GetPosition().GetBottom().IsValid())
985 {
986 int y = converter.GetPixels(childAttr.GetTextBoxAttr().GetPosition().GetBottom());
987 if (childAttr.GetTextBoxAttr().GetPosition().GetBottom().GetPosition() == wxTEXT_BOX_ATTR_POSITION_RELATIVE)
988 rect.y = availableContainerSpace.y + availableContainerSpace.height - rect.height;
989 else
990 rect.y += y;
991 }
992
993 if (rect.GetWidth() > availableParentSpace.GetWidth())
994 rect.SetWidth(availableParentSpace.GetWidth());
995
996 return rect;
997 }
998
999 // Dump to output stream for debugging
1000 void wxRichTextObject::Dump(wxTextOutputStream& stream)
1001 {
1002 stream << GetClassInfo()->GetClassName() << wxT("\n");
1003 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");
1004 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");
1005 }
1006
1007 // Gets the containing buffer
1008 wxRichTextBuffer* wxRichTextObject::GetBuffer() const
1009 {
1010 const wxRichTextObject* obj = this;
1011 while (obj && !wxDynamicCast(obj, wxRichTextBuffer))
1012 obj = obj->GetParent();
1013 return wxDynamicCast(obj, wxRichTextBuffer);
1014 }
1015
1016 // Get the absolute object position, by traversing up the child/parent hierarchy
1017 wxPoint wxRichTextObject::GetAbsolutePosition() const
1018 {
1019 wxPoint pt = GetPosition();
1020
1021 wxRichTextObject* p = GetParent();
1022 while (p)
1023 {
1024 pt = pt + p->GetPosition();
1025 p = p->GetParent();
1026 }
1027
1028 return pt;
1029 }
1030
1031 // Hit-testing: returns a flag indicating hit test details, plus
1032 // information about position
1033 int wxRichTextObject::HitTest(wxDC& WXUNUSED(dc), wxRichTextDrawingContext& WXUNUSED(context), const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int WXUNUSED(flags))
1034 {
1035 if (!IsShown())
1036 return wxRICHTEXT_HITTEST_NONE;
1037
1038 wxRect rect = GetRect();
1039 if (pt.x >= rect.x && pt.x < rect.x + rect.width &&
1040 pt.y >= rect.y && pt.y < rect.y + rect.height)
1041 {
1042 *obj = this;
1043 *contextObj = GetParentContainer();
1044 textPosition = GetRange().GetStart();
1045 return wxRICHTEXT_HITTEST_ON;
1046 }
1047 else
1048 return wxRICHTEXT_HITTEST_NONE;
1049 }
1050
1051 // Lays out the object first with a given amount of space, and then if no width was specified in attr,
1052 // lays out the object again using the maximum ('best') size
1053 bool wxRichTextObject::LayoutToBestSize(wxDC& dc, wxRichTextDrawingContext& context, wxRichTextBuffer* buffer,
1054 const wxRichTextAttr& parentAttr, const wxRichTextAttr& attr,
1055 const wxRect& availableParentSpace, const wxRect& availableContainerSpace,
1056 int style)
1057 {
1058 wxRect availableChildRect = AdjustAvailableSpace(dc, buffer, parentAttr, attr, availableParentSpace, availableContainerSpace);
1059 wxRect originalAvailableRect = availableChildRect;
1060 Layout(dc, context, availableChildRect, availableContainerSpace, style);
1061
1062 wxSize maxSize = GetMaxSize();
1063
1064 // Don't ignore if maxSize.x is zero, since we need to redo the paragraph's lines
1065 // on this basis
1066 if (!attr.GetTextBoxAttr().GetWidth().IsValid() && maxSize.x < availableChildRect.width)
1067 {
1068 // Redo the layout with a fixed, minimum size this time.
1069 Invalidate(wxRICHTEXT_ALL);
1070 wxRichTextAttr newAttr(attr);
1071 newAttr.GetTextBoxAttr().GetWidth().SetValue(maxSize.x, wxTEXT_ATTR_UNITS_PIXELS);
1072 newAttr.GetTextBoxAttr().GetWidth().SetPosition(wxTEXT_BOX_ATTR_POSITION_ABSOLUTE);
1073
1074 availableChildRect = AdjustAvailableSpace(dc, buffer, parentAttr, newAttr, availableParentSpace, availableContainerSpace);
1075
1076 // If a paragraph, align the whole paragraph.
1077 // Problem with this: if we're limited by a floating object, a line may be centered
1078 // w.r.t. the smaller resulting box rather than the actual available width.
1079 if (attr.HasAlignment() && !GetContainer()->GetFloatCollector()->HasFloats()) // FIXME: aligning whole paragraph not compatible with floating objects
1080 {
1081 // centering, right-justification
1082 if (attr.GetAlignment() == wxTEXT_ALIGNMENT_CENTRE)
1083 {
1084 availableChildRect.x = (originalAvailableRect.GetWidth() - availableChildRect.GetWidth())/2 + availableChildRect.x;
1085 }
1086 else if (attr.GetAlignment() == wxTEXT_ALIGNMENT_RIGHT)
1087 {
1088 availableChildRect.x = availableChildRect.x + originalAvailableRect.GetWidth() - availableChildRect.GetWidth();
1089 }
1090 }
1091
1092 Layout(dc, context, availableChildRect, availableContainerSpace, style);
1093 }
1094
1095 /*
1096 __________________
1097 | ____________ |
1098 | | | |
1099
1100
1101 */
1102
1103 return true;
1104 }
1105
1106 // Move the object recursively, by adding the offset from old to new
1107 void wxRichTextObject::Move(const wxPoint& pt)
1108 {
1109 SetPosition(pt);
1110 }
1111
1112
1113 /*!
1114 * wxRichTextCompositeObject
1115 * This is the base for drawable objects.
1116 */
1117
1118 IMPLEMENT_CLASS(wxRichTextCompositeObject, wxRichTextObject)
1119
1120 wxRichTextCompositeObject::wxRichTextCompositeObject(wxRichTextObject* parent):
1121 wxRichTextObject(parent)
1122 {
1123 }
1124
1125 wxRichTextCompositeObject::~wxRichTextCompositeObject()
1126 {
1127 DeleteChildren();
1128 }
1129
1130 /// Get the nth child
1131 wxRichTextObject* wxRichTextCompositeObject::GetChild(size_t n) const
1132 {
1133 wxASSERT ( n < m_children.GetCount() );
1134
1135 return m_children.Item(n)->GetData();
1136 }
1137
1138 /// Append a child, returning the position
1139 size_t wxRichTextCompositeObject::AppendChild(wxRichTextObject* child)
1140 {
1141 m_children.Append(child);
1142 child->SetParent(this);
1143 return m_children.GetCount() - 1;
1144 }
1145
1146 /// Insert the child in front of the given object, or at the beginning
1147 bool wxRichTextCompositeObject::InsertChild(wxRichTextObject* child, wxRichTextObject* inFrontOf)
1148 {
1149 if (inFrontOf)
1150 {
1151 wxRichTextObjectList::compatibility_iterator node = m_children.Find(inFrontOf);
1152 m_children.Insert(node, child);
1153 }
1154 else
1155 m_children.Insert(child);
1156 child->SetParent(this);
1157
1158 return true;
1159 }
1160
1161 /// Delete the child
1162 bool wxRichTextCompositeObject::RemoveChild(wxRichTextObject* child, bool deleteChild)
1163 {
1164 wxRichTextObjectList::compatibility_iterator node = m_children.Find(child);
1165 if (node)
1166 {
1167 wxRichTextObject* obj = node->GetData();
1168 m_children.Erase(node);
1169 if (deleteChild)
1170 delete obj;
1171
1172 return true;
1173 }
1174 return false;
1175 }
1176
1177 /// Delete all children
1178 bool wxRichTextCompositeObject::DeleteChildren()
1179 {
1180 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1181 while (node)
1182 {
1183 wxRichTextObjectList::compatibility_iterator oldNode = node;
1184
1185 wxRichTextObject* child = node->GetData();
1186 child->Dereference(); // Only delete if reference count is zero
1187
1188 node = node->GetNext();
1189 m_children.Erase(oldNode);
1190 }
1191
1192 return true;
1193 }
1194
1195 /// Get the child count
1196 size_t wxRichTextCompositeObject::GetChildCount() const
1197 {
1198 return m_children.GetCount();
1199 }
1200
1201 /// Copy
1202 void wxRichTextCompositeObject::Copy(const wxRichTextCompositeObject& obj)
1203 {
1204 wxRichTextObject::Copy(obj);
1205
1206 DeleteChildren();
1207
1208 wxRichTextObjectList::compatibility_iterator node = obj.m_children.GetFirst();
1209 while (node)
1210 {
1211 wxRichTextObject* child = node->GetData();
1212 wxRichTextObject* newChild = child->Clone();
1213 newChild->SetParent(this);
1214 m_children.Append(newChild);
1215
1216 node = node->GetNext();
1217 }
1218 }
1219
1220 /// Hit-testing: returns a flag indicating hit test details, plus
1221 /// information about position
1222 int wxRichTextCompositeObject::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int flags)
1223 {
1224 if (!IsShown())
1225 return wxRICHTEXT_HITTEST_NONE;
1226
1227 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1228 while (node)
1229 {
1230 wxRichTextObject* child = node->GetData();
1231
1232 if (child->IsShown() && child->IsTopLevel() && (flags & wxRICHTEXT_HITTEST_NO_NESTED_OBJECTS))
1233 {
1234 // Just check if we hit the overall object
1235 int ret = child->wxRichTextObject::HitTest(dc, context, pt, textPosition, obj, contextObj, flags);
1236 if (ret != wxRICHTEXT_HITTEST_NONE)
1237 return ret;
1238 }
1239 else if (child->IsShown())
1240 {
1241 int ret = child->HitTest(dc, context, pt, textPosition, obj, contextObj, flags);
1242 if (ret != wxRICHTEXT_HITTEST_NONE)
1243 return ret;
1244 }
1245
1246 node = node->GetNext();
1247 }
1248
1249 return wxRICHTEXT_HITTEST_NONE;
1250 }
1251
1252 /// Finds the absolute position and row height for the given character position
1253 bool wxRichTextCompositeObject::FindPosition(wxDC& dc, wxRichTextDrawingContext& context, long index, wxPoint& pt, int* height, bool forceLineStart)
1254 {
1255 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1256 while (node)
1257 {
1258 wxRichTextObject* child = node->GetData();
1259
1260 // Don't recurse if the child is a top-level object,
1261 // such as a text box, because the character position will no longer
1262 // apply. By definition, a top-level object has its own range of
1263 // character positions.
1264 if (!child->IsTopLevel() && child->FindPosition(dc, context, index, pt, height, forceLineStart))
1265 return true;
1266
1267 node = node->GetNext();
1268 }
1269
1270 return false;
1271 }
1272
1273 /// Calculate range
1274 void wxRichTextCompositeObject::CalculateRange(long start, long& end)
1275 {
1276 long current = start;
1277 long lastEnd = current;
1278
1279 if (IsTopLevel())
1280 {
1281 current = 0;
1282 lastEnd = 0;
1283 }
1284
1285 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1286 while (node)
1287 {
1288 wxRichTextObject* child = node->GetData();
1289 long childEnd = 0;
1290
1291 child->CalculateRange(current, childEnd);
1292 lastEnd = childEnd;
1293
1294 current = childEnd + 1;
1295
1296 node = node->GetNext();
1297 }
1298
1299 if (IsTopLevel())
1300 {
1301 // A top-level object always has a range of size 1,
1302 // because its children don't count at this level.
1303 end = start;
1304 m_range.SetRange(start, start);
1305
1306 // An object with no children has zero length
1307 if (m_children.GetCount() == 0)
1308 lastEnd --;
1309 m_ownRange.SetRange(0, lastEnd);
1310 }
1311 else
1312 {
1313 end = lastEnd;
1314
1315 // An object with no children has zero length
1316 if (m_children.GetCount() == 0)
1317 end --;
1318
1319 m_range.SetRange(start, end);
1320 }
1321 }
1322
1323 /// Delete range from layout.
1324 bool wxRichTextCompositeObject::DeleteRange(const wxRichTextRange& range)
1325 {
1326 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1327
1328 while (node)
1329 {
1330 wxRichTextObject* obj = (wxRichTextObject*) node->GetData();
1331 wxRichTextObjectList::compatibility_iterator next = node->GetNext();
1332
1333 // Delete the range in each paragraph
1334
1335 // When a chunk has been deleted, internally the content does not
1336 // now match the ranges.
1337 // However, so long as deletion is not done on the same object twice this is OK.
1338 // If you may delete content from the same object twice, recalculate
1339 // the ranges inbetween DeleteRange calls by calling CalculateRanges, and
1340 // adjust the range you're deleting accordingly.
1341
1342 if (!obj->GetRange().IsOutside(range))
1343 {
1344 // No need to delete within a top-level object; just removing this object will do fine
1345 if (!obj->IsTopLevel())
1346 obj->DeleteRange(range);
1347
1348 // Delete an empty object, or paragraph within this range.
1349 if (obj->IsEmpty() ||
1350 (range.GetStart() <= obj->GetRange().GetStart() && range.GetEnd() >= obj->GetRange().GetEnd()))
1351 {
1352 // An empty paragraph has length 1, so won't be deleted unless the
1353 // whole range is deleted.
1354 RemoveChild(obj, true);
1355 }
1356 }
1357
1358 node = next;
1359 }
1360
1361 return true;
1362 }
1363
1364 /// Get any text in this object for the given range
1365 wxString wxRichTextCompositeObject::GetTextForRange(const wxRichTextRange& range) const
1366 {
1367 wxString text;
1368 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1369 while (node)
1370 {
1371 wxRichTextObject* child = node->GetData();
1372 wxRichTextRange childRange = range;
1373 if (!child->GetRange().IsOutside(range))
1374 {
1375 childRange.LimitTo(child->GetRange());
1376
1377 wxString childText = child->GetTextForRange(childRange);
1378
1379 text += childText;
1380 }
1381 node = node->GetNext();
1382 }
1383
1384 return text;
1385 }
1386
1387 /// Get the child object at the given character position
1388 wxRichTextObject* wxRichTextCompositeObject::GetChildAtPosition(long pos) const
1389 {
1390 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1391 while (node)
1392 {
1393 wxRichTextObject* child = node->GetData();
1394 if (child->GetRange().GetStart() == pos)
1395 return child;
1396 node = node->GetNext();
1397 }
1398 return NULL;
1399 }
1400
1401 /// Recursively merge all pieces that can be merged.
1402 bool wxRichTextCompositeObject::Defragment(const wxRichTextRange& range)
1403 {
1404 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1405 while (node)
1406 {
1407 wxRichTextObject* child = node->GetData();
1408 if (range == wxRICHTEXT_ALL || !child->GetRange().IsOutside(range))
1409 {
1410 wxRichTextCompositeObject* composite = wxDynamicCast(child, wxRichTextCompositeObject);
1411 if (composite)
1412 composite->Defragment();
1413
1414 if (node->GetNext())
1415 {
1416 wxRichTextObject* nextChild = node->GetNext()->GetData();
1417 if (child->CanMerge(nextChild) && child->Merge(nextChild))
1418 {
1419 nextChild->Dereference();
1420 m_children.Erase(node->GetNext());
1421
1422 // Don't set node -- we'll see if we can merge again with the next
1423 // child.
1424 }
1425 else
1426 node = node->GetNext();
1427 }
1428 else
1429 node = node->GetNext();
1430 }
1431 else
1432 node = node->GetNext();
1433 }
1434
1435 // Delete any remaining empty objects, but leave at least one empty object per composite object.
1436 if (GetChildCount() > 1)
1437 {
1438 node = m_children.GetFirst();
1439 while (node)
1440 {
1441 wxRichTextObjectList::compatibility_iterator next = node->GetNext();
1442 wxRichTextObject* child = node->GetData();
1443 if (range == wxRICHTEXT_ALL || !child->GetRange().IsOutside(range))
1444 {
1445 if (child->IsEmpty())
1446 {
1447 child->Dereference();
1448 m_children.Erase(node);
1449 }
1450 node = next;
1451 }
1452 else
1453 node = node->GetNext();
1454 }
1455 }
1456
1457 return true;
1458 }
1459
1460 /// Dump to output stream for debugging
1461 void wxRichTextCompositeObject::Dump(wxTextOutputStream& stream)
1462 {
1463 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1464 while (node)
1465 {
1466 wxRichTextObject* child = node->GetData();
1467 child->Dump(stream);
1468 node = node->GetNext();
1469 }
1470 }
1471
1472 /// Get/set the object size for the given range. Returns false if the range
1473 /// is invalid for this object.
1474 bool wxRichTextCompositeObject::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, wxPoint position, wxArrayInt* partialExtents) const
1475 {
1476 if (!range.IsWithin(GetRange()))
1477 return false;
1478
1479 wxSize sz;
1480
1481 wxArrayInt childExtents;
1482 wxArrayInt* p;
1483 if (partialExtents)
1484 p = & childExtents;
1485 else
1486 p = NULL;
1487
1488 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1489 while (node)
1490 {
1491 wxRichTextObject* child = node->GetData();
1492 if (!child->GetRange().IsOutside(range))
1493 {
1494 // Floating objects have a zero size within the paragraph.
1495 if (child->IsFloating())
1496 {
1497 if (partialExtents)
1498 {
1499 int lastSize;
1500 if (partialExtents->GetCount() > 0)
1501 lastSize = (*partialExtents)[partialExtents->GetCount()-1];
1502 else
1503 lastSize = 0;
1504
1505 partialExtents->Add(0 /* zero size */ + lastSize);
1506 }
1507 }
1508 else
1509 {
1510 wxSize childSize;
1511
1512 wxRichTextRange rangeToUse = range;
1513 rangeToUse.LimitTo(child->GetRange());
1514 if (child->IsTopLevel())
1515 rangeToUse = child->GetOwnRange();
1516
1517 int childDescent = 0;
1518
1519 // At present wxRICHTEXT_HEIGHT_ONLY is only fast if we're already cached the size,
1520 // but it's only going to be used after caching has taken place.
1521 if ((flags & wxRICHTEXT_HEIGHT_ONLY) && child->GetCachedSize().y != 0)
1522 {
1523 childDescent = child->GetDescent();
1524 childSize = child->GetCachedSize();
1525
1526 sz.y = wxMax(sz.y, childSize.y);
1527 sz.x += childSize.x;
1528 descent = wxMax(descent, childDescent);
1529 }
1530 else if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, context, flags, wxPoint(position.x + sz.x, position.y), p))
1531 {
1532 sz.y = wxMax(sz.y, childSize.y);
1533 sz.x += childSize.x;
1534 descent = wxMax(descent, childDescent);
1535
1536 if ((flags & wxRICHTEXT_CACHE_SIZE) && (rangeToUse == child->GetRange() || child->IsTopLevel()))
1537 {
1538 child->SetCachedSize(childSize);
1539 child->SetDescent(childDescent);
1540 }
1541
1542 if (partialExtents)
1543 {
1544 int lastSize;
1545 if (partialExtents->GetCount() > 0)
1546 lastSize = (*partialExtents)[partialExtents->GetCount()-1];
1547 else
1548 lastSize = 0;
1549
1550 size_t i;
1551 for (i = 0; i < childExtents.GetCount(); i++)
1552 {
1553 partialExtents->Add(childExtents[i] + lastSize);
1554 }
1555 }
1556 }
1557 }
1558
1559 if (p)
1560 p->Clear();
1561 }
1562
1563 node = node->GetNext();
1564 }
1565 size = sz;
1566 return true;
1567 }
1568
1569 // Invalidate the buffer. With no argument, invalidates whole buffer.
1570 void wxRichTextCompositeObject::Invalidate(const wxRichTextRange& invalidRange)
1571 {
1572 wxRichTextObject::Invalidate(invalidRange);
1573
1574 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1575 while (node)
1576 {
1577 wxRichTextObject* child = node->GetData();
1578 if (invalidRange != wxRICHTEXT_ALL && invalidRange != wxRICHTEXT_NONE && child->GetRange().IsOutside(invalidRange))
1579 {
1580 // Skip
1581 }
1582 else if (child->IsTopLevel())
1583 {
1584 if (invalidRange == wxRICHTEXT_NONE)
1585 child->Invalidate(wxRICHTEXT_NONE);
1586 else
1587 child->Invalidate(wxRICHTEXT_ALL); // All children must be invalidated if within parent range
1588 }
1589 else
1590 child->Invalidate(invalidRange);
1591 node = node->GetNext();
1592 }
1593 }
1594
1595 // Move the object recursively, by adding the offset from old to new
1596 void wxRichTextCompositeObject::Move(const wxPoint& pt)
1597 {
1598 wxPoint oldPos = GetPosition();
1599 SetPosition(pt);
1600 wxPoint offset = pt - oldPos;
1601
1602 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1603 while (node)
1604 {
1605 wxRichTextObject* child = node->GetData();
1606 wxPoint childPos = child->GetPosition() + offset;
1607 child->Move(childPos);
1608 node = node->GetNext();
1609 }
1610 }
1611
1612
1613 /*!
1614 * wxRichTextParagraphLayoutBox
1615 * This box knows how to lay out paragraphs.
1616 */
1617
1618 IMPLEMENT_DYNAMIC_CLASS(wxRichTextParagraphLayoutBox, wxRichTextCompositeObject)
1619
1620 wxRichTextParagraphLayoutBox::wxRichTextParagraphLayoutBox(wxRichTextObject* parent):
1621 wxRichTextCompositeObject(parent)
1622 {
1623 Init();
1624 }
1625
1626 wxRichTextParagraphLayoutBox::~wxRichTextParagraphLayoutBox()
1627 {
1628 if (m_floatCollector)
1629 {
1630 delete m_floatCollector;
1631 m_floatCollector = NULL;
1632 }
1633 }
1634
1635 /// Initialize the object.
1636 void wxRichTextParagraphLayoutBox::Init()
1637 {
1638 m_ctrl = NULL;
1639
1640 // For now, assume is the only box and has no initial size.
1641 m_range = wxRichTextRange(0, -1);
1642 m_ownRange = wxRichTextRange(0, -1);
1643
1644 m_invalidRange = wxRICHTEXT_ALL;
1645
1646 m_partialParagraph = false;
1647 m_floatCollector = NULL;
1648 }
1649
1650 void wxRichTextParagraphLayoutBox::Clear()
1651 {
1652 DeleteChildren();
1653
1654 if (m_floatCollector)
1655 delete m_floatCollector;
1656 m_floatCollector = NULL;
1657 m_partialParagraph = false;
1658 }
1659
1660 /// Copy
1661 void wxRichTextParagraphLayoutBox::Copy(const wxRichTextParagraphLayoutBox& obj)
1662 {
1663 Clear();
1664
1665 wxRichTextCompositeObject::Copy(obj);
1666
1667 m_partialParagraph = obj.m_partialParagraph;
1668 m_defaultAttributes = obj.m_defaultAttributes;
1669 }
1670
1671 // Gather information about floating objects; only gather floats for those paragraphs that
1672 // will not be formatted again due to optimization, after which floats will be gathered per-paragraph
1673 // during layout.
1674 bool wxRichTextParagraphLayoutBox::UpdateFloatingObjects(const wxRect& availableRect, wxRichTextObject* untilObj)
1675 {
1676 if (m_floatCollector != NULL)
1677 delete m_floatCollector;
1678 m_floatCollector = new wxRichTextFloatCollector(availableRect);
1679 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1680 // Only gather floats up to the point we'll start formatting paragraphs.
1681 while (untilObj && node && node->GetData() != untilObj)
1682 {
1683 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
1684 wxASSERT (child != NULL);
1685 if (child)
1686 m_floatCollector->CollectFloat(child);
1687 node = node->GetNext();
1688 }
1689
1690 return true;
1691 }
1692
1693 // Returns the style sheet associated with the overall buffer.
1694 wxRichTextStyleSheet* wxRichTextParagraphLayoutBox::GetStyleSheet() const
1695 {
1696 return GetBuffer() ? GetBuffer()->GetStyleSheet() : (wxRichTextStyleSheet*) NULL;
1697 }
1698
1699 // Get the number of floating objects at this level
1700 int wxRichTextParagraphLayoutBox::GetFloatingObjectCount() const
1701 {
1702 if (m_floatCollector)
1703 return m_floatCollector->GetFloatingObjectCount();
1704 else
1705 return 0;
1706 }
1707
1708 // Get a list of floating objects
1709 bool wxRichTextParagraphLayoutBox::GetFloatingObjects(wxRichTextObjectList& objects) const
1710 {
1711 if (m_floatCollector)
1712 {
1713 return m_floatCollector->GetFloatingObjects(objects);
1714 }
1715 else
1716 return false;
1717 }
1718
1719 // Calculate ranges
1720 void wxRichTextParagraphLayoutBox::UpdateRanges()
1721 {
1722 long start = 0;
1723 if (GetParent())
1724 start = GetRange().GetStart();
1725 long end;
1726 CalculateRange(start, end);
1727 }
1728
1729 // HitTest
1730 int wxRichTextParagraphLayoutBox::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int flags)
1731 {
1732 if (!IsShown())
1733 return wxRICHTEXT_HITTEST_NONE;
1734
1735 int ret = wxRICHTEXT_HITTEST_NONE;
1736 if (m_floatCollector && (flags & wxRICHTEXT_HITTEST_NO_FLOATING_OBJECTS) == 0)
1737 ret = m_floatCollector->HitTest(dc, context, pt, textPosition, obj, flags);
1738
1739 if (ret == wxRICHTEXT_HITTEST_NONE)
1740 return wxRichTextCompositeObject::HitTest(dc, context, pt, textPosition, obj, contextObj, flags);
1741 else
1742 {
1743 *contextObj = this;
1744 return ret;
1745 }
1746 }
1747
1748 /// Draw the floating objects
1749 void wxRichTextParagraphLayoutBox::DrawFloats(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
1750 {
1751 if (m_floatCollector)
1752 m_floatCollector->Draw(dc, context, range, selection, rect, descent, style);
1753 }
1754
1755 void wxRichTextParagraphLayoutBox::MoveAnchoredObjectToParagraph(wxRichTextParagraph* from, wxRichTextParagraph* to, wxRichTextObject* obj)
1756 {
1757 if (from == to)
1758 return;
1759
1760 from->RemoveChild(obj);
1761 to->AppendChild(obj);
1762 }
1763
1764 /// Draw the item
1765 bool wxRichTextParagraphLayoutBox::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
1766 {
1767 if (!IsShown())
1768 return true;
1769
1770 wxRect thisRect(GetPosition(), GetCachedSize());
1771
1772 wxRichTextAttr attr(GetAttributes());
1773 context.ApplyVirtualAttributes(attr, this);
1774
1775 int flags = style;
1776 if (selection.IsValid() && GetParentContainer() != this && selection.WithinSelection(GetRange().GetStart(), GetParentContainer()))
1777 flags |= wxRICHTEXT_DRAW_SELECTED;
1778
1779 // Don't draw guidelines if at top level
1780 int theseFlags = flags;
1781 if (!GetParent())
1782 theseFlags &= ~wxRICHTEXT_DRAW_GUIDELINES;
1783 DrawBoxAttributes(dc, GetBuffer(), attr, thisRect, theseFlags);
1784
1785 DrawFloats(dc, context, range, selection, rect, descent, style);
1786 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1787 while (node)
1788 {
1789 wxRichTextObject* child = node->GetData();
1790
1791 if (child && !child->GetRange().IsOutside(range))
1792 {
1793 wxRect childRect(child->GetPosition(), child->GetCachedSize());
1794 wxRichTextRange childRange = range;
1795 if (child->IsTopLevel())
1796 {
1797 childRange = child->GetOwnRange();
1798 }
1799
1800 if (((style & wxRICHTEXT_DRAW_IGNORE_CACHE) == 0) && childRect.GetTop() > rect.GetBottom())
1801 {
1802 // Stop drawing
1803 break;
1804 }
1805 else if (((style & wxRICHTEXT_DRAW_IGNORE_CACHE) == 0) && childRect.GetBottom() < rect.GetTop())
1806 {
1807 // Skip
1808 }
1809 else
1810 child->Draw(dc, context, childRange, selection, rect, descent, style);
1811 }
1812
1813 node = node->GetNext();
1814 }
1815 return true;
1816 }
1817
1818 /// Lay the item out
1819 bool wxRichTextParagraphLayoutBox::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& parentRect, int style)
1820 {
1821 SetPosition(rect.GetPosition());
1822
1823 if (!IsShown())
1824 return true;
1825
1826 wxRect availableSpace;
1827 bool formatRect = (style & wxRICHTEXT_LAYOUT_SPECIFIED_RECT) == wxRICHTEXT_LAYOUT_SPECIFIED_RECT;
1828
1829 wxRichTextAttr attr(GetAttributes());
1830 context.ApplyVirtualAttributes(attr, this);
1831
1832 // If only laying out a specific area, the passed rect has a different meaning:
1833 // the visible part of the buffer. This is used in wxRichTextCtrl::OnSize,
1834 // so that during a size, only the visible part will be relaid out, or
1835 // it would take too long causing flicker. As an approximation, we assume that
1836 // everything up to the start of the visible area is laid out correctly.
1837 if (formatRect)
1838 {
1839 wxRect rect2(0, 0, rect.width, rect.height);
1840 availableSpace = GetAvailableContentArea(dc, context, rect2);
1841
1842 // Invalidate the part of the buffer from the first visible line
1843 // to the end. If other parts of the buffer are currently invalid,
1844 // then they too will be taken into account if they are above
1845 // the visible point.
1846 long startPos = 0;
1847 wxRichTextLine* line = GetLineAtYPosition(rect.y);
1848 if (line)
1849 startPos = line->GetAbsoluteRange().GetStart();
1850
1851 Invalidate(wxRichTextRange(startPos, GetOwnRange().GetEnd()));
1852 }
1853 else
1854 {
1855 availableSpace = GetAvailableContentArea(dc, context, rect);
1856 }
1857
1858 // Fix the width if we're at the top level
1859 if (!GetParent())
1860 attr.GetTextBoxAttr().GetWidth().SetValue(rect.GetWidth(), wxTEXT_ATTR_UNITS_PIXELS);
1861
1862 int leftMargin, rightMargin, topMargin, bottomMargin;
1863 wxRichTextObject::GetTotalMargin(dc, GetBuffer(), attr, leftMargin, rightMargin,
1864 topMargin, bottomMargin);
1865
1866 int maxWidth = 0;
1867 int maxHeight = 0;
1868
1869 // The maximum paragraph maximum width, so we can set the overall maximum width for this object
1870 int maxMaxWidth = 0;
1871
1872 // The maximum paragraph minimum width, so we can set the overall minimum width for this object
1873 int maxMinWidth = 0;
1874
1875 // If we have vertical alignment, we must recalculate everything.
1876 bool hasVerticalAlignment = (attr.GetTextBoxAttr().HasVerticalAlignment() &&
1877 (attr.GetTextBoxAttr().GetVerticalAlignment() > wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT_TOP));
1878
1879 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1880
1881 bool layoutAll = true;
1882
1883 // Get invalid range, rounding to paragraph start/end.
1884 wxRichTextRange invalidRange = GetInvalidRange(true);
1885
1886 if (invalidRange == wxRICHTEXT_NONE && !formatRect)
1887 return true;
1888
1889 if (invalidRange == wxRICHTEXT_ALL || hasVerticalAlignment)
1890 layoutAll = true;
1891 else // If we know what range is affected, start laying out from that point on.
1892 if (invalidRange.GetStart() >= GetOwnRange().GetStart())
1893 {
1894 wxRichTextParagraph* firstParagraph = GetParagraphAtPosition(invalidRange.GetStart());
1895 if (firstParagraph)
1896 {
1897 wxRichTextObjectList::compatibility_iterator firstNode = m_children.Find(firstParagraph);
1898 wxRichTextObjectList::compatibility_iterator previousNode;
1899 if ( firstNode )
1900 previousNode = firstNode->GetPrevious();
1901 if (firstNode)
1902 {
1903 if (previousNode)
1904 {
1905 wxRichTextParagraph* previousParagraph = wxDynamicCast(previousNode->GetData(), wxRichTextParagraph);
1906 availableSpace.y = previousParagraph->GetPosition().y + previousParagraph->GetCachedSize().y;
1907 }
1908
1909 // Now we're going to start iterating from the first affected paragraph.
1910 node = firstNode;
1911
1912 layoutAll = false;
1913 }
1914 }
1915 }
1916
1917 // Gather information about only those floating objects that will not be formatted,
1918 // after which floats will be gathered per-paragraph during layout.
1919 UpdateFloatingObjects(availableSpace, node ? node->GetData() : (wxRichTextObject*) NULL);
1920
1921 // A way to force speedy rest-of-buffer layout (the 'else' below)
1922 bool forceQuickLayout = false;
1923
1924 // First get the size of the paragraphs we won't be laying out
1925 wxRichTextObjectList::compatibility_iterator n = m_children.GetFirst();
1926 while (n && n != node)
1927 {
1928 wxRichTextParagraph* child = wxDynamicCast(n->GetData(), wxRichTextParagraph);
1929 if (child)
1930 {
1931 maxWidth = wxMax(maxWidth, child->GetCachedSize().x);
1932 maxMinWidth = wxMax(maxMinWidth, child->GetMinSize().x);
1933 maxMaxWidth = wxMax(maxMaxWidth, child->GetMaxSize().x);
1934 }
1935 n = n->GetNext();
1936 }
1937
1938 while (node)
1939 {
1940 // Assume this box only contains paragraphs
1941
1942 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
1943 // Unsure if this is needed
1944 // wxCHECK_MSG( child, false, wxT("Unknown object in layout") );
1945
1946 if (child && child->IsShown())
1947 {
1948 // TODO: what if the child hasn't been laid out (e.g. involved in Undo) but still has 'old' lines
1949 if ( !forceQuickLayout &&
1950 (layoutAll ||
1951 child->GetLines().IsEmpty() ||
1952 !child->GetRange().IsOutside(invalidRange)) )
1953 {
1954 // Lays out the object first with a given amount of space, and then if no width was specified in attr,
1955 // lays out the object again using the minimum size
1956 child->LayoutToBestSize(dc, context, GetBuffer(),
1957 attr, child->GetAttributes(), availableSpace, rect, style&~wxRICHTEXT_LAYOUT_SPECIFIED_RECT);
1958
1959 // Layout must set the cached size
1960 availableSpace.y += child->GetCachedSize().y;
1961 maxWidth = wxMax(maxWidth, child->GetCachedSize().x);
1962 maxMinWidth = wxMax(maxMinWidth, child->GetMinSize().x);
1963 maxMaxWidth = wxMax(maxMaxWidth, child->GetMaxSize().x);
1964
1965 // If we're just formatting the visible part of the buffer,
1966 // and we're now past the bottom of the window, and we don't have any
1967 // floating objects (since they may cause wrapping to change for the rest of the
1968 // the buffer), start quick layout.
1969 if (!hasVerticalAlignment && formatRect && child->GetPosition().y > rect.GetBottom() && GetFloatingObjectCount() == 0)
1970 forceQuickLayout = true;
1971 }
1972 else
1973 {
1974 // We're outside the immediately affected range, so now let's just
1975 // move everything up or down. This assumes that all the children have previously
1976 // been laid out and have wrapped line lists associated with them.
1977 // TODO: check all paragraphs before the affected range.
1978
1979 int inc = availableSpace.y - child->GetPosition().y;
1980
1981 while (node)
1982 {
1983 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
1984 if (child)
1985 {
1986 if (child->GetLines().GetCount() == 0)
1987 {
1988 // Lays out the object first with a given amount of space, and then if no width was specified in attr,
1989 // lays out the object again using the minimum size
1990 child->LayoutToBestSize(dc, context, GetBuffer(),
1991 attr, child->GetAttributes(), availableSpace, rect, style&~wxRICHTEXT_LAYOUT_SPECIFIED_RECT);
1992
1993 //child->Layout(dc, availableChildRect, style);
1994 }
1995 else
1996 child->Move(wxPoint(child->GetPosition().x, child->GetPosition().y + inc));
1997
1998 availableSpace.y += child->GetCachedSize().y;
1999 maxWidth = wxMax(maxWidth, child->GetCachedSize().x);
2000 maxMinWidth = wxMax(maxMinWidth, child->GetMinSize().x);
2001 maxMaxWidth = wxMax(maxMaxWidth, child->GetMaxSize().x);
2002 }
2003
2004 node = node->GetNext();
2005 }
2006 break;
2007 }
2008 }
2009
2010 node = node->GetNext();
2011 }
2012
2013 node = m_children.GetLast();
2014 if (node && node->GetData()->IsShown())
2015 {
2016 wxRichTextObject* child = node->GetData();
2017 maxHeight = child->GetPosition().y - (GetPosition().y + topMargin) + child->GetCachedSize().y;
2018 }
2019 else
2020 maxHeight = 0; // topMargin + bottomMargin;
2021
2022 // Check the bottom edge of any floating object
2023 if (GetFloatCollector() && GetFloatCollector()->HasFloats())
2024 {
2025 int bottom = GetFloatCollector()->GetLastRectBottom();
2026 if (bottom > maxHeight)
2027 maxHeight = bottom;
2028 }
2029
2030 if (attr.GetTextBoxAttr().GetSize().GetWidth().IsValid())
2031 {
2032 wxRect r = AdjustAvailableSpace(dc, GetBuffer(), wxRichTextAttr() /* not used */, attr, parentRect, parentRect);
2033 int w = r.GetWidth();
2034
2035 // Convert external to content rect
2036 w = w - leftMargin - rightMargin;
2037 maxWidth = wxMax(maxWidth, w);
2038 maxMaxWidth = wxMax(maxMaxWidth, w);
2039 }
2040 else
2041 {
2042 // TODO: Make sure the layout box's position reflects
2043 // the position of the children, but without
2044 // breaking layout of a box within a paragraph.
2045 }
2046
2047 // TODO: (also in para layout) should set the
2048 // object's size to an absolute one if specified,
2049 // but if not specified, calculate it from content.
2050
2051 // We need to add back the margins etc.
2052 {
2053 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
2054 contentRect = wxRect(wxPoint(0, 0), wxSize(maxWidth, maxHeight));
2055 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
2056 SetCachedSize(marginRect.GetSize());
2057 }
2058
2059 // The maximum size is the greatest of all maximum widths for all paragraphs.
2060 {
2061 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
2062 contentRect = wxRect(wxPoint(0, 0), wxSize(maxMaxWidth, maxHeight)); // Actually max height is a lie, we can't know it
2063 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
2064 SetMaxSize(marginRect.GetSize());
2065 }
2066
2067 // The minimum size is the greatest of all minimum widths for all paragraphs.
2068 {
2069 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
2070 contentRect = wxRect(wxPoint(0, 0), wxSize(maxMinWidth, maxHeight)); // Actually max height is a lie, we can't know it
2071 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
2072 SetMinSize(marginRect.GetSize());
2073 }
2074
2075 if (attr.GetTextBoxAttr().HasVerticalAlignment() &&
2076 (attr.GetTextBoxAttr().GetVerticalAlignment() > wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT_TOP))
2077 {
2078 int yOffset = 0;
2079 int leftOverSpace = availableSpace.height - topMargin - bottomMargin - maxHeight;
2080 if (leftOverSpace > 0)
2081 {
2082 if (attr.GetTextBoxAttr().GetVerticalAlignment() == wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT_CENTRE)
2083 {
2084 yOffset = (leftOverSpace/2);
2085 }
2086 else if (attr.GetTextBoxAttr().GetVerticalAlignment() == wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT_BOTTOM)
2087 {
2088 yOffset = leftOverSpace;
2089 }
2090 }
2091
2092 // Move all the children to vertically align the content
2093 // This doesn't take into account floating objects, unfortunately.
2094 if (yOffset != 0)
2095 {
2096 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2097 while (node)
2098 {
2099 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2100 if (child)
2101 child->Move(wxPoint(child->GetPosition().x, child->GetPosition().y + yOffset));
2102
2103 node = node->GetNext();
2104 }
2105 }
2106 }
2107
2108 m_invalidRange = wxRICHTEXT_NONE;
2109
2110 return true;
2111 }
2112
2113 /// Get/set the size for the given range.
2114 bool wxRichTextParagraphLayoutBox::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, wxPoint position, wxArrayInt* WXUNUSED(partialExtents)) const
2115 {
2116 wxSize sz;
2117
2118 wxRichTextObjectList::compatibility_iterator startPara = wxRichTextObjectList::compatibility_iterator();
2119 wxRichTextObjectList::compatibility_iterator endPara = wxRichTextObjectList::compatibility_iterator();
2120
2121 // First find the first paragraph whose starting position is within the range.
2122 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2123 while (node)
2124 {
2125 // child is a paragraph
2126 wxRichTextObject* child = node->GetData();
2127 const wxRichTextRange& r = child->GetRange();
2128
2129 if (r.GetStart() <= range.GetStart() && r.GetEnd() >= range.GetStart())
2130 {
2131 startPara = node;
2132 break;
2133 }
2134
2135 node = node->GetNext();
2136 }
2137
2138 // Next find the last paragraph containing part of the range
2139 node = m_children.GetFirst();
2140 while (node)
2141 {
2142 // child is a paragraph
2143 wxRichTextObject* child = node->GetData();
2144 const wxRichTextRange& r = child->GetRange();
2145
2146 if (r.GetStart() <= range.GetEnd() && r.GetEnd() >= range.GetEnd())
2147 {
2148 endPara = node;
2149 break;
2150 }
2151
2152 node = node->GetNext();
2153 }
2154
2155 if (!startPara || !endPara)
2156 return false;
2157
2158 // Now we can add up the sizes
2159 for (node = startPara; node ; node = node->GetNext())
2160 {
2161 // child is a paragraph
2162 wxRichTextObject* child = node->GetData();
2163 const wxRichTextRange& childRange = child->GetRange();
2164 wxRichTextRange rangeToFind = range;
2165 rangeToFind.LimitTo(childRange);
2166
2167 if (child->IsTopLevel())
2168 rangeToFind = child->GetOwnRange();
2169
2170 wxSize childSize;
2171
2172 int childDescent = 0;
2173 child->GetRangeSize(rangeToFind, childSize, childDescent, dc, context, flags, position);
2174
2175 descent = wxMax(childDescent, descent);
2176
2177 sz.x = wxMax(sz.x, childSize.x);
2178 sz.y += childSize.y;
2179
2180 if (node == endPara)
2181 break;
2182 }
2183
2184 size = sz;
2185
2186 return true;
2187 }
2188
2189 /// Get the paragraph at the given position
2190 wxRichTextParagraph* wxRichTextParagraphLayoutBox::GetParagraphAtPosition(long pos, bool caretPosition) const
2191 {
2192 if (caretPosition)
2193 pos ++;
2194
2195 // First find the first paragraph whose starting position is within the range.
2196 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2197 while (node)
2198 {
2199 // child is a paragraph
2200 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2201 // wxASSERT (child != NULL);
2202
2203 if (child)
2204 {
2205 // Return first child in buffer if position is -1
2206 // if (pos == -1)
2207 // return child;
2208
2209 if (child->GetRange().Contains(pos))
2210 return child;
2211 }
2212
2213 node = node->GetNext();
2214 }
2215 return NULL;
2216 }
2217
2218 /// Get the line at the given position
2219 wxRichTextLine* wxRichTextParagraphLayoutBox::GetLineAtPosition(long pos, bool caretPosition) const
2220 {
2221 if (caretPosition)
2222 pos ++;
2223
2224 // First find the first paragraph whose starting position is within the range.
2225 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2226 while (node)
2227 {
2228 wxRichTextObject* obj = (wxRichTextObject*) node->GetData();
2229 if (obj->GetRange().Contains(pos))
2230 {
2231 // child is a paragraph
2232 wxRichTextParagraph* child = wxDynamicCast(obj, wxRichTextParagraph);
2233 // wxASSERT (child != NULL);
2234
2235 if (child)
2236 {
2237 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
2238 while (node2)
2239 {
2240 wxRichTextLine* line = node2->GetData();
2241
2242 wxRichTextRange range = line->GetAbsoluteRange();
2243
2244 if (range.Contains(pos) ||
2245
2246 // If the position is end-of-paragraph, then return the last line of
2247 // of the paragraph.
2248 ((range.GetEnd() == child->GetRange().GetEnd()-1) && (pos == child->GetRange().GetEnd())))
2249 return line;
2250
2251 node2 = node2->GetNext();
2252 }
2253 }
2254 }
2255
2256 node = node->GetNext();
2257 }
2258
2259 int lineCount = GetLineCount();
2260 if (lineCount > 0)
2261 return GetLineForVisibleLineNumber(lineCount-1);
2262 else
2263 return NULL;
2264 }
2265
2266 /// Get the line at the given y pixel position, or the last line.
2267 wxRichTextLine* wxRichTextParagraphLayoutBox::GetLineAtYPosition(int y) const
2268 {
2269 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2270 while (node)
2271 {
2272 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2273 // wxASSERT (child != NULL);
2274
2275 if (child)
2276 {
2277 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
2278 while (node2)
2279 {
2280 wxRichTextLine* line = node2->GetData();
2281
2282 wxRect rect(line->GetRect());
2283
2284 if (y <= rect.GetBottom())
2285 return line;
2286
2287 node2 = node2->GetNext();
2288 }
2289 }
2290
2291 node = node->GetNext();
2292 }
2293
2294 // Return last line
2295 int lineCount = GetLineCount();
2296 if (lineCount > 0)
2297 return GetLineForVisibleLineNumber(lineCount-1);
2298 else
2299 return NULL;
2300 }
2301
2302 /// Get the number of visible lines
2303 int wxRichTextParagraphLayoutBox::GetLineCount() const
2304 {
2305 int count = 0;
2306
2307 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2308 while (node)
2309 {
2310 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2311 // wxASSERT (child != NULL);
2312
2313 if (child)
2314 count += child->GetLines().GetCount();
2315
2316 node = node->GetNext();
2317 }
2318 return count;
2319 }
2320
2321
2322 /// Get the paragraph for a given line
2323 wxRichTextParagraph* wxRichTextParagraphLayoutBox::GetParagraphForLine(wxRichTextLine* line) const
2324 {
2325 return GetParagraphAtPosition(line->GetAbsoluteRange().GetStart());
2326 }
2327
2328 /// Get the line size at the given position
2329 wxSize wxRichTextParagraphLayoutBox::GetLineSizeAtPosition(long pos, bool caretPosition) const
2330 {
2331 wxRichTextLine* line = GetLineAtPosition(pos, caretPosition);
2332 if (line)
2333 {
2334 return line->GetSize();
2335 }
2336 else
2337 return wxSize(0, 0);
2338 }
2339
2340
2341 /// Convenience function to add a paragraph of text
2342 wxRichTextRange wxRichTextParagraphLayoutBox::AddParagraph(const wxString& text, wxRichTextAttr* paraStyle)
2343 {
2344 // Don't use the base style, just the default style, and the base style will
2345 // be combined at display time.
2346 // Divide into paragraph and character styles.
2347
2348 wxRichTextAttr defaultCharStyle;
2349 wxRichTextAttr defaultParaStyle;
2350
2351 // If the default style is a named paragraph style, don't apply any character formatting
2352 // to the initial text string.
2353 if (GetDefaultStyle().HasParagraphStyleName() && GetStyleSheet())
2354 {
2355 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(GetDefaultStyle().GetParagraphStyleName());
2356 if (def)
2357 defaultParaStyle = def->GetStyleMergedWithBase(GetStyleSheet());
2358 }
2359 else
2360 wxRichTextSplitParaCharStyles(GetDefaultStyle(), defaultParaStyle, defaultCharStyle);
2361
2362 wxRichTextAttr* pStyle = paraStyle ? paraStyle : (wxRichTextAttr*) & defaultParaStyle;
2363 wxRichTextAttr* cStyle = & defaultCharStyle;
2364
2365 wxRichTextParagraph* para = new wxRichTextParagraph(text, this, pStyle, cStyle);
2366 para->GetAttributes().GetTextBoxAttr().Reset();
2367
2368 AppendChild(para);
2369
2370 UpdateRanges();
2371
2372 return para->GetRange();
2373 }
2374
2375 /// Adds multiple paragraphs, based on newlines.
2376 wxRichTextRange wxRichTextParagraphLayoutBox::AddParagraphs(const wxString& text, wxRichTextAttr* paraStyle)
2377 {
2378 // Don't use the base style, just the default style, and the base style will
2379 // be combined at display time.
2380 // Divide into paragraph and character styles.
2381
2382 wxRichTextAttr defaultCharStyle;
2383 wxRichTextAttr defaultParaStyle;
2384
2385 // If the default style is a named paragraph style, don't apply any character formatting
2386 // to the initial text string.
2387 if (GetDefaultStyle().HasParagraphStyleName() && GetStyleSheet())
2388 {
2389 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(GetDefaultStyle().GetParagraphStyleName());
2390 if (def)
2391 defaultParaStyle = def->GetStyleMergedWithBase(GetStyleSheet());
2392 }
2393 else
2394 wxRichTextSplitParaCharStyles(GetDefaultStyle(), defaultParaStyle, defaultCharStyle);
2395
2396 wxRichTextAttr* pStyle = paraStyle ? paraStyle : (wxRichTextAttr*) & defaultParaStyle;
2397 wxRichTextAttr* cStyle = & defaultCharStyle;
2398
2399 wxRichTextParagraph* firstPara = NULL;
2400 wxRichTextParagraph* lastPara = NULL;
2401
2402 wxRichTextRange range(-1, -1);
2403
2404 size_t i = 0;
2405 size_t len = text.length();
2406 wxString line;
2407 wxRichTextParagraph* para = new wxRichTextParagraph(wxEmptyString, this, pStyle, cStyle);
2408 para->GetAttributes().GetTextBoxAttr().Reset();
2409
2410 AppendChild(para);
2411
2412 firstPara = para;
2413 lastPara = para;
2414
2415 while (i < len)
2416 {
2417 wxChar ch = text[i];
2418 if (ch == wxT('\n') || ch == wxT('\r'))
2419 {
2420 if (i != (len-1))
2421 {
2422 wxRichTextPlainText* plainText = (wxRichTextPlainText*) para->GetChildren().GetFirst()->GetData();
2423 plainText->SetText(line);
2424
2425 para = new wxRichTextParagraph(wxEmptyString, this, pStyle, cStyle);
2426 para->GetAttributes().GetTextBoxAttr().Reset();
2427
2428 AppendChild(para);
2429
2430 lastPara = para;
2431 line = wxEmptyString;
2432 }
2433 }
2434 else
2435 line += ch;
2436
2437 i ++;
2438 }
2439
2440 if (!line.empty())
2441 {
2442 wxRichTextPlainText* plainText = (wxRichTextPlainText*) para->GetChildren().GetFirst()->GetData();
2443 plainText->SetText(line);
2444 }
2445
2446 UpdateRanges();
2447
2448 return wxRichTextRange(firstPara->GetRange().GetStart(), lastPara->GetRange().GetEnd());
2449 }
2450
2451 /// Convenience function to add an image
2452 wxRichTextRange wxRichTextParagraphLayoutBox::AddImage(const wxImage& image, wxRichTextAttr* paraStyle)
2453 {
2454 // Don't use the base style, just the default style, and the base style will
2455 // be combined at display time.
2456 // Divide into paragraph and character styles.
2457
2458 wxRichTextAttr defaultCharStyle;
2459 wxRichTextAttr defaultParaStyle;
2460
2461 // If the default style is a named paragraph style, don't apply any character formatting
2462 // to the initial text string.
2463 if (GetDefaultStyle().HasParagraphStyleName() && GetStyleSheet())
2464 {
2465 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(GetDefaultStyle().GetParagraphStyleName());
2466 if (def)
2467 defaultParaStyle = def->GetStyleMergedWithBase(GetStyleSheet());
2468 }
2469 else
2470 wxRichTextSplitParaCharStyles(GetDefaultStyle(), defaultParaStyle, defaultCharStyle);
2471
2472 wxRichTextAttr* pStyle = paraStyle ? paraStyle : (wxRichTextAttr*) & defaultParaStyle;
2473 wxRichTextAttr* cStyle = & defaultCharStyle;
2474
2475 wxRichTextParagraph* para = new wxRichTextParagraph(this, pStyle);
2476 para->GetAttributes().GetTextBoxAttr().Reset();
2477 AppendChild(para);
2478 para->AppendChild(new wxRichTextImage(image, this, cStyle));
2479
2480 UpdateRanges();
2481
2482 return para->GetRange();
2483 }
2484
2485
2486 /// Insert fragment into this box at the given position. If partialParagraph is true,
2487 /// it is assumed that the last (or only) paragraph is just a piece of data with no paragraph
2488 /// marker.
2489
2490 bool wxRichTextParagraphLayoutBox::InsertFragment(long position, wxRichTextParagraphLayoutBox& fragment)
2491 {
2492 // First, find the first paragraph whose starting position is within the range.
2493 wxRichTextParagraph* para = GetParagraphAtPosition(position);
2494 if (para)
2495 {
2496 wxRichTextAttr originalAttr = para->GetAttributes();
2497
2498 wxRichTextObjectList::compatibility_iterator node = m_children.Find(para);
2499
2500 // Now split at this position, returning the object to insert the new
2501 // ones in front of.
2502 wxRichTextObject* nextObject = para->SplitAt(position);
2503
2504 // Special case: partial paragraph, just one paragraph. Might be a small amount of
2505 // text, for example, so let's optimize.
2506
2507 if (fragment.GetPartialParagraph() && fragment.GetChildren().GetCount() == 1)
2508 {
2509 // Add the first para to this para...
2510 wxRichTextObjectList::compatibility_iterator firstParaNode = fragment.GetChildren().GetFirst();
2511 if (!firstParaNode)
2512 return false;
2513
2514 // Iterate through the fragment paragraph inserting the content into this paragraph.
2515 wxRichTextParagraph* firstPara = wxDynamicCast(firstParaNode->GetData(), wxRichTextParagraph);
2516 wxASSERT (firstPara != NULL);
2517
2518 wxRichTextObjectList::compatibility_iterator objectNode = firstPara->GetChildren().GetFirst();
2519 while (objectNode)
2520 {
2521 wxRichTextObject* newObj = objectNode->GetData()->Clone();
2522
2523 if (!nextObject)
2524 {
2525 // Append
2526 para->AppendChild(newObj);
2527 }
2528 else
2529 {
2530 // Insert before nextObject
2531 para->InsertChild(newObj, nextObject);
2532 }
2533
2534 objectNode = objectNode->GetNext();
2535 }
2536
2537 return true;
2538 }
2539 else
2540 {
2541 // Procedure for inserting a fragment consisting of a number of
2542 // paragraphs:
2543 //
2544 // 1. Remove and save the content that's after the insertion point, for adding
2545 // back once we've added the fragment.
2546 // 2. Add the content from the first fragment paragraph to the current
2547 // paragraph.
2548 // 3. Add remaining fragment paragraphs after the current paragraph.
2549 // 4. Add back the saved content from the first paragraph. If partialParagraph
2550 // is true, add it to the last paragraph added and not a new one.
2551
2552 // 1. Remove and save objects after split point.
2553 wxList savedObjects;
2554 if (nextObject)
2555 para->MoveToList(nextObject, savedObjects);
2556
2557 // 2. Add the content from the 1st fragment paragraph.
2558 wxRichTextObjectList::compatibility_iterator firstParaNode = fragment.GetChildren().GetFirst();
2559 if (!firstParaNode)
2560 return false;
2561
2562 wxRichTextParagraph* firstPara = wxDynamicCast(firstParaNode->GetData(), wxRichTextParagraph);
2563 wxASSERT(firstPara != NULL);
2564
2565 if (!(fragment.GetAttributes().GetFlags() & wxTEXT_ATTR_KEEP_FIRST_PARA_STYLE))
2566 para->SetAttributes(firstPara->GetAttributes());
2567
2568 // Save empty paragraph attributes for appending later
2569 // These are character attributes deliberately set for a new paragraph. Without this,
2570 // we couldn't pass default attributes when appending a new paragraph.
2571 wxRichTextAttr emptyParagraphAttributes;
2572
2573 wxRichTextObjectList::compatibility_iterator objectNode = firstPara->GetChildren().GetFirst();
2574
2575 if (objectNode && firstPara->GetChildren().GetCount() == 1 && objectNode->GetData()->IsEmpty())
2576 emptyParagraphAttributes = objectNode->GetData()->GetAttributes();
2577
2578 while (objectNode)
2579 {
2580 wxRichTextObject* newObj = objectNode->GetData()->Clone();
2581
2582 // Append
2583 para->AppendChild(newObj);
2584
2585 objectNode = objectNode->GetNext();
2586 }
2587
2588 // 3. Add remaining fragment paragraphs after the current paragraph.
2589 wxRichTextObjectList::compatibility_iterator nextParagraphNode = node->GetNext();
2590 wxRichTextObject* nextParagraph = NULL;
2591 if (nextParagraphNode)
2592 nextParagraph = nextParagraphNode->GetData();
2593
2594 wxRichTextObjectList::compatibility_iterator i = fragment.GetChildren().GetFirst()->GetNext();
2595 wxRichTextParagraph* finalPara = para;
2596
2597 bool needExtraPara = (!i || !fragment.GetPartialParagraph());
2598
2599 // If there was only one paragraph, we need to insert a new one.
2600 while (i)
2601 {
2602 wxRichTextParagraph* para = wxDynamicCast(i->GetData(), wxRichTextParagraph);
2603 wxASSERT( para != NULL );
2604
2605 finalPara = (wxRichTextParagraph*) para->Clone();
2606
2607 if (nextParagraph)
2608 InsertChild(finalPara, nextParagraph);
2609 else
2610 AppendChild(finalPara);
2611
2612 i = i->GetNext();
2613 }
2614
2615 // If there was only one paragraph, or we have full paragraphs in our fragment,
2616 // we need to insert a new one.
2617 if (needExtraPara)
2618 {
2619 finalPara = new wxRichTextParagraph;
2620
2621 if (nextParagraph)
2622 InsertChild(finalPara, nextParagraph);
2623 else
2624 AppendChild(finalPara);
2625 }
2626
2627 // 4. Add back the remaining content.
2628 if (finalPara)
2629 {
2630 if (nextObject)
2631 finalPara->MoveFromList(savedObjects);
2632
2633 // Ensure there's at least one object
2634 if (finalPara->GetChildCount() == 0)
2635 {
2636 wxRichTextPlainText* text = new wxRichTextPlainText(wxEmptyString);
2637 text->SetAttributes(emptyParagraphAttributes);
2638
2639 finalPara->AppendChild(text);
2640 }
2641 }
2642
2643 if ((fragment.GetAttributes().GetFlags() & wxTEXT_ATTR_KEEP_FIRST_PARA_STYLE) && firstPara)
2644 finalPara->SetAttributes(firstPara->GetAttributes());
2645 else if (finalPara && finalPara != para)
2646 finalPara->SetAttributes(originalAttr);
2647
2648 return true;
2649 }
2650 }
2651 else
2652 {
2653 // Append
2654 wxRichTextObjectList::compatibility_iterator i = fragment.GetChildren().GetFirst();
2655 while (i)
2656 {
2657 wxRichTextParagraph* para = wxDynamicCast(i->GetData(), wxRichTextParagraph);
2658 wxASSERT( para != NULL );
2659
2660 AppendChild(para->Clone());
2661
2662 i = i->GetNext();
2663 }
2664
2665 return true;
2666 }
2667 }
2668
2669 /// Make a copy of the fragment corresponding to the given range, putting it in 'fragment'.
2670 /// If there was an incomplete paragraph at the end, partialParagraph is set to true.
2671 bool wxRichTextParagraphLayoutBox::CopyFragment(const wxRichTextRange& range, wxRichTextParagraphLayoutBox& fragment)
2672 {
2673 wxRichTextObjectList::compatibility_iterator i = GetChildren().GetFirst();
2674 while (i)
2675 {
2676 wxRichTextParagraph* para = wxDynamicCast(i->GetData(), wxRichTextParagraph);
2677 wxASSERT( para != NULL );
2678
2679 if (!para->GetRange().IsOutside(range))
2680 {
2681 fragment.AppendChild(para->Clone());
2682 }
2683 i = i->GetNext();
2684 }
2685
2686 // Now top and tail the first and last paragraphs in our new fragment (which might be the same).
2687 if (!fragment.IsEmpty())
2688 {
2689 wxRichTextParagraph* firstPara = wxDynamicCast(fragment.GetChildren().GetFirst()->GetData(), wxRichTextParagraph);
2690 wxASSERT( firstPara != NULL );
2691
2692 wxRichTextParagraph* lastPara = wxDynamicCast(fragment.GetChildren().GetLast()->GetData(), wxRichTextParagraph);
2693 wxASSERT( lastPara != NULL );
2694
2695 if (!firstPara || !lastPara)
2696 return false;
2697
2698 bool isFragment = (range.GetEnd() < lastPara->GetRange().GetEnd());
2699
2700 long firstPos = firstPara->GetRange().GetStart();
2701
2702 // Adjust for renumbering from zero
2703 wxRichTextRange topTailRange(range.GetStart() - firstPos, range.GetEnd() - firstPos);
2704
2705 long end;
2706 fragment.CalculateRange(0, end);
2707
2708 // Chop off the start of the paragraph
2709 if (topTailRange.GetStart() > 0)
2710 {
2711 wxRichTextRange r(0, topTailRange.GetStart()-1);
2712 firstPara->DeleteRange(r);
2713
2714 // Make sure the numbering is correct
2715 fragment.CalculateRange(0, end);
2716
2717 // Now, we've deleted some positions, so adjust the range
2718 // accordingly.
2719 topTailRange.SetStart(range.GetLength());
2720 topTailRange.SetEnd(fragment.GetOwnRange().GetEnd());
2721 }
2722 else
2723 {
2724 topTailRange.SetStart(range.GetLength());
2725 topTailRange.SetEnd(fragment.GetOwnRange().GetEnd());
2726 }
2727
2728 if (topTailRange.GetStart() < lastPara->GetRange().GetEnd())
2729 {
2730 lastPara->DeleteRange(topTailRange);
2731
2732 // Make sure the numbering is correct
2733 long end;
2734 fragment.CalculateRange(0, end);
2735
2736 // We only have part of a paragraph at the end
2737 fragment.SetPartialParagraph(true);
2738 }
2739 else
2740 {
2741 // We have a partial paragraph (don't save last new paragraph marker)
2742 // or complete paragraph
2743 fragment.SetPartialParagraph(isFragment);
2744 }
2745 }
2746
2747 return true;
2748 }
2749
2750 /// Given a position, get the number of the visible line (potentially many to a paragraph),
2751 /// starting from zero at the start of the buffer.
2752 long wxRichTextParagraphLayoutBox::GetVisibleLineNumber(long pos, bool caretPosition, bool startOfLine) const
2753 {
2754 if (caretPosition)
2755 pos ++;
2756
2757 int lineCount = 0;
2758
2759 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2760 while (node)
2761 {
2762 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2763 // wxASSERT( child != NULL );
2764
2765 if (child)
2766 {
2767 if (child->GetRange().Contains(pos))
2768 {
2769 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
2770 while (node2)
2771 {
2772 wxRichTextLine* line = node2->GetData();
2773 wxRichTextRange lineRange = line->GetAbsoluteRange();
2774
2775 if (lineRange.Contains(pos) || pos == lineRange.GetStart())
2776 {
2777 // If the caret is displayed at the end of the previous wrapped line,
2778 // we want to return the line it's _displayed_ at (not the actual line
2779 // containing the position).
2780 if (lineRange.GetStart() == pos && !startOfLine && child->GetRange().GetStart() != pos)
2781 return lineCount - 1;
2782 else
2783 return lineCount;
2784 }
2785
2786 lineCount ++;
2787
2788 node2 = node2->GetNext();
2789 }
2790 // If we didn't find it in the lines, it must be
2791 // the last position of the paragraph. So return the last line.
2792 return lineCount-1;
2793 }
2794 else
2795 lineCount += child->GetLines().GetCount();
2796 }
2797
2798 node = node->GetNext();
2799 }
2800
2801 // Not found
2802 return -1;
2803 }
2804
2805 /// Given a line number, get the corresponding wxRichTextLine object.
2806 wxRichTextLine* wxRichTextParagraphLayoutBox::GetLineForVisibleLineNumber(long lineNumber) const
2807 {
2808 int lineCount = 0;
2809
2810 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2811 while (node)
2812 {
2813 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2814 // wxASSERT(child != NULL);
2815
2816 if (child)
2817 {
2818 if (lineNumber < (int) (child->GetLines().GetCount() + lineCount))
2819 {
2820 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
2821 while (node2)
2822 {
2823 wxRichTextLine* line = node2->GetData();
2824
2825 if (lineCount == lineNumber)
2826 return line;
2827
2828 lineCount ++;
2829
2830 node2 = node2->GetNext();
2831 }
2832 }
2833 else
2834 lineCount += child->GetLines().GetCount();
2835 }
2836
2837 node = node->GetNext();
2838 }
2839
2840 // Didn't find it
2841 return NULL;
2842 }
2843
2844 /// Delete range from layout.
2845 bool wxRichTextParagraphLayoutBox::DeleteRange(const wxRichTextRange& range)
2846 {
2847 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2848
2849 wxRichTextParagraph* firstPara = NULL;
2850 while (node)
2851 {
2852 wxRichTextParagraph* obj = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2853 // wxASSERT (obj != NULL);
2854
2855 wxRichTextObjectList::compatibility_iterator next = node->GetNext();
2856
2857 if (obj)
2858 {
2859 // Delete the range in each paragraph
2860
2861 if (!obj->GetRange().IsOutside(range))
2862 {
2863 // Deletes the content of this object within the given range
2864 obj->DeleteRange(range);
2865
2866 wxRichTextRange thisRange = obj->GetRange();
2867 wxRichTextAttr thisAttr = obj->GetAttributes();
2868
2869 // If the whole paragraph is within the range to delete,
2870 // delete the whole thing.
2871 if (range.GetStart() <= thisRange.GetStart() && range.GetEnd() >= thisRange.GetEnd())
2872 {
2873 // Delete the whole object
2874 RemoveChild(obj, true);
2875 obj = NULL;
2876 }
2877 else if (!firstPara)
2878 firstPara = obj;
2879
2880 // If the range includes the paragraph end, we need to join this
2881 // and the next paragraph.
2882 if (range.GetEnd() <= thisRange.GetEnd())
2883 {
2884 // We need to move the objects from the next paragraph
2885 // to this paragraph
2886
2887 wxRichTextParagraph* nextParagraph = NULL;
2888 if ((range.GetEnd() < thisRange.GetEnd()) && obj)
2889 nextParagraph = obj;
2890 else
2891 {
2892 // We're ending at the end of the paragraph, so merge the _next_ paragraph.
2893 if (next)
2894 nextParagraph = wxDynamicCast(next->GetData(), wxRichTextParagraph);
2895 }
2896
2897 bool applyFinalParagraphStyle = firstPara && nextParagraph && nextParagraph != firstPara;
2898
2899 wxRichTextAttr nextParaAttr;
2900 if (applyFinalParagraphStyle)
2901 {
2902 // Special case when deleting the end of a paragraph - use _this_ paragraph's style,
2903 // not the next one.
2904 if (range.GetStart() == range.GetEnd() && range.GetStart() == thisRange.GetEnd())
2905 nextParaAttr = thisAttr;
2906 else
2907 nextParaAttr = nextParagraph->GetAttributes();
2908 }
2909
2910 if (firstPara && nextParagraph && firstPara != nextParagraph)
2911 {
2912 // Move the objects to the previous para
2913 wxRichTextObjectList::compatibility_iterator node1 = nextParagraph->GetChildren().GetFirst();
2914
2915 while (node1)
2916 {
2917 wxRichTextObject* obj1 = node1->GetData();
2918
2919 firstPara->AppendChild(obj1);
2920
2921 wxRichTextObjectList::compatibility_iterator next1 = node1->GetNext();
2922 nextParagraph->GetChildren().Erase(node1);
2923
2924 node1 = next1;
2925 }
2926
2927 // Delete the paragraph
2928 RemoveChild(nextParagraph, true);
2929 }
2930
2931 // Avoid empty paragraphs
2932 if (firstPara && firstPara->GetChildren().GetCount() == 0)
2933 {
2934 wxRichTextPlainText* text = new wxRichTextPlainText(wxEmptyString);
2935 firstPara->AppendChild(text);
2936 }
2937
2938 if (applyFinalParagraphStyle)
2939 firstPara->SetAttributes(nextParaAttr);
2940
2941 return true;
2942 }
2943 }
2944 }
2945
2946 node = next;
2947 }
2948
2949 return true;
2950 }
2951
2952 /// Get any text in this object for the given range
2953 wxString wxRichTextParagraphLayoutBox::GetTextForRange(const wxRichTextRange& range) const
2954 {
2955 int lineCount = 0;
2956 wxString text;
2957 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2958 while (node)
2959 {
2960 wxRichTextObject* child = node->GetData();
2961 if (!child->GetRange().IsOutside(range))
2962 {
2963 wxRichTextRange childRange = range;
2964 childRange.LimitTo(child->GetRange());
2965
2966 wxString childText = child->GetTextForRange(childRange);
2967
2968 text += childText;
2969
2970 if ((childRange.GetEnd() == child->GetRange().GetEnd()) && node->GetNext())
2971 text += wxT("\n");
2972
2973 lineCount ++;
2974 }
2975 node = node->GetNext();
2976 }
2977
2978 return text;
2979 }
2980
2981 /// Get all the text
2982 wxString wxRichTextParagraphLayoutBox::GetText() const
2983 {
2984 return GetTextForRange(GetOwnRange());
2985 }
2986
2987 /// Get the paragraph by number
2988 wxRichTextParagraph* wxRichTextParagraphLayoutBox::GetParagraphAtLine(long paragraphNumber) const
2989 {
2990 if ((size_t) paragraphNumber >= GetChildCount())
2991 return NULL;
2992
2993 return (wxRichTextParagraph*) GetChild((size_t) paragraphNumber);
2994 }
2995
2996 /// Get the length of the paragraph
2997 int wxRichTextParagraphLayoutBox::GetParagraphLength(long paragraphNumber) const
2998 {
2999 wxRichTextParagraph* para = GetParagraphAtLine(paragraphNumber);
3000 if (para)
3001 return para->GetRange().GetLength() - 1; // don't include newline
3002 else
3003 return 0;
3004 }
3005
3006 /// Get the text of the paragraph
3007 wxString wxRichTextParagraphLayoutBox::GetParagraphText(long paragraphNumber) const
3008 {
3009 wxRichTextParagraph* para = GetParagraphAtLine(paragraphNumber);
3010 if (para)
3011 return para->GetTextForRange(para->GetRange());
3012 else
3013 return wxEmptyString;
3014 }
3015
3016 /// Convert zero-based line column and paragraph number to a position.
3017 long wxRichTextParagraphLayoutBox::XYToPosition(long x, long y) const
3018 {
3019 wxRichTextParagraph* para = GetParagraphAtLine(y);
3020 if (para)
3021 {
3022 return para->GetRange().GetStart() + x;
3023 }
3024 else
3025 return -1;
3026 }
3027
3028 /// Convert zero-based position to line column and paragraph number
3029 bool wxRichTextParagraphLayoutBox::PositionToXY(long pos, long* x, long* y) const
3030 {
3031 wxRichTextParagraph* para = GetParagraphAtPosition(pos);
3032 if (para)
3033 {
3034 int count = 0;
3035 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3036 while (node)
3037 {
3038 wxRichTextObject* child = node->GetData();
3039 if (child == para)
3040 break;
3041 count ++;
3042 node = node->GetNext();
3043 }
3044
3045 *y = count;
3046 *x = pos - para->GetRange().GetStart();
3047
3048 return true;
3049 }
3050 else
3051 return false;
3052 }
3053
3054 /// Get the leaf object in a paragraph at this position.
3055 /// Given a line number, get the corresponding wxRichTextLine object.
3056 wxRichTextObject* wxRichTextParagraphLayoutBox::GetLeafObjectAtPosition(long position) const
3057 {
3058 wxRichTextParagraph* para = GetParagraphAtPosition(position);
3059 if (para)
3060 {
3061 wxRichTextObjectList::compatibility_iterator node = para->GetChildren().GetFirst();
3062
3063 while (node)
3064 {
3065 wxRichTextObject* child = node->GetData();
3066 if (child->GetRange().Contains(position))
3067 return child;
3068
3069 node = node->GetNext();
3070 }
3071 if (position == para->GetRange().GetEnd() && para->GetChildCount() > 0)
3072 return para->GetChildren().GetLast()->GetData();
3073 }
3074 return NULL;
3075 }
3076
3077 /// Set character or paragraph text attributes: apply character styles only to immediate text nodes
3078 bool wxRichTextParagraphLayoutBox::SetStyle(const wxRichTextRange& range, const wxRichTextAttr& style, int flags)
3079 {
3080 bool characterStyle = false;
3081 bool paragraphStyle = false;
3082
3083 if (style.IsCharacterStyle())
3084 characterStyle = true;
3085 if (style.IsParagraphStyle())
3086 paragraphStyle = true;
3087
3088 wxRichTextBuffer* buffer = GetBuffer();
3089
3090 bool withUndo = ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0);
3091 bool applyMinimal = ((flags & wxRICHTEXT_SETSTYLE_OPTIMIZE) != 0);
3092 bool parasOnly = ((flags & wxRICHTEXT_SETSTYLE_PARAGRAPHS_ONLY) != 0);
3093 bool charactersOnly = ((flags & wxRICHTEXT_SETSTYLE_CHARACTERS_ONLY) != 0);
3094 bool resetExistingStyle = ((flags & wxRICHTEXT_SETSTYLE_RESET) != 0);
3095 bool removeStyle = ((flags & wxRICHTEXT_SETSTYLE_REMOVE) != 0);
3096
3097 // Apply paragraph style first, if any
3098 wxRichTextAttr wholeStyle(style);
3099
3100 if (!removeStyle && wholeStyle.HasParagraphStyleName() && buffer->GetStyleSheet())
3101 {
3102 wxRichTextParagraphStyleDefinition* def = buffer->GetStyleSheet()->FindParagraphStyle(wholeStyle.GetParagraphStyleName());
3103 if (def)
3104 wxRichTextApplyStyle(wholeStyle, def->GetStyleMergedWithBase(buffer->GetStyleSheet()));
3105 }
3106
3107 // Limit the attributes to be set to the content to only character attributes.
3108 wxRichTextAttr characterAttributes(wholeStyle);
3109 characterAttributes.SetFlags(characterAttributes.GetFlags() & (wxTEXT_ATTR_CHARACTER));
3110
3111 if (!removeStyle && characterAttributes.HasCharacterStyleName() && buffer->GetStyleSheet())
3112 {
3113 wxRichTextCharacterStyleDefinition* def = buffer->GetStyleSheet()->FindCharacterStyle(characterAttributes.GetCharacterStyleName());
3114 if (def)
3115 wxRichTextApplyStyle(characterAttributes, def->GetStyleMergedWithBase(buffer->GetStyleSheet()));
3116 }
3117
3118 // If we are associated with a control, make undoable; otherwise, apply immediately
3119 // to the data.
3120
3121 bool haveControl = (buffer->GetRichTextCtrl() != NULL);
3122
3123 wxRichTextAction* action = NULL;
3124
3125 if (haveControl && withUndo)
3126 {
3127 action = new wxRichTextAction(NULL, _("Change Style"), wxRICHTEXT_CHANGE_STYLE, buffer, this, buffer->GetRichTextCtrl());
3128 action->SetRange(range);
3129 action->SetPosition(buffer->GetRichTextCtrl()->GetCaretPosition());
3130 }
3131
3132 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3133 while (node)
3134 {
3135 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3136 // wxASSERT (para != NULL);
3137
3138 if (para && para->GetChildCount() > 0)
3139 {
3140 // Stop searching if we're beyond the range of interest
3141 if (para->GetRange().GetStart() > range.GetEnd())
3142 break;
3143
3144 if (!para->GetRange().IsOutside(range))
3145 {
3146 // We'll be using a copy of the paragraph to make style changes,
3147 // not updating the buffer directly.
3148 wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL);
3149
3150 if (haveControl && withUndo)
3151 {
3152 newPara = new wxRichTextParagraph(*para);
3153 action->GetNewParagraphs().AppendChild(newPara);
3154
3155 // Also store the old ones for Undo
3156 action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para));
3157 }
3158 else
3159 newPara = para;
3160
3161 // If we're specifying paragraphs only, then we really mean character formatting
3162 // to be included in the paragraph style
3163 if ((paragraphStyle || parasOnly) && !charactersOnly)
3164 {
3165 if (removeStyle)
3166 {
3167 // Removes the given style from the paragraph
3168 wxRichTextRemoveStyle(newPara->GetAttributes(), style);
3169 }
3170 else if (resetExistingStyle)
3171 newPara->GetAttributes() = wholeStyle;
3172 else
3173 {
3174 if (applyMinimal)
3175 {
3176 // Only apply attributes that will make a difference to the combined
3177 // style as seen on the display
3178 wxRichTextAttr combinedAttr(para->GetCombinedAttributes(true));
3179 wxRichTextApplyStyle(newPara->GetAttributes(), wholeStyle, & combinedAttr);
3180 }
3181 else
3182 wxRichTextApplyStyle(newPara->GetAttributes(), wholeStyle);
3183 }
3184 }
3185
3186 // When applying paragraph styles dynamically, don't change the text objects' attributes
3187 // since they will computed as needed. Only apply the character styling if it's _only_
3188 // character styling. This policy is subject to change and might be put under user control.
3189
3190 // Hm. we might well be applying a mix of paragraph and character styles, in which
3191 // case we _do_ want to apply character styles regardless of what para styles are set.
3192 // But if we're applying a paragraph style, which has some character attributes, but
3193 // we only want the paragraphs to hold this character style, then we _don't_ want to
3194 // apply the character style. So we need to be able to choose.
3195
3196 if (!parasOnly && (characterStyle|charactersOnly) && range.GetStart() != newPara->GetRange().GetEnd())
3197 {
3198 wxRichTextRange childRange(range);
3199 childRange.LimitTo(newPara->GetRange());
3200
3201 // Find the starting position and if necessary split it so
3202 // we can start applying a different style.
3203 // TODO: check that the style actually changes or is different
3204 // from style outside of range
3205 wxRichTextObject* firstObject wxDUMMY_INITIALIZE(NULL);
3206 wxRichTextObject* lastObject wxDUMMY_INITIALIZE(NULL);
3207
3208 if (childRange.GetStart() == newPara->GetRange().GetStart())
3209 firstObject = newPara->GetChildren().GetFirst()->GetData();
3210 else
3211 firstObject = newPara->SplitAt(range.GetStart());
3212
3213 // Increment by 1 because we're apply the style one _after_ the split point
3214 long splitPoint = childRange.GetEnd();
3215 if (splitPoint != newPara->GetRange().GetEnd())
3216 splitPoint ++;
3217
3218 // Find last object
3219 if (splitPoint == newPara->GetRange().GetEnd())
3220 lastObject = newPara->GetChildren().GetLast()->GetData();
3221 else
3222 // lastObject is set as a side-effect of splitting. It's
3223 // returned as the object before the new object.
3224 (void) newPara->SplitAt(splitPoint, & lastObject);
3225
3226 wxASSERT(firstObject != NULL);
3227 wxASSERT(lastObject != NULL);
3228
3229 if (!firstObject || !lastObject)
3230 continue;
3231
3232 wxRichTextObjectList::compatibility_iterator firstNode = newPara->GetChildren().Find(firstObject);
3233 wxRichTextObjectList::compatibility_iterator lastNode = newPara->GetChildren().Find(lastObject);
3234
3235 wxASSERT(firstNode);
3236 wxASSERT(lastNode);
3237
3238 wxRichTextObjectList::compatibility_iterator node2 = firstNode;
3239
3240 while (node2)
3241 {
3242 wxRichTextObject* child = node2->GetData();
3243
3244 if (removeStyle)
3245 {
3246 // Removes the given style from the paragraph
3247 wxRichTextRemoveStyle(child->GetAttributes(), style);
3248 }
3249 else if (resetExistingStyle)
3250 child->GetAttributes() = characterAttributes;
3251 else
3252 {
3253 if (applyMinimal)
3254 {
3255 // Only apply attributes that will make a difference to the combined
3256 // style as seen on the display
3257 wxRichTextAttr combinedAttr(newPara->GetCombinedAttributes(child->GetAttributes(), true));
3258 wxRichTextApplyStyle(child->GetAttributes(), characterAttributes, & combinedAttr);
3259 }
3260 else
3261 wxRichTextApplyStyle(child->GetAttributes(), characterAttributes);
3262 }
3263
3264 if (node2 == lastNode)
3265 break;
3266
3267 node2 = node2->GetNext();
3268 }
3269 }
3270 }
3271 }
3272
3273 node = node->GetNext();
3274 }
3275
3276 // Do action, or delay it until end of batch.
3277 if (haveControl && withUndo)
3278 buffer->SubmitAction(action);
3279
3280 return true;
3281 }
3282
3283 // Just change the attributes for this single object.
3284 void wxRichTextParagraphLayoutBox::SetStyle(wxRichTextObject* obj, const wxRichTextAttr& textAttr, int flags)
3285 {
3286 wxRichTextBuffer* buffer = GetBuffer();
3287 bool withUndo = flags & wxRICHTEXT_SETSTYLE_WITH_UNDO;
3288 bool resetExistingStyle = ((flags & wxRICHTEXT_SETSTYLE_RESET) != 0);
3289 bool haveControl = (buffer->GetRichTextCtrl() != NULL);
3290
3291 wxRichTextAction *action = NULL;
3292 wxRichTextAttr newAttr = obj->GetAttributes();
3293 if (resetExistingStyle)
3294 newAttr = textAttr;
3295 else
3296 newAttr.Apply(textAttr);
3297
3298 if (haveControl && withUndo)
3299 {
3300 action = new wxRichTextAction(NULL, _("Change Object Style"), wxRICHTEXT_CHANGE_ATTRIBUTES, buffer, obj->GetContainer(), buffer->GetRichTextCtrl());
3301 action->SetRange(obj->GetRange().FromInternal());
3302 action->SetPosition(buffer->GetRichTextCtrl()->GetCaretPosition());
3303 action->MakeObject(obj);
3304
3305 action->GetAttributes() = newAttr;
3306 }
3307 else
3308 obj->GetAttributes() = newAttr;
3309
3310 if (haveControl && withUndo)
3311 buffer->SubmitAction(action);
3312 }
3313
3314 /// Get the text attributes for this position.
3315 bool wxRichTextParagraphLayoutBox::GetStyle(long position, wxRichTextAttr& style)
3316 {
3317 return DoGetStyle(position, style, true);
3318 }
3319
3320 bool wxRichTextParagraphLayoutBox::GetUncombinedStyle(long position, wxRichTextAttr& style)
3321 {
3322 return DoGetStyle(position, style, false);
3323 }
3324
3325 /// Implementation helper for GetStyle. If combineStyles is true, combine base, paragraph and
3326 /// context attributes.
3327 bool wxRichTextParagraphLayoutBox::DoGetStyle(long position, wxRichTextAttr& style, bool combineStyles)
3328 {
3329 wxRichTextObject* obj wxDUMMY_INITIALIZE(NULL);
3330
3331 if (style.IsParagraphStyle())
3332 {
3333 obj = GetParagraphAtPosition(position);
3334 if (obj)
3335 {
3336 if (combineStyles)
3337 {
3338 // Start with the base style
3339 style = GetAttributes();
3340 style.GetTextBoxAttr().Reset();
3341
3342 // Apply the paragraph style
3343 wxRichTextApplyStyle(style, obj->GetAttributes());
3344 }
3345 else
3346 style = obj->GetAttributes();
3347
3348 return true;
3349 }
3350 }
3351 else
3352 {
3353 obj = GetLeafObjectAtPosition(position);
3354 if (obj)
3355 {
3356 if (combineStyles)
3357 {
3358 wxRichTextParagraph* para = wxDynamicCast(obj->GetParent(), wxRichTextParagraph);
3359 style = para ? para->GetCombinedAttributes(obj->GetAttributes()) : obj->GetAttributes();
3360 }
3361 else
3362 style = obj->GetAttributes();
3363
3364 return true;
3365 }
3366 }
3367 return false;
3368 }
3369
3370 static bool wxHasStyle(long flags, long style)
3371 {
3372 return (flags & style) != 0;
3373 }
3374
3375 /// Combines 'style' with 'currentStyle' for the purpose of summarising the attributes of a range of
3376 /// content.
3377 bool wxRichTextParagraphLayoutBox::CollectStyle(wxRichTextAttr& currentStyle, const wxRichTextAttr& style, wxRichTextAttr& clashingAttr, wxRichTextAttr& absentAttr)
3378 {
3379 currentStyle.CollectCommonAttributes(style, clashingAttr, absentAttr);
3380
3381 return true;
3382 }
3383
3384 /// Get the combined style for a range - if any attribute is different within the range,
3385 /// that attribute is not present within the flags.
3386 /// *** Note that this is not recursive, and so assumes that content inside a paragraph is not itself
3387 /// nested.
3388 bool wxRichTextParagraphLayoutBox::GetStyleForRange(const wxRichTextRange& range, wxRichTextAttr& style)
3389 {
3390 style = wxRichTextAttr();
3391
3392 wxRichTextAttr clashingAttr;
3393 wxRichTextAttr absentAttrPara, absentAttrChar;
3394
3395 wxRichTextObjectList::compatibility_iterator node = GetChildren().GetFirst();
3396 while (node)
3397 {
3398 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3399 if (para && !(para->GetRange().GetStart() > range.GetEnd() || para->GetRange().GetEnd() < range.GetStart()))
3400 {
3401 if (para->GetChildren().GetCount() == 0)
3402 {
3403 wxRichTextAttr paraStyle = para->GetCombinedAttributes(true /* use box attributes */);
3404
3405 CollectStyle(style, paraStyle, clashingAttr, absentAttrPara);
3406 }
3407 else
3408 {
3409 wxRichTextRange paraRange(para->GetRange());
3410 paraRange.LimitTo(range);
3411
3412 // First collect paragraph attributes only
3413 wxRichTextAttr paraStyle = para->GetCombinedAttributes();
3414 paraStyle.SetFlags(paraStyle.GetFlags() & wxTEXT_ATTR_PARAGRAPH);
3415 CollectStyle(style, paraStyle, clashingAttr, absentAttrPara);
3416
3417 wxRichTextObjectList::compatibility_iterator childNode = para->GetChildren().GetFirst();
3418
3419 while (childNode)
3420 {
3421 wxRichTextObject* child = childNode->GetData();
3422 if (!(child->GetRange().GetStart() > range.GetEnd() || child->GetRange().GetEnd() < range.GetStart()))
3423 {
3424 wxRichTextAttr childStyle = para->GetCombinedAttributes(child->GetAttributes(), true /* include box attributes */);
3425
3426 // Now collect character attributes only
3427 childStyle.SetFlags(childStyle.GetFlags() & wxTEXT_ATTR_CHARACTER);
3428
3429 CollectStyle(style, childStyle, clashingAttr, absentAttrChar);
3430 }
3431
3432 childNode = childNode->GetNext();
3433 }
3434 }
3435 }
3436 node = node->GetNext();
3437 }
3438 return true;
3439 }
3440
3441 /// Set default style
3442 bool wxRichTextParagraphLayoutBox::SetDefaultStyle(const wxRichTextAttr& style)
3443 {
3444 m_defaultAttributes = style;
3445 return true;
3446 }
3447
3448 /// Test if this whole range has character attributes of the specified kind. If any
3449 /// of the attributes are different within the range, the test fails. You
3450 /// can use this to implement, for example, bold button updating. style must have
3451 /// flags indicating which attributes are of interest.
3452 bool wxRichTextParagraphLayoutBox::HasCharacterAttributes(const wxRichTextRange& range, const wxRichTextAttr& style) const
3453 {
3454 int foundCount = 0;
3455 int matchingCount = 0;
3456
3457 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3458 while (node)
3459 {
3460 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3461 // wxASSERT (para != NULL);
3462
3463 if (para)
3464 {
3465 // Stop searching if we're beyond the range of interest
3466 if (para->GetRange().GetStart() > range.GetEnd())
3467 return foundCount == matchingCount && foundCount != 0;
3468
3469 if (!para->GetRange().IsOutside(range))
3470 {
3471 wxRichTextObjectList::compatibility_iterator node2 = para->GetChildren().GetFirst();
3472
3473 while (node2)
3474 {
3475 wxRichTextObject* child = node2->GetData();
3476 // Allow for empty string if no buffer
3477 wxRichTextRange childRange = child->GetRange();
3478 if (childRange.GetLength() == 0 && GetRange().GetLength() == 1)
3479 childRange.SetEnd(childRange.GetEnd()+1);
3480
3481 if (!childRange.IsOutside(range) && wxDynamicCast(child, wxRichTextPlainText))
3482 {
3483 foundCount ++;
3484 wxRichTextAttr textAttr = para->GetCombinedAttributes(child->GetAttributes());
3485
3486 if (textAttr.EqPartial(style, false /* strong test - attributes must be valid in both objects */))
3487 matchingCount ++;
3488 }
3489
3490 node2 = node2->GetNext();
3491 }
3492 }
3493 }
3494
3495 node = node->GetNext();
3496 }
3497
3498 return foundCount == matchingCount && foundCount != 0;
3499 }
3500
3501 /// Test if this whole range has paragraph attributes of the specified kind. If any
3502 /// of the attributes are different within the range, the test fails. You
3503 /// can use this to implement, for example, centering button updating. style must have
3504 /// flags indicating which attributes are of interest.
3505 bool wxRichTextParagraphLayoutBox::HasParagraphAttributes(const wxRichTextRange& range, const wxRichTextAttr& style) const
3506 {
3507 int foundCount = 0;
3508 int matchingCount = 0;
3509
3510 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3511 while (node)
3512 {
3513 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3514 // wxASSERT (para != NULL);
3515
3516 if (para)
3517 {
3518 // Stop searching if we're beyond the range of interest
3519 if (para->GetRange().GetStart() > range.GetEnd())
3520 return foundCount == matchingCount && foundCount != 0;
3521
3522 if (!para->GetRange().IsOutside(range))
3523 {
3524 wxRichTextAttr textAttr = GetAttributes();
3525 // Apply the paragraph style
3526 wxRichTextApplyStyle(textAttr, para->GetAttributes());
3527
3528 foundCount ++;
3529 if (textAttr.EqPartial(style, false /* strong test */))
3530 matchingCount ++;
3531 }
3532 }
3533
3534 node = node->GetNext();
3535 }
3536 return foundCount == matchingCount && foundCount != 0;
3537 }
3538
3539 void wxRichTextParagraphLayoutBox::PrepareContent(wxRichTextParagraphLayoutBox& container)
3540 {
3541 wxRichTextBuffer* buffer = GetBuffer();
3542 if (buffer && buffer->GetRichTextCtrl())
3543 buffer->GetRichTextCtrl()->PrepareContent(container);
3544 }
3545
3546 /// Set character or paragraph properties
3547 bool wxRichTextParagraphLayoutBox::SetProperties(const wxRichTextRange& range, const wxRichTextProperties& properties, int flags)
3548 {
3549 wxRichTextBuffer* buffer = GetBuffer();
3550
3551 bool withUndo = ((flags & wxRICHTEXT_SETPROPERTIES_WITH_UNDO) != 0);
3552 bool parasOnly = ((flags & wxRICHTEXT_SETPROPERTIES_PARAGRAPHS_ONLY) != 0);
3553 bool charactersOnly = ((flags & wxRICHTEXT_SETPROPERTIES_CHARACTERS_ONLY) != 0);
3554 bool resetExistingProperties = ((flags & wxRICHTEXT_SETPROPERTIES_RESET) != 0);
3555 bool removeProperties = ((flags & wxRICHTEXT_SETPROPERTIES_REMOVE) != 0);
3556
3557 // If we are associated with a control, make undoable; otherwise, apply immediately
3558 // to the data.
3559
3560 bool haveControl = (buffer->GetRichTextCtrl() != NULL);
3561
3562 wxRichTextAction* action = NULL;
3563
3564 if (haveControl && withUndo)
3565 {
3566 action = new wxRichTextAction(NULL, _("Change Properties"), wxRICHTEXT_CHANGE_PROPERTIES, buffer, this, buffer->GetRichTextCtrl());
3567 action->SetRange(range);
3568 action->SetPosition(buffer->GetRichTextCtrl()->GetCaretPosition());
3569 }
3570
3571 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3572 while (node)
3573 {
3574 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3575 // wxASSERT (para != NULL);
3576
3577 if (para && para->GetChildCount() > 0)
3578 {
3579 // Stop searching if we're beyond the range of interest
3580 if (para->GetRange().GetStart() > range.GetEnd())
3581 break;
3582
3583 if (!para->GetRange().IsOutside(range))
3584 {
3585 // We'll be using a copy of the paragraph to make style changes,
3586 // not updating the buffer directly.
3587 wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL);
3588
3589 if (haveControl && withUndo)
3590 {
3591 newPara = new wxRichTextParagraph(*para);
3592 action->GetNewParagraphs().AppendChild(newPara);
3593
3594 // Also store the old ones for Undo
3595 action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para));
3596 }
3597 else
3598 newPara = para;
3599
3600 if (parasOnly)
3601 {
3602 if (removeProperties)
3603 {
3604 // Removes the given style from the paragraph
3605 // TODO
3606 newPara->GetProperties().RemoveProperties(properties);
3607 }
3608 else if (resetExistingProperties)
3609 newPara->GetProperties() = properties;
3610 else
3611 newPara->GetProperties().MergeProperties(properties);
3612 }
3613
3614 // When applying paragraph styles dynamically, don't change the text objects' attributes
3615 // since they will computed as needed. Only apply the character styling if it's _only_
3616 // character styling. This policy is subject to change and might be put under user control.
3617
3618 // Hm. we might well be applying a mix of paragraph and character styles, in which
3619 // case we _do_ want to apply character styles regardless of what para styles are set.
3620 // But if we're applying a paragraph style, which has some character attributes, but
3621 // we only want the paragraphs to hold this character style, then we _don't_ want to
3622 // apply the character style. So we need to be able to choose.
3623
3624 if (!parasOnly && charactersOnly && range.GetStart() != newPara->GetRange().GetEnd())
3625 {
3626 wxRichTextRange childRange(range);
3627 childRange.LimitTo(newPara->GetRange());
3628
3629 // Find the starting position and if necessary split it so
3630 // we can start applying different properties.
3631 // TODO: check that the properties actually change or are different
3632 // from properties outside of range
3633 wxRichTextObject* firstObject wxDUMMY_INITIALIZE(NULL);
3634 wxRichTextObject* lastObject wxDUMMY_INITIALIZE(NULL);
3635
3636 if (childRange.GetStart() == newPara->GetRange().GetStart())
3637 firstObject = newPara->GetChildren().GetFirst()->GetData();
3638 else
3639 firstObject = newPara->SplitAt(range.GetStart());
3640
3641 // Increment by 1 because we're apply the style one _after_ the split point
3642 long splitPoint = childRange.GetEnd();
3643 if (splitPoint != newPara->GetRange().GetEnd())
3644 splitPoint ++;
3645
3646 // Find last object
3647 if (splitPoint == newPara->GetRange().GetEnd())
3648 lastObject = newPara->GetChildren().GetLast()->GetData();
3649 else
3650 // lastObject is set as a side-effect of splitting. It's
3651 // returned as the object before the new object.
3652 (void) newPara->SplitAt(splitPoint, & lastObject);
3653
3654 wxASSERT(firstObject != NULL);
3655 wxASSERT(lastObject != NULL);
3656
3657 if (!firstObject || !lastObject)
3658 continue;
3659
3660 wxRichTextObjectList::compatibility_iterator firstNode = newPara->GetChildren().Find(firstObject);
3661 wxRichTextObjectList::compatibility_iterator lastNode = newPara->GetChildren().Find(lastObject);
3662
3663 wxASSERT(firstNode);
3664 wxASSERT(lastNode);
3665
3666 wxRichTextObjectList::compatibility_iterator node2 = firstNode;
3667
3668 while (node2)
3669 {
3670 wxRichTextObject* child = node2->GetData();
3671
3672 if (removeProperties)
3673 {
3674 // Removes the given properties from the paragraph
3675 child->GetProperties().RemoveProperties(properties);
3676 }
3677 else if (resetExistingProperties)
3678 child->GetProperties() = properties;
3679 else
3680 {
3681 child->GetProperties().MergeProperties(properties);
3682 }
3683
3684 if (node2 == lastNode)
3685 break;
3686
3687 node2 = node2->GetNext();
3688 }
3689 }
3690 }
3691 }
3692
3693 node = node->GetNext();
3694 }
3695
3696 // Do action, or delay it until end of batch.
3697 if (haveControl && withUndo)
3698 buffer->SubmitAction(action);
3699
3700 return true;
3701 }
3702
3703 void wxRichTextParagraphLayoutBox::Reset()
3704 {
3705 Clear();
3706
3707 wxRichTextBuffer* buffer = GetBuffer();
3708 if (buffer && buffer->GetRichTextCtrl())
3709 {
3710 wxRichTextEvent event(wxEVT_COMMAND_RICHTEXT_BUFFER_RESET, buffer->GetRichTextCtrl()->GetId());
3711 event.SetEventObject(buffer->GetRichTextCtrl());
3712 event.SetContainer(this);
3713
3714 buffer->SendEvent(event, true);
3715 }
3716
3717 AddParagraph(wxEmptyString);
3718
3719 PrepareContent(*this);
3720
3721 InvalidateHierarchy(wxRICHTEXT_ALL);
3722 }
3723
3724 /// Invalidate the buffer. With no argument, invalidates whole buffer.
3725 void wxRichTextParagraphLayoutBox::Invalidate(const wxRichTextRange& invalidRange)
3726 {
3727 wxRichTextCompositeObject::Invalidate(invalidRange);
3728
3729 DoInvalidate(invalidRange);
3730 }
3731
3732 // Do the (in)validation for this object only
3733 void wxRichTextParagraphLayoutBox::DoInvalidate(const wxRichTextRange& invalidRange)
3734 {
3735 if (invalidRange == wxRICHTEXT_ALL)
3736 {
3737 m_invalidRange = wxRICHTEXT_ALL;
3738 }
3739 // Already invalidating everything
3740 else if (m_invalidRange == wxRICHTEXT_ALL)
3741 {
3742 }
3743 else
3744 {
3745 if ((invalidRange.GetStart() < m_invalidRange.GetStart()) || m_invalidRange.GetStart() == -1)
3746 m_invalidRange.SetStart(invalidRange.GetStart());
3747 if (invalidRange.GetEnd() > m_invalidRange.GetEnd())
3748 m_invalidRange.SetEnd(invalidRange.GetEnd());
3749 }
3750 }
3751
3752 // Do the (in)validation both up and down the hierarchy
3753 void wxRichTextParagraphLayoutBox::InvalidateHierarchy(const wxRichTextRange& invalidRange)
3754 {
3755 Invalidate(invalidRange);
3756
3757 if (invalidRange != wxRICHTEXT_NONE)
3758 {
3759 // Now go up the hierarchy
3760 wxRichTextObject* thisObj = this;
3761 wxRichTextObject* p = GetParent();
3762 while (p)
3763 {
3764 wxRichTextParagraphLayoutBox* l = wxDynamicCast(p, wxRichTextParagraphLayoutBox);
3765 if (l)
3766 l->DoInvalidate(thisObj->GetRange());
3767
3768 thisObj = p;
3769 p = p->GetParent();
3770 }
3771 }
3772 }
3773
3774 /// Get invalid range, rounding to entire paragraphs if argument is true.
3775 wxRichTextRange wxRichTextParagraphLayoutBox::GetInvalidRange(bool wholeParagraphs) const
3776 {
3777 if (m_invalidRange == wxRICHTEXT_ALL || m_invalidRange == wxRICHTEXT_NONE)
3778 return m_invalidRange;
3779
3780 wxRichTextRange range = m_invalidRange;
3781
3782 if (wholeParagraphs)
3783 {
3784 wxRichTextParagraph* para1 = GetParagraphAtPosition(range.GetStart());
3785 if (para1)
3786 range.SetStart(para1->GetRange().GetStart());
3787 // floating layout make all child should be relayout
3788 range.SetEnd(GetOwnRange().GetEnd());
3789 }
3790 return range;
3791 }
3792
3793 /// Apply the style sheet to the buffer, for example if the styles have changed.
3794 bool wxRichTextParagraphLayoutBox::ApplyStyleSheet(wxRichTextStyleSheet* styleSheet)
3795 {
3796 wxASSERT(styleSheet != NULL);
3797 if (!styleSheet)
3798 return false;
3799
3800 int foundCount = 0;
3801
3802 wxRichTextAttr attr(GetBasicStyle());
3803 if (GetBasicStyle().HasParagraphStyleName())
3804 {
3805 wxRichTextParagraphStyleDefinition* paraDef = styleSheet->FindParagraphStyle(GetBasicStyle().GetParagraphStyleName());
3806 if (paraDef)
3807 {
3808 attr.Apply(paraDef->GetStyleMergedWithBase(styleSheet));
3809 SetBasicStyle(attr);
3810 foundCount ++;
3811 }
3812 }
3813
3814 if (GetBasicStyle().HasCharacterStyleName())
3815 {
3816 wxRichTextCharacterStyleDefinition* charDef = styleSheet->FindCharacterStyle(GetBasicStyle().GetCharacterStyleName());
3817 if (charDef)
3818 {
3819 attr.Apply(charDef->GetStyleMergedWithBase(styleSheet));
3820 SetBasicStyle(attr);
3821 foundCount ++;
3822 }
3823 }
3824
3825 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3826 while (node)
3827 {
3828 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3829 // wxASSERT (para != NULL);
3830
3831 if (para)
3832 {
3833 // Combine paragraph and list styles. If there is a list style in the original attributes,
3834 // the current indentation overrides anything else and is used to find the item indentation.
3835 // Also, for applying paragraph styles, consider having 2 modes: (1) we merge with what we have,
3836 // thereby taking into account all user changes, (2) reset the style completely (except for indentation/list
3837 // exception as above).
3838 // Problem: when changing from one list style to another, there's a danger that the level info will get lost.
3839 // So when changing a list style interactively, could retrieve level based on current style, then
3840 // set appropriate indent and apply new style.
3841
3842 int outline = -1;
3843 int num = -1;
3844 if (para->GetAttributes().HasOutlineLevel())
3845 outline = para->GetAttributes().GetOutlineLevel();
3846 if (para->GetAttributes().HasBulletNumber())
3847 num = para->GetAttributes().GetBulletNumber();
3848
3849 if (!para->GetAttributes().GetParagraphStyleName().IsEmpty() && !para->GetAttributes().GetListStyleName().IsEmpty())
3850 {
3851 int currentIndent = para->GetAttributes().GetLeftIndent();
3852
3853 wxRichTextParagraphStyleDefinition* paraDef = styleSheet->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName());
3854 wxRichTextListStyleDefinition* listDef = styleSheet->FindListStyle(para->GetAttributes().GetListStyleName());
3855 if (paraDef && !listDef)
3856 {
3857 para->GetAttributes() = paraDef->GetStyleMergedWithBase(styleSheet);
3858 foundCount ++;
3859 }
3860 else if (listDef && !paraDef)
3861 {
3862 // Set overall style defined for the list style definition
3863 para->GetAttributes() = listDef->GetStyleMergedWithBase(styleSheet);
3864
3865 // Apply the style for this level
3866 wxRichTextApplyStyle(para->GetAttributes(), * listDef->GetLevelAttributes(listDef->FindLevelForIndent(currentIndent)));
3867 foundCount ++;
3868 }
3869 else if (listDef && paraDef)
3870 {
3871 // Combines overall list style, style for level, and paragraph style
3872 para->GetAttributes() = listDef->CombineWithParagraphStyle(currentIndent, paraDef->GetStyleMergedWithBase(styleSheet));
3873 foundCount ++;
3874 }
3875 }
3876 else if (para->GetAttributes().GetParagraphStyleName().IsEmpty() && !para->GetAttributes().GetListStyleName().IsEmpty())
3877 {
3878 int currentIndent = para->GetAttributes().GetLeftIndent();
3879
3880 wxRichTextListStyleDefinition* listDef = styleSheet->FindListStyle(para->GetAttributes().GetListStyleName());
3881
3882 // Overall list definition style
3883 para->GetAttributes() = listDef->GetStyleMergedWithBase(styleSheet);
3884
3885 // Style for this level
3886 wxRichTextApplyStyle(para->GetAttributes(), * listDef->GetLevelAttributes(listDef->FindLevelForIndent(currentIndent)));
3887
3888 foundCount ++;
3889 }
3890 else if (!para->GetAttributes().GetParagraphStyleName().IsEmpty() && para->GetAttributes().GetListStyleName().IsEmpty())
3891 {
3892 wxRichTextParagraphStyleDefinition* def = styleSheet->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName());
3893 if (def)
3894 {
3895 para->GetAttributes() = def->GetStyleMergedWithBase(styleSheet);
3896 foundCount ++;
3897 }
3898 }
3899
3900 if (outline != -1)
3901 para->GetAttributes().SetOutlineLevel(outline);
3902 if (num != -1)
3903 para->GetAttributes().SetBulletNumber(num);
3904 }
3905
3906 node = node->GetNext();
3907 }
3908 return foundCount != 0;
3909 }
3910
3911 /// Set list style
3912 bool wxRichTextParagraphLayoutBox::SetListStyle(const wxRichTextRange& range, wxRichTextListStyleDefinition* def, int flags, int startFrom, int specifiedLevel)
3913 {
3914 wxRichTextBuffer* buffer = GetBuffer();
3915 wxRichTextStyleSheet* styleSheet = buffer->GetStyleSheet();
3916
3917 bool withUndo = ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0);
3918 // bool applyMinimal = ((flags & wxRICHTEXT_SETSTYLE_OPTIMIZE) != 0);
3919 bool specifyLevel = ((flags & wxRICHTEXT_SETSTYLE_SPECIFY_LEVEL) != 0);
3920 bool renumber = ((flags & wxRICHTEXT_SETSTYLE_RENUMBER) != 0);
3921
3922 // Current number, if numbering
3923 int n = startFrom;
3924
3925 wxASSERT (!specifyLevel || (specifyLevel && (specifiedLevel >= 0)));
3926
3927 // If we are associated with a control, make undoable; otherwise, apply immediately
3928 // to the data.
3929
3930 bool haveControl = (buffer->GetRichTextCtrl() != NULL);
3931
3932 wxRichTextAction* action = NULL;
3933
3934 if (haveControl && withUndo)
3935 {
3936 action = new wxRichTextAction(NULL, _("Change List Style"), wxRICHTEXT_CHANGE_STYLE, buffer, this, buffer->GetRichTextCtrl());
3937 action->SetRange(range);
3938 action->SetPosition(buffer->GetRichTextCtrl()->GetCaretPosition());
3939 }
3940
3941 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3942 while (node)
3943 {
3944 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3945 // wxASSERT (para != NULL);
3946
3947 if (para && para->GetChildCount() > 0)
3948 {
3949 // Stop searching if we're beyond the range of interest
3950 if (para->GetRange().GetStart() > range.GetEnd())
3951 break;
3952
3953 if (!para->GetRange().IsOutside(range))
3954 {
3955 // We'll be using a copy of the paragraph to make style changes,
3956 // not updating the buffer directly.
3957 wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL);
3958
3959 if (haveControl && withUndo)
3960 {
3961 newPara = new wxRichTextParagraph(*para);
3962 action->GetNewParagraphs().AppendChild(newPara);
3963
3964 // Also store the old ones for Undo
3965 action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para));
3966 }
3967 else
3968 newPara = para;
3969
3970 if (def)
3971 {
3972 int thisIndent = newPara->GetAttributes().GetLeftIndent();
3973 int thisLevel = specifyLevel ? specifiedLevel : def->FindLevelForIndent(thisIndent);
3974
3975 // How is numbering going to work?
3976 // If we are renumbering, or numbering for the first time, we need to keep
3977 // track of the number for each level. But we might be simply applying a different
3978 // list style.
3979 // In Word, applying a style to several paragraphs, even if at different levels,
3980 // reverts the level back to the same one. So we could do the same here.
3981 // Renumbering will need to be done when we promote/demote a paragraph.
3982
3983 // Apply the overall list style, and item style for this level
3984 wxRichTextAttr listStyle(def->GetCombinedStyleForLevel(thisLevel, styleSheet));
3985 wxRichTextApplyStyle(newPara->GetAttributes(), listStyle);
3986
3987 // Now we need to do numbering
3988 // Preserve the existing list item continuation bullet style, if any
3989 if (para->GetAttributes().HasBulletStyle() && (para->GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_CONTINUATION))
3990 newPara->GetAttributes().SetBulletStyle(newPara->GetAttributes().GetBulletStyle()|wxTEXT_ATTR_BULLET_STYLE_CONTINUATION);
3991 else
3992 {
3993 // Now we need to do numbering
3994 if (renumber)
3995 {
3996 newPara->GetAttributes().SetBulletNumber(n);
3997 }
3998
3999 n ++;
4000 }
4001 }
4002 else if (!newPara->GetAttributes().GetListStyleName().IsEmpty())
4003 {
4004 // if def is NULL, remove list style, applying any associated paragraph style
4005 // to restore the attributes
4006
4007 newPara->GetAttributes().SetListStyleName(wxEmptyString);
4008 newPara->GetAttributes().SetLeftIndent(0, 0);
4009 newPara->GetAttributes().SetBulletText(wxEmptyString);
4010
4011 // Eliminate the main list-related attributes
4012 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);
4013
4014 if (styleSheet && !newPara->GetAttributes().GetParagraphStyleName().IsEmpty())
4015 {
4016 wxRichTextParagraphStyleDefinition* def = styleSheet->FindParagraphStyle(newPara->GetAttributes().GetParagraphStyleName());
4017 if (def)
4018 {
4019 newPara->GetAttributes() = def->GetStyleMergedWithBase(styleSheet);
4020 }
4021 }
4022 }
4023 }
4024 }
4025
4026 node = node->GetNext();
4027 }
4028
4029 // Do action, or delay it until end of batch.
4030 if (haveControl && withUndo)
4031 buffer->SubmitAction(action);
4032
4033 return true;
4034 }
4035
4036 bool wxRichTextParagraphLayoutBox::SetListStyle(const wxRichTextRange& range, const wxString& defName, int flags, int startFrom, int specifiedLevel)
4037 {
4038 wxRichTextBuffer* buffer = GetBuffer();
4039 if (buffer && buffer->GetStyleSheet())
4040 {
4041 wxRichTextListStyleDefinition* def = buffer->GetStyleSheet()->FindListStyle(defName);
4042 if (def)
4043 return SetListStyle(range, def, flags, startFrom, specifiedLevel);
4044 }
4045 return false;
4046 }
4047
4048 /// Clear list for given range
4049 bool wxRichTextParagraphLayoutBox::ClearListStyle(const wxRichTextRange& range, int flags)
4050 {
4051 return SetListStyle(range, NULL, flags);
4052 }
4053
4054 /// Number/renumber any list elements in the given range
4055 bool wxRichTextParagraphLayoutBox::NumberList(const wxRichTextRange& range, wxRichTextListStyleDefinition* def, int flags, int startFrom, int specifiedLevel)
4056 {
4057 return DoNumberList(range, range, 0, def, flags, startFrom, specifiedLevel);
4058 }
4059
4060 /// Number/renumber any list elements in the given range. Also do promotion or demotion of items, if specified
4061 bool wxRichTextParagraphLayoutBox::DoNumberList(const wxRichTextRange& range, const wxRichTextRange& promotionRange, int promoteBy,
4062 wxRichTextListStyleDefinition* def, int flags, int startFrom, int specifiedLevel)
4063 {
4064 wxRichTextBuffer* buffer = GetBuffer();
4065 wxRichTextStyleSheet* styleSheet = buffer->GetStyleSheet();
4066
4067 bool withUndo = ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0);
4068 // bool applyMinimal = ((flags & wxRICHTEXT_SETSTYLE_OPTIMIZE) != 0);
4069 #if wxDEBUG_LEVEL
4070 bool specifyLevel = ((flags & wxRICHTEXT_SETSTYLE_SPECIFY_LEVEL) != 0);
4071 #endif
4072
4073 bool renumber = ((flags & wxRICHTEXT_SETSTYLE_RENUMBER) != 0);
4074
4075 // Max number of levels
4076 const int maxLevels = 10;
4077
4078 // The level we're looking at now
4079 int currentLevel = -1;
4080
4081 // The item number for each level
4082 int levels[maxLevels];
4083 int i;
4084
4085 // Reset all numbering
4086 for (i = 0; i < maxLevels; i++)
4087 {
4088 if (startFrom != -1)
4089 levels[i] = startFrom-1;
4090 else if (renumber) // start again
4091 levels[i] = 0;
4092 else
4093 levels[i] = -1; // start from the number we found, if any
4094 }
4095
4096 #if wxDEBUG_LEVEL
4097 wxASSERT(!specifyLevel || (specifyLevel && (specifiedLevel >= 0)));
4098 #endif
4099
4100 // If we are associated with a control, make undoable; otherwise, apply immediately
4101 // to the data.
4102
4103 bool haveControl = (buffer->GetRichTextCtrl() != NULL);
4104
4105 wxRichTextAction* action = NULL;
4106
4107 if (haveControl && withUndo)
4108 {
4109 action = new wxRichTextAction(NULL, _("Renumber List"), wxRICHTEXT_CHANGE_STYLE, buffer, this, buffer->GetRichTextCtrl());
4110 action->SetRange(range);
4111 action->SetPosition(buffer->GetRichTextCtrl()->GetCaretPosition());
4112 }
4113
4114 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
4115 while (node)
4116 {
4117 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
4118 // wxASSERT (para != NULL);
4119
4120 if (para && para->GetChildCount() > 0)
4121 {
4122 // Stop searching if we're beyond the range of interest
4123 if (para->GetRange().GetStart() > range.GetEnd())
4124 break;
4125
4126 if (!para->GetRange().IsOutside(range))
4127 {
4128 // We'll be using a copy of the paragraph to make style changes,
4129 // not updating the buffer directly.
4130 wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL);
4131
4132 if (haveControl && withUndo)
4133 {
4134 newPara = new wxRichTextParagraph(*para);
4135 action->GetNewParagraphs().AppendChild(newPara);
4136
4137 // Also store the old ones for Undo
4138 action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para));
4139 }
4140 else
4141 newPara = para;
4142
4143 wxRichTextListStyleDefinition* defToUse = def;
4144 if (!defToUse)
4145 {
4146 if (styleSheet && !newPara->GetAttributes().GetListStyleName().IsEmpty())
4147 defToUse = styleSheet->FindListStyle(newPara->GetAttributes().GetListStyleName());
4148 }
4149
4150 if (defToUse)
4151 {
4152 int thisIndent = newPara->GetAttributes().GetLeftIndent();
4153 int thisLevel = defToUse->FindLevelForIndent(thisIndent);
4154
4155 // If we've specified a level to apply to all, change the level.
4156 if (specifiedLevel != -1)
4157 thisLevel = specifiedLevel;
4158
4159 // Do promotion if specified
4160 if ((promoteBy != 0) && !para->GetRange().IsOutside(promotionRange))
4161 {
4162 thisLevel = thisLevel - promoteBy;
4163 if (thisLevel < 0)
4164 thisLevel = 0;
4165 if (thisLevel > 9)
4166 thisLevel = 9;
4167 }
4168
4169 // Apply the overall list style, and item style for this level
4170 wxRichTextAttr listStyle(defToUse->GetCombinedStyleForLevel(thisLevel, styleSheet));
4171 wxRichTextApplyStyle(newPara->GetAttributes(), listStyle);
4172
4173 // Preserve the existing list item continuation bullet style, if any
4174 if (para->GetAttributes().HasBulletStyle() && (para->GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_CONTINUATION))
4175 newPara->GetAttributes().SetBulletStyle(newPara->GetAttributes().GetBulletStyle()|wxTEXT_ATTR_BULLET_STYLE_CONTINUATION);
4176
4177 // OK, we've (re)applied the style, now let's get the numbering right.
4178
4179 if (currentLevel == -1)
4180 currentLevel = thisLevel;
4181
4182 // Same level as before, do nothing except increment level's number afterwards
4183 if (currentLevel == thisLevel)
4184 {
4185 }
4186 // A deeper level: start renumbering all levels after current level
4187 else if (thisLevel > currentLevel)
4188 {
4189 for (i = currentLevel+1; i <= thisLevel; i++)
4190 {
4191 levels[i] = 0;
4192 }
4193 currentLevel = thisLevel;
4194 }
4195 else if (thisLevel < currentLevel)
4196 {
4197 currentLevel = thisLevel;
4198 }
4199
4200 // Use the current numbering if -1 and we have a bullet number already
4201 if (levels[currentLevel] == -1)
4202 {
4203 if (newPara->GetAttributes().HasBulletNumber())
4204 levels[currentLevel] = newPara->GetAttributes().GetBulletNumber();
4205 else
4206 levels[currentLevel] = 1;
4207 }
4208 else
4209 {
4210 if (!(para->GetAttributes().HasBulletStyle() && (para->GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_CONTINUATION)))
4211 levels[currentLevel] ++;
4212 }
4213
4214 newPara->GetAttributes().SetBulletNumber(levels[currentLevel]);
4215
4216 // Create the bullet text if an outline list
4217 if (listStyle.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE)
4218 {
4219 wxString text;
4220 for (i = 0; i <= currentLevel; i++)
4221 {
4222 if (!text.IsEmpty())
4223 text += wxT(".");
4224 text += wxString::Format(wxT("%d"), levels[i]);
4225 }
4226 newPara->GetAttributes().SetBulletText(text);
4227 }
4228 }
4229 }
4230 }
4231
4232 node = node->GetNext();
4233 }
4234
4235 // Do action, or delay it until end of batch.
4236 if (haveControl && withUndo)
4237 buffer->SubmitAction(action);
4238
4239 return true;
4240 }
4241
4242 bool wxRichTextParagraphLayoutBox::NumberList(const wxRichTextRange& range, const wxString& defName, int flags, int startFrom, int specifiedLevel)
4243 {
4244 wxRichTextBuffer* buffer = GetBuffer();
4245 if (buffer->GetStyleSheet())
4246 {
4247 wxRichTextListStyleDefinition* def = NULL;
4248 if (!defName.IsEmpty())
4249 def = buffer->GetStyleSheet()->FindListStyle(defName);
4250 return NumberList(range, def, flags, startFrom, specifiedLevel);
4251 }
4252 return false;
4253 }
4254
4255 /// Promote the list items within the given range. promoteBy can be a positive or negative number, e.g. 1 or -1
4256 bool wxRichTextParagraphLayoutBox::PromoteList(int promoteBy, const wxRichTextRange& range, wxRichTextListStyleDefinition* def, int flags, int specifiedLevel)
4257 {
4258 // TODO
4259 // One strategy is to first work out the range within which renumbering must occur. Then could pass these two ranges
4260 // to NumberList with a flag indicating promotion is required within one of the ranges.
4261 // Find first and last paragraphs in range. Then for first, calculate new indentation and look back until we find
4262 // a paragraph that either has no list style, or has one that is different or whose indentation is less.
4263 // We start renumbering from the para after that different para we found. We specify that the numbering of that
4264 // list position will start from 1.
4265 // Similarly, we look after the last para in the promote range for an indentation that is less (or no list style).
4266 // We can end the renumbering at this point.
4267
4268 // For now, only renumber within the promotion range.
4269
4270 return DoNumberList(range, range, promoteBy, def, flags, 1, specifiedLevel);
4271 }
4272
4273 bool wxRichTextParagraphLayoutBox::PromoteList(int promoteBy, const wxRichTextRange& range, const wxString& defName, int flags, int specifiedLevel)
4274 {
4275 wxRichTextBuffer* buffer = GetBuffer();
4276 if (buffer->GetStyleSheet())
4277 {
4278 wxRichTextListStyleDefinition* def = NULL;
4279 if (!defName.IsEmpty())
4280 def = buffer->GetStyleSheet()->FindListStyle(defName);
4281 return PromoteList(promoteBy, range, def, flags, specifiedLevel);
4282 }
4283 return false;
4284 }
4285
4286 /// Fills in the attributes for numbering a paragraph after previousParagraph. It also finds the
4287 /// position of the paragraph that it had to start looking from.
4288 bool wxRichTextParagraphLayoutBox::FindNextParagraphNumber(wxRichTextParagraph* previousParagraph, wxRichTextAttr& attr) const
4289 {
4290 // Search for a paragraph that isn't a continuation paragraph (no bullet)
4291 while (previousParagraph && previousParagraph->GetAttributes().HasBulletStyle() && previousParagraph->GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_CONTINUATION)
4292 {
4293 wxRichTextObjectList::compatibility_iterator node = ((wxRichTextCompositeObject*) previousParagraph->GetParent())->GetChildren().Find(previousParagraph);
4294 if (node)
4295 {
4296 node = node->GetPrevious();
4297 if (node)
4298 previousParagraph = wxDynamicCast(node->GetData(), wxRichTextParagraph);
4299 else
4300 previousParagraph = NULL;
4301 }
4302 else
4303 previousParagraph = NULL;
4304 }
4305
4306 if (!previousParagraph->GetAttributes().HasFlag(wxTEXT_ATTR_BULLET_STYLE) || previousParagraph->GetAttributes().GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_NONE)
4307 return false;
4308
4309 wxRichTextBuffer* buffer = GetBuffer();
4310 wxRichTextStyleSheet* styleSheet = buffer->GetStyleSheet();
4311 if (styleSheet && !previousParagraph->GetAttributes().GetListStyleName().IsEmpty())
4312 {
4313 wxRichTextListStyleDefinition* def = styleSheet->FindListStyle(previousParagraph->GetAttributes().GetListStyleName());
4314 if (def)
4315 {
4316 // int thisIndent = previousParagraph->GetAttributes().GetLeftIndent();
4317 // int thisLevel = def->FindLevelForIndent(thisIndent);
4318
4319 bool isOutline = (previousParagraph->GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE) != 0;
4320
4321 attr.SetFlags(previousParagraph->GetAttributes().GetFlags() & (wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_BULLET_NUMBER|wxTEXT_ATTR_BULLET_TEXT|wxTEXT_ATTR_BULLET_NAME));
4322 if (previousParagraph->GetAttributes().HasBulletName())
4323 attr.SetBulletName(previousParagraph->GetAttributes().GetBulletName());
4324 attr.SetBulletStyle(previousParagraph->GetAttributes().GetBulletStyle());
4325 attr.SetListStyleName(previousParagraph->GetAttributes().GetListStyleName());
4326
4327 int nextNumber = previousParagraph->GetAttributes().GetBulletNumber() + 1;
4328 attr.SetBulletNumber(nextNumber);
4329
4330 if (isOutline)
4331 {
4332 wxString text = previousParagraph->GetAttributes().GetBulletText();
4333 if (!text.IsEmpty())
4334 {
4335 int pos = text.Find(wxT('.'), true);
4336 if (pos != wxNOT_FOUND)
4337 {
4338 text = text.Mid(0, text.Length() - pos - 1);
4339 }
4340 else
4341 text = wxEmptyString;
4342 if (!text.IsEmpty())
4343 text += wxT(".");
4344 text += wxString::Format(wxT("%d"), nextNumber);
4345 attr.SetBulletText(text);
4346 }
4347 }
4348
4349 return true;
4350 }
4351 else
4352 return false;
4353 }
4354 else
4355 return false;
4356 }
4357
4358 /*!
4359 * wxRichTextParagraph
4360 * This object represents a single paragraph (or in a straight text editor, a line).
4361 */
4362
4363 IMPLEMENT_DYNAMIC_CLASS(wxRichTextParagraph, wxRichTextCompositeObject)
4364
4365 wxArrayInt wxRichTextParagraph::sm_defaultTabs;
4366
4367 wxRichTextParagraph::wxRichTextParagraph(wxRichTextObject* parent, wxRichTextAttr* style):
4368 wxRichTextCompositeObject(parent)
4369 {
4370 if (style)
4371 SetAttributes(*style);
4372 }
4373
4374 wxRichTextParagraph::wxRichTextParagraph(const wxString& text, wxRichTextObject* parent, wxRichTextAttr* paraStyle, wxRichTextAttr* charStyle):
4375 wxRichTextCompositeObject(parent)
4376 {
4377 if (paraStyle)
4378 SetAttributes(*paraStyle);
4379
4380 AppendChild(new wxRichTextPlainText(text, this, charStyle));
4381 }
4382
4383 wxRichTextParagraph::~wxRichTextParagraph()
4384 {
4385 ClearLines();
4386 }
4387
4388 /// Draw the item
4389 bool wxRichTextParagraph::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int WXUNUSED(descent), int style)
4390 {
4391 if (!IsShown())
4392 return true;
4393
4394 // Currently we don't merge these attributes with the parent, but we
4395 // should consider whether we should (e.g. if we set a border colour
4396 // for all paragraphs). But generally box attributes are likely to be
4397 // different for different objects.
4398 wxRect paraRect = GetRect();
4399 wxRichTextAttr attr = GetCombinedAttributes();
4400 context.ApplyVirtualAttributes(attr, this);
4401
4402 DrawBoxAttributes(dc, GetBuffer(), attr, paraRect);
4403
4404 // Draw the bullet, if any
4405 if ((attr.GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_NONE) == 0 && (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_CONTINUATION) == 0)
4406 {
4407 if (attr.GetLeftSubIndent() != 0)
4408 {
4409 int spaceBeforePara = ConvertTenthsMMToPixels(dc, attr.GetParagraphSpacingBefore());
4410 int leftIndent = ConvertTenthsMMToPixels(dc, attr.GetLeftIndent());
4411
4412 wxRichTextAttr bulletAttr(attr);
4413
4414 // Combine with the font of the first piece of content, if one is specified
4415 if (GetChildren().GetCount() > 0)
4416 {
4417 wxRichTextObject* firstObj = (wxRichTextObject*) GetChildren().GetFirst()->GetData();
4418 if (!firstObj->IsFloatable() && firstObj->GetAttributes().HasFont())
4419 {
4420 wxRichTextApplyStyle(bulletAttr, firstObj->GetAttributes());
4421 }
4422 }
4423
4424 // Get line height from first line, if any
4425 wxRichTextLine* line = m_cachedLines.GetFirst() ? (wxRichTextLine* ) m_cachedLines.GetFirst()->GetData() : NULL;
4426
4427 wxPoint linePos;
4428 int lineHeight wxDUMMY_INITIALIZE(0);
4429 if (line)
4430 {
4431 lineHeight = line->GetSize().y;
4432 linePos = line->GetPosition() + GetPosition();
4433 }
4434 else
4435 {
4436 wxFont font;
4437 if (bulletAttr.HasFont() && GetBuffer())
4438 font = GetBuffer()->GetFontTable().FindFont(bulletAttr);
4439 else
4440 font = (*wxNORMAL_FONT);
4441
4442 wxCheckSetFont(dc, font);
4443
4444 lineHeight = dc.GetCharHeight();
4445 linePos = GetPosition();
4446 linePos.y += spaceBeforePara;
4447 }
4448
4449 wxRect bulletRect(GetPosition().x + leftIndent, linePos.y, linePos.x - (GetPosition().x + leftIndent), lineHeight);
4450
4451 if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_BITMAP)
4452 {
4453 if (wxRichTextBuffer::GetRenderer())
4454 wxRichTextBuffer::GetRenderer()->DrawBitmapBullet(this, dc, bulletAttr, bulletRect);
4455 }
4456 else if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_STANDARD)
4457 {
4458 if (wxRichTextBuffer::GetRenderer())
4459 wxRichTextBuffer::GetRenderer()->DrawStandardBullet(this, dc, bulletAttr, bulletRect);
4460 }
4461 else
4462 {
4463 wxString bulletText = GetBulletText();
4464
4465 if (!bulletText.empty() && wxRichTextBuffer::GetRenderer())
4466 wxRichTextBuffer::GetRenderer()->DrawTextBullet(this, dc, bulletAttr, bulletRect, bulletText);
4467 }
4468 }
4469 }
4470
4471 // Draw the range for each line, one object at a time.
4472
4473 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
4474 while (node)
4475 {
4476 wxRichTextLine* line = node->GetData();
4477 wxRichTextRange lineRange = line->GetAbsoluteRange();
4478
4479 // Lines are specified relative to the paragraph
4480
4481 wxPoint linePosition = line->GetPosition() + GetPosition();
4482
4483 // Don't draw if off the screen
4484 if (((style & wxRICHTEXT_DRAW_IGNORE_CACHE) != 0) || ((linePosition.y + line->GetSize().y) >= rect.y && linePosition.y <= rect.y + rect.height))
4485 {
4486 wxPoint objectPosition = linePosition;
4487 int maxDescent = line->GetDescent();
4488
4489 // Loop through objects until we get to the one within range
4490 wxRichTextObjectList::compatibility_iterator node2 = m_children.GetFirst();
4491
4492 int i = 0;
4493 while (node2)
4494 {
4495 wxRichTextObject* child = node2->GetData();
4496
4497 if (!child->IsFloating() && child->GetRange().GetLength() > 0 && !child->GetRange().IsOutside(lineRange) && !lineRange.IsOutside(range))
4498 {
4499 // Draw this part of the line at the correct position
4500 wxRichTextRange objectRange(child->GetRange());
4501 objectRange.LimitTo(lineRange);
4502
4503 wxSize objectSize;
4504 if (child->IsTopLevel())
4505 {
4506 objectSize = child->GetCachedSize();
4507 objectRange = child->GetOwnRange();
4508 }
4509 else
4510 {
4511 #if wxRICHTEXT_USE_OPTIMIZED_LINE_DRAWING && wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
4512 if (i < (int) line->GetObjectSizes().GetCount())
4513 {
4514 objectSize.x = line->GetObjectSizes()[(size_t) i];
4515 }
4516 else
4517 #endif
4518 {
4519 int descent = 0;
4520 child->GetRangeSize(objectRange, objectSize, descent, dc, context, wxRICHTEXT_UNFORMATTED, objectPosition);
4521 }
4522 }
4523
4524 // Use the child object's width, but the whole line's height
4525 wxRect childRect(objectPosition, wxSize(objectSize.x, line->GetSize().y));
4526 child->Draw(dc, context, objectRange, selection, childRect, maxDescent, style);
4527
4528 objectPosition.x += objectSize.x;
4529 i ++;
4530 }
4531 else if (child->GetRange().GetStart() > lineRange.GetEnd())
4532 // Can break out of inner loop now since we've passed this line's range
4533 break;
4534
4535 node2 = node2->GetNext();
4536 }
4537 }
4538
4539 node = node->GetNext();
4540 }
4541
4542 return true;
4543 }
4544
4545 // Get the range width using partial extents calculated for the whole paragraph.
4546 static int wxRichTextGetRangeWidth(const wxRichTextParagraph& para, const wxRichTextRange& range, const wxArrayInt& partialExtents)
4547 {
4548 wxASSERT(partialExtents.GetCount() >= (size_t) range.GetLength());
4549
4550 if (partialExtents.GetCount() < (size_t) range.GetLength())
4551 return 0;
4552
4553 int leftMostPos = 0;
4554 if (range.GetStart() - para.GetRange().GetStart() > 0)
4555 leftMostPos = partialExtents[range.GetStart() - para.GetRange().GetStart() - 1];
4556
4557 int rightMostPos = partialExtents[range.GetEnd() - para.GetRange().GetStart()];
4558
4559 int w = rightMostPos - leftMostPos;
4560
4561 return w;
4562 }
4563
4564 /// Lay the item out
4565 bool wxRichTextParagraph::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& parentRect, int style)
4566 {
4567 // Deal with floating objects firstly before the normal layout
4568 wxRichTextBuffer* buffer = GetBuffer();
4569 wxASSERT(buffer);
4570 wxRichTextFloatCollector* collector = GetContainer()->GetFloatCollector();
4571 wxASSERT(collector);
4572 LayoutFloat(dc, context, rect, style, collector);
4573
4574 wxRichTextAttr attr = GetCombinedAttributes();
4575 context.ApplyVirtualAttributes(attr, this);
4576
4577 // ClearLines();
4578
4579 // Increase the size of the paragraph due to spacing
4580 int spaceBeforePara = ConvertTenthsMMToPixels(dc, attr.GetParagraphSpacingBefore());
4581 int spaceAfterPara = ConvertTenthsMMToPixels(dc, attr.GetParagraphSpacingAfter());
4582 int leftIndent = ConvertTenthsMMToPixels(dc, attr.GetLeftIndent());
4583 int leftSubIndent = ConvertTenthsMMToPixels(dc, attr.GetLeftSubIndent());
4584 int rightIndent = ConvertTenthsMMToPixels(dc, attr.GetRightIndent());
4585
4586 int lineSpacing = 0;
4587
4588 // Let's assume line spacing of 10 is normal, 15 is 1.5, 20 is 2, etc.
4589 if (attr.HasLineSpacing() && attr.GetLineSpacing() > 0 && attr.GetFont().IsOk())
4590 {
4591 wxCheckSetFont(dc, attr.GetFont());
4592 lineSpacing = (int) (double(dc.GetCharHeight()) * (double(attr.GetLineSpacing())/10.0 - 1.0));
4593 }
4594
4595 // Start position for each line relative to the paragraph
4596 int startPositionFirstLine = leftIndent;
4597 int startPositionSubsequentLines = leftIndent + leftSubIndent;
4598
4599 // If we have a bullet in this paragraph, the start position for the first line's text
4600 // is actually leftIndent + leftSubIndent.
4601 if (attr.GetBulletStyle() != wxTEXT_ATTR_BULLET_STYLE_NONE)
4602 startPositionFirstLine = startPositionSubsequentLines;
4603
4604 long lastEndPos = GetRange().GetStart()-1;
4605 long lastCompletedEndPos = lastEndPos;
4606
4607 int currentWidth = 0;
4608 SetPosition(rect.GetPosition());
4609
4610 wxPoint currentPosition(0, spaceBeforePara); // We will calculate lines relative to paragraph
4611 int lineHeight = 0;
4612 int maxWidth = 0;
4613 int maxHeight = currentPosition.y;
4614 int maxAscent = 0;
4615 int maxDescent = 0;
4616 int lineCount = 0;
4617 int lineAscent = 0;
4618 int lineDescent = 0;
4619
4620 wxRichTextObjectList::compatibility_iterator node;
4621
4622 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
4623 wxUnusedVar(style);
4624 wxArrayInt partialExtents;
4625
4626 wxSize paraSize;
4627 int paraDescent = 0;
4628
4629 // This calculates the partial text extents
4630 GetRangeSize(GetRange(), paraSize, paraDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_CACHE_SIZE, rect.GetPosition(), & partialExtents);
4631 #else
4632 node = m_children.GetFirst();
4633 while (node)
4634 {
4635 wxRichTextObject* child = node->GetData();
4636
4637 //child->SetCachedSize(wxDefaultSize);
4638 child->Layout(dc, context, rect, style);
4639
4640 node = node->GetNext();
4641 }
4642 #endif
4643
4644 // Split up lines
4645
4646 // We may need to go back to a previous child, in which case create the new line,
4647 // find the child corresponding to the start position of the string, and
4648 // continue.
4649
4650 wxRect availableRect;
4651
4652 node = m_children.GetFirst();
4653 while (node)
4654 {
4655 wxRichTextObject* child = node->GetData();
4656
4657 // If floating, ignore. We already laid out floats.
4658 // Also ignore if empty object, except if we haven't got any
4659 // size yet.
4660 if (child->IsFloating() || !child->IsShown() ||
4661 (child->GetRange().GetLength() == 0 && maxHeight > spaceBeforePara)
4662 )
4663 {
4664 node = node->GetNext();
4665 continue;
4666 }
4667
4668 // If this is e.g. a composite text box, it will need to be laid out itself.
4669 // But if just a text fragment or image, for example, this will
4670 // do nothing. NB: won't we need to set the position after layout?
4671 // since for example if position is dependent on vertical line size, we
4672 // can't tell the position until the size is determined. So possibly introduce
4673 // another layout phase.
4674
4675 // We may only be looking at part of a child, if we searched back for wrapping
4676 // and found a suitable point some way into the child. So get the size for the fragment
4677 // if necessary.
4678
4679 long nextBreakPos = GetFirstLineBreakPosition(lastEndPos+1);
4680 long lastPosToUse = child->GetRange().GetEnd();
4681 bool lineBreakInThisObject = (nextBreakPos > -1 && nextBreakPos <= child->GetRange().GetEnd());
4682
4683 if (lineBreakInThisObject)
4684 lastPosToUse = nextBreakPos;
4685
4686 wxSize childSize;
4687 int childDescent = 0;
4688
4689 int startOffset = (lineCount == 0 ? startPositionFirstLine : startPositionSubsequentLines);
4690 availableRect = wxRect(rect.x + startOffset, rect.y + currentPosition.y,
4691 rect.width - startOffset - rightIndent, rect.height);
4692
4693 if (child->IsTopLevel())
4694 {
4695 wxSize oldSize = child->GetCachedSize();
4696
4697 child->Invalidate(wxRICHTEXT_ALL);
4698 child->SetPosition(wxPoint(0, 0));
4699
4700 // Lays out the object first with a given amount of space, and then if no width was specified in attr,
4701 // lays out the object again using the minimum size
4702 // The position will be determined by its location in its line,
4703 // and not by the child's actual position.
4704 child->LayoutToBestSize(dc, context, buffer,
4705 attr, child->GetAttributes(), availableRect, parentRect, style);
4706
4707 if (oldSize != child->GetCachedSize())
4708 {
4709 partialExtents.Clear();
4710
4711 // Recalculate the partial text extents since the child object changed size
4712 GetRangeSize(GetRange(), paraSize, paraDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_CACHE_SIZE, wxPoint(0,0), & partialExtents);
4713 }
4714 }
4715
4716 // Problem: we need to layout composites here for which we need the available width,
4717 // but we can't get the available width without using the float collector which
4718 // needs to know the object height.
4719
4720 if ((nextBreakPos == -1) && (lastEndPos == child->GetRange().GetStart() - 1)) // i.e. we want to get the whole thing
4721 {
4722 childSize = child->GetCachedSize();
4723 childDescent = child->GetDescent();
4724 }
4725 else
4726 {
4727 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
4728 // Get height only, then the width using the partial extents
4729 GetRangeSize(wxRichTextRange(lastEndPos+1, lastPosToUse), childSize, childDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_HEIGHT_ONLY);
4730 childSize.x = wxRichTextGetRangeWidth(*this, wxRichTextRange(lastEndPos+1, lastPosToUse), partialExtents);
4731 #else
4732 GetRangeSize(wxRichTextRange(lastEndPos+1, lastPosToUse), childSize, childDescent, dc, context, wxRICHTEXT_UNFORMATTED, rect.GetPosition());
4733 #endif
4734 }
4735
4736 bool doLoop = true;
4737 int loopIterations = 0;
4738
4739 // If there are nested objects that need to lay themselves out, we have to do this in a
4740 // loop because the height of the object may well depend on the available width.
4741 // And because of floating object positioning, the available width depends on the
4742 // height of the object and whether it will clash with the floating objects.
4743 // So, we see whether the available width changes due to the presence of floating images.
4744 // If it does, then we'll use the new restricted width to find the object height again.
4745 // If this causes another restriction in the available width, we'll try again, until
4746 // either we lose patience or the available width settles down.
4747 do
4748 {
4749 loopIterations ++;
4750
4751 wxRect oldAvailableRect = availableRect;
4752
4753 // Available width depends on the floating objects and the line height.
4754 // Note: the floating objects may be placed vertically along the two sides of
4755 // buffer, so we may have different available line widths with different
4756 // [startY, endY]. So, we can't determine how wide the available
4757 // space is until we know the exact line height.
4758 if (childDescent == 0)
4759 {
4760 lineHeight = wxMax(lineHeight, childSize.y);
4761 lineDescent = maxDescent;
4762 lineAscent = maxAscent;
4763 }
4764 else
4765 {
4766 lineDescent = wxMax(childDescent, maxDescent);
4767 lineAscent = wxMax(childSize.y-childDescent, maxAscent);
4768 }
4769 lineHeight = wxMax(lineHeight, (lineDescent + lineAscent));
4770 wxRect floatAvailableRect = collector->GetAvailableRect(rect.y + currentPosition.y, rect.y + currentPosition.y + lineHeight);
4771
4772 // Adjust availableRect to the space that is available when taking floating objects into account.
4773
4774 if (floatAvailableRect.x + startOffset > availableRect.x)
4775 {
4776 int newX = floatAvailableRect.x + startOffset;
4777 int newW = availableRect.width - (newX - availableRect.x);
4778 availableRect.x = newX;
4779 availableRect.width = newW;
4780 }
4781
4782 if (floatAvailableRect.width < availableRect.width)
4783 availableRect.width = floatAvailableRect.width;
4784
4785 currentPosition.x = availableRect.x - rect.x;
4786
4787 if (child->IsTopLevel() && loopIterations <= 20)
4788 {
4789 if (availableRect != oldAvailableRect)
4790 {
4791 wxSize oldSize = child->GetCachedSize();
4792
4793 // Lays out the object first with a given amount of space, and then if no width was specified in attr,
4794 // lays out the object again using the minimum size
4795 child->Invalidate(wxRICHTEXT_ALL);
4796 child->LayoutToBestSize(dc, context, buffer,
4797 attr, child->GetAttributes(), availableRect, parentRect, style);
4798 childSize = child->GetCachedSize();
4799 childDescent = child->GetDescent();
4800
4801 if (oldSize != child->GetCachedSize())
4802 {
4803 partialExtents.Clear();
4804
4805 // Recalculate the partial text extents since the child object changed size
4806 GetRangeSize(GetRange(), paraSize, paraDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_CACHE_SIZE, wxPoint(0,0), & partialExtents);
4807 }
4808
4809 // Go around the loop finding the available rect for the given floating objects
4810 }
4811 else
4812 doLoop = false;
4813 }
4814 else
4815 doLoop = false;
4816 }
4817 while (doLoop);
4818
4819 if (child->IsTopLevel())
4820 {
4821 // We can move it to the correct position at this point
4822 // TODO: probably need to add margin
4823 child->Move(GetPosition() + wxPoint(currentWidth + (wxMax(leftIndent, leftIndent + leftSubIndent)), currentPosition.y));
4824 }
4825
4826 // Cases:
4827 // 1) There was a line break BEFORE the natural break
4828 // 2) There was a line break AFTER the natural break
4829 // 3) It's the last line
4830 // 4) The child still fits (carry on) - 'else' clause
4831
4832 if ((lineBreakInThisObject && (childSize.x + currentWidth <= availableRect.width))
4833 ||
4834 (childSize.x + currentWidth > availableRect.width)
4835 ||
4836 ((childSize.x + currentWidth <= availableRect.width) && !node->GetNext())
4837
4838 )
4839 {
4840 long wrapPosition = 0;
4841 if ((childSize.x + currentWidth <= availableRect.width) && !node->GetNext() && !lineBreakInThisObject)
4842 wrapPosition = child->GetRange().GetEnd();
4843 else
4844
4845 // Find a place to wrap. This may walk back to previous children,
4846 // for example if a word spans several objects.
4847 // Note: one object must contains only one wxTextAtrr, so the line height will not
4848 // change inside one object. Thus, we can pass the remain line width to the
4849 // FindWrapPosition function.
4850 if (!FindWrapPosition(wxRichTextRange(lastCompletedEndPos+1, child->GetRange().GetEnd()), dc, context, availableRect.width, wrapPosition, & partialExtents))
4851 {
4852 // If the function failed, just cut it off at the end of this child.
4853 wrapPosition = child->GetRange().GetEnd();
4854 }
4855
4856 // FindWrapPosition can still return a value that will put us in an endless wrapping loop
4857 if (wrapPosition <= lastCompletedEndPos)
4858 wrapPosition = wxMax(lastCompletedEndPos+1,child->GetRange().GetEnd());
4859
4860 // Line end position shouldn't be the same as the end, or greater.
4861 if (wrapPosition >= GetRange().GetEnd())
4862 wrapPosition = wxMax(0, GetRange().GetEnd()-1);
4863
4864 // wxLogDebug(wxT("Split at %ld"), wrapPosition);
4865
4866 // Let's find the actual size of the current line now
4867 wxSize actualSize;
4868 wxRichTextRange actualRange(lastCompletedEndPos+1, wrapPosition);
4869
4870 childDescent = 0;
4871
4872 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
4873 if (!child->IsEmpty())
4874 {
4875 // Get height only, then the width using the partial extents
4876 GetRangeSize(actualRange, actualSize, childDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_HEIGHT_ONLY);
4877 actualSize.x = wxRichTextGetRangeWidth(*this, actualRange, partialExtents);
4878 }
4879 else
4880 #endif
4881 GetRangeSize(actualRange, actualSize, childDescent, dc, context, wxRICHTEXT_UNFORMATTED);
4882
4883 currentWidth = actualSize.x;
4884
4885 // The descent for the whole line at this point, is the correct max descent
4886 maxDescent = childDescent;
4887 // Maximum ascent
4888 maxAscent = actualSize.y-childDescent;
4889
4890 // lineHeight is given by the height for the whole line, since it will
4891 // take into account ascend/descend.
4892 lineHeight = actualSize.y;
4893
4894 if (lineHeight == 0 && buffer)
4895 {
4896 wxFont font(buffer->GetFontTable().FindFont(attr));
4897 wxCheckSetFont(dc, font);
4898 lineHeight = dc.GetCharHeight();
4899 }
4900
4901 if (maxDescent == 0)
4902 {
4903 int w, h;
4904 dc.GetTextExtent(wxT("X"), & w, &h, & maxDescent);
4905 }
4906
4907 // Add a new line
4908 wxRichTextLine* line = AllocateLine(lineCount);
4909
4910 // Set relative range so we won't have to change line ranges when paragraphs are moved
4911 line->SetRange(wxRichTextRange(actualRange.GetStart() - GetRange().GetStart(), actualRange.GetEnd() - GetRange().GetStart()));
4912 line->SetPosition(currentPosition);
4913 line->SetSize(wxSize(currentWidth, lineHeight));
4914 line->SetDescent(maxDescent);
4915
4916 maxHeight = currentPosition.y + lineHeight;
4917
4918 // Now move down a line. TODO: add margins, spacing
4919 currentPosition.y += lineHeight;
4920 currentPosition.y += lineSpacing;
4921 maxDescent = 0;
4922 maxAscent = 0;
4923 maxWidth = wxMax(maxWidth, currentWidth+startOffset);
4924 currentWidth = 0;
4925
4926 lineCount ++;
4927
4928 // TODO: account for zero-length objects
4929 // wxASSERT(wrapPosition > lastCompletedEndPos);
4930
4931 lastEndPos = wrapPosition;
4932 lastCompletedEndPos = lastEndPos;
4933
4934 lineHeight = 0;
4935
4936 if (wrapPosition < GetRange().GetEnd()-1)
4937 {
4938 // May need to set the node back to a previous one, due to searching back in wrapping
4939 wxRichTextObject* childAfterWrapPosition = FindObjectAtPosition(wrapPosition+1);
4940 if (childAfterWrapPosition)
4941 node = m_children.Find(childAfterWrapPosition);
4942 else
4943 node = node->GetNext();
4944 }
4945 else
4946 node = node->GetNext();
4947
4948 // Apply paragraph styles such as alignment to the wrapped line
4949 ApplyParagraphStyle(line, attr, availableRect, dc);
4950 }
4951 else
4952 {
4953 // We still fit, so don't add a line, and keep going
4954 currentWidth += childSize.x;
4955
4956 if (childDescent == 0)
4957 {
4958 // An object with a zero descend value wants to take up the whole
4959 // height regardless of baseline
4960 lineHeight = wxMax(lineHeight, childSize.y);
4961 }
4962 else
4963 {
4964 maxDescent = wxMax(childDescent, maxDescent);
4965 maxAscent = wxMax(childSize.y-childDescent, maxAscent);
4966 }
4967
4968 lineHeight = wxMax(lineHeight, (maxDescent + maxAscent));
4969
4970 maxWidth = wxMax(maxWidth, currentWidth+startOffset);
4971 lastEndPos = child->GetRange().GetEnd();
4972
4973 node = node->GetNext();
4974 }
4975 }
4976
4977 //wxASSERT(!(lastCompletedEndPos != -1 && lastCompletedEndPos < GetRange().GetEnd()-1));
4978
4979 // Remove remaining unused line objects, if any
4980 ClearUnusedLines(lineCount);
4981
4982 // We need to add back the margins etc.
4983 {
4984 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
4985 contentRect = wxRect(wxPoint(0, 0), wxSize(maxWidth, currentPosition.y + spaceAfterPara));
4986 GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
4987 SetCachedSize(marginRect.GetSize());
4988 }
4989
4990 // The maximum size is the length of the paragraph stretched out into a line.
4991 // So if there were a single word, or an image, or a fixed-size text box, the object could be shrunk around
4992 // this size. TODO: take into account line breaks.
4993 {
4994 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
4995 contentRect = wxRect(wxPoint(0, 0), wxSize(paraSize.x + wxMax(leftIndent, leftIndent + leftSubIndent) + rightIndent, currentPosition.y + spaceAfterPara));
4996 GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
4997 SetMaxSize(marginRect.GetSize());
4998 }
4999
5000 // Find the greatest minimum size. Currently we only look at non-text objects,
5001 // which isn't ideal but it would be slow to find the maximum word width to
5002 // use as the minimum.
5003 {
5004 int minWidth = 0;
5005 node = m_children.GetFirst();
5006 while (node)
5007 {
5008 wxRichTextObject* child = node->GetData();
5009
5010 // If floating, ignore. We already laid out floats.
5011 // Also ignore if empty object, except if we haven't got any
5012 // size yet.
5013 if (!child->IsFloating() && child->GetRange().GetLength() != 0 && !wxDynamicCast(child, wxRichTextPlainText))
5014 {
5015 if (child->GetCachedSize().x > minWidth)
5016 minWidth = child->GetMinSize().x;
5017 }
5018 node = node->GetNext();
5019 }
5020
5021 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
5022 contentRect = wxRect(wxPoint(0, 0), wxSize(minWidth, currentPosition.y + spaceAfterPara));
5023 GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
5024 SetMinSize(marginRect.GetSize());
5025 }
5026
5027 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
5028 #if wxRICHTEXT_USE_OPTIMIZED_LINE_DRAWING
5029 // Use the text extents to calculate the size of each fragment in each line
5030 wxRichTextLineList::compatibility_iterator lineNode = m_cachedLines.GetFirst();
5031 while (lineNode)
5032 {
5033 wxRichTextLine* line = lineNode->GetData();
5034 wxRichTextRange lineRange = line->GetAbsoluteRange();
5035
5036 // Loop through objects until we get to the one within range
5037 wxRichTextObjectList::compatibility_iterator node2 = m_children.GetFirst();
5038
5039 while (node2)
5040 {
5041 wxRichTextObject* child = node2->GetData();
5042
5043 if (child->GetRange().GetLength() > 0 && !child->GetRange().IsOutside(lineRange))
5044 {
5045 wxRichTextRange rangeToUse = lineRange;
5046 rangeToUse.LimitTo(child->GetRange());
5047
5048 // Find the size of the child from the text extents, and store in an array
5049 // for drawing later
5050 int left = 0;
5051 if (rangeToUse.GetStart() > GetRange().GetStart())
5052 left = partialExtents[(rangeToUse.GetStart()-1) - GetRange().GetStart()];
5053 int right = partialExtents[rangeToUse.GetEnd() - GetRange().GetStart()];
5054 int sz = right - left;
5055 line->GetObjectSizes().Add(sz);
5056 }
5057 else if (child->GetRange().GetStart() > lineRange.GetEnd())
5058 // Can break out of inner loop now since we've passed this line's range
5059 break;
5060
5061 node2 = node2->GetNext();
5062 }
5063
5064 lineNode = lineNode->GetNext();
5065 }
5066 #endif
5067 #endif
5068
5069 return true;
5070 }
5071
5072 /// Apply paragraph styles, such as centering, to wrapped lines
5073 /// TODO: take into account box attributes, possibly
5074 void wxRichTextParagraph::ApplyParagraphStyle(wxRichTextLine* line, const wxRichTextAttr& attr, const wxRect& rect, wxDC& dc)
5075 {
5076 if (!attr.HasAlignment())
5077 return;
5078
5079 wxPoint pos = line->GetPosition();
5080 wxPoint originalPos = pos;
5081 wxSize size = line->GetSize();
5082
5083 // centering, right-justification
5084 if (attr.HasAlignment() && attr.GetAlignment() == wxTEXT_ALIGNMENT_CENTRE)
5085 {
5086 int rightIndent = ConvertTenthsMMToPixels(dc, attr.GetRightIndent());
5087 pos.x = (rect.GetWidth() - rightIndent - size.x)/2 + pos.x;
5088 line->SetPosition(pos);
5089 }
5090 else if (attr.HasAlignment() && attr.GetAlignment() == wxTEXT_ALIGNMENT_RIGHT)
5091 {
5092 int rightIndent = ConvertTenthsMMToPixels(dc, attr.GetRightIndent());
5093 pos.x = pos.x + rect.GetWidth() - size.x - rightIndent;
5094 line->SetPosition(pos);
5095 }
5096
5097 if (pos != originalPos)
5098 {
5099 wxPoint inc = pos - originalPos;
5100
5101 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
5102
5103 while (node)
5104 {
5105 wxRichTextObject* child = node->GetData();
5106 if (child->IsTopLevel() && !child->GetRange().IsOutside(line->GetAbsoluteRange()))
5107 child->Move(child->GetPosition() + inc);
5108
5109 node = node->GetNext();
5110 }
5111 }
5112 }
5113
5114 /// Insert text at the given position
5115 bool wxRichTextParagraph::InsertText(long pos, const wxString& text)
5116 {
5117 wxRichTextObject* childToUse = NULL;
5118 wxRichTextObjectList::compatibility_iterator nodeToUse = wxRichTextObjectList::compatibility_iterator();
5119
5120 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
5121 while (node)
5122 {
5123 wxRichTextObject* child = node->GetData();
5124 if (child->GetRange().Contains(pos) && child->GetRange().GetLength() > 0)
5125 {
5126 childToUse = child;
5127 nodeToUse = node;
5128 break;
5129 }
5130
5131 node = node->GetNext();
5132 }
5133
5134 if (childToUse)
5135 {
5136 wxRichTextPlainText* textObject = wxDynamicCast(childToUse, wxRichTextPlainText);
5137 if (textObject)
5138 {
5139 int posInString = pos - textObject->GetRange().GetStart();
5140
5141 wxString newText = textObject->GetText().Mid(0, posInString) +
5142 text + textObject->GetText().Mid(posInString);
5143 textObject->SetText(newText);
5144
5145 int textLength = text.length();
5146
5147 textObject->SetRange(wxRichTextRange(textObject->GetRange().GetStart(),
5148 textObject->GetRange().GetEnd() + textLength));
5149
5150 // Increment the end range of subsequent fragments in this paragraph.
5151 // We'll set the paragraph range itself at a higher level.
5152
5153 wxRichTextObjectList::compatibility_iterator node = nodeToUse->GetNext();
5154 while (node)
5155 {
5156 wxRichTextObject* child = node->GetData();
5157 child->SetRange(wxRichTextRange(textObject->GetRange().GetStart() + textLength,
5158 textObject->GetRange().GetEnd() + textLength));
5159
5160 node = node->GetNext();
5161 }
5162
5163 return true;
5164 }
5165 else
5166 {
5167 // TODO: if not a text object, insert at closest position, e.g. in front of it
5168 }
5169 }
5170 else
5171 {
5172 // Add at end.
5173 // Don't pass parent initially to suppress auto-setting of parent range.
5174 // We'll do that at a higher level.
5175 wxRichTextPlainText* textObject = new wxRichTextPlainText(text, this);
5176
5177 AppendChild(textObject);
5178 return true;
5179 }
5180
5181 return false;
5182 }
5183
5184 void wxRichTextParagraph::Copy(const wxRichTextParagraph& obj)
5185 {
5186 wxRichTextCompositeObject::Copy(obj);
5187 }
5188
5189 /// Clear the cached lines
5190 void wxRichTextParagraph::ClearLines()
5191 {
5192 WX_CLEAR_LIST(wxRichTextLineList, m_cachedLines);
5193 }
5194
5195 /// Get/set the object size for the given range. Returns false if the range
5196 /// is invalid for this object.
5197 bool wxRichTextParagraph::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, wxPoint position, wxArrayInt* partialExtents) const
5198 {
5199 if (!range.IsWithin(GetRange()))
5200 return false;
5201
5202 if (flags & wxRICHTEXT_UNFORMATTED)
5203 {
5204 // Just use unformatted data, assume no line breaks
5205 wxSize sz;
5206
5207 wxArrayInt childExtents;
5208 wxArrayInt* p;
5209 if (partialExtents)
5210 p = & childExtents;
5211 else
5212 p = NULL;
5213
5214 int maxDescent = 0;
5215 int maxAscent = 0;
5216 int maxLineHeight = 0;
5217
5218 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
5219 while (node)
5220 {
5221 wxRichTextObject* child = node->GetData();
5222 if (!child->GetRange().IsOutside(range))
5223 {
5224 // Floating objects have a zero size within the paragraph.
5225 if (child->IsFloating())
5226 {
5227 if (partialExtents)
5228 {
5229 int lastSize;
5230 if (partialExtents->GetCount() > 0)
5231 lastSize = (*partialExtents)[partialExtents->GetCount()-1];
5232 else
5233 lastSize = 0;
5234
5235 partialExtents->Add(0 /* zero size */ + lastSize);
5236 }
5237 }
5238 else
5239 {
5240 wxSize childSize;
5241
5242 wxRichTextRange rangeToUse = range;
5243 rangeToUse.LimitTo(child->GetRange());
5244 int childDescent = 0;
5245
5246 // At present wxRICHTEXT_HEIGHT_ONLY is only fast if we've already cached the size,
5247 // but it's only going to be used after caching has taken place.
5248 if ((flags & wxRICHTEXT_HEIGHT_ONLY) && child->GetCachedSize().y != 0)
5249 {
5250 childDescent = child->GetDescent();
5251 childSize = child->GetCachedSize();
5252
5253 if (childDescent == 0)
5254 {
5255 maxLineHeight = wxMax(maxLineHeight, childSize.y);
5256 }
5257 else
5258 {
5259 maxDescent = wxMax(maxDescent, childDescent);
5260 maxAscent = wxMax(maxAscent, (childSize.y - childDescent));
5261 }
5262
5263 maxLineHeight = wxMax(maxLineHeight, (maxAscent + maxDescent));
5264
5265 sz.y = wxMax(sz.y, maxLineHeight);
5266 sz.x += childSize.x;
5267 descent = maxDescent;
5268 }
5269 else if (child->IsTopLevel())
5270 {
5271 childDescent = child->GetDescent();
5272 childSize = child->GetCachedSize();
5273
5274 if (childDescent == 0)
5275 {
5276 maxLineHeight = wxMax(maxLineHeight, childSize.y);
5277 }
5278 else
5279 {
5280 maxDescent = wxMax(maxDescent, childDescent);
5281 maxAscent = wxMax(maxAscent, (childSize.y - childDescent));
5282 }
5283
5284 maxLineHeight = wxMax(maxLineHeight, (maxAscent + maxDescent));
5285
5286 sz.y = wxMax(sz.y, maxLineHeight);
5287 sz.x += childSize.x;
5288 descent = maxDescent;
5289
5290 // FIXME: this won't change the original values.
5291 // Should we be calling GetRangeSize above instead of using cached values?
5292 #if 0
5293 if ((flags & wxRICHTEXT_CACHE_SIZE) && (rangeToUse == child->GetRange()))
5294 {
5295 child->SetCachedSize(childSize);
5296 child->SetDescent(childDescent);
5297 }
5298 #endif
5299
5300 if (partialExtents)
5301 {
5302 int lastSize;
5303 if (partialExtents->GetCount() > 0)
5304 lastSize = (*partialExtents)[partialExtents->GetCount()-1];
5305 else
5306 lastSize = 0;
5307
5308 partialExtents->Add(childSize.x + lastSize);
5309 }
5310 }
5311 else if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, context, flags, wxPoint(position.x + sz.x, position.y), p))
5312 {
5313 if (childDescent == 0)
5314 {
5315 maxLineHeight = wxMax(maxLineHeight, childSize.y);
5316 }
5317 else
5318 {
5319 maxDescent = wxMax(maxDescent, childDescent);
5320 maxAscent = wxMax(maxAscent, (childSize.y - childDescent));
5321 }
5322
5323 maxLineHeight = wxMax(maxLineHeight, (maxAscent + maxDescent));
5324
5325 sz.y = wxMax(sz.y, maxLineHeight);
5326 sz.x += childSize.x;
5327 descent = maxDescent;
5328
5329 if ((flags & wxRICHTEXT_CACHE_SIZE) && (rangeToUse == child->GetRange()))
5330 {
5331 child->SetCachedSize(childSize);
5332 child->SetDescent(childDescent);
5333 }
5334
5335 if (partialExtents)
5336 {
5337 int lastSize;
5338 if (partialExtents->GetCount() > 0)
5339 lastSize = (*partialExtents)[partialExtents->GetCount()-1];
5340 else
5341 lastSize = 0;
5342
5343 size_t i;
5344 for (i = 0; i < childExtents.GetCount(); i++)
5345 {
5346 partialExtents->Add(childExtents[i] + lastSize);
5347 }
5348 }
5349 }
5350 }
5351
5352 if (p)
5353 p->Clear();
5354 }
5355
5356 node = node->GetNext();
5357 }
5358 size = sz;
5359 }
5360 else
5361 {
5362 // Use formatted data, with line breaks
5363 wxSize sz;
5364
5365 // We're going to loop through each line, and then for each line,
5366 // call GetRangeSize for the fragment that comprises that line.
5367 // Only we have to do that multiple times within the line, because
5368 // the line may be broken into pieces. For now ignore line break commands
5369 // (so we can assume that getting the unformatted size for a fragment
5370 // within a line is the actual size)
5371
5372 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
5373 while (node)
5374 {
5375 wxRichTextLine* line = node->GetData();
5376 wxRichTextRange lineRange = line->GetAbsoluteRange();
5377 if (!lineRange.IsOutside(range))
5378 {
5379 int maxDescent = 0;
5380 int maxAscent = 0;
5381 int maxLineHeight = 0;
5382 int maxLineWidth = 0;
5383
5384 wxRichTextObjectList::compatibility_iterator node2 = m_children.GetFirst();
5385 while (node2)
5386 {
5387 wxRichTextObject* child = node2->GetData();
5388
5389 if (!child->IsFloating() && !child->GetRange().IsOutside(lineRange))
5390 {
5391 wxRichTextRange rangeToUse = lineRange;
5392 rangeToUse.LimitTo(child->GetRange());
5393 if (child->IsTopLevel())
5394 rangeToUse = child->GetOwnRange();
5395
5396 wxSize childSize;
5397 int childDescent = 0;
5398 if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, context, flags, wxPoint(position.x + sz.x, position.y)))
5399 {
5400 if (childDescent == 0)
5401 {
5402 // Assume that if descent is zero, this child can occupy the full line height
5403 // and does not need space for the line's maximum descent. So we influence
5404 // the overall max line height only.
5405 maxLineHeight = wxMax(maxLineHeight, childSize.y);
5406 }
5407 else
5408 {
5409 maxAscent = wxMax(maxAscent, (childSize.y - childDescent));
5410 maxDescent = wxMax(maxAscent, childDescent);
5411 }
5412 maxLineHeight = wxMax(maxLineHeight, (maxAscent + maxDescent));
5413 maxLineWidth += childSize.x;
5414 }
5415 }
5416
5417 node2 = node2->GetNext();
5418 }
5419
5420 descent = wxMax(descent, maxDescent);
5421
5422 // Increase size by a line (TODO: paragraph spacing)
5423 sz.y += maxLineHeight;
5424 sz.x = wxMax(sz.x, maxLineWidth);
5425 }
5426 node = node->GetNext();
5427 }
5428 size = sz;
5429 }
5430 return true;
5431 }
5432
5433 /// Finds the absolute position and row height for the given character position
5434 bool wxRichTextParagraph::FindPosition(wxDC& dc, wxRichTextDrawingContext& context, long index, wxPoint& pt, int* height, bool forceLineStart)
5435 {
5436 if (index == -1)
5437 {
5438 wxRichTextLine* line = ((wxRichTextParagraphLayoutBox*)GetParent())->GetLineAtPosition(0);
5439 if (line)
5440 *height = line->GetSize().y;
5441 else
5442 *height = dc.GetCharHeight();
5443
5444 // -1 means 'the start of the buffer'.
5445 pt = GetPosition();
5446 if (line)
5447 pt = pt + line->GetPosition();
5448
5449 return true;
5450 }
5451
5452 // The final position in a paragraph is taken to mean the position
5453 // at the start of the next paragraph.
5454 if (index == GetRange().GetEnd())
5455 {
5456 wxRichTextParagraphLayoutBox* parent = wxDynamicCast(GetParent(), wxRichTextParagraphLayoutBox);
5457 wxASSERT( parent != NULL );
5458
5459 // Find the height at the next paragraph, if any
5460 wxRichTextLine* line = parent->GetLineAtPosition(index + 1);
5461 if (line)
5462 {
5463 *height = line->GetSize().y;
5464 pt = line->GetAbsolutePosition();
5465 }
5466 else
5467 {
5468 *height = dc.GetCharHeight();
5469 int indent = ConvertTenthsMMToPixels(dc, m_attributes.GetLeftIndent());
5470 pt = wxPoint(indent, GetCachedSize().y);
5471 }
5472
5473 return true;
5474 }
5475
5476 if (index < GetRange().GetStart() || index > GetRange().GetEnd())
5477 return false;
5478
5479 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
5480 while (node)
5481 {
5482 wxRichTextLine* line = node->GetData();
5483 wxRichTextRange lineRange = line->GetAbsoluteRange();
5484 if (index >= lineRange.GetStart() && index <= lineRange.GetEnd())
5485 {
5486 // If this is the last point in the line, and we're forcing the
5487 // returned value to be the start of the next line, do the required
5488 // thing.
5489 if (index == lineRange.GetEnd() && forceLineStart)
5490 {
5491 if (node->GetNext())
5492 {
5493 wxRichTextLine* nextLine = node->GetNext()->GetData();
5494 *height = nextLine->GetSize().y;
5495 pt = nextLine->GetAbsolutePosition();
5496 return true;
5497 }
5498 }
5499
5500 pt.y = line->GetPosition().y + GetPosition().y;
5501
5502 wxRichTextRange r(lineRange.GetStart(), index);
5503 wxSize rangeSize;
5504 int descent = 0;
5505
5506 // We find the size of the line up to this point,
5507 // then we can add this size to the line start position and
5508 // paragraph start position to find the actual position.
5509
5510 if (GetRangeSize(r, rangeSize, descent, dc, context, wxRICHTEXT_UNFORMATTED, line->GetPosition()+ GetPosition()))
5511 {
5512 pt.x = line->GetPosition().x + GetPosition().x + rangeSize.x;
5513 *height = line->GetSize().y;
5514
5515 return true;
5516 }
5517
5518 }
5519
5520 node = node->GetNext();
5521 }
5522
5523 return false;
5524 }
5525
5526 /// Hit-testing: returns a flag indicating hit test details, plus
5527 /// information about position
5528 int wxRichTextParagraph::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int flags)
5529 {
5530 if (!IsShown())
5531 return wxRICHTEXT_HITTEST_NONE;
5532
5533 // If we're in the top-level container, then we can return
5534 // a suitable hit test code even if the point is outside the container area,
5535 // so that we can position the caret sensibly even if we don't
5536 // click on valid content. If we're not at the top-level, and the point
5537 // is not within this paragraph object, then we don't want to stop more
5538 // precise hit-testing from working prematurely, so return immediately.
5539 // NEW STRATEGY: use the parent boundary to test whether we're in the
5540 // right region, not the paragraph, since the paragraph may be positioned
5541 // some way in from where the user clicks.
5542 {
5543 long tmpPos;
5544 wxRichTextObject* tempObj, *tempContextObj;
5545 if (GetParent() && GetParent()->wxRichTextObject::HitTest(dc, context, pt, tmpPos, & tempObj, & tempContextObj, flags) == wxRICHTEXT_HITTEST_NONE)
5546 return wxRICHTEXT_HITTEST_NONE;
5547 }
5548
5549 wxRichTextObjectList::compatibility_iterator objNode = m_children.GetFirst();
5550 while (objNode)
5551 {
5552 wxRichTextObject* child = objNode->GetData();
5553 // Don't recurse if we have wxRICHTEXT_HITTEST_NO_NESTED_OBJECTS,
5554 // and also, if this seems composite but actually is marked as atomic,
5555 // don't recurse.
5556 if (child->IsTopLevel() && ((flags & wxRICHTEXT_HITTEST_NO_NESTED_OBJECTS) == 0) &&
5557 (! (((flags & wxRICHTEXT_HITTEST_HONOUR_ATOMIC) != 0) && child->IsAtomic())))
5558 {
5559 {
5560 int hitTest = child->HitTest(dc, context, pt, textPosition, obj, contextObj);
5561 if (hitTest != wxRICHTEXT_HITTEST_NONE)
5562 return hitTest;
5563 }
5564 }
5565
5566 objNode = objNode->GetNext();
5567 }
5568
5569 wxPoint paraPos = GetPosition();
5570
5571 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
5572 while (node)
5573 {
5574 wxRichTextLine* line = node->GetData();
5575 wxPoint linePos = paraPos + line->GetPosition();
5576 wxSize lineSize = line->GetSize();
5577 wxRichTextRange lineRange = line->GetAbsoluteRange();
5578
5579 if (pt.y <= linePos.y + lineSize.y)
5580 {
5581 if (pt.x < linePos.x)
5582 {
5583 textPosition = lineRange.GetStart();
5584 *obj = FindObjectAtPosition(textPosition);
5585 *contextObj = GetContainer();
5586 return wxRICHTEXT_HITTEST_BEFORE|wxRICHTEXT_HITTEST_OUTSIDE;
5587 }
5588 else if (pt.x >= (linePos.x + lineSize.x))
5589 {
5590 textPosition = lineRange.GetEnd();
5591 *obj = FindObjectAtPosition(textPosition);
5592 *contextObj = GetContainer();
5593 return wxRICHTEXT_HITTEST_AFTER|wxRICHTEXT_HITTEST_OUTSIDE;
5594 }
5595 else
5596 {
5597 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
5598 wxArrayInt partialExtents;
5599
5600 wxSize paraSize;
5601 int paraDescent;
5602
5603 // This calculates the partial text extents
5604 GetRangeSize(lineRange, paraSize, paraDescent, dc, context, wxRICHTEXT_UNFORMATTED, linePos, & partialExtents);
5605
5606 int lastX = linePos.x;
5607 size_t i;
5608 for (i = 0; i < partialExtents.GetCount(); i++)
5609 {
5610 int nextX = partialExtents[i] + linePos.x;
5611
5612 if (pt.x >= lastX && pt.x <= nextX)
5613 {
5614 textPosition = i + lineRange.GetStart(); // minus 1?
5615
5616 *obj = FindObjectAtPosition(textPosition);
5617 *contextObj = GetContainer();
5618
5619 // So now we know it's between i-1 and i.
5620 // Let's see if we can be more precise about
5621 // which side of the position it's on.
5622
5623 int midPoint = (nextX + lastX)/2;
5624 if (pt.x >= midPoint)
5625 return wxRICHTEXT_HITTEST_AFTER;
5626 else
5627 return wxRICHTEXT_HITTEST_BEFORE;
5628 }
5629
5630 lastX = nextX;
5631 }
5632 #else
5633 long i;
5634 int lastX = linePos.x;
5635 for (i = lineRange.GetStart(); i <= lineRange.GetEnd(); i++)
5636 {
5637 wxSize childSize;
5638 int descent = 0;
5639
5640 wxRichTextRange rangeToUse(lineRange.GetStart(), i);
5641
5642 GetRangeSize(rangeToUse, childSize, descent, dc, context, wxRICHTEXT_UNFORMATTED, linePos);
5643
5644 int nextX = childSize.x + linePos.x;
5645
5646 if (pt.x >= lastX && pt.x <= nextX)
5647 {
5648 textPosition = i;
5649
5650 *obj = FindObjectAtPosition(textPosition);
5651 *contextObj = GetContainer();
5652
5653 // So now we know it's between i-1 and i.
5654 // Let's see if we can be more precise about
5655 // which side of the position it's on.
5656
5657 int midPoint = (nextX + lastX)/2;
5658 if (pt.x >= midPoint)
5659 return wxRICHTEXT_HITTEST_AFTER;
5660 else
5661 return wxRICHTEXT_HITTEST_BEFORE;
5662 }
5663 else
5664 {
5665 lastX = nextX;
5666 }
5667 }
5668 #endif
5669 }
5670 }
5671
5672 node = node->GetNext();
5673 }
5674
5675 return wxRICHTEXT_HITTEST_NONE;
5676 }
5677
5678 /// Split an object at this position if necessary, and return
5679 /// the previous object, or NULL if inserting at beginning.
5680 wxRichTextObject* wxRichTextParagraph::SplitAt(long pos, wxRichTextObject** previousObject)
5681 {
5682 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
5683 while (node)
5684 {
5685 wxRichTextObject* child = node->GetData();
5686
5687 if (pos == child->GetRange().GetStart())
5688 {
5689 if (previousObject)
5690 {
5691 if (node->GetPrevious())
5692 *previousObject = node->GetPrevious()->GetData();
5693 else
5694 *previousObject = NULL;
5695 }
5696
5697 return child;
5698 }
5699
5700 if (child->GetRange().Contains(pos))
5701 {
5702 // This should create a new object, transferring part of
5703 // the content to the old object and the rest to the new object.
5704 wxRichTextObject* newObject = child->DoSplit(pos);
5705
5706 // If we couldn't split this object, just insert in front of it.
5707 if (!newObject)
5708 {
5709 // Maybe this is an empty string, try the next one
5710 // return child;
5711 }
5712 else
5713 {
5714 // Insert the new object after 'child'
5715 if (node->GetNext())
5716 m_children.Insert(node->GetNext(), newObject);
5717 else
5718 m_children.Append(newObject);
5719 newObject->SetParent(this);
5720
5721 if (previousObject)
5722 *previousObject = child;
5723
5724 return newObject;
5725 }
5726 }
5727
5728 node = node->GetNext();
5729 }
5730 if (previousObject)
5731 *previousObject = NULL;
5732 return NULL;
5733 }
5734
5735 /// Move content to a list from obj on
5736 void wxRichTextParagraph::MoveToList(wxRichTextObject* obj, wxList& list)
5737 {
5738 wxRichTextObjectList::compatibility_iterator node = m_children.Find(obj);
5739 while (node)
5740 {
5741 wxRichTextObject* child = node->GetData();
5742 list.Append(child);
5743
5744 wxRichTextObjectList::compatibility_iterator oldNode = node;
5745
5746 node = node->GetNext();
5747
5748 m_children.DeleteNode(oldNode);
5749 }
5750 }
5751
5752 /// Add content back from list
5753 void wxRichTextParagraph::MoveFromList(wxList& list)
5754 {
5755 for (wxList::compatibility_iterator node = list.GetFirst(); node; node = node->GetNext())
5756 {
5757 AppendChild((wxRichTextObject*) node->GetData());
5758 }
5759 }
5760
5761 /// Calculate range
5762 void wxRichTextParagraph::CalculateRange(long start, long& end)
5763 {
5764 wxRichTextCompositeObject::CalculateRange(start, end);
5765
5766 // Add one for end of paragraph
5767 end ++;
5768
5769 m_range.SetRange(start, end);
5770 }
5771
5772 /// Find the object at the given position
5773 wxRichTextObject* wxRichTextParagraph::FindObjectAtPosition(long position)
5774 {
5775 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
5776 while (node)
5777 {
5778 wxRichTextObject* obj = node->GetData();
5779 if (obj->GetRange().Contains(position) ||
5780 obj->GetRange().GetStart() == position ||
5781 obj->GetRange().GetEnd() == position)
5782 return obj;
5783
5784 node = node->GetNext();
5785 }
5786 return NULL;
5787 }
5788
5789 /// Get the plain text searching from the start or end of the range.
5790 /// The resulting string may be shorter than the range given.
5791 bool wxRichTextParagraph::GetContiguousPlainText(wxString& text, const wxRichTextRange& range, bool fromStart)
5792 {
5793 text = wxEmptyString;
5794
5795 if (fromStart)
5796 {
5797 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
5798 while (node)
5799 {
5800 wxRichTextObject* obj = node->GetData();
5801 if (!obj->GetRange().IsOutside(range))
5802 {
5803 wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText);
5804 if (textObj)
5805 {
5806 text += textObj->GetTextForRange(range);
5807 }
5808 else
5809 {
5810 text += wxT(" ");
5811 }
5812 }
5813
5814 node = node->GetNext();
5815 }
5816 }
5817 else
5818 {
5819 wxRichTextObjectList::compatibility_iterator node = m_children.GetLast();
5820 while (node)
5821 {
5822 wxRichTextObject* obj = node->GetData();
5823 if (!obj->GetRange().IsOutside(range))
5824 {
5825 wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText);
5826 if (textObj)
5827 {
5828 text = textObj->GetTextForRange(range) + text;
5829 }
5830 else
5831 {
5832 text = wxT(" ") + text;
5833 }
5834 }
5835
5836 node = node->GetPrevious();
5837 }
5838 }
5839
5840 return true;
5841 }
5842
5843 /// Find a suitable wrap position.
5844 bool wxRichTextParagraph::FindWrapPosition(const wxRichTextRange& range, wxDC& dc, wxRichTextDrawingContext& context, int availableSpace, long& wrapPosition, wxArrayInt* partialExtents)
5845 {
5846 if (range.GetLength() <= 0)
5847 return false;
5848
5849 // Find the first position where the line exceeds the available space.
5850 wxSize sz;
5851 long breakPosition = range.GetEnd();
5852
5853 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
5854 if (partialExtents && partialExtents->GetCount() >= (size_t) (GetRange().GetLength()-1)) // the final position in a paragraph is the newline
5855 {
5856 int widthBefore;
5857
5858 if (range.GetStart() > GetRange().GetStart())
5859 widthBefore = (*partialExtents)[range.GetStart() - GetRange().GetStart() - 1];
5860 else
5861 widthBefore = 0;
5862
5863 size_t i;
5864 for (i = (size_t) range.GetStart(); i <= (size_t) range.GetEnd(); i++)
5865 {
5866 int widthFromStartOfThisRange = (*partialExtents)[i - GetRange().GetStart()] - widthBefore;
5867
5868 if (widthFromStartOfThisRange > availableSpace)
5869 {
5870 breakPosition = i-1;
5871 break;
5872 }
5873 }
5874 }
5875 else
5876 #endif
5877 {
5878 // Binary chop for speed
5879 long minPos = range.GetStart();
5880 long maxPos = range.GetEnd();
5881 while (true)
5882 {
5883 if (minPos == maxPos)
5884 {
5885 int descent = 0;
5886 GetRangeSize(wxRichTextRange(range.GetStart(), minPos), sz, descent, dc, context, wxRICHTEXT_UNFORMATTED);
5887
5888 if (sz.x > availableSpace)
5889 breakPosition = minPos - 1;
5890 break;
5891 }
5892 else if ((maxPos - minPos) == 1)
5893 {
5894 int descent = 0;
5895 GetRangeSize(wxRichTextRange(range.GetStart(), minPos), sz, descent, dc, context, wxRICHTEXT_UNFORMATTED);
5896
5897 if (sz.x > availableSpace)
5898 breakPosition = minPos - 1;
5899 else
5900 {
5901 GetRangeSize(wxRichTextRange(range.GetStart(), maxPos), sz, descent, dc, context, wxRICHTEXT_UNFORMATTED);
5902 if (sz.x > availableSpace)
5903 breakPosition = maxPos-1;
5904 }
5905 break;
5906 }
5907 else
5908 {
5909 long nextPos = minPos + ((maxPos - minPos) / 2);
5910
5911 int descent = 0;
5912 GetRangeSize(wxRichTextRange(range.GetStart(), nextPos), sz, descent, dc, context, wxRICHTEXT_UNFORMATTED);
5913
5914 if (sz.x > availableSpace)
5915 {
5916 maxPos = nextPos;
5917 }
5918 else
5919 {
5920 minPos = nextPos;
5921 }
5922 }
5923 }
5924 }
5925
5926 // Now we know the last position on the line.
5927 // Let's try to find a word break.
5928
5929 wxString plainText;
5930 if (GetContiguousPlainText(plainText, wxRichTextRange(range.GetStart(), breakPosition), false))
5931 {
5932 int newLinePos = plainText.Find(wxRichTextLineBreakChar);
5933 if (newLinePos != wxNOT_FOUND)
5934 {
5935 breakPosition = wxMax(0, range.GetStart() + newLinePos);
5936 }
5937 else
5938 {
5939 int spacePos = plainText.Find(wxT(' '), true);
5940 int tabPos = plainText.Find(wxT('\t'), true);
5941 int pos = wxMax(spacePos, tabPos);
5942 if (pos != wxNOT_FOUND)
5943 {
5944 int positionsFromEndOfString = plainText.length() - pos - 1;
5945 breakPosition = breakPosition - positionsFromEndOfString;
5946 }
5947 }
5948 }
5949
5950 wrapPosition = breakPosition;
5951
5952 return true;
5953 }
5954
5955 /// Get the bullet text for this paragraph.
5956 wxString wxRichTextParagraph::GetBulletText()
5957 {
5958 if (GetAttributes().GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_NONE ||
5959 (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_BITMAP))
5960 return wxEmptyString;
5961
5962 int number = GetAttributes().GetBulletNumber();
5963
5964 wxString text;
5965 if ((GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ARABIC) || (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE))
5966 {
5967 text.Printf(wxT("%d"), number);
5968 }
5969 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_LETTERS_UPPER)
5970 {
5971 // TODO: Unicode, and also check if number > 26
5972 text.Printf(wxT("%c"), (wxChar) (number+64));
5973 }
5974 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_LETTERS_LOWER)
5975 {
5976 // TODO: Unicode, and also check if number > 26
5977 text.Printf(wxT("%c"), (wxChar) (number+96));
5978 }
5979 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ROMAN_UPPER)
5980 {
5981 text = wxRichTextDecimalToRoman(number);
5982 }
5983 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ROMAN_LOWER)
5984 {
5985 text = wxRichTextDecimalToRoman(number);
5986 text.MakeLower();
5987 }
5988 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_SYMBOL)
5989 {
5990 text = GetAttributes().GetBulletText();
5991 }
5992
5993 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE)
5994 {
5995 // The outline style relies on the text being computed statically,
5996 // since it depends on other levels points (e.g. 1.2.1.1). So normally the bullet text
5997 // should be stored in the attributes; if not, just use the number for this
5998 // level, as previously computed.
5999 if (!GetAttributes().GetBulletText().IsEmpty())
6000 text = GetAttributes().GetBulletText();
6001 }
6002
6003 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_PARENTHESES)
6004 {
6005 text = wxT("(") + text + wxT(")");
6006 }
6007 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_RIGHT_PARENTHESIS)
6008 {
6009 text = text + wxT(")");
6010 }
6011
6012 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_PERIOD)
6013 {
6014 text += wxT(".");
6015 }
6016
6017 return text;
6018 }
6019
6020 /// Allocate or reuse a line object
6021 wxRichTextLine* wxRichTextParagraph::AllocateLine(int pos)
6022 {
6023 if (pos < (int) m_cachedLines.GetCount())
6024 {
6025 wxRichTextLine* line = m_cachedLines.Item(pos)->GetData();
6026 line->Init(this);
6027 return line;
6028 }
6029 else
6030 {
6031 wxRichTextLine* line = new wxRichTextLine(this);
6032 m_cachedLines.Append(line);
6033 return line;
6034 }
6035 }
6036
6037 /// Clear remaining unused line objects, if any
6038 bool wxRichTextParagraph::ClearUnusedLines(int lineCount)
6039 {
6040 int cachedLineCount = m_cachedLines.GetCount();
6041 if ((int) cachedLineCount > lineCount)
6042 {
6043 for (int i = 0; i < (int) (cachedLineCount - lineCount); i ++)
6044 {
6045 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetLast();
6046 wxRichTextLine* line = node->GetData();
6047 m_cachedLines.Erase(node);
6048 delete line;
6049 }
6050 }
6051 return true;
6052 }
6053
6054 /// Get combined attributes of the base style, paragraph style and character style. We use this to dynamically
6055 /// retrieve the actual style.
6056 wxRichTextAttr wxRichTextParagraph::GetCombinedAttributes(const wxRichTextAttr& contentStyle, bool includingBoxAttr) const
6057 {
6058 wxRichTextAttr attr;
6059 wxRichTextParagraphLayoutBox* buf = wxDynamicCast(GetParent(), wxRichTextParagraphLayoutBox);
6060 if (buf)
6061 {
6062 attr = buf->GetBasicStyle();
6063 if (!includingBoxAttr)
6064 {
6065 attr.GetTextBoxAttr().Reset();
6066 // The background colour will be painted by the container, and we don't
6067 // want to unnecessarily overwrite the background when we're drawing text
6068 // because this may erase the guideline (which appears just under the text
6069 // if there's no padding).
6070 attr.SetFlags(attr.GetFlags() & ~wxTEXT_ATTR_BACKGROUND_COLOUR);
6071 }
6072 wxRichTextApplyStyle(attr, GetAttributes());
6073 }
6074 else
6075 attr = GetAttributes();
6076
6077 wxRichTextApplyStyle(attr, contentStyle);
6078 return attr;
6079 }
6080
6081 /// Get combined attributes of the base style and paragraph style.
6082 wxRichTextAttr wxRichTextParagraph::GetCombinedAttributes(bool includingBoxAttr) const
6083 {
6084 wxRichTextAttr attr;
6085 wxRichTextParagraphLayoutBox* buf = wxDynamicCast(GetParent(), wxRichTextParagraphLayoutBox);
6086 if (buf)
6087 {
6088 attr = buf->GetBasicStyle();
6089 if (!includingBoxAttr)
6090 attr.GetTextBoxAttr().Reset();
6091 wxRichTextApplyStyle(attr, GetAttributes());
6092 }
6093 else
6094 attr = GetAttributes();
6095
6096 return attr;
6097 }
6098
6099 // Create default tabstop array
6100 void wxRichTextParagraph::InitDefaultTabs()
6101 {
6102 // create a default tab list at 10 mm each.
6103 for (int i = 0; i < 20; ++i)
6104 {
6105 sm_defaultTabs.Add(i*100);
6106 }
6107 }
6108
6109 // Clear default tabstop array
6110 void wxRichTextParagraph::ClearDefaultTabs()
6111 {
6112 sm_defaultTabs.Clear();
6113 }
6114
6115 void wxRichTextParagraph::LayoutFloat(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, int style, wxRichTextFloatCollector* floatCollector)
6116 {
6117 wxRichTextObjectList::compatibility_iterator node = GetChildren().GetFirst();
6118 while (node)
6119 {
6120 wxRichTextObject* anchored = node->GetData();
6121 if (anchored && anchored->IsFloating() && !floatCollector->HasFloat(anchored))
6122 {
6123 wxSize size;
6124 int descent, x = 0;
6125 anchored->GetRangeSize(anchored->GetRange(), size, descent, dc, context, style);
6126
6127 int offsetY = 0;
6128 if (anchored->GetAttributes().GetTextBoxAttr().GetTop().IsValid())
6129 {
6130 offsetY = anchored->GetAttributes().GetTextBoxAttr().GetTop().GetValue();
6131 if (anchored->GetAttributes().GetTextBoxAttr().GetTop().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
6132 {
6133 offsetY = ConvertTenthsMMToPixels(dc, offsetY);
6134 }
6135 }
6136
6137 int pos = floatCollector->GetFitPosition(anchored->GetAttributes().GetTextBoxAttr().GetFloatMode(), rect.y + offsetY, size.y);
6138
6139 /* Update the offset */
6140 int newOffsetY = pos - rect.y;
6141 if (newOffsetY != offsetY)
6142 {
6143 if (anchored->GetAttributes().GetTextBoxAttr().GetTop().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
6144 newOffsetY = ConvertPixelsToTenthsMM(dc, newOffsetY);
6145 anchored->GetAttributes().GetTextBoxAttr().GetTop().SetValue(newOffsetY);
6146 }
6147
6148 if (anchored->GetAttributes().GetTextBoxAttr().GetFloatMode() == wxTEXT_BOX_ATTR_FLOAT_LEFT)
6149 x = rect.x;
6150 else if (anchored->GetAttributes().GetTextBoxAttr().GetFloatMode() == wxTEXT_BOX_ATTR_FLOAT_RIGHT)
6151 x = rect.x + rect.width - size.x;
6152
6153 anchored->SetPosition(wxPoint(x, pos));
6154 anchored->SetCachedSize(size);
6155 floatCollector->CollectFloat(this, anchored);
6156 }
6157
6158 node = node->GetNext();
6159 }
6160 }
6161
6162 // Get the first position from pos that has a line break character.
6163 long wxRichTextParagraph::GetFirstLineBreakPosition(long pos)
6164 {
6165 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
6166 while (node)
6167 {
6168 wxRichTextObject* obj = node->GetData();
6169 if (pos >= obj->GetRange().GetStart() && pos <= obj->GetRange().GetEnd())
6170 {
6171 wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText);
6172 if (textObj)
6173 {
6174 long breakPos = textObj->GetFirstLineBreakPosition(pos);
6175 if (breakPos > -1)
6176 return breakPos;
6177 }
6178 }
6179 node = node->GetNext();
6180 }
6181 return -1;
6182 }
6183
6184 /*!
6185 * wxRichTextLine
6186 * This object represents a line in a paragraph, and stores
6187 * offsets from the start of the paragraph representing the
6188 * start and end positions of the line.
6189 */
6190
6191 wxRichTextLine::wxRichTextLine(wxRichTextParagraph* parent)
6192 {
6193 Init(parent);
6194 }
6195
6196 /// Initialisation
6197 void wxRichTextLine::Init(wxRichTextParagraph* parent)
6198 {
6199 m_parent = parent;
6200 m_range.SetRange(-1, -1);
6201 m_pos = wxPoint(0, 0);
6202 m_size = wxSize(0, 0);
6203 m_descent = 0;
6204 #if wxRICHTEXT_USE_OPTIMIZED_LINE_DRAWING
6205 m_objectSizes.Clear();
6206 #endif
6207 }
6208
6209 /// Copy
6210 void wxRichTextLine::Copy(const wxRichTextLine& obj)
6211 {
6212 m_range = obj.m_range;
6213 #if wxRICHTEXT_USE_OPTIMIZED_LINE_DRAWING
6214 m_objectSizes = obj.m_objectSizes;
6215 #endif
6216 }
6217
6218 /// Get the absolute object position
6219 wxPoint wxRichTextLine::GetAbsolutePosition() const
6220 {
6221 return m_parent->GetPosition() + m_pos;
6222 }
6223
6224 /// Get the absolute range
6225 wxRichTextRange wxRichTextLine::GetAbsoluteRange() const
6226 {
6227 wxRichTextRange range(m_range.GetStart() + m_parent->GetRange().GetStart(), 0);
6228 range.SetEnd(range.GetStart() + m_range.GetLength()-1);
6229 return range;
6230 }
6231
6232 /*!
6233 * wxRichTextPlainText
6234 * This object represents a single piece of text.
6235 */
6236
6237 IMPLEMENT_DYNAMIC_CLASS(wxRichTextPlainText, wxRichTextObject)
6238
6239 wxRichTextPlainText::wxRichTextPlainText(const wxString& text, wxRichTextObject* parent, wxRichTextAttr* style):
6240 wxRichTextObject(parent)
6241 {
6242 if (style)
6243 SetAttributes(*style);
6244
6245 m_text = text;
6246 }
6247
6248 #define USE_KERNING_FIX 1
6249
6250 // If insufficient tabs are defined, this is the tab width used
6251 #define WIDTH_FOR_DEFAULT_TABS 50
6252
6253 /// Draw the item
6254 bool wxRichTextPlainText::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int WXUNUSED(style))
6255 {
6256 wxRichTextParagraph* para = wxDynamicCast(GetParent(), wxRichTextParagraph);
6257 wxASSERT (para != NULL);
6258
6259 wxRichTextAttr textAttr(para ? para->GetCombinedAttributes(GetAttributes(), false /* no box attributes */) : GetAttributes());
6260 context.ApplyVirtualAttributes(textAttr, this);
6261
6262 // Let's make the assumption for now that for content in a paragraph, including
6263 // text, we never have a discontinuous selection. So we only deal with a
6264 // single range.
6265 wxRichTextRange selectionRange;
6266 if (selection.IsValid())
6267 {
6268 wxRichTextRangeArray selectionRanges = selection.GetSelectionForObject(this);
6269 if (selectionRanges.GetCount() > 0)
6270 selectionRange = selectionRanges[0];
6271 else
6272 selectionRange = wxRICHTEXT_NO_SELECTION;
6273 }
6274 else
6275 selectionRange = wxRICHTEXT_NO_SELECTION;
6276
6277 int offset = GetRange().GetStart();
6278
6279 // Replace line break characters with spaces
6280 wxString str = m_text;
6281 wxString toRemove = wxRichTextLineBreakChar;
6282 str.Replace(toRemove, wxT(" "));
6283 if (textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_CAPITALS))
6284 str.MakeUpper();
6285
6286 long len = range.GetLength();
6287 wxString stringChunk = str.Mid(range.GetStart() - offset, (size_t) len);
6288
6289 // Test for the optimized situations where all is selected, or none
6290 // is selected.
6291
6292 wxFont textFont(GetBuffer()->GetFontTable().FindFont(textAttr));
6293 wxCheckSetFont(dc, textFont);
6294 int charHeight = dc.GetCharHeight();
6295
6296 int x, y;
6297 if ( textFont.IsOk() )
6298 {
6299 if ( textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUPERSCRIPT) )
6300 {
6301 if (textFont.IsUsingSizeInPixels())
6302 {
6303 double size = static_cast<double>(textFont.GetPixelSize().y) / wxSCRIPT_MUL_FACTOR;
6304 textFont.SetPixelSize(wxSize(0, static_cast<int>(size)));
6305 x = rect.x;
6306 y = rect.y;
6307 }
6308 else
6309 {
6310 double size = static_cast<double>(textFont.GetPointSize()) / wxSCRIPT_MUL_FACTOR;
6311 textFont.SetPointSize(static_cast<int>(size));
6312 x = rect.x;
6313 y = rect.y;
6314 }
6315 wxCheckSetFont(dc, textFont);
6316 }
6317 else if ( textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUBSCRIPT) )
6318 {
6319 if (textFont.IsUsingSizeInPixels())
6320 {
6321 double size = static_cast<double>(textFont.GetPixelSize().y) / wxSCRIPT_MUL_FACTOR;
6322 textFont.SetPixelSize(wxSize(0, static_cast<int>(size)));
6323 x = rect.x;
6324 int sub_height = static_cast<int>(static_cast<double>(charHeight) / wxSCRIPT_MUL_FACTOR);
6325 y = rect.y + (rect.height - sub_height + (descent - m_descent));
6326 }
6327 else
6328 {
6329 double size = static_cast<double>(textFont.GetPointSize()) / wxSCRIPT_MUL_FACTOR;
6330 textFont.SetPointSize(static_cast<int>(size));
6331 x = rect.x;
6332 int sub_height = static_cast<int>(static_cast<double>(charHeight) / wxSCRIPT_MUL_FACTOR);
6333 y = rect.y + (rect.height - sub_height + (descent - m_descent));
6334 }
6335 wxCheckSetFont(dc, textFont);
6336 }
6337 else
6338 {
6339 x = rect.x;
6340 y = rect.y + (rect.height - charHeight - (descent - m_descent));
6341 }
6342 }
6343 else
6344 {
6345 x = rect.x;
6346 y = rect.y + (rect.height - charHeight - (descent - m_descent));
6347 }
6348
6349 // TODO: new selection code
6350
6351 // (a) All selected.
6352 if (selectionRange.GetStart() <= range.GetStart() && selectionRange.GetEnd() >= range.GetEnd())
6353 {
6354 DrawTabbedString(dc, textAttr, rect, stringChunk, x, y, true);
6355 }
6356 // (b) None selected.
6357 else if (selectionRange.GetEnd() < range.GetStart() || selectionRange.GetStart() > range.GetEnd())
6358 {
6359 // Draw all unselected
6360 DrawTabbedString(dc, textAttr, rect, stringChunk, x, y, false);
6361 }
6362 else
6363 {
6364 // (c) Part selected, part not
6365 // Let's draw unselected chunk, selected chunk, then unselected chunk.
6366
6367 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
6368
6369 // 1. Initial unselected chunk, if any, up until start of selection.
6370 if (selectionRange.GetStart() > range.GetStart() && selectionRange.GetStart() <= range.GetEnd())
6371 {
6372 int r1 = range.GetStart();
6373 int s1 = selectionRange.GetStart()-1;
6374 int fragmentLen = s1 - r1 + 1;
6375 if (fragmentLen < 0)
6376 {
6377 wxLogDebug(wxT("Mid(%d, %d"), (int)(r1 - offset), (int)fragmentLen);
6378 }
6379 wxString stringFragment = str.Mid(r1 - offset, fragmentLen);
6380
6381 DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, false);
6382
6383 #if USE_KERNING_FIX
6384 if (stringChunk.Find(wxT("\t")) == wxNOT_FOUND)
6385 {
6386 // Compensate for kerning difference
6387 wxString stringFragment2(str.Mid(r1 - offset, fragmentLen+1));
6388 wxString stringFragment3(str.Mid(r1 - offset + fragmentLen, 1));
6389
6390 wxCoord w1, h1, w2, h2, w3, h3;
6391 dc.GetTextExtent(stringFragment, & w1, & h1);
6392 dc.GetTextExtent(stringFragment2, & w2, & h2);
6393 dc.GetTextExtent(stringFragment3, & w3, & h3);
6394
6395 int kerningDiff = (w1 + w3) - w2;
6396 x = x - kerningDiff;
6397 }
6398 #endif
6399 }
6400
6401 // 2. Selected chunk, if any.
6402 if (selectionRange.GetEnd() >= range.GetStart())
6403 {
6404 int s1 = wxMax(selectionRange.GetStart(), range.GetStart());
6405 int s2 = wxMin(selectionRange.GetEnd(), range.GetEnd());
6406
6407 int fragmentLen = s2 - s1 + 1;
6408 if (fragmentLen < 0)
6409 {
6410 wxLogDebug(wxT("Mid(%d, %d"), (int)(s1 - offset), (int)fragmentLen);
6411 }
6412 wxString stringFragment = str.Mid(s1 - offset, fragmentLen);
6413
6414 DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, true);
6415
6416 #if USE_KERNING_FIX
6417 if (stringChunk.Find(wxT("\t")) == wxNOT_FOUND)
6418 {
6419 // Compensate for kerning difference
6420 wxString stringFragment2(str.Mid(s1 - offset, fragmentLen+1));
6421 wxString stringFragment3(str.Mid(s1 - offset + fragmentLen, 1));
6422
6423 wxCoord w1, h1, w2, h2, w3, h3;
6424 dc.GetTextExtent(stringFragment, & w1, & h1);
6425 dc.GetTextExtent(stringFragment2, & w2, & h2);
6426 dc.GetTextExtent(stringFragment3, & w3, & h3);
6427
6428 int kerningDiff = (w1 + w3) - w2;
6429 x = x - kerningDiff;
6430 }
6431 #endif
6432 }
6433
6434 // 3. Remaining unselected chunk, if any
6435 if (selectionRange.GetEnd() < range.GetEnd())
6436 {
6437 int s2 = wxMin(selectionRange.GetEnd()+1, range.GetEnd());
6438 int r2 = range.GetEnd();
6439
6440 int fragmentLen = r2 - s2 + 1;
6441 if (fragmentLen < 0)
6442 {
6443 wxLogDebug(wxT("Mid(%d, %d"), (int)(s2 - offset), (int)fragmentLen);
6444 }
6445 wxString stringFragment = str.Mid(s2 - offset, fragmentLen);
6446
6447 DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, false);
6448 }
6449 }
6450
6451 return true;
6452 }
6453
6454 bool wxRichTextPlainText::DrawTabbedString(wxDC& dc, const wxRichTextAttr& attr, const wxRect& rect,wxString& str, wxCoord& x, wxCoord& y, bool selected)
6455 {
6456 bool hasTabs = (str.Find(wxT('\t')) != wxNOT_FOUND);
6457
6458 wxArrayInt tabArray;
6459 int tabCount;
6460 if (hasTabs)
6461 {
6462 if (attr.GetTabs().IsEmpty())
6463 tabArray = wxRichTextParagraph::GetDefaultTabs();
6464 else
6465 tabArray = attr.GetTabs();
6466 tabCount = tabArray.GetCount();
6467
6468 for (int i = 0; i < tabCount; ++i)
6469 {
6470 int pos = tabArray[i];
6471 pos = ConvertTenthsMMToPixels(dc, pos);
6472 tabArray[i] = pos;
6473 }
6474 }
6475 else
6476 tabCount = 0;
6477
6478 int nextTabPos = -1;
6479 int tabPos = -1;
6480 wxCoord w, h;
6481
6482 if (selected)
6483 {
6484 wxColour highlightColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT));
6485 wxColour highlightTextColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT));
6486
6487 wxCheckSetBrush(dc, wxBrush(highlightColour));
6488 wxCheckSetPen(dc, wxPen(highlightColour));
6489 dc.SetTextForeground(highlightTextColour);
6490 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
6491 }
6492 else
6493 {
6494 dc.SetTextForeground(attr.GetTextColour());
6495
6496 if (attr.HasFlag(wxTEXT_ATTR_BACKGROUND_COLOUR) && attr.GetBackgroundColour().IsOk())
6497 {
6498 dc.SetBackgroundMode(wxBRUSHSTYLE_SOLID);
6499 dc.SetTextBackground(attr.GetBackgroundColour());
6500 }
6501 else
6502 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
6503 }
6504
6505 wxCoord x_orig = GetParent()->GetPosition().x;
6506 while (hasTabs)
6507 {
6508 // the string has a tab
6509 // break up the string at the Tab
6510 wxString stringChunk = str.BeforeFirst(wxT('\t'));
6511 str = str.AfterFirst(wxT('\t'));
6512 dc.GetTextExtent(stringChunk, & w, & h);
6513 tabPos = x + w;
6514 bool not_found = true;
6515 for (int i = 0; i < tabCount && not_found; ++i)
6516 {
6517 nextTabPos = tabArray.Item(i) + x_orig;
6518
6519 // Find the next tab position.
6520 // Even if we're at the end of the tab array, we must still draw the chunk.
6521
6522 if (nextTabPos > tabPos || (i == (tabCount - 1)))
6523 {
6524 if (nextTabPos <= tabPos)
6525 {
6526 int defaultTabWidth = ConvertTenthsMMToPixels(dc, WIDTH_FOR_DEFAULT_TABS);
6527 nextTabPos = tabPos + defaultTabWidth;
6528 }
6529
6530 not_found = false;
6531 if (selected)
6532 {
6533 w = nextTabPos - x;
6534 wxRect selRect(x, rect.y, w, rect.GetHeight());
6535 dc.DrawRectangle(selRect);
6536 }
6537 dc.DrawText(stringChunk, x, y);
6538
6539 if (attr.HasTextEffects() && (attr.GetTextEffects() & wxTEXT_ATTR_EFFECT_STRIKETHROUGH))
6540 {
6541 wxPen oldPen = dc.GetPen();
6542 wxCheckSetPen(dc, wxPen(attr.GetTextColour(), 1));
6543 dc.DrawLine(x, (int) (y+(h/2)+0.5), x+w, (int) (y+(h/2)+0.5));
6544 wxCheckSetPen(dc, oldPen);
6545 }
6546
6547 x = nextTabPos;
6548 }
6549 }
6550 hasTabs = (str.Find(wxT('\t')) != wxNOT_FOUND);
6551 }
6552
6553 if (!str.IsEmpty())
6554 {
6555 dc.GetTextExtent(str, & w, & h);
6556 if (selected)
6557 {
6558 wxRect selRect(x, rect.y, w, rect.GetHeight());
6559 dc.DrawRectangle(selRect);
6560 }
6561 dc.DrawText(str, x, y);
6562
6563 if (attr.HasTextEffects() && (attr.GetTextEffects() & wxTEXT_ATTR_EFFECT_STRIKETHROUGH))
6564 {
6565 wxPen oldPen = dc.GetPen();
6566 wxCheckSetPen(dc, wxPen(attr.GetTextColour(), 1));
6567 dc.DrawLine(x, (int) (y+(h/2)+0.5), x+w, (int) (y+(h/2)+0.5));
6568 wxCheckSetPen(dc, oldPen);
6569 }
6570
6571 x += w;
6572 }
6573
6574 return true;
6575 }
6576
6577 /// Lay the item out
6578 bool wxRichTextPlainText::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& WXUNUSED(rect), const wxRect& WXUNUSED(parentRect), int WXUNUSED(style))
6579 {
6580 // Only lay out if we haven't already cached the size
6581 if (m_size.x == -1)
6582 GetRangeSize(GetRange(), m_size, m_descent, dc, context, 0, wxPoint(0, 0));
6583 m_maxSize = m_size;
6584 // Eventually we want to have a reasonable estimate of minimum size.
6585 m_minSize = wxSize(0, 0);
6586 return true;
6587 }
6588
6589 /// Copy
6590 void wxRichTextPlainText::Copy(const wxRichTextPlainText& obj)
6591 {
6592 wxRichTextObject::Copy(obj);
6593
6594 m_text = obj.m_text;
6595 }
6596
6597 /// Get/set the object size for the given range. Returns false if the range
6598 /// is invalid for this object.
6599 bool wxRichTextPlainText::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int WXUNUSED(flags), wxPoint position, wxArrayInt* partialExtents) const
6600 {
6601 if (!range.IsWithin(GetRange()))
6602 return false;
6603
6604 wxRichTextParagraph* para = wxDynamicCast(GetParent(), wxRichTextParagraph);
6605 wxASSERT (para != NULL);
6606
6607 int relativeX = position.x - GetParent()->GetPosition().x;
6608
6609 wxRichTextAttr textAttr(para ? para->GetCombinedAttributes(GetAttributes()) : GetAttributes());
6610 context.ApplyVirtualAttributes(textAttr, (wxRichTextObject*) this);
6611
6612 // Always assume unformatted text, since at this level we have no knowledge
6613 // of line breaks - and we don't need it, since we'll calculate size within
6614 // formatted text by doing it in chunks according to the line ranges
6615
6616 bool bScript(false);
6617 wxFont font(GetBuffer()->GetFontTable().FindFont(textAttr));
6618 if (font.IsOk())
6619 {
6620 if ( textAttr.HasTextEffects() && ( (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUPERSCRIPT)
6621 || (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUBSCRIPT) ) )
6622 {
6623 wxFont textFont = font;
6624 if (textFont.IsUsingSizeInPixels())
6625 {
6626 double size = static_cast<double>(textFont.GetPixelSize().y) / wxSCRIPT_MUL_FACTOR;
6627 textFont.SetPixelSize(wxSize(0, static_cast<int>(size)));
6628 }
6629 else
6630 {
6631 double size = static_cast<double>(textFont.GetPointSize()) / wxSCRIPT_MUL_FACTOR;
6632 textFont.SetPointSize(static_cast<int>(size));
6633 }
6634 wxCheckSetFont(dc, textFont);
6635 bScript = true;
6636 }
6637 else
6638 {
6639 wxCheckSetFont(dc, font);
6640 }
6641 }
6642
6643 bool haveDescent = false;
6644 int startPos = range.GetStart() - GetRange().GetStart();
6645 long len = range.GetLength();
6646
6647 wxString str(m_text);
6648 wxString toReplace = wxRichTextLineBreakChar;
6649 str.Replace(toReplace, wxT(" "));
6650
6651 wxString stringChunk = str.Mid(startPos, (size_t) len);
6652
6653 if (textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_CAPITALS))
6654 stringChunk.MakeUpper();
6655
6656 wxCoord w, h;
6657 int width = 0;
6658 if (stringChunk.Find(wxT('\t')) != wxNOT_FOUND)
6659 {
6660 // the string has a tab
6661 wxArrayInt tabArray;
6662 if (textAttr.GetTabs().IsEmpty())
6663 tabArray = wxRichTextParagraph::GetDefaultTabs();
6664 else
6665 tabArray = textAttr.GetTabs();
6666
6667 int tabCount = tabArray.GetCount();
6668
6669 for (int i = 0; i < tabCount; ++i)
6670 {
6671 int pos = tabArray[i];
6672 pos = ((wxRichTextPlainText*) this)->ConvertTenthsMMToPixels(dc, pos);
6673 tabArray[i] = pos;
6674 }
6675
6676 int nextTabPos = -1;
6677
6678 while (stringChunk.Find(wxT('\t')) >= 0)
6679 {
6680 int absoluteWidth = 0;
6681
6682 // the string has a tab
6683 // break up the string at the Tab
6684 wxString stringFragment = stringChunk.BeforeFirst(wxT('\t'));
6685 stringChunk = stringChunk.AfterFirst(wxT('\t'));
6686
6687 if (partialExtents)
6688 {
6689 int oldWidth;
6690 if (partialExtents->GetCount() > 0)
6691 oldWidth = (*partialExtents)[partialExtents->GetCount()-1];
6692 else
6693 oldWidth = 0;
6694
6695 // Add these partial extents
6696 wxArrayInt p;
6697 dc.GetPartialTextExtents(stringFragment, p);
6698 size_t j;
6699 for (j = 0; j < p.GetCount(); j++)
6700 partialExtents->Add(oldWidth + p[j]);
6701
6702 if (partialExtents->GetCount() > 0)
6703 absoluteWidth = (*partialExtents)[(*partialExtents).GetCount()-1] + relativeX;
6704 else
6705 absoluteWidth = relativeX;
6706 }
6707 else
6708 {
6709 dc.GetTextExtent(stringFragment, & w, & h);
6710 width += w;
6711 absoluteWidth = width + relativeX;
6712 haveDescent = true;
6713 }
6714
6715 bool notFound = true;
6716 for (int i = 0; i < tabCount && notFound; ++i)
6717 {
6718 nextTabPos = tabArray.Item(i);
6719
6720 // Find the next tab position.
6721 // Even if we're at the end of the tab array, we must still process the chunk.
6722
6723 if (nextTabPos > absoluteWidth || (i == (tabCount - 1)))
6724 {
6725 if (nextTabPos <= absoluteWidth)
6726 {
6727 int defaultTabWidth = ((wxRichTextPlainText*) this)->ConvertTenthsMMToPixels(dc, WIDTH_FOR_DEFAULT_TABS);
6728 nextTabPos = absoluteWidth + defaultTabWidth;
6729 }
6730
6731 notFound = false;
6732 width = nextTabPos - relativeX;
6733
6734 if (partialExtents)
6735 partialExtents->Add(width);
6736 }
6737 }
6738 }
6739 }
6740
6741 if (!stringChunk.IsEmpty())
6742 {
6743 if (partialExtents)
6744 {
6745 int oldWidth;
6746 if (partialExtents->GetCount() > 0)
6747 oldWidth = (*partialExtents)[partialExtents->GetCount()-1];
6748 else
6749 oldWidth = 0;
6750
6751 // Add these partial extents
6752 wxArrayInt p;
6753 dc.GetPartialTextExtents(stringChunk, p);
6754 size_t j;
6755 for (j = 0; j < p.GetCount(); j++)
6756 partialExtents->Add(oldWidth + p[j]);
6757 }
6758 else
6759 {
6760 dc.GetTextExtent(stringChunk, & w, & h, & descent);
6761 width += w;
6762 haveDescent = true;
6763 }
6764 }
6765
6766 if (partialExtents)
6767 {
6768 int charHeight = dc.GetCharHeight();
6769 if ((*partialExtents).GetCount() > 0)
6770 w = (*partialExtents)[partialExtents->GetCount()-1];
6771 else
6772 w = 0;
6773 size = wxSize(w, charHeight);
6774 }
6775 else
6776 {
6777 size = wxSize(width, dc.GetCharHeight());
6778 }
6779
6780 if (!haveDescent)
6781 dc.GetTextExtent(wxT("X"), & w, & h, & descent);
6782
6783 if ( bScript )
6784 dc.SetFont(font);
6785
6786 return true;
6787 }
6788
6789 /// Do a split, returning an object containing the second part, and setting
6790 /// the first part in 'this'.
6791 wxRichTextObject* wxRichTextPlainText::DoSplit(long pos)
6792 {
6793 long index = pos - GetRange().GetStart();
6794
6795 if (index < 0 || index >= (int) m_text.length())
6796 return NULL;
6797
6798 wxString firstPart = m_text.Mid(0, index);
6799 wxString secondPart = m_text.Mid(index);
6800
6801 m_text = firstPart;
6802
6803 wxRichTextPlainText* newObject = new wxRichTextPlainText(secondPart);
6804 newObject->SetAttributes(GetAttributes());
6805 newObject->SetProperties(GetProperties());
6806
6807 newObject->SetRange(wxRichTextRange(pos, GetRange().GetEnd()));
6808 GetRange().SetEnd(pos-1);
6809
6810 return newObject;
6811 }
6812
6813 /// Calculate range
6814 void wxRichTextPlainText::CalculateRange(long start, long& end)
6815 {
6816 end = start + m_text.length() - 1;
6817 m_range.SetRange(start, end);
6818 }
6819
6820 /// Delete range
6821 bool wxRichTextPlainText::DeleteRange(const wxRichTextRange& range)
6822 {
6823 wxRichTextRange r = range;
6824
6825 r.LimitTo(GetRange());
6826
6827 if (r.GetStart() == GetRange().GetStart() && r.GetEnd() == GetRange().GetEnd())
6828 {
6829 m_text.Empty();
6830 return true;
6831 }
6832
6833 long startIndex = r.GetStart() - GetRange().GetStart();
6834 long len = r.GetLength();
6835
6836 m_text = m_text.Mid(0, startIndex) + m_text.Mid(startIndex+len);
6837 return true;
6838 }
6839
6840 /// Get text for the given range.
6841 wxString wxRichTextPlainText::GetTextForRange(const wxRichTextRange& range) const
6842 {
6843 wxRichTextRange r = range;
6844
6845 r.LimitTo(GetRange());
6846
6847 long startIndex = r.GetStart() - GetRange().GetStart();
6848 long len = r.GetLength();
6849
6850 return m_text.Mid(startIndex, len);
6851 }
6852
6853 /// Returns true if this object can merge itself with the given one.
6854 bool wxRichTextPlainText::CanMerge(wxRichTextObject* object) const
6855 {
6856 return object->GetClassInfo() == wxCLASSINFO(wxRichTextPlainText) &&
6857 (m_text.empty() || (wxTextAttrEq(GetAttributes(), object->GetAttributes()) && m_properties == object->GetProperties()));
6858 }
6859
6860 /// Returns true if this object merged itself with the given one.
6861 /// The calling code will then delete the given object.
6862 bool wxRichTextPlainText::Merge(wxRichTextObject* object)
6863 {
6864 wxRichTextPlainText* textObject = wxDynamicCast(object, wxRichTextPlainText);
6865 wxASSERT( textObject != NULL );
6866
6867 if (textObject)
6868 {
6869 m_text += textObject->GetText();
6870 wxRichTextApplyStyle(m_attributes, textObject->GetAttributes());
6871 return true;
6872 }
6873 else
6874 return false;
6875 }
6876
6877 /// Dump to output stream for debugging
6878 void wxRichTextPlainText::Dump(wxTextOutputStream& stream)
6879 {
6880 wxRichTextObject::Dump(stream);
6881 stream << m_text << wxT("\n");
6882 }
6883
6884 /// Get the first position from pos that has a line break character.
6885 long wxRichTextPlainText::GetFirstLineBreakPosition(long pos)
6886 {
6887 int i;
6888 int len = m_text.length();
6889 int startPos = pos - m_range.GetStart();
6890 for (i = startPos; i < len; i++)
6891 {
6892 wxChar ch = m_text[i];
6893 if (ch == wxRichTextLineBreakChar)
6894 {
6895 return i + m_range.GetStart();
6896 }
6897 }
6898 return -1;
6899 }
6900
6901 /*!
6902 * wxRichTextBuffer
6903 * This is a kind of box, used to represent the whole buffer
6904 */
6905
6906 IMPLEMENT_DYNAMIC_CLASS(wxRichTextBuffer, wxRichTextParagraphLayoutBox)
6907
6908 wxList wxRichTextBuffer::sm_handlers;
6909 wxList wxRichTextBuffer::sm_drawingHandlers;
6910 wxRichTextFieldTypeHashMap wxRichTextBuffer::sm_fieldTypes;
6911 wxRichTextRenderer* wxRichTextBuffer::sm_renderer = NULL;
6912 int wxRichTextBuffer::sm_bulletRightMargin = 20;
6913 float wxRichTextBuffer::sm_bulletProportion = (float) 0.3;
6914
6915 /// Initialisation
6916 void wxRichTextBuffer::Init()
6917 {
6918 m_commandProcessor = new wxCommandProcessor;
6919 m_styleSheet = NULL;
6920 m_modified = false;
6921 m_batchedCommandDepth = 0;
6922 m_batchedCommand = NULL;
6923 m_suppressUndo = 0;
6924 m_handlerFlags = 0;
6925 m_scale = 1.0;
6926 m_dimensionScale = 1.0;
6927 m_fontScale = 1.0;
6928 SetMargins(4);
6929 }
6930
6931 /// Initialisation
6932 wxRichTextBuffer::~wxRichTextBuffer()
6933 {
6934 delete m_commandProcessor;
6935 delete m_batchedCommand;
6936
6937 ClearStyleStack();
6938 ClearEventHandlers();
6939 }
6940
6941 void wxRichTextBuffer::ResetAndClearCommands()
6942 {
6943 Reset();
6944
6945 GetCommandProcessor()->ClearCommands();
6946
6947 Modify(false);
6948 Invalidate(wxRICHTEXT_ALL);
6949 }
6950
6951 void wxRichTextBuffer::Copy(const wxRichTextBuffer& obj)
6952 {
6953 wxRichTextParagraphLayoutBox::Copy(obj);
6954
6955 m_styleSheet = obj.m_styleSheet;
6956 m_modified = obj.m_modified;
6957 m_batchedCommandDepth = 0;
6958 if (m_batchedCommand)
6959 delete m_batchedCommand;
6960 m_batchedCommand = NULL;
6961 m_suppressUndo = obj.m_suppressUndo;
6962 m_invalidRange = obj.m_invalidRange;
6963 m_dimensionScale = obj.m_dimensionScale;
6964 m_fontScale = obj.m_fontScale;
6965 }
6966
6967 /// Push style sheet to top of stack
6968 bool wxRichTextBuffer::PushStyleSheet(wxRichTextStyleSheet* styleSheet)
6969 {
6970 if (m_styleSheet)
6971 styleSheet->InsertSheet(m_styleSheet);
6972
6973 SetStyleSheet(styleSheet);
6974
6975 return true;
6976 }
6977
6978 /// Pop style sheet from top of stack
6979 wxRichTextStyleSheet* wxRichTextBuffer::PopStyleSheet()
6980 {
6981 if (m_styleSheet)
6982 {
6983 wxRichTextStyleSheet* oldSheet = m_styleSheet;
6984 m_styleSheet = oldSheet->GetNextSheet();
6985 oldSheet->Unlink();
6986
6987 return oldSheet;
6988 }
6989 else
6990 return NULL;
6991 }
6992
6993 /// Submit command to insert paragraphs
6994 bool wxRichTextBuffer::InsertParagraphsWithUndo(long pos, const wxRichTextParagraphLayoutBox& paragraphs, wxRichTextCtrl* ctrl, int flags)
6995 {
6996 return ctrl->GetFocusObject()->InsertParagraphsWithUndo(this, pos, paragraphs, ctrl, flags);
6997 }
6998
6999 /// Submit command to insert paragraphs
7000 bool wxRichTextParagraphLayoutBox::InsertParagraphsWithUndo(wxRichTextBuffer* buffer, long pos, const wxRichTextParagraphLayoutBox& paragraphs, wxRichTextCtrl* ctrl, int WXUNUSED(flags))
7001 {
7002 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
7003
7004 action->GetNewParagraphs() = paragraphs;
7005
7006 action->SetPosition(pos);
7007
7008 wxRichTextRange range = wxRichTextRange(pos, pos + paragraphs.GetOwnRange().GetEnd() - 1);
7009 if (!paragraphs.GetPartialParagraph())
7010 range.SetEnd(range.GetEnd()+1);
7011
7012 // Set the range we'll need to delete in Undo
7013 action->SetRange(range);
7014
7015 buffer->SubmitAction(action);
7016
7017 return true;
7018 }
7019
7020 /// Submit command to insert the given text
7021 bool wxRichTextBuffer::InsertTextWithUndo(long pos, const wxString& text, wxRichTextCtrl* ctrl, int flags)
7022 {
7023 return ctrl->GetFocusObject()->InsertTextWithUndo(this, pos, text, ctrl, flags);
7024 }
7025
7026 /// Submit command to insert the given text
7027 bool wxRichTextParagraphLayoutBox::InsertTextWithUndo(wxRichTextBuffer* buffer, long pos, const wxString& text, wxRichTextCtrl* ctrl, int flags)
7028 {
7029 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
7030
7031 wxRichTextAttr* p = NULL;
7032 wxRichTextAttr paraAttr;
7033 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
7034 {
7035 // Get appropriate paragraph style
7036 paraAttr = GetStyleForNewParagraph(buffer, pos, false, false);
7037 if (!paraAttr.IsDefault())
7038 p = & paraAttr;
7039 }
7040
7041 action->GetNewParagraphs().AddParagraphs(text, p);
7042
7043 int length = action->GetNewParagraphs().GetOwnRange().GetLength();
7044
7045 if (!text.empty() && text.Last() != wxT('\n'))
7046 {
7047 // Don't count the newline when undoing
7048 length --;
7049 action->GetNewParagraphs().SetPartialParagraph(true);
7050 }
7051 else if (!text.empty() && text.Last() == wxT('\n'))
7052 length --;
7053
7054 action->SetPosition(pos);
7055
7056 // Set the range we'll need to delete in Undo
7057 action->SetRange(wxRichTextRange(pos, pos + length - 1));
7058
7059 buffer->SubmitAction(action);
7060
7061 return true;
7062 }
7063
7064 /// Submit command to insert the given text
7065 bool wxRichTextBuffer::InsertNewlineWithUndo(long pos, wxRichTextCtrl* ctrl, int flags)
7066 {
7067 return ctrl->GetFocusObject()->InsertNewlineWithUndo(this, pos, ctrl, flags);
7068 }
7069
7070 /// Submit command to insert the given text
7071 bool wxRichTextParagraphLayoutBox::InsertNewlineWithUndo(wxRichTextBuffer* buffer, long pos, wxRichTextCtrl* ctrl, int flags)
7072 {
7073 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
7074
7075 wxRichTextAttr* p = NULL;
7076 wxRichTextAttr paraAttr;
7077 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
7078 {
7079 paraAttr = GetStyleForNewParagraph(buffer, pos, false, true /* look for next paragraph style */);
7080 if (!paraAttr.IsDefault())
7081 p = & paraAttr;
7082 }
7083
7084 wxRichTextAttr attr(buffer->GetDefaultStyle());
7085 // Don't include box attributes such as margins
7086 attr.GetTextBoxAttr().Reset();
7087
7088 wxRichTextParagraph* newPara = new wxRichTextParagraph(wxEmptyString, this, & attr);
7089 action->GetNewParagraphs().AppendChild(newPara);
7090 action->GetNewParagraphs().UpdateRanges();
7091 action->GetNewParagraphs().SetPartialParagraph(false);
7092 wxRichTextParagraph* para = GetParagraphAtPosition(pos, false);
7093 long pos1 = pos;
7094
7095 if (p)
7096 newPara->SetAttributes(*p);
7097
7098 if (flags & wxRICHTEXT_INSERT_INTERACTIVE)
7099 {
7100 if (para && para->GetRange().GetEnd() == pos)
7101 pos1 ++;
7102
7103 // Now see if we need to number the paragraph.
7104 if (newPara->GetAttributes().HasBulletNumber())
7105 {
7106 wxRichTextAttr numberingAttr;
7107 if (FindNextParagraphNumber(para, numberingAttr))
7108 wxRichTextApplyStyle(newPara->GetAttributes(), (const wxRichTextAttr&) numberingAttr);
7109 }
7110 }
7111
7112 action->SetPosition(pos);
7113
7114 // Use the default character style
7115 if (!buffer->GetDefaultStyle().IsDefault() && newPara->GetChildren().GetFirst())
7116 {
7117 // Check whether the default style merely reflects the paragraph/basic style,
7118 // in which case don't apply it.
7119 wxRichTextAttr defaultStyle(buffer->GetDefaultStyle());
7120 defaultStyle.GetTextBoxAttr().Reset();
7121 wxRichTextAttr toApply;
7122 if (para)
7123 {
7124 wxRichTextAttr combinedAttr = para->GetCombinedAttributes(true /* include box attributes */);
7125 wxRichTextAttr newAttr;
7126 // This filters out attributes that are accounted for by the current
7127 // paragraph/basic style
7128 wxRichTextApplyStyle(toApply, defaultStyle, & combinedAttr);
7129 }
7130 else
7131 toApply = defaultStyle;
7132
7133 if (!toApply.IsDefault())
7134 newPara->GetChildren().GetFirst()->GetData()->SetAttributes(toApply);
7135 }
7136
7137 // Set the range we'll need to delete in Undo
7138 action->SetRange(wxRichTextRange(pos1, pos1));
7139
7140 buffer->SubmitAction(action);
7141
7142 return true;
7143 }
7144
7145 /// Submit command to insert the given image
7146 bool wxRichTextBuffer::InsertImageWithUndo(long pos, const wxRichTextImageBlock& imageBlock, wxRichTextCtrl* ctrl, int flags,
7147 const wxRichTextAttr& textAttr)
7148 {
7149 return ctrl->GetFocusObject()->InsertImageWithUndo(this, pos, imageBlock, ctrl, flags, textAttr);
7150 }
7151
7152 /// Submit command to insert the given image
7153 bool wxRichTextParagraphLayoutBox::InsertImageWithUndo(wxRichTextBuffer* buffer, long pos, const wxRichTextImageBlock& imageBlock,
7154 wxRichTextCtrl* ctrl, int flags,
7155 const wxRichTextAttr& textAttr)
7156 {
7157 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Image"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
7158
7159 wxRichTextAttr* p = NULL;
7160 wxRichTextAttr paraAttr;
7161 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
7162 {
7163 paraAttr = GetStyleForNewParagraph(buffer, pos);
7164 if (!paraAttr.IsDefault())
7165 p = & paraAttr;
7166 }
7167
7168 wxRichTextAttr attr(buffer->GetDefaultStyle());
7169
7170 // Don't include box attributes such as margins
7171 attr.GetTextBoxAttr().Reset();
7172
7173 wxRichTextParagraph* newPara = new wxRichTextParagraph(this, & attr);
7174 if (p)
7175 newPara->SetAttributes(*p);
7176
7177 wxRichTextImage* imageObject = new wxRichTextImage(imageBlock, newPara);
7178 newPara->AppendChild(imageObject);
7179 imageObject->SetAttributes(textAttr);
7180 action->GetNewParagraphs().AppendChild(newPara);
7181 action->GetNewParagraphs().UpdateRanges();
7182
7183 action->GetNewParagraphs().SetPartialParagraph(true);
7184
7185 action->SetPosition(pos);
7186
7187 // Set the range we'll need to delete in Undo
7188 action->SetRange(wxRichTextRange(pos, pos));
7189
7190 buffer->SubmitAction(action);
7191
7192 return true;
7193 }
7194
7195 // Insert an object with no change of it
7196 wxRichTextObject* wxRichTextBuffer::InsertObjectWithUndo(long pos, wxRichTextObject *object, wxRichTextCtrl* ctrl, int flags)
7197 {
7198 return ctrl->GetFocusObject()->InsertObjectWithUndo(this, pos, object, ctrl, flags);
7199 }
7200
7201 // Insert an object with no change of it
7202 wxRichTextObject* wxRichTextParagraphLayoutBox::InsertObjectWithUndo(wxRichTextBuffer* buffer, long pos, wxRichTextObject *object, wxRichTextCtrl* ctrl, int flags)
7203 {
7204 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Object"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
7205
7206 wxRichTextAttr* p = NULL;
7207 wxRichTextAttr paraAttr;
7208 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
7209 {
7210 paraAttr = GetStyleForNewParagraph(buffer, pos);
7211 if (!paraAttr.IsDefault())
7212 p = & paraAttr;
7213 }
7214
7215 wxRichTextAttr attr(buffer->GetDefaultStyle());
7216
7217 // Don't include box attributes such as margins
7218 attr.GetTextBoxAttr().Reset();
7219
7220 wxRichTextParagraph* newPara = new wxRichTextParagraph(this, & attr);
7221 if (p)
7222 newPara->SetAttributes(*p);
7223
7224 newPara->AppendChild(object);
7225 action->GetNewParagraphs().AppendChild(newPara);
7226 action->GetNewParagraphs().UpdateRanges();
7227
7228 action->GetNewParagraphs().SetPartialParagraph(true);
7229
7230 action->SetPosition(pos);
7231
7232 // Set the range we'll need to delete in Undo
7233 action->SetRange(wxRichTextRange(pos, pos));
7234
7235 buffer->SubmitAction(action);
7236
7237 wxRichTextObject* obj = GetLeafObjectAtPosition(pos);
7238 return obj;
7239 }
7240
7241 wxRichTextField* wxRichTextParagraphLayoutBox::InsertFieldWithUndo(wxRichTextBuffer* buffer, long pos, const wxString& fieldType,
7242 const wxRichTextProperties& properties,
7243 wxRichTextCtrl* ctrl, int flags,
7244 const wxRichTextAttr& textAttr)
7245 {
7246 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Field"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
7247
7248 wxRichTextAttr* p = NULL;
7249 wxRichTextAttr paraAttr;
7250 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
7251 {
7252 paraAttr = GetStyleForNewParagraph(buffer, pos);
7253 if (!paraAttr.IsDefault())
7254 p = & paraAttr;
7255 }
7256
7257 wxRichTextAttr attr(buffer->GetDefaultStyle());
7258
7259 // Don't include box attributes such as margins
7260 attr.GetTextBoxAttr().Reset();
7261
7262 wxRichTextParagraph* newPara = new wxRichTextParagraph(this, & attr);
7263 if (p)
7264 newPara->SetAttributes(*p);
7265
7266 wxRichTextField* fieldObject = new wxRichTextField();
7267 fieldObject->wxRichTextObject::SetProperties(properties);
7268 fieldObject->SetFieldType(fieldType);
7269 fieldObject->SetAttributes(textAttr);
7270 newPara->AppendChild(fieldObject);
7271 action->GetNewParagraphs().AppendChild(newPara);
7272 action->GetNewParagraphs().UpdateRanges();
7273 action->GetNewParagraphs().SetPartialParagraph(true);
7274 action->SetPosition(pos);
7275
7276 // Set the range we'll need to delete in Undo
7277 action->SetRange(wxRichTextRange(pos, pos));
7278
7279 buffer->SubmitAction(action);
7280
7281 wxRichTextField* obj = wxDynamicCast(GetLeafObjectAtPosition(pos), wxRichTextField);
7282 return obj;
7283 }
7284
7285 /// Get the style that is appropriate for a new paragraph at this position.
7286 /// If the previous paragraph has a paragraph style name, look up the next-paragraph
7287 /// style.
7288 wxRichTextAttr wxRichTextParagraphLayoutBox::GetStyleForNewParagraph(wxRichTextBuffer* buffer, long pos, bool caretPosition, bool lookUpNewParaStyle) const
7289 {
7290 wxRichTextParagraph* para = GetParagraphAtPosition(pos, caretPosition);
7291 if (para)
7292 {
7293 wxRichTextAttr attr;
7294 bool foundAttributes = false;
7295
7296 // Look for a matching paragraph style
7297 if (lookUpNewParaStyle && !para->GetAttributes().GetParagraphStyleName().IsEmpty() && buffer->GetStyleSheet())
7298 {
7299 wxRichTextParagraphStyleDefinition* paraDef = buffer->GetStyleSheet()->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName());
7300 if (paraDef)
7301 {
7302 // If we're not at the end of the paragraph, then we apply THIS style, and not the designated next style.
7303 if (para->GetRange().GetEnd() == pos && !paraDef->GetNextStyle().IsEmpty())
7304 {
7305 wxRichTextParagraphStyleDefinition* nextParaDef = buffer->GetStyleSheet()->FindParagraphStyle(paraDef->GetNextStyle());
7306 if (nextParaDef)
7307 {
7308 foundAttributes = true;
7309 attr = nextParaDef->GetStyleMergedWithBase(buffer->GetStyleSheet());
7310 }
7311 }
7312
7313 // If we didn't find the 'next style', use this style instead.
7314 if (!foundAttributes)
7315 {
7316 foundAttributes = true;
7317 attr = paraDef->GetStyleMergedWithBase(buffer->GetStyleSheet());
7318 }
7319 }
7320 }
7321
7322 // Also apply list style if present
7323 if (lookUpNewParaStyle && !para->GetAttributes().GetListStyleName().IsEmpty() && buffer->GetStyleSheet())
7324 {
7325 wxRichTextListStyleDefinition* listDef = buffer->GetStyleSheet()->FindListStyle(para->GetAttributes().GetListStyleName());
7326 if (listDef)
7327 {
7328 int thisIndent = para->GetAttributes().GetLeftIndent();
7329 int thisLevel = para->GetAttributes().HasOutlineLevel() ? para->GetAttributes().GetOutlineLevel() : listDef->FindLevelForIndent(thisIndent);
7330
7331 // Apply the overall list style, and item style for this level
7332 wxRichTextAttr listStyle(listDef->GetCombinedStyleForLevel(thisLevel, buffer->GetStyleSheet()));
7333 wxRichTextApplyStyle(attr, listStyle);
7334 attr.SetOutlineLevel(thisLevel);
7335 if (para->GetAttributes().HasBulletNumber())
7336 attr.SetBulletNumber(para->GetAttributes().GetBulletNumber());
7337 }
7338 }
7339
7340 if (!foundAttributes)
7341 {
7342 attr = para->GetAttributes();
7343 int flags = attr.GetFlags();
7344
7345 // Eliminate character styles
7346 flags &= ( (~ wxTEXT_ATTR_FONT) |
7347 (~ wxTEXT_ATTR_TEXT_COLOUR) |
7348 (~ wxTEXT_ATTR_BACKGROUND_COLOUR) );
7349 attr.SetFlags(flags);
7350 }
7351
7352 return attr;
7353 }
7354 else
7355 return wxRichTextAttr();
7356 }
7357
7358 /// Submit command to delete this range
7359 bool wxRichTextBuffer::DeleteRangeWithUndo(const wxRichTextRange& range, wxRichTextCtrl* ctrl)
7360 {
7361 return ctrl->GetFocusObject()->DeleteRangeWithUndo(range, ctrl, this);
7362 }
7363
7364 /// Submit command to delete this range
7365 bool wxRichTextParagraphLayoutBox::DeleteRangeWithUndo(const wxRichTextRange& range, wxRichTextCtrl* ctrl, wxRichTextBuffer* buffer)
7366 {
7367 wxRichTextAction* action = new wxRichTextAction(NULL, _("Delete"), wxRICHTEXT_DELETE, buffer, this, ctrl);
7368
7369 action->SetPosition(ctrl->GetCaretPosition());
7370
7371 // Set the range to delete
7372 action->SetRange(range);
7373
7374 // Copy the fragment that we'll need to restore in Undo
7375 CopyFragment(range, action->GetOldParagraphs());
7376
7377 // See if we're deleting a paragraph marker, in which case we need to
7378 // make a note not to copy the attributes from the 2nd paragraph to the 1st.
7379 if (range.GetStart() == range.GetEnd())
7380 {
7381 wxRichTextParagraph* para = GetParagraphAtPosition(range.GetStart());
7382 if (para && para->GetRange().GetEnd() == range.GetEnd())
7383 {
7384 wxRichTextParagraph* nextPara = GetParagraphAtPosition(range.GetStart()+1);
7385 if (nextPara && nextPara != para)
7386 {
7387 action->GetOldParagraphs().GetChildren().GetFirst()->GetData()->SetAttributes(nextPara->GetAttributes());
7388 action->GetOldParagraphs().GetAttributes().SetFlags(action->GetOldParagraphs().GetAttributes().GetFlags() | wxTEXT_ATTR_KEEP_FIRST_PARA_STYLE);
7389 }
7390 }
7391 }
7392
7393 buffer->SubmitAction(action);
7394
7395 return true;
7396 }
7397
7398 /// Collapse undo/redo commands
7399 bool wxRichTextBuffer::BeginBatchUndo(const wxString& cmdName)
7400 {
7401 if (m_batchedCommandDepth == 0)
7402 {
7403 wxASSERT(m_batchedCommand == NULL);
7404 if (m_batchedCommand)
7405 {
7406 GetCommandProcessor()->Store(m_batchedCommand);
7407 }
7408 m_batchedCommand = new wxRichTextCommand(cmdName);
7409 }
7410
7411 m_batchedCommandDepth ++;
7412
7413 return true;
7414 }
7415
7416 /// Collapse undo/redo commands
7417 bool wxRichTextBuffer::EndBatchUndo()
7418 {
7419 m_batchedCommandDepth --;
7420
7421 wxASSERT(m_batchedCommandDepth >= 0);
7422 wxASSERT(m_batchedCommand != NULL);
7423
7424 if (m_batchedCommandDepth == 0)
7425 {
7426 GetCommandProcessor()->Store(m_batchedCommand);
7427 m_batchedCommand = NULL;
7428 }
7429
7430 return true;
7431 }
7432
7433 /// Submit immediately, or delay according to whether collapsing is on
7434 bool wxRichTextBuffer::SubmitAction(wxRichTextAction* action)
7435 {
7436 if (action && !action->GetNewParagraphs().IsEmpty())
7437 PrepareContent(action->GetNewParagraphs());
7438
7439 if (BatchingUndo() && m_batchedCommand && !SuppressingUndo())
7440 {
7441 wxRichTextCommand* cmd = new wxRichTextCommand(action->GetName());
7442 cmd->AddAction(action);
7443 cmd->Do();
7444 cmd->GetActions().Clear();
7445 delete cmd;
7446
7447 m_batchedCommand->AddAction(action);
7448 }
7449 else
7450 {
7451 wxRichTextCommand* cmd = new wxRichTextCommand(action->GetName());
7452 cmd->AddAction(action);
7453
7454 // Only store it if we're not suppressing undo.
7455 return GetCommandProcessor()->Submit(cmd, !SuppressingUndo());
7456 }
7457
7458 return true;
7459 }
7460
7461 /// Begin suppressing undo/redo commands.
7462 bool wxRichTextBuffer::BeginSuppressUndo()
7463 {
7464 m_suppressUndo ++;
7465
7466 return true;
7467 }
7468
7469 /// End suppressing undo/redo commands.
7470 bool wxRichTextBuffer::EndSuppressUndo()
7471 {
7472 m_suppressUndo --;
7473
7474 return true;
7475 }
7476
7477 /// Begin using a style
7478 bool wxRichTextBuffer::BeginStyle(const wxRichTextAttr& style)
7479 {
7480 wxRichTextAttr newStyle(GetDefaultStyle());
7481 newStyle.GetTextBoxAttr().Reset();
7482
7483 // Save the old default style
7484 m_attributeStack.Append((wxObject*) new wxRichTextAttr(newStyle));
7485
7486 wxRichTextApplyStyle(newStyle, style);
7487 newStyle.SetFlags(style.GetFlags()|newStyle.GetFlags());
7488
7489 SetDefaultStyle(newStyle);
7490
7491 return true;
7492 }
7493
7494 /// End the style
7495 bool wxRichTextBuffer::EndStyle()
7496 {
7497 if (!m_attributeStack.GetFirst())
7498 {
7499 wxLogDebug(_("Too many EndStyle calls!"));
7500 return false;
7501 }
7502
7503 wxList::compatibility_iterator node = m_attributeStack.GetLast();
7504 wxRichTextAttr* attr = (wxRichTextAttr*)node->GetData();
7505 m_attributeStack.Erase(node);
7506
7507 SetDefaultStyle(*attr);
7508
7509 delete attr;
7510 return true;
7511 }
7512
7513 /// End all styles
7514 bool wxRichTextBuffer::EndAllStyles()
7515 {
7516 while (m_attributeStack.GetCount() != 0)
7517 EndStyle();
7518 return true;
7519 }
7520
7521 /// Clear the style stack
7522 void wxRichTextBuffer::ClearStyleStack()
7523 {
7524 for (wxList::compatibility_iterator node = m_attributeStack.GetFirst(); node; node = node->GetNext())
7525 delete (wxRichTextAttr*) node->GetData();
7526 m_attributeStack.Clear();
7527 }
7528
7529 /// Begin using bold
7530 bool wxRichTextBuffer::BeginBold()
7531 {
7532 wxRichTextAttr attr;
7533 attr.SetFontWeight(wxFONTWEIGHT_BOLD);
7534
7535 return BeginStyle(attr);
7536 }
7537
7538 /// Begin using italic
7539 bool wxRichTextBuffer::BeginItalic()
7540 {
7541 wxRichTextAttr attr;
7542 attr.SetFontStyle(wxFONTSTYLE_ITALIC);
7543
7544 return BeginStyle(attr);
7545 }
7546
7547 /// Begin using underline
7548 bool wxRichTextBuffer::BeginUnderline()
7549 {
7550 wxRichTextAttr attr;
7551 attr.SetFontUnderlined(true);
7552
7553 return BeginStyle(attr);
7554 }
7555
7556 /// Begin using point size
7557 bool wxRichTextBuffer::BeginFontSize(int pointSize)
7558 {
7559 wxRichTextAttr attr;
7560 attr.SetFontSize(pointSize);
7561
7562 return BeginStyle(attr);
7563 }
7564
7565 /// Begin using this font
7566 bool wxRichTextBuffer::BeginFont(const wxFont& font)
7567 {
7568 wxRichTextAttr attr;
7569 attr.SetFont(font);
7570
7571 return BeginStyle(attr);
7572 }
7573
7574 /// Begin using this colour
7575 bool wxRichTextBuffer::BeginTextColour(const wxColour& colour)
7576 {
7577 wxRichTextAttr attr;
7578 attr.SetFlags(wxTEXT_ATTR_TEXT_COLOUR);
7579 attr.SetTextColour(colour);
7580
7581 return BeginStyle(attr);
7582 }
7583
7584 /// Begin using alignment
7585 bool wxRichTextBuffer::BeginAlignment(wxTextAttrAlignment alignment)
7586 {
7587 wxRichTextAttr attr;
7588 attr.SetFlags(wxTEXT_ATTR_ALIGNMENT);
7589 attr.SetAlignment(alignment);
7590
7591 return BeginStyle(attr);
7592 }
7593
7594 /// Begin left indent
7595 bool wxRichTextBuffer::BeginLeftIndent(int leftIndent, int leftSubIndent)
7596 {
7597 wxRichTextAttr attr;
7598 attr.SetFlags(wxTEXT_ATTR_LEFT_INDENT);
7599 attr.SetLeftIndent(leftIndent, leftSubIndent);
7600
7601 return BeginStyle(attr);
7602 }
7603
7604 /// Begin right indent
7605 bool wxRichTextBuffer::BeginRightIndent(int rightIndent)
7606 {
7607 wxRichTextAttr attr;
7608 attr.SetFlags(wxTEXT_ATTR_RIGHT_INDENT);
7609 attr.SetRightIndent(rightIndent);
7610
7611 return BeginStyle(attr);
7612 }
7613
7614 /// Begin paragraph spacing
7615 bool wxRichTextBuffer::BeginParagraphSpacing(int before, int after)
7616 {
7617 long flags = 0;
7618 if (before != 0)
7619 flags |= wxTEXT_ATTR_PARA_SPACING_BEFORE;
7620 if (after != 0)
7621 flags |= wxTEXT_ATTR_PARA_SPACING_AFTER;
7622
7623 wxRichTextAttr attr;
7624 attr.SetFlags(flags);
7625 attr.SetParagraphSpacingBefore(before);
7626 attr.SetParagraphSpacingAfter(after);
7627
7628 return BeginStyle(attr);
7629 }
7630
7631 /// Begin line spacing
7632 bool wxRichTextBuffer::BeginLineSpacing(int lineSpacing)
7633 {
7634 wxRichTextAttr attr;
7635 attr.SetFlags(wxTEXT_ATTR_LINE_SPACING);
7636 attr.SetLineSpacing(lineSpacing);
7637
7638 return BeginStyle(attr);
7639 }
7640
7641 /// Begin numbered bullet
7642 bool wxRichTextBuffer::BeginNumberedBullet(int bulletNumber, int leftIndent, int leftSubIndent, int bulletStyle)
7643 {
7644 wxRichTextAttr attr;
7645 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT);
7646 attr.SetBulletStyle(bulletStyle);
7647 attr.SetBulletNumber(bulletNumber);
7648 attr.SetLeftIndent(leftIndent, leftSubIndent);
7649
7650 return BeginStyle(attr);
7651 }
7652
7653 /// Begin symbol bullet
7654 bool wxRichTextBuffer::BeginSymbolBullet(const wxString& symbol, int leftIndent, int leftSubIndent, int bulletStyle)
7655 {
7656 wxRichTextAttr attr;
7657 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT);
7658 attr.SetBulletStyle(bulletStyle);
7659 attr.SetLeftIndent(leftIndent, leftSubIndent);
7660 attr.SetBulletText(symbol);
7661
7662 return BeginStyle(attr);
7663 }
7664
7665 /// Begin standard bullet
7666 bool wxRichTextBuffer::BeginStandardBullet(const wxString& bulletName, int leftIndent, int leftSubIndent, int bulletStyle)
7667 {
7668 wxRichTextAttr attr;
7669 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT);
7670 attr.SetBulletStyle(bulletStyle);
7671 attr.SetLeftIndent(leftIndent, leftSubIndent);
7672 attr.SetBulletName(bulletName);
7673
7674 return BeginStyle(attr);
7675 }
7676
7677 /// Begin named character style
7678 bool wxRichTextBuffer::BeginCharacterStyle(const wxString& characterStyle)
7679 {
7680 if (GetStyleSheet())
7681 {
7682 wxRichTextCharacterStyleDefinition* def = GetStyleSheet()->FindCharacterStyle(characterStyle);
7683 if (def)
7684 {
7685 wxRichTextAttr attr = def->GetStyleMergedWithBase(GetStyleSheet());
7686 return BeginStyle(attr);
7687 }
7688 }
7689 return false;
7690 }
7691
7692 /// Begin named paragraph style
7693 bool wxRichTextBuffer::BeginParagraphStyle(const wxString& paragraphStyle)
7694 {
7695 if (GetStyleSheet())
7696 {
7697 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(paragraphStyle);
7698 if (def)
7699 {
7700 wxRichTextAttr attr = def->GetStyleMergedWithBase(GetStyleSheet());
7701 return BeginStyle(attr);
7702 }
7703 }
7704 return false;
7705 }
7706
7707 /// Begin named list style
7708 bool wxRichTextBuffer::BeginListStyle(const wxString& listStyle, int level, int number)
7709 {
7710 if (GetStyleSheet())
7711 {
7712 wxRichTextListStyleDefinition* def = GetStyleSheet()->FindListStyle(listStyle);
7713 if (def)
7714 {
7715 wxRichTextAttr attr(def->GetCombinedStyleForLevel(level));
7716
7717 attr.SetBulletNumber(number);
7718
7719 return BeginStyle(attr);
7720 }
7721 }
7722 return false;
7723 }
7724
7725 /// Begin URL
7726 bool wxRichTextBuffer::BeginURL(const wxString& url, const wxString& characterStyle)
7727 {
7728 wxRichTextAttr attr;
7729
7730 if (!characterStyle.IsEmpty() && GetStyleSheet())
7731 {
7732 wxRichTextCharacterStyleDefinition* def = GetStyleSheet()->FindCharacterStyle(characterStyle);
7733 if (def)
7734 {
7735 attr = def->GetStyleMergedWithBase(GetStyleSheet());
7736 }
7737 }
7738 attr.SetURL(url);
7739
7740 return BeginStyle(attr);
7741 }
7742
7743 /// Adds a handler to the end
7744 void wxRichTextBuffer::AddHandler(wxRichTextFileHandler *handler)
7745 {
7746 sm_handlers.Append(handler);
7747 }
7748
7749 /// Inserts a handler at the front
7750 void wxRichTextBuffer::InsertHandler(wxRichTextFileHandler *handler)
7751 {
7752 sm_handlers.Insert( handler );
7753 }
7754
7755 /// Removes a handler
7756 bool wxRichTextBuffer::RemoveHandler(const wxString& name)
7757 {
7758 wxRichTextFileHandler *handler = FindHandler(name);
7759 if (handler)
7760 {
7761 sm_handlers.DeleteObject(handler);
7762 delete handler;
7763 return true;
7764 }
7765 else
7766 return false;
7767 }
7768
7769 /// Finds a handler by filename or, if supplied, type
7770 wxRichTextFileHandler *wxRichTextBuffer::FindHandlerFilenameOrType(const wxString& filename,
7771 wxRichTextFileType imageType)
7772 {
7773 if (imageType != wxRICHTEXT_TYPE_ANY)
7774 return FindHandler(imageType);
7775 else if (!filename.IsEmpty())
7776 {
7777 wxString path, file, ext;
7778 wxFileName::SplitPath(filename, & path, & file, & ext);
7779 return FindHandler(ext, imageType);
7780 }
7781 else
7782 return NULL;
7783 }
7784
7785
7786 /// Finds a handler by name
7787 wxRichTextFileHandler* wxRichTextBuffer::FindHandler(const wxString& name)
7788 {
7789 wxList::compatibility_iterator node = sm_handlers.GetFirst();
7790 while (node)
7791 {
7792 wxRichTextFileHandler *handler = (wxRichTextFileHandler*)node->GetData();
7793 if (handler->GetName().Lower() == name.Lower()) return handler;
7794
7795 node = node->GetNext();
7796 }
7797 return NULL;
7798 }
7799
7800 /// Finds a handler by extension and type
7801 wxRichTextFileHandler* wxRichTextBuffer::FindHandler(const wxString& extension, wxRichTextFileType type)
7802 {
7803 wxList::compatibility_iterator node = sm_handlers.GetFirst();
7804 while (node)
7805 {
7806 wxRichTextFileHandler *handler = (wxRichTextFileHandler*)node->GetData();
7807 if ( handler->GetExtension().Lower() == extension.Lower() &&
7808 (type == wxRICHTEXT_TYPE_ANY || handler->GetType() == type) )
7809 return handler;
7810 node = node->GetNext();
7811 }
7812 return 0;
7813 }
7814
7815 /// Finds a handler by type
7816 wxRichTextFileHandler* wxRichTextBuffer::FindHandler(wxRichTextFileType type)
7817 {
7818 wxList::compatibility_iterator node = sm_handlers.GetFirst();
7819 while (node)
7820 {
7821 wxRichTextFileHandler *handler = (wxRichTextFileHandler *)node->GetData();
7822 if (handler->GetType() == type) return handler;
7823 node = node->GetNext();
7824 }
7825 return NULL;
7826 }
7827
7828 void wxRichTextBuffer::InitStandardHandlers()
7829 {
7830 if (!FindHandler(wxRICHTEXT_TYPE_TEXT))
7831 AddHandler(new wxRichTextPlainTextHandler);
7832 }
7833
7834 void wxRichTextBuffer::CleanUpHandlers()
7835 {
7836 wxList::compatibility_iterator node = sm_handlers.GetFirst();
7837 while (node)
7838 {
7839 wxRichTextFileHandler* handler = (wxRichTextFileHandler*)node->GetData();
7840 wxList::compatibility_iterator next = node->GetNext();
7841 delete handler;
7842 node = next;
7843 }
7844
7845 sm_handlers.Clear();
7846 }
7847
7848 wxString wxRichTextBuffer::GetExtWildcard(bool combine, bool save, wxArrayInt* types)
7849 {
7850 if (types)
7851 types->Clear();
7852
7853 wxString wildcard;
7854
7855 wxList::compatibility_iterator node = GetHandlers().GetFirst();
7856 int count = 0;
7857 while (node)
7858 {
7859 wxRichTextFileHandler* handler = (wxRichTextFileHandler*) node->GetData();
7860 if (handler->IsVisible() && ((save && handler->CanSave()) || (!save && handler->CanLoad())))
7861 {
7862 if (combine)
7863 {
7864 if (count > 0)
7865 wildcard += wxT(";");
7866 wildcard += wxT("*.") + handler->GetExtension();
7867 }
7868 else
7869 {
7870 if (count > 0)
7871 wildcard += wxT("|");
7872 wildcard += handler->GetName();
7873 wildcard += wxT(" ");
7874 wildcard += _("files");
7875 wildcard += wxT(" (*.");
7876 wildcard += handler->GetExtension();
7877 wildcard += wxT(")|*.");
7878 wildcard += handler->GetExtension();
7879 if (types)
7880 types->Add(handler->GetType());
7881 }
7882 count ++;
7883 }
7884
7885 node = node->GetNext();
7886 }
7887
7888 if (combine)
7889 wildcard = wxT("(") + wildcard + wxT(")|") + wildcard;
7890 return wildcard;
7891 }
7892
7893 /// Load a file
7894 bool wxRichTextBuffer::LoadFile(const wxString& filename, wxRichTextFileType type)
7895 {
7896 wxRichTextFileHandler* handler = FindHandlerFilenameOrType(filename, type);
7897 if (handler)
7898 {
7899 SetDefaultStyle(wxRichTextAttr());
7900 handler->SetFlags(GetHandlerFlags());
7901 bool success = handler->LoadFile(this, filename);
7902 Invalidate(wxRICHTEXT_ALL);
7903 return success;
7904 }
7905 else
7906 return false;
7907 }
7908
7909 /// Save a file
7910 bool wxRichTextBuffer::SaveFile(const wxString& filename, wxRichTextFileType type)
7911 {
7912 wxRichTextFileHandler* handler = FindHandlerFilenameOrType(filename, type);
7913 if (handler)
7914 {
7915 handler->SetFlags(GetHandlerFlags());
7916 return handler->SaveFile(this, filename);
7917 }
7918 else
7919 return false;
7920 }
7921
7922 /// Load from a stream
7923 bool wxRichTextBuffer::LoadFile(wxInputStream& stream, wxRichTextFileType type)
7924 {
7925 wxRichTextFileHandler* handler = FindHandler(type);
7926 if (handler)
7927 {
7928 SetDefaultStyle(wxRichTextAttr());
7929 handler->SetFlags(GetHandlerFlags());
7930 bool success = handler->LoadFile(this, stream);
7931 Invalidate(wxRICHTEXT_ALL);
7932 return success;
7933 }
7934 else
7935 return false;
7936 }
7937
7938 /// Save to a stream
7939 bool wxRichTextBuffer::SaveFile(wxOutputStream& stream, wxRichTextFileType type)
7940 {
7941 wxRichTextFileHandler* handler = FindHandler(type);
7942 if (handler)
7943 {
7944 handler->SetFlags(GetHandlerFlags());
7945 return handler->SaveFile(this, stream);
7946 }
7947 else
7948 return false;
7949 }
7950
7951 /// Copy the range to the clipboard
7952 bool wxRichTextBuffer::CopyToClipboard(const wxRichTextRange& range)
7953 {
7954 bool success = false;
7955 wxRichTextParagraphLayoutBox* container = this;
7956 if (GetRichTextCtrl())
7957 container = GetRichTextCtrl()->GetFocusObject();
7958
7959 #if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
7960
7961 if (!wxTheClipboard->IsOpened() && wxTheClipboard->Open())
7962 {
7963 wxTheClipboard->Clear();
7964
7965 // Add composite object
7966
7967 wxDataObjectComposite* compositeObject = new wxDataObjectComposite();
7968
7969 {
7970 wxString text = container->GetTextForRange(range);
7971
7972 #ifdef __WXMSW__
7973 text = wxTextFile::Translate(text, wxTextFileType_Dos);
7974 #endif
7975
7976 compositeObject->Add(new wxTextDataObject(text), false /* not preferred */);
7977 }
7978
7979 // Add rich text buffer data object. This needs the XML handler to be present.
7980
7981 if (FindHandler(wxRICHTEXT_TYPE_XML))
7982 {
7983 wxRichTextBuffer* richTextBuf = new wxRichTextBuffer;
7984 container->CopyFragment(range, *richTextBuf);
7985
7986 compositeObject->Add(new wxRichTextBufferDataObject(richTextBuf), true /* preferred */);
7987 }
7988
7989 if (wxTheClipboard->SetData(compositeObject))
7990 success = true;
7991
7992 wxTheClipboard->Close();
7993 }
7994
7995 #else
7996 wxUnusedVar(range);
7997 #endif
7998 return success;
7999 }
8000
8001 /// Paste the clipboard content to the buffer
8002 bool wxRichTextBuffer::PasteFromClipboard(long position)
8003 {
8004 bool success = false;
8005 wxRichTextParagraphLayoutBox* container = this;
8006 if (GetRichTextCtrl())
8007 container = GetRichTextCtrl()->GetFocusObject();
8008
8009 #if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
8010 if (CanPasteFromClipboard())
8011 {
8012 if (wxTheClipboard->Open())
8013 {
8014 if (wxTheClipboard->IsSupported(wxDataFormat(wxRichTextBufferDataObject::GetRichTextBufferFormatId())))
8015 {
8016 wxRichTextBufferDataObject data;
8017 wxTheClipboard->GetData(data);
8018 wxRichTextBuffer* richTextBuffer = data.GetRichTextBuffer();
8019 if (richTextBuffer)
8020 {
8021 container->InsertParagraphsWithUndo(this, position+1, *richTextBuffer, GetRichTextCtrl(), 0);
8022 if (GetRichTextCtrl())
8023 GetRichTextCtrl()->ShowPosition(position + richTextBuffer->GetOwnRange().GetEnd());
8024 delete richTextBuffer;
8025 }
8026 }
8027 else if (wxTheClipboard->IsSupported(wxDF_TEXT)
8028 #if wxUSE_UNICODE
8029 || wxTheClipboard->IsSupported(wxDF_UNICODETEXT)
8030 #endif
8031 )
8032 {
8033 wxTextDataObject data;
8034 wxTheClipboard->GetData(data);
8035 wxString text(data.GetText());
8036 #ifdef __WXMSW__
8037 wxString text2;
8038 text2.Alloc(text.Length()+1);
8039 size_t i;
8040 for (i = 0; i < text.Length(); i++)
8041 {
8042 wxChar ch = text[i];
8043 if (ch != wxT('\r'))
8044 text2 += ch;
8045 }
8046 #else
8047 wxString text2 = text;
8048 #endif
8049 container->InsertTextWithUndo(this, position+1, text2, GetRichTextCtrl(), wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE);
8050
8051 if (GetRichTextCtrl())
8052 GetRichTextCtrl()->ShowPosition(position + text2.Length());
8053
8054 success = true;
8055 }
8056 else if (wxTheClipboard->IsSupported(wxDF_BITMAP))
8057 {
8058 wxBitmapDataObject data;
8059 wxTheClipboard->GetData(data);
8060 wxBitmap bitmap(data.GetBitmap());
8061 wxImage image(bitmap.ConvertToImage());
8062
8063 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Image"), wxRICHTEXT_INSERT, this, container, GetRichTextCtrl(), false);
8064
8065 action->GetNewParagraphs().AddImage(image);
8066
8067 if (action->GetNewParagraphs().GetChildCount() == 1)
8068 action->GetNewParagraphs().SetPartialParagraph(true);
8069
8070 action->SetPosition(position+1);
8071
8072 // Set the range we'll need to delete in Undo
8073 action->SetRange(wxRichTextRange(position+1, position+1));
8074
8075 SubmitAction(action);
8076
8077 success = true;
8078 }
8079 wxTheClipboard->Close();
8080 }
8081 }
8082 #else
8083 wxUnusedVar(position);
8084 #endif
8085 return success;
8086 }
8087
8088 /// Can we paste from the clipboard?
8089 bool wxRichTextBuffer::CanPasteFromClipboard() const
8090 {
8091 bool canPaste = false;
8092 #if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
8093 if (!wxTheClipboard->IsOpened() && wxTheClipboard->Open())
8094 {
8095 if (wxTheClipboard->IsSupported(wxDF_TEXT)
8096 #if wxUSE_UNICODE
8097 || wxTheClipboard->IsSupported(wxDF_UNICODETEXT)
8098 #endif
8099 || wxTheClipboard->IsSupported(wxDataFormat(wxRichTextBufferDataObject::GetRichTextBufferFormatId())) ||
8100 wxTheClipboard->IsSupported(wxDF_BITMAP))
8101 {
8102 canPaste = true;
8103 }
8104 wxTheClipboard->Close();
8105 }
8106 #endif
8107 return canPaste;
8108 }
8109
8110 /// Dumps contents of buffer for debugging purposes
8111 void wxRichTextBuffer::Dump()
8112 {
8113 wxString text;
8114 {
8115 wxStringOutputStream stream(& text);
8116 wxTextOutputStream textStream(stream);
8117 Dump(textStream);
8118 }
8119
8120 wxLogDebug(text);
8121 }
8122
8123 /// Add an event handler
8124 bool wxRichTextBuffer::AddEventHandler(wxEvtHandler* handler)
8125 {
8126 m_eventHandlers.Append(handler);
8127 return true;
8128 }
8129
8130 /// Remove an event handler
8131 bool wxRichTextBuffer::RemoveEventHandler(wxEvtHandler* handler, bool deleteHandler)
8132 {
8133 wxList::compatibility_iterator node = m_eventHandlers.Find(handler);
8134 if (node)
8135 {
8136 m_eventHandlers.Erase(node);
8137 if (deleteHandler)
8138 delete handler;
8139
8140 return true;
8141 }
8142 else
8143 return false;
8144 }
8145
8146 /// Clear event handlers
8147 void wxRichTextBuffer::ClearEventHandlers()
8148 {
8149 m_eventHandlers.Clear();
8150 }
8151
8152 /// Send event to event handlers. If sendToAll is true, will send to all event handlers,
8153 /// otherwise will stop at the first successful one.
8154 bool wxRichTextBuffer::SendEvent(wxEvent& event, bool sendToAll)
8155 {
8156 bool success = false;
8157 for (wxList::compatibility_iterator node = m_eventHandlers.GetFirst(); node; node = node->GetNext())
8158 {
8159 wxEvtHandler* handler = (wxEvtHandler*) node->GetData();
8160 if (handler->ProcessEvent(event))
8161 {
8162 success = true;
8163 if (!sendToAll)
8164 return true;
8165 }
8166 }
8167 return success;
8168 }
8169
8170 /// Set style sheet and notify of the change
8171 bool wxRichTextBuffer::SetStyleSheetAndNotify(wxRichTextStyleSheet* sheet)
8172 {
8173 wxRichTextStyleSheet* oldSheet = GetStyleSheet();
8174
8175 wxWindowID winid = wxID_ANY;
8176 if (GetRichTextCtrl())
8177 winid = GetRichTextCtrl()->GetId();
8178
8179 wxRichTextEvent event(wxEVT_COMMAND_RICHTEXT_STYLESHEET_REPLACING, winid);
8180 event.SetEventObject(GetRichTextCtrl());
8181 event.SetContainer(GetRichTextCtrl()->GetFocusObject());
8182 event.SetOldStyleSheet(oldSheet);
8183 event.SetNewStyleSheet(sheet);
8184 event.Allow();
8185
8186 if (SendEvent(event) && !event.IsAllowed())
8187 {
8188 if (sheet != oldSheet)
8189 delete sheet;
8190
8191 return false;
8192 }
8193
8194 if (oldSheet && oldSheet != sheet)
8195 delete oldSheet;
8196
8197 SetStyleSheet(sheet);
8198
8199 event.SetEventType(wxEVT_COMMAND_RICHTEXT_STYLESHEET_REPLACED);
8200 event.SetOldStyleSheet(NULL);
8201 event.Allow();
8202
8203 return SendEvent(event);
8204 }
8205
8206 /// Set renderer, deleting old one
8207 void wxRichTextBuffer::SetRenderer(wxRichTextRenderer* renderer)
8208 {
8209 if (sm_renderer)
8210 delete sm_renderer;
8211 sm_renderer = renderer;
8212 }
8213
8214 /// Hit-testing: returns a flag indicating hit test details, plus
8215 /// information about position
8216 int wxRichTextBuffer::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int flags)
8217 {
8218 int ret = wxRichTextParagraphLayoutBox::HitTest(dc, context, pt, textPosition, obj, contextObj, flags);
8219 if (ret != wxRICHTEXT_HITTEST_NONE)
8220 {
8221 return ret;
8222 }
8223 else
8224 {
8225 textPosition = m_ownRange.GetEnd()-1;
8226 *obj = this;
8227 *contextObj = this;
8228 return wxRICHTEXT_HITTEST_AFTER|wxRICHTEXT_HITTEST_OUTSIDE;
8229 }
8230 }
8231
8232 void wxRichTextBuffer::SetFontScale(double fontScale)
8233 {
8234 m_fontScale = fontScale;
8235 m_fontTable.SetFontScale(fontScale);
8236 }
8237
8238 void wxRichTextBuffer::SetDimensionScale(double dimScale)
8239 {
8240 m_dimensionScale = dimScale;
8241 }
8242
8243 bool wxRichTextStdRenderer::DrawStandardBullet(wxRichTextParagraph* paragraph, wxDC& dc, const wxRichTextAttr& bulletAttr, const wxRect& rect)
8244 {
8245 if (bulletAttr.GetTextColour().IsOk())
8246 {
8247 wxCheckSetPen(dc, wxPen(bulletAttr.GetTextColour()));
8248 wxCheckSetBrush(dc, wxBrush(bulletAttr.GetTextColour()));
8249 }
8250 else
8251 {
8252 wxCheckSetPen(dc, *wxBLACK_PEN);
8253 wxCheckSetBrush(dc, *wxBLACK_BRUSH);
8254 }
8255
8256 wxFont font;
8257 if (bulletAttr.HasFont())
8258 {
8259 font = paragraph->GetBuffer()->GetFontTable().FindFont(bulletAttr);
8260 }
8261 else
8262 font = (*wxNORMAL_FONT);
8263
8264 wxCheckSetFont(dc, font);
8265
8266 int charHeight = dc.GetCharHeight();
8267
8268 int bulletWidth = (int) (((float) charHeight) * wxRichTextBuffer::GetBulletProportion());
8269 int bulletHeight = bulletWidth;
8270
8271 int x = rect.x;
8272
8273 // Calculate the top position of the character (as opposed to the whole line height)
8274 int y = rect.y + (rect.height - charHeight);
8275
8276 // Calculate where the bullet should be positioned
8277 y = y + (charHeight+1)/2 - (bulletHeight+1)/2;
8278
8279 // The margin between a bullet and text.
8280 int margin = paragraph->ConvertTenthsMMToPixels(dc, wxRichTextBuffer::GetBulletRightMargin());
8281
8282 if (bulletAttr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_RIGHT)
8283 x = rect.x + rect.width - bulletWidth - margin;
8284 else if (bulletAttr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_CENTRE)
8285 x = x + (rect.width)/2 - bulletWidth/2;
8286
8287 if (bulletAttr.GetBulletName() == wxT("standard/square"))
8288 {
8289 dc.DrawRectangle(x, y, bulletWidth, bulletHeight);
8290 }
8291 else if (bulletAttr.GetBulletName() == wxT("standard/diamond"))
8292 {
8293 wxPoint pts[5];
8294 pts[0].x = x; pts[0].y = y + bulletHeight/2;
8295 pts[1].x = x + bulletWidth/2; pts[1].y = y;
8296 pts[2].x = x + bulletWidth; pts[2].y = y + bulletHeight/2;
8297 pts[3].x = x + bulletWidth/2; pts[3].y = y + bulletHeight;
8298
8299 dc.DrawPolygon(4, pts);
8300 }
8301 else if (bulletAttr.GetBulletName() == wxT("standard/triangle"))
8302 {
8303 wxPoint pts[3];
8304 pts[0].x = x; pts[0].y = y;
8305 pts[1].x = x + bulletWidth; pts[1].y = y + bulletHeight/2;
8306 pts[2].x = x; pts[2].y = y + bulletHeight;
8307
8308 dc.DrawPolygon(3, pts);
8309 }
8310 else if (bulletAttr.GetBulletName() == wxT("standard/circle-outline"))
8311 {
8312 wxCheckSetBrush(dc, *wxTRANSPARENT_BRUSH);
8313 dc.DrawEllipse(x, y, bulletWidth, bulletHeight);
8314 }
8315 else // "standard/circle", and catch-all
8316 {
8317 dc.DrawEllipse(x, y, bulletWidth, bulletHeight);
8318 }
8319
8320 return true;
8321 }
8322
8323 bool wxRichTextStdRenderer::DrawTextBullet(wxRichTextParagraph* paragraph, wxDC& dc, const wxRichTextAttr& attr, const wxRect& rect, const wxString& text)
8324 {
8325 if (!text.empty())
8326 {
8327 wxFont font;
8328 if ((attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_SYMBOL) && !attr.GetBulletFont().IsEmpty() && attr.HasFont())
8329 {
8330 wxRichTextAttr fontAttr;
8331 if (attr.HasFontPixelSize())
8332 fontAttr.SetFontPixelSize(attr.GetFontSize());
8333 else
8334 fontAttr.SetFontPointSize(attr.GetFontSize());
8335 fontAttr.SetFontStyle(attr.GetFontStyle());
8336 fontAttr.SetFontWeight(attr.GetFontWeight());
8337 fontAttr.SetFontUnderlined(attr.GetFontUnderlined());
8338 fontAttr.SetFontFaceName(attr.GetBulletFont());
8339 font = paragraph->GetBuffer()->GetFontTable().FindFont(fontAttr);
8340 }
8341 else if (attr.HasFont())
8342 font = paragraph->GetBuffer()->GetFontTable().FindFont(attr);
8343 else
8344 font = (*wxNORMAL_FONT);
8345
8346 wxCheckSetFont(dc, font);
8347
8348 if (attr.GetTextColour().IsOk())
8349 dc.SetTextForeground(attr.GetTextColour());
8350
8351 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
8352
8353 int charHeight = dc.GetCharHeight();
8354 wxCoord tw, th;
8355 dc.GetTextExtent(text, & tw, & th);
8356
8357 int x = rect.x;
8358
8359 // Calculate the top position of the character (as opposed to the whole line height)
8360 int y = rect.y + (rect.height - charHeight);
8361
8362 // The margin between a bullet and text.
8363 int margin = paragraph->ConvertTenthsMMToPixels(dc, wxRichTextBuffer::GetBulletRightMargin());
8364
8365 if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_RIGHT)
8366 x = (rect.x + rect.width) - tw - margin;
8367 else if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_CENTRE)
8368 x = x + (rect.width)/2 - tw/2;
8369
8370 dc.DrawText(text, x, y);
8371
8372 return true;
8373 }
8374 else
8375 return false;
8376 }
8377
8378 bool wxRichTextStdRenderer::DrawBitmapBullet(wxRichTextParagraph* WXUNUSED(paragraph), wxDC& WXUNUSED(dc), const wxRichTextAttr& WXUNUSED(attr), const wxRect& WXUNUSED(rect))
8379 {
8380 // Currently unimplemented. The intention is to store bitmaps by name in a media store associated
8381 // with the buffer. The store will allow retrieval from memory, disk or other means.
8382 return false;
8383 }
8384
8385 /// Enumerate the standard bullet names currently supported
8386 bool wxRichTextStdRenderer::EnumerateStandardBulletNames(wxArrayString& bulletNames)
8387 {
8388 bulletNames.Add(wxTRANSLATE("standard/circle"));
8389 bulletNames.Add(wxTRANSLATE("standard/circle-outline"));
8390 bulletNames.Add(wxTRANSLATE("standard/square"));
8391 bulletNames.Add(wxTRANSLATE("standard/diamond"));
8392 bulletNames.Add(wxTRANSLATE("standard/triangle"));
8393
8394 return true;
8395 }
8396
8397 /*!
8398 * wxRichTextBox
8399 */
8400
8401 IMPLEMENT_DYNAMIC_CLASS(wxRichTextBox, wxRichTextParagraphLayoutBox)
8402
8403 wxRichTextBox::wxRichTextBox(wxRichTextObject* parent):
8404 wxRichTextParagraphLayoutBox(parent)
8405 {
8406 }
8407
8408 /// Draw the item
8409 bool wxRichTextBox::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
8410 {
8411 if (!IsShown())
8412 return true;
8413
8414 // TODO: if the active object in the control, draw an indication.
8415 // We need to add the concept of active object, and not just focus object,
8416 // so we can apply commands (properties, delete, ...) to objects such as text boxes and images.
8417 // Ultimately we would like to be able to interactively resize an active object
8418 // using drag handles.
8419 return wxRichTextParagraphLayoutBox::Draw(dc, context, range, selection, rect, descent, style);
8420 }
8421
8422 /// Copy
8423 void wxRichTextBox::Copy(const wxRichTextBox& obj)
8424 {
8425 wxRichTextParagraphLayoutBox::Copy(obj);
8426 }
8427
8428 // Edit properties via a GUI
8429 bool wxRichTextBox::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer)
8430 {
8431 wxRichTextObjectPropertiesDialog boxDlg(this, wxGetTopLevelParent(parent), wxID_ANY, _("Box Properties"));
8432 boxDlg.SetAttributes(GetAttributes());
8433
8434 if (boxDlg.ShowModal() == wxID_OK)
8435 {
8436 // By passing wxRICHTEXT_SETSTYLE_RESET, indeterminate attributes set by the user will be set as
8437 // indeterminate in the object.
8438 boxDlg.ApplyStyle(buffer->GetRichTextCtrl(), wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_RESET);
8439 return true;
8440 }
8441 else
8442 return false;
8443 }
8444
8445 /*!
8446 * wxRichTextField
8447 */
8448
8449 IMPLEMENT_DYNAMIC_CLASS(wxRichTextField, wxRichTextParagraphLayoutBox)
8450
8451 wxRichTextField::wxRichTextField(const wxString& fieldType, wxRichTextObject* parent):
8452 wxRichTextParagraphLayoutBox(parent)
8453 {
8454 SetFieldType(fieldType);
8455 }
8456
8457 /// Draw the item
8458 bool wxRichTextField::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
8459 {
8460 if (!IsShown())
8461 return true;
8462
8463 wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType());
8464 if (fieldType && fieldType->Draw(this, dc, context, range, selection, rect, descent, style))
8465 return true;
8466
8467 // Fallback; but don't draw guidelines.
8468 style &= ~wxRICHTEXT_DRAW_GUIDELINES;
8469 return wxRichTextParagraphLayoutBox::Draw(dc, context, range, selection, rect, descent, style);
8470 }
8471
8472 bool wxRichTextField::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& parentRect, int style)
8473 {
8474 wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType());
8475 if (fieldType && fieldType->Layout(this, dc, context, rect, parentRect, style))
8476 return true;
8477
8478 // Fallback
8479 return wxRichTextParagraphLayoutBox::Layout(dc, context, rect, parentRect, style);
8480 }
8481
8482 bool wxRichTextField::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, wxPoint position, wxArrayInt* partialExtents) const
8483 {
8484 wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType());
8485 if (fieldType)
8486 return fieldType->GetRangeSize((wxRichTextField*) this, range, size, descent, dc, context, flags, position, partialExtents);
8487
8488 return wxRichTextParagraphLayoutBox::GetRangeSize(range, size, descent, dc, context, flags, position, partialExtents);
8489 }
8490
8491 /// Calculate range
8492 void wxRichTextField::CalculateRange(long start, long& end)
8493 {
8494 if (IsTopLevel())
8495 wxRichTextParagraphLayoutBox::CalculateRange(start, end);
8496 else
8497 wxRichTextObject::CalculateRange(start, end);
8498 }
8499
8500 /// Copy
8501 void wxRichTextField::Copy(const wxRichTextField& obj)
8502 {
8503 wxRichTextParagraphLayoutBox::Copy(obj);
8504
8505 UpdateField(GetBuffer());
8506 }
8507
8508 // Edit properties via a GUI
8509 bool wxRichTextField::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer)
8510 {
8511 wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType());
8512 if (fieldType)
8513 return fieldType->EditProperties(this, parent, buffer);
8514
8515 return false;
8516 }
8517
8518 bool wxRichTextField::CanEditProperties() const
8519 {
8520 wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType());
8521 if (fieldType)
8522 return fieldType->CanEditProperties((wxRichTextField*) this);
8523
8524 return false;
8525 }
8526
8527 wxString wxRichTextField::GetPropertiesMenuLabel() const
8528 {
8529 wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType());
8530 if (fieldType)
8531 return fieldType->GetPropertiesMenuLabel((wxRichTextField*) this);
8532
8533 return wxEmptyString;
8534 }
8535
8536 bool wxRichTextField::UpdateField(wxRichTextBuffer* buffer)
8537 {
8538 wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType());
8539 if (fieldType)
8540 return fieldType->UpdateField(buffer, (wxRichTextField*) this);
8541
8542 return false;
8543 }
8544
8545 bool wxRichTextField::IsTopLevel() const
8546 {
8547 wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType());
8548 if (fieldType)
8549 return fieldType->IsTopLevel((wxRichTextField*) this);
8550
8551 return true;
8552 }
8553
8554 IMPLEMENT_CLASS(wxRichTextFieldType, wxObject)
8555
8556 IMPLEMENT_CLASS(wxRichTextFieldTypeStandard, wxRichTextFieldType)
8557
8558 wxRichTextFieldTypeStandard::wxRichTextFieldTypeStandard(const wxString& name, const wxString& label, int displayStyle)
8559 {
8560 Init();
8561
8562 SetName(name);
8563 SetLabel(label);
8564 SetDisplayStyle(displayStyle);
8565 }
8566
8567 wxRichTextFieldTypeStandard::wxRichTextFieldTypeStandard(const wxString& name, const wxBitmap& bitmap, int displayStyle)
8568 {
8569 Init();
8570
8571 SetName(name);
8572 SetBitmap(bitmap);
8573 SetDisplayStyle(displayStyle);
8574 }
8575
8576 void wxRichTextFieldTypeStandard::Init()
8577 {
8578 m_displayStyle = wxRICHTEXT_FIELD_STYLE_RECTANGLE;
8579 m_font = wxFont(6, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
8580 m_textColour = *wxWHITE;
8581 m_borderColour = *wxBLACK;
8582 m_backgroundColour = *wxBLACK;
8583 m_verticalPadding = 1;
8584 m_horizontalPadding = 3;
8585 m_horizontalMargin = 2;
8586 m_verticalMargin = 0;
8587 }
8588
8589 void wxRichTextFieldTypeStandard::Copy(const wxRichTextFieldTypeStandard& field)
8590 {
8591 wxRichTextFieldType::Copy(field);
8592
8593 m_label = field.m_label;
8594 m_displayStyle = field.m_displayStyle;
8595 m_font = field.m_font;
8596 m_textColour = field.m_textColour;
8597 m_borderColour = field.m_borderColour;
8598 m_backgroundColour = field.m_backgroundColour;
8599 m_verticalPadding = field.m_verticalPadding;
8600 m_horizontalPadding = field.m_horizontalPadding;
8601 m_horizontalMargin = field.m_horizontalMargin;
8602 m_bitmap = field.m_bitmap;
8603 }
8604
8605 bool wxRichTextFieldTypeStandard::Draw(wxRichTextField* obj, wxDC& dc, wxRichTextDrawingContext& WXUNUSED(context), const wxRichTextRange& WXUNUSED(range), const wxRichTextSelection& selection, const wxRect& rect, int descent, int WXUNUSED(style))
8606 {
8607 if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_COMPOSITE)
8608 return false; // USe default composite drawing
8609 else // if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_RECTANGLE || m_displayStyle == wxRICHTEXT_FIELD_STYLE_NOBORDER)
8610 {
8611 int borderSize = 1;
8612
8613 wxPen borderPen(m_borderColour, 1, wxSOLID);
8614 wxBrush backgroundBrush(m_backgroundColour);
8615 wxColour textColour(m_textColour);
8616
8617 if (selection.WithinSelection(obj->GetRange().GetStart(), obj))
8618 {
8619 wxColour highlightColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT));
8620 wxColour highlightTextColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT));
8621
8622 borderPen = wxPen(highlightTextColour, 1, wxSOLID);
8623 backgroundBrush = wxBrush(highlightColour);
8624
8625 wxCheckSetBrush(dc, backgroundBrush);
8626 wxCheckSetPen(dc, wxPen(highlightColour, 1, wxSOLID));
8627 dc.DrawRectangle(rect);
8628 }
8629
8630 if (m_displayStyle != wxRICHTEXT_FIELD_STYLE_NO_BORDER)
8631 borderSize = 0;
8632
8633 // objectRect is the area where the content is drawn, after margins around it have been taken into account
8634 wxRect objectRect = wxRect(wxPoint(rect.x + m_horizontalMargin, rect.y + wxMax(0, rect.height - descent - obj->GetCachedSize().y)),
8635 wxSize(obj->GetCachedSize().x - 2*m_horizontalMargin - borderSize, obj->GetCachedSize().y));
8636
8637 // clientArea is where the text is actually written
8638 wxRect clientArea = objectRect;
8639
8640 if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_RECTANGLE)
8641 {
8642 dc.SetPen(borderPen);
8643 dc.SetBrush(backgroundBrush);
8644 dc.DrawRoundedRectangle(objectRect, 4.0);
8645 }
8646 else if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_START_TAG)
8647 {
8648 int arrowLength = objectRect.height/2;
8649 clientArea.width -= (arrowLength - m_horizontalPadding);
8650
8651 wxPoint pts[5];
8652 pts[0].x = objectRect.x; pts[0].y = objectRect.y;
8653 pts[1].x = objectRect.x + objectRect.width - arrowLength; pts[1].y = objectRect.y;
8654 pts[2].x = objectRect.x + objectRect.width; pts[2].y = objectRect.y + (objectRect.height/2);
8655 pts[3].x = objectRect.x + objectRect.width - arrowLength; pts[3].y = objectRect.y + objectRect.height;
8656 pts[4].x = objectRect.x; pts[4].y = objectRect.y + objectRect.height;
8657 dc.SetPen(borderPen);
8658 dc.SetBrush(backgroundBrush);
8659 dc.DrawPolygon(5, pts);
8660 }
8661 else if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_END_TAG)
8662 {
8663 int arrowLength = objectRect.height/2;
8664 clientArea.width -= (arrowLength - m_horizontalPadding);
8665 clientArea.x += (arrowLength - m_horizontalPadding);
8666
8667 wxPoint pts[5];
8668 pts[0].x = objectRect.x + objectRect.width; pts[0].y = objectRect.y;
8669 pts[1].x = objectRect.x + arrowLength; pts[1].y = objectRect.y;
8670 pts[2].x = objectRect.x; pts[2].y = objectRect.y + (objectRect.height/2);
8671 pts[3].x = objectRect.x + arrowLength; pts[3].y = objectRect.y + objectRect.height;
8672 pts[4].x = objectRect.x + objectRect.width; pts[4].y = objectRect.y + objectRect.height;
8673 dc.SetPen(borderPen);
8674 dc.SetBrush(backgroundBrush);
8675 dc.DrawPolygon(5, pts);
8676 }
8677
8678 if (m_bitmap.IsOk())
8679 {
8680 int x = clientArea.x + (clientArea.width - m_bitmap.GetWidth())/2;
8681 int y = clientArea.y + m_verticalPadding;
8682 dc.DrawBitmap(m_bitmap, x, y, true);
8683
8684 if (selection.WithinSelection(obj->GetRange().GetStart(), obj))
8685 {
8686 wxCheckSetBrush(dc, *wxBLACK_BRUSH);
8687 wxCheckSetPen(dc, *wxBLACK_PEN);
8688 dc.SetLogicalFunction(wxINVERT);
8689 dc.DrawRectangle(wxRect(x, y, m_bitmap.GetWidth(), m_bitmap.GetHeight()));
8690 dc.SetLogicalFunction(wxCOPY);
8691 }
8692 }
8693 else
8694 {
8695 wxString label(m_label);
8696 if (label.IsEmpty())
8697 label = wxT("??");
8698 int w, h, maxDescent;
8699 dc.SetFont(m_font);
8700 dc.GetTextExtent(m_label, & w, &h, & maxDescent);
8701 dc.SetTextForeground(textColour);
8702
8703 int x = clientArea.x + (clientArea.width - w)/2;
8704 int y = clientArea.y + (clientArea.height - (h - maxDescent))/2;
8705 dc.DrawText(m_label, x, y);
8706 }
8707 }
8708
8709 return true;
8710 }
8711
8712 bool wxRichTextFieldTypeStandard::Layout(wxRichTextField* obj, wxDC& dc, wxRichTextDrawingContext& context, const wxRect& WXUNUSED(rect), const wxRect& WXUNUSED(parentRect), int style)
8713 {
8714 if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_COMPOSITE)
8715 return false; // USe default composite layout
8716
8717 wxSize size = GetSize(obj, dc, context, style);
8718 obj->SetCachedSize(size);
8719 obj->SetMinSize(size);
8720 obj->SetMaxSize(size);
8721 return true;
8722 }
8723
8724 bool wxRichTextFieldTypeStandard::GetRangeSize(wxRichTextField* obj, const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, wxPoint position, wxArrayInt* partialExtents) const
8725 {
8726 if (IsTopLevel(obj))
8727 return obj->wxRichTextParagraphLayoutBox::GetRangeSize(range, size, descent, dc, context, flags, position);
8728 else
8729 {
8730 wxSize sz = GetSize(obj, dc, context, 0);
8731 if (partialExtents)
8732 {
8733 int lastSize;
8734 if (partialExtents->GetCount() > 0)
8735 lastSize = (*partialExtents)[partialExtents->GetCount()-1];
8736 else
8737 lastSize = 0;
8738 partialExtents->Add(lastSize + sz.x);
8739 }
8740 size = sz;
8741 return true;
8742 }
8743 }
8744
8745 wxSize wxRichTextFieldTypeStandard::GetSize(wxRichTextField* WXUNUSED(obj), wxDC& dc, wxRichTextDrawingContext& WXUNUSED(context), int WXUNUSED(style)) const
8746 {
8747 int borderSize = 1;
8748 int w = 0, h = 0, maxDescent = 0;
8749
8750 wxSize sz;
8751 if (m_bitmap.IsOk())
8752 {
8753 w = m_bitmap.GetWidth();
8754 h = m_bitmap.GetHeight();
8755
8756 sz = wxSize(w + m_horizontalMargin*2, h + m_verticalMargin*2);
8757 }
8758 else
8759 {
8760 wxString label(m_label);
8761 if (label.IsEmpty())
8762 label = wxT("??");
8763 dc.SetFont(m_font);
8764 dc.GetTextExtent(label, & w, &h, & maxDescent);
8765
8766 sz = wxSize(w + m_horizontalPadding*2 + m_horizontalMargin*2, h + m_verticalPadding *2 + m_verticalMargin*2);
8767 }
8768
8769 if (m_displayStyle != wxRICHTEXT_FIELD_STYLE_NO_BORDER)
8770 {
8771 sz.x += borderSize*2;
8772 sz.y += borderSize*2;
8773 }
8774
8775 if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_START_TAG || m_displayStyle == wxRICHTEXT_FIELD_STYLE_END_TAG)
8776 {
8777 // Add space for the arrow
8778 sz.x += (sz.y/2 - m_horizontalPadding);
8779 }
8780
8781 return sz;
8782 }
8783
8784 IMPLEMENT_DYNAMIC_CLASS(wxRichTextCell, wxRichTextBox)
8785
8786 wxRichTextCell::wxRichTextCell(wxRichTextObject* parent):
8787 wxRichTextBox(parent)
8788 {
8789 }
8790
8791 /// Draw the item
8792 bool wxRichTextCell::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
8793 {
8794 return wxRichTextBox::Draw(dc, context, range, selection, rect, descent, style);
8795 }
8796
8797 /// Copy
8798 void wxRichTextCell::Copy(const wxRichTextCell& obj)
8799 {
8800 wxRichTextBox::Copy(obj);
8801 }
8802
8803 // Edit properties via a GUI
8804 bool wxRichTextCell::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer)
8805 {
8806 // We need to gather common attributes for all selected cells.
8807
8808 wxRichTextTable* table = wxDynamicCast(GetParent(), wxRichTextTable);
8809 bool multipleCells = false;
8810 wxRichTextAttr attr;
8811
8812 if (table && buffer && buffer->GetRichTextCtrl() && buffer->GetRichTextCtrl()->GetSelection().IsValid() &&
8813 buffer->GetRichTextCtrl()->GetSelection().GetContainer() == GetParent())
8814 {
8815 wxRichTextAttr clashingAttr, absentAttr;
8816 const wxRichTextSelection& sel = buffer->GetRichTextCtrl()->GetSelection();
8817 size_t i;
8818 int selectedCellCount = 0;
8819 for (i = 0; i < sel.GetCount(); i++)
8820 {
8821 const wxRichTextRange& range = sel[i];
8822 wxRichTextCell* cell = table->GetCell(range.GetStart());
8823 if (cell)
8824 {
8825 wxRichTextAttr cellStyle = cell->GetAttributes();
8826
8827 CollectStyle(attr, cellStyle, clashingAttr, absentAttr);
8828
8829 selectedCellCount ++;
8830 }
8831 }
8832 multipleCells = selectedCellCount > 1;
8833 }
8834 else
8835 {
8836 attr = GetAttributes();
8837 }
8838
8839 wxString caption;
8840 if (multipleCells)
8841 caption = _("Multiple Cell Properties");
8842 else
8843 caption = _("Cell Properties");
8844
8845 wxRichTextObjectPropertiesDialog cellDlg(this, wxGetTopLevelParent(parent), wxID_ANY, caption);
8846 cellDlg.SetAttributes(attr);
8847
8848 wxRichTextSizePage* sizePage = wxDynamicCast(cellDlg.FindPage(wxCLASSINFO(wxRichTextSizePage)), wxRichTextSizePage);
8849 if (sizePage)
8850 {
8851 // We don't want position and floating controls for a cell.
8852 sizePage->ShowPositionControls(false);
8853 sizePage->ShowFloatingControls(false);
8854 }
8855
8856 if (cellDlg.ShowModal() == wxID_OK)
8857 {
8858 if (multipleCells)
8859 {
8860 const wxRichTextSelection& sel = buffer->GetRichTextCtrl()->GetSelection();
8861 // Apply the style; we interpret indeterminate attributes as 'don't touch this attribute'
8862 // since it may represent clashing attributes across multiple objects.
8863 table->SetCellStyle(sel, attr);
8864 }
8865 else
8866 // For a single object, indeterminate attributes set by the user should be reflected in the
8867 // actual object style, so pass the wxRICHTEXT_SETSTYLE_RESET flag to assign
8868 // the style directly instead of applying (which ignores indeterminate attributes,
8869 // leaving them as they were).
8870 cellDlg.ApplyStyle(buffer->GetRichTextCtrl(), wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_RESET);
8871 return true;
8872 }
8873 else
8874 return false;
8875 }
8876
8877 WX_DEFINE_OBJARRAY(wxRichTextObjectPtrArrayArray)
8878
8879 IMPLEMENT_DYNAMIC_CLASS(wxRichTextTable, wxRichTextBox)
8880
8881 wxRichTextTable::wxRichTextTable(wxRichTextObject* parent): wxRichTextBox(parent)
8882 {
8883 m_rowCount = 0;
8884 m_colCount = 0;
8885 }
8886
8887 // Draws the object.
8888 bool wxRichTextTable::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
8889 {
8890 return wxRichTextBox::Draw(dc, context, range, selection, rect, descent, style);
8891 }
8892
8893 WX_DECLARE_OBJARRAY(wxRect, wxRichTextRectArray);
8894 WX_DEFINE_OBJARRAY(wxRichTextRectArray);
8895
8896 // Lays the object out. rect is the space available for layout. Often it will
8897 // be the specified overall space for this object, if trying to constrain
8898 // layout to a particular size, or it could be the total space available in the
8899 // parent. rect is the overall size, so we must subtract margins and padding.
8900 // to get the actual available space.
8901 bool wxRichTextTable::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& WXUNUSED(parentRect), int style)
8902 {
8903 SetPosition(rect.GetPosition());
8904
8905 // TODO: the meaty bit. Calculate sizes of all cells and rows. Try to use
8906 // minimum size if within alloted size, then divide up remaining size
8907 // between rows/cols.
8908
8909 double scale = 1.0;
8910 wxRichTextBuffer* buffer = GetBuffer();
8911 if (buffer) scale = buffer->GetScale();
8912
8913 wxRect availableSpace = GetAvailableContentArea(dc, context, rect);
8914 wxTextAttrDimensionConverter converter(dc, scale, availableSpace.GetSize());
8915
8916 wxRichTextAttr attr(GetAttributes());
8917 context.ApplyVirtualAttributes(attr, this);
8918
8919 // If we have no fixed table size, and assuming we're not pushed for
8920 // space, then we don't have to try to stretch the table to fit the contents.
8921 bool stretchToFitTableWidth = false;
8922
8923 int tableWidth = rect.width;
8924 if (attr.GetTextBoxAttr().GetWidth().IsValid())
8925 {
8926 tableWidth = converter.GetPixels(attr.GetTextBoxAttr().GetWidth());
8927
8928 // Fixed table width, so we do want to stretch columns out if necessary.
8929 stretchToFitTableWidth = true;
8930
8931 // Shouldn't be able to exceed the size passed to this function
8932 tableWidth = wxMin(rect.width, tableWidth);
8933 }
8934
8935 // Get internal padding
8936 int paddingLeft = 0, paddingTop = 0;
8937 if (attr.GetTextBoxAttr().GetPadding().GetLeft().IsValid())
8938 paddingLeft = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetLeft());
8939 if (attr.GetTextBoxAttr().GetPadding().GetTop().IsValid())
8940 paddingTop = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetTop());
8941
8942 // Assume that left and top padding are also used for inter-cell padding.
8943 int paddingX = paddingLeft;
8944 int paddingY = paddingTop;
8945
8946 int totalLeftMargin = 0, totalRightMargin = 0, totalTopMargin = 0, totalBottomMargin = 0;
8947 GetTotalMargin(dc, buffer, attr, totalLeftMargin, totalRightMargin, totalTopMargin, totalBottomMargin);
8948
8949 // Internal table width - the area for content
8950 int internalTableWidth = tableWidth - totalLeftMargin - totalRightMargin;
8951
8952 int rowCount = m_cells.GetCount();
8953 if (m_colCount == 0 || rowCount == 0)
8954 {
8955 wxRect overallRect(rect.x, rect.y, totalLeftMargin + totalRightMargin, totalTopMargin + totalBottomMargin);
8956 SetCachedSize(overallRect.GetSize());
8957
8958 // Zero content size
8959 SetMinSize(overallRect.GetSize());
8960 SetMaxSize(GetMinSize());
8961 return true;
8962 }
8963
8964 // The final calculated widths
8965 wxArrayInt colWidths;
8966 colWidths.Add(0, m_colCount);
8967
8968 wxArrayInt absoluteColWidths;
8969 absoluteColWidths.Add(0, m_colCount);
8970
8971 wxArrayInt percentageColWidths;
8972 percentageColWidths.Add(0, m_colCount);
8973 // wxArrayInt percentageColWidthsSpanning(m_colCount);
8974 // These are only relevant when the first column contains spanning information.
8975 // wxArrayInt columnSpans(m_colCount); // Each contains 1 for non-spanning cell, > 1 for spanning cell.
8976 wxArrayInt maxColWidths;
8977 maxColWidths.Add(0, m_colCount);
8978 wxArrayInt minColWidths;
8979 minColWidths.Add(0, m_colCount);
8980
8981 wxSize tableSize(tableWidth, 0);
8982
8983 int i, j, k;
8984
8985 for (i = 0; i < m_colCount; i++)
8986 {
8987 absoluteColWidths[i] = 0;
8988 // absoluteColWidthsSpanning[i] = 0;
8989 percentageColWidths[i] = -1;
8990 // percentageColWidthsSpanning[i] = -1;
8991 colWidths[i] = 0;
8992 maxColWidths[i] = 0;
8993 minColWidths[i] = 0;
8994 // columnSpans[i] = 1;
8995 }
8996
8997 // (0) Determine which cells are visible according to spans
8998 // 1 2 3 4 5
8999 // __________________
9000 // | | | | | 1
9001 // |------| |----|
9002 // |------| | | 2
9003 // |------| | | 3
9004 // |------------------|
9005 // |__________________| 4
9006
9007 // To calculate cell visibility:
9008 // First find all spanning cells. Build an array of span records with start x, y and end x, y.
9009 // Then for each cell, test whether we're within one of those cells, and unless we're at the start of
9010 // that cell, hide the cell.
9011
9012 // We can also use this array to match the size of spanning cells to the grid. Or just do
9013 // this when we iterate through all cells.
9014
9015 // 0.1: add spanning cells to an array
9016 wxRichTextRectArray rectArray;
9017 for (j = 0; j < m_rowCount; j++)
9018 {
9019 for (i = 0; i < m_colCount; i++)
9020 {
9021 wxRichTextBox* cell = GetCell(j, i);
9022 int colSpan = 1, rowSpan = 1;
9023 if (cell->GetProperties().HasProperty(wxT("colspan")))
9024 colSpan = cell->GetProperties().GetPropertyLong(wxT("colspan"));
9025 if (cell->GetProperties().HasProperty(wxT("rowspan")))
9026 rowSpan = cell->GetProperties().GetPropertyLong(wxT("rowspan"));
9027 if (colSpan > 1 || rowSpan > 1)
9028 {
9029 rectArray.Add(wxRect(i, j, colSpan, rowSpan));
9030 }
9031 }
9032 }
9033 // 0.2: find which cells are subsumed by a spanning cell
9034 for (j = 0; j < m_rowCount; j++)
9035 {
9036 for (i = 0; i < m_colCount; i++)
9037 {
9038 wxRichTextBox* cell = GetCell(j, i);
9039 if (rectArray.GetCount() == 0)
9040 {
9041 cell->Show(true);
9042 }
9043 else
9044 {
9045 int colSpan = 1, rowSpan = 1;
9046 if (cell->GetProperties().HasProperty(wxT("colspan")))
9047 colSpan = cell->GetProperties().GetPropertyLong(wxT("colspan"));
9048 if (cell->GetProperties().HasProperty(wxT("rowspan")))
9049 rowSpan = cell->GetProperties().GetPropertyLong(wxT("rowspan"));
9050 if (colSpan > 1 || rowSpan > 1)
9051 {
9052 // Assume all spanning cells are shown
9053 cell->Show(true);
9054 }
9055 else
9056 {
9057 bool shown = true;
9058 for (k = 0; k < (int) rectArray.GetCount(); k++)
9059 {
9060 if (rectArray[k].Contains(wxPoint(i, j)))
9061 {
9062 shown = false;
9063 break;
9064 }
9065 }
9066 cell->Show(shown);
9067 }
9068 }
9069 }
9070 }
9071
9072 // TODO: find the first spanned cell in each row that spans the most columns and doesn't
9073 // overlap with a spanned cell starting at a previous column position.
9074 // This means we need to keep an array of rects so we can check. However
9075 // it does also mean that some spans simply may not be taken into account
9076 // where there are different spans happening on different rows. In these cases,
9077 // they will simply be as wide as their constituent columns.
9078
9079 // (1) Do an initial layout for all cells to get minimum and maximum size, and get
9080 // the absolute or percentage width of each column.
9081
9082 for (j = 0; j < m_rowCount; j++)
9083 {
9084 // First get the overall margins so we can calculate percentage widths based on
9085 // the available content space for all cells on the row
9086
9087 int overallRowContentMargin = 0;
9088 int visibleCellCount = 0;
9089
9090 for (i = 0; i < m_colCount; i++)
9091 {
9092 wxRichTextBox* cell = GetCell(j, i);
9093 if (cell->IsShown())
9094 {
9095 int cellTotalLeftMargin = 0, cellTotalRightMargin = 0, cellTotalTopMargin = 0, cellTotalBottomMargin = 0;
9096 GetTotalMargin(dc, buffer, cell->GetAttributes(), cellTotalLeftMargin, cellTotalRightMargin, cellTotalTopMargin, cellTotalBottomMargin);
9097
9098 overallRowContentMargin += (cellTotalLeftMargin + cellTotalRightMargin);
9099 visibleCellCount ++;
9100 }
9101 }
9102
9103 // Add in inter-cell padding
9104 overallRowContentMargin += ((visibleCellCount-1) * paddingX);
9105
9106 int rowContentWidth = internalTableWidth - overallRowContentMargin;
9107 wxSize rowTableSize(rowContentWidth, 0);
9108 wxTextAttrDimensionConverter converter(dc, scale, rowTableSize);
9109
9110 for (i = 0; i < m_colCount; i++)
9111 {
9112 wxRichTextBox* cell = GetCell(j, i);
9113 if (cell->IsShown())
9114 {
9115 int colSpan = 1;
9116 if (cell->GetProperties().HasProperty(wxT("colspan")))
9117 colSpan = cell->GetProperties().GetPropertyLong(wxT("colspan"));
9118
9119 // Lay out cell to find min/max widths
9120 cell->Invalidate(wxRICHTEXT_ALL);
9121 cell->Layout(dc, context, availableSpace, availableSpace, style);
9122
9123 if (colSpan == 1)
9124 {
9125 int absoluteCellWidth = -1;
9126 int percentageCellWidth = -1;
9127
9128 // I think we need to calculate percentages from the internal table size,
9129 // minus the padding between cells which we'll need to calculate from the
9130 // (number of VISIBLE cells - 1)*paddingX. Then percentages that add up to 100%
9131 // will add up to 100%. In CSS, the width specifies the cell's content rect width,
9132 // so if we want to conform to that we'll need to add in the overall cell margins.
9133 // However, this will make it difficult to specify percentages that add up to
9134 // 100% and still fit within the table width.
9135 // Let's say two cells have 50% width. They have 10 pixels of overall margin each.
9136 // The table content rect is 500 pixels and the inter-cell padding is 20 pixels.
9137 // If we're using internal content size for the width, we would calculate the
9138 // the overall cell width for n cells as:
9139 // (500 - 20*(n-1) - overallCellMargin1 - overallCellMargin2 - ...) * percentage / 100
9140 // + thisOverallCellMargin
9141 // = 500 - 20 - 10 - 10) * 0.5 + 10 = 240 pixels overall cell width.
9142 // Adding this back, we get 240 + 240 + 20 = 500 pixels.
9143
9144 if (cell->GetAttributes().GetTextBoxAttr().GetWidth().IsValid())
9145 {
9146 int w = converter.GetPixels(cell->GetAttributes().GetTextBoxAttr().GetWidth());
9147 if (cell->GetAttributes().GetTextBoxAttr().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE)
9148 {
9149 percentageCellWidth = w;
9150 }
9151 else
9152 {
9153 absoluteCellWidth = w;
9154 }
9155 // Override absolute width with minimum width if necessary
9156 if (cell->GetMinSize().x > 0 && absoluteCellWidth !=1 && cell->GetMinSize().x > absoluteCellWidth)
9157 absoluteCellWidth = cell->GetMinSize().x;
9158 }
9159
9160 if (absoluteCellWidth != -1)
9161 {
9162 if (absoluteCellWidth > absoluteColWidths[i])
9163 absoluteColWidths[i] = absoluteCellWidth;
9164 }
9165
9166 if (percentageCellWidth != -1)
9167 {
9168 if (percentageCellWidth > percentageColWidths[i])
9169 percentageColWidths[i] = percentageCellWidth;
9170 }
9171
9172 if (colSpan == 1 && cell->GetMinSize().x && cell->GetMinSize().x > minColWidths[i])
9173 minColWidths[i] = cell->GetMinSize().x;
9174 if (colSpan == 1 && cell->GetMaxSize().x && cell->GetMaxSize().x > maxColWidths[i])
9175 maxColWidths[i] = cell->GetMaxSize().x;
9176 }
9177 }
9178 }
9179 }
9180
9181 // (2) Allocate initial column widths from minimum widths, absolute values and proportions
9182 // TODO: simply merge this into (1).
9183 for (i = 0; i < m_colCount; i++)
9184 {
9185 if (absoluteColWidths[i] > 0)
9186 {
9187 colWidths[i] = absoluteColWidths[i];
9188 }
9189 else if (percentageColWidths[i] > 0)
9190 {
9191 colWidths[i] = percentageColWidths[i];
9192
9193 // This is rubbish - we calculated the absolute widths from percentages, so
9194 // we can't do it again here.
9195 //colWidths[i] = (int) (double(percentageColWidths[i]) * double(tableWidth) / 100.0 + 0.5);
9196 }
9197 }
9198
9199 // (3) Process absolute or proportional widths of spanning columns,
9200 // now that we know what our fixed column widths are going to be.
9201 // Spanned cells will try to adjust columns so the span will fit.
9202 // Even existing fixed column widths can be expanded if necessary.
9203 // Actually, currently fixed columns widths aren't adjusted; instead,
9204 // the algorithm favours earlier rows and adjusts unspecified column widths
9205 // the first time only. After that, we can't know whether the column has been
9206 // specified explicitly or not. (We could make a note if necessary.)
9207 for (j = 0; j < m_rowCount; j++)
9208 {
9209 // First get the overall margins so we can calculate percentage widths based on
9210 // the available content space for all cells on the row
9211
9212 int overallRowContentMargin = 0;
9213 int visibleCellCount = 0;
9214
9215 for (i = 0; i < m_colCount; i++)
9216 {
9217 wxRichTextBox* cell = GetCell(j, i);
9218 if (cell->IsShown())
9219 {
9220 int cellTotalLeftMargin = 0, cellTotalRightMargin = 0, cellTotalTopMargin = 0, cellTotalBottomMargin = 0;
9221 GetTotalMargin(dc, buffer, cell->GetAttributes(), cellTotalLeftMargin, cellTotalRightMargin, cellTotalTopMargin, cellTotalBottomMargin);
9222
9223 overallRowContentMargin += (cellTotalLeftMargin + cellTotalRightMargin);
9224 visibleCellCount ++;
9225 }
9226 }
9227
9228 // Add in inter-cell padding
9229 overallRowContentMargin += ((visibleCellCount-1) * paddingX);
9230
9231 int rowContentWidth = internalTableWidth - overallRowContentMargin;
9232 wxSize rowTableSize(rowContentWidth, 0);
9233 wxTextAttrDimensionConverter converter(dc, scale, rowTableSize);
9234
9235 for (i = 0; i < m_colCount; i++)
9236 {
9237 wxRichTextBox* cell = GetCell(j, i);
9238 if (cell->IsShown())
9239 {
9240 int colSpan = 1;
9241 if (cell->GetProperties().HasProperty(wxT("colspan")))
9242 colSpan = cell->GetProperties().GetPropertyLong(wxT("colspan"));
9243
9244 if (colSpan > 1)
9245 {
9246 int spans = wxMin(colSpan, m_colCount - i);
9247 int cellWidth = 0;
9248 if (spans > 0)
9249 {
9250 if (cell->GetAttributes().GetTextBoxAttr().GetWidth().IsValid())
9251 {
9252 cellWidth = converter.GetPixels(cell->GetAttributes().GetTextBoxAttr().GetWidth());
9253 // Override absolute width with minimum width if necessary
9254 if (cell->GetMinSize().x > 0 && cellWidth !=1 && cell->GetMinSize().x > cellWidth)
9255 cellWidth = cell->GetMinSize().x;
9256 }
9257 else
9258 {
9259 // Do we want to do this? It's the only chance we get to
9260 // use the cell's min/max sizes, so we need to work out
9261 // how we're going to balance the unspecified spanning cell
9262 // width with the possibility more-constrained constituent cell widths.
9263 // Say there's a tiny bitmap giving it a max width of 10 pixels. We
9264 // don't want to constraint all the spanned columns to fit into this cell.
9265 // OK, let's say that if any of the constituent columns don't fit,
9266 // then we simply stop constraining the columns; instead, we'll just fit the spanning
9267 // cells to the columns later.
9268 cellWidth = cell->GetMinSize().x;
9269 if (cell->GetMaxSize().x > cellWidth)
9270 cellWidth = cell->GetMaxSize().x;
9271 }
9272
9273 // Subtract the padding between cells
9274 int spanningWidth = cellWidth;
9275 spanningWidth -= paddingX * (spans-1);
9276
9277 if (spanningWidth > 0)
9278 {
9279 // Now share the spanning width between columns within that span
9280 // TODO: take into account min widths of columns within the span
9281 int spanningWidthLeft = spanningWidth;
9282 int stretchColCount = 0;
9283 for (k = i; k < (i+spans); k++)
9284 {
9285 if (colWidths[k] > 0) // absolute or proportional width has been specified
9286 spanningWidthLeft -= colWidths[k];
9287 else
9288 stretchColCount ++;
9289 }
9290 // Now divide what's left between the remaining columns
9291 int colShare = 0;
9292 if (stretchColCount > 0)
9293 colShare = spanningWidthLeft / stretchColCount;
9294 int colShareRemainder = spanningWidthLeft - (colShare * stretchColCount);
9295
9296 // If fixed-width columns are currently too big, then we'll later
9297 // stretch the spanned cell to fit.
9298
9299 if (spanningWidthLeft > 0)
9300 {
9301 for (k = i; k < (i+spans); k++)
9302 {
9303 if (colWidths[k] <= 0) // absolute or proportional width has not been specified
9304 {
9305 int newWidth = colShare;
9306 if (k == (i+spans-1))
9307 newWidth += colShareRemainder; // ensure all pixels are filled
9308 colWidths[k] = newWidth;
9309 }
9310 }
9311 }
9312 }
9313 }
9314 }
9315 }
9316 }
9317 }
9318
9319 // (4) Next, share any remaining space out between columns that have not yet been calculated.
9320 // TODO: take into account min widths of columns within the span
9321 int tableWidthMinusPadding = internalTableWidth - (m_colCount-1)*paddingX;
9322 int widthLeft = tableWidthMinusPadding;
9323 int stretchColCount = 0;
9324 for (i = 0; i < m_colCount; i++)
9325 {
9326 // TODO: we need to take into account min widths.
9327 // Subtract min width from width left, then
9328 // add the colShare to the min width
9329 if (colWidths[i] > 0) // absolute or proportional width has been specified
9330 widthLeft -= colWidths[i];
9331 else
9332 {
9333 if (minColWidths[i] > 0)
9334 widthLeft -= minColWidths[i];
9335
9336 stretchColCount ++;
9337 }
9338 }
9339
9340 // Now divide what's left between the remaining columns
9341 int colShare = 0;
9342 if (stretchColCount > 0)
9343 colShare = widthLeft / stretchColCount;
9344 int colShareRemainder = widthLeft - (colShare * stretchColCount);
9345
9346 // Check we don't have enough space, in which case shrink all columns, overriding
9347 // any absolute/proportional widths
9348 // TODO: actually we would like to divide up the shrinkage according to size.
9349 // How do we calculate the proportions that will achieve this?
9350 // Could first choose an arbitrary value for stretching cells, and then calculate
9351 // factors to multiply each width by.
9352 // TODO: want to record this fact and pass to an iteration that tries e.g. min widths
9353 if (widthLeft < 0 || (stretchToFitTableWidth && (stretchColCount == 0)))
9354 {
9355 colShare = tableWidthMinusPadding / m_colCount;
9356 colShareRemainder = tableWidthMinusPadding - (colShare * m_colCount);
9357 for (i = 0; i < m_colCount; i++)
9358 {
9359 colWidths[i] = 0;
9360 minColWidths[i] = 0;
9361 }
9362 }
9363
9364 // We have to adjust the columns if either we need to shrink the
9365 // table to fit the parent/table width, or we explicitly set the
9366 // table width and need to stretch out the table.
9367 if (widthLeft < 0 || stretchToFitTableWidth)
9368 {
9369 for (i = 0; i < m_colCount; i++)
9370 {
9371 if (colWidths[i] <= 0) // absolute or proportional width has not been specified
9372 {
9373 if (minColWidths[i] > 0)
9374 colWidths[i] = minColWidths[i] + colShare;
9375 else
9376 colWidths[i] = colShare;
9377 if (i == (m_colCount-1))
9378 colWidths[i] += colShareRemainder; // ensure all pixels are filled
9379 }
9380 }
9381 }
9382
9383 // TODO: if spanned cells have no specified or max width, make them the
9384 // as big as the columns they span. Do this for all spanned cells in all
9385 // rows, of course. Size any spanned cells left over at the end - even if they
9386 // have width > 0, make sure they're limited to the appropriate column edge.
9387
9388
9389 /*
9390 Sort out confusion between content width
9391 and overall width later. For now, assume we specify overall width.
9392
9393 So, now we've laid out the table to fit into the given space
9394 and have used specified widths and minimum widths.
9395
9396 Now we need to consider how we will try to take maximum width into account.
9397
9398 */
9399
9400 // (??) TODO: take max width into account
9401
9402 // (6) Lay out all cells again with the current values
9403
9404 int maxRight = 0;
9405 int y = availableSpace.y;
9406 for (j = 0; j < m_rowCount; j++)
9407 {
9408 int x = availableSpace.x; // TODO: take into account centering etc.
9409 int maxCellHeight = 0;
9410 int maxSpecifiedCellHeight = 0;
9411
9412 wxArrayInt actualWidths;
9413 actualWidths.Add(0, m_colCount);
9414
9415 wxTextAttrDimensionConverter converter(dc, scale);
9416 for (i = 0; i < m_colCount; i++)
9417 {
9418 wxRichTextCell* cell = GetCell(j, i);
9419 if (cell->IsShown())
9420 {
9421 // Get max specified cell height
9422 // Don't handle percentages for height
9423 if (cell->GetAttributes().GetTextBoxAttr().GetHeight().IsValid() && cell->GetAttributes().GetTextBoxAttr().GetHeight().GetUnits() != wxTEXT_ATTR_UNITS_PERCENTAGE)
9424 {
9425 int h = converter.GetPixels(cell->GetAttributes().GetTextBoxAttr().GetHeight());
9426 if (h > maxSpecifiedCellHeight)
9427 maxSpecifiedCellHeight = h;
9428 }
9429
9430 if (colWidths[i] > 0) // absolute or proportional width has been specified
9431 {
9432 int colSpan = 1;
9433 if (cell->GetProperties().HasProperty(wxT("colspan")))
9434 colSpan = cell->GetProperties().GetPropertyLong(wxT("colspan"));
9435
9436 wxRect availableCellSpace;
9437
9438 // TODO: take into acount spans
9439 if (colSpan > 1)
9440 {
9441 // Calculate the size of this spanning cell from its constituent columns
9442 int xx = x;
9443 int spans = wxMin(colSpan, m_colCount - i);
9444 for (k = i; k < spans; k++)
9445 {
9446 if (k != i)
9447 xx += paddingX;
9448 xx += colWidths[k];
9449 }
9450 availableCellSpace = wxRect(x, y, xx, -1);
9451 }
9452 else
9453 availableCellSpace = wxRect(x, y, colWidths[i], -1);
9454
9455 // Store actual width so we can force cell to be the appropriate width on the final loop
9456 actualWidths[i] = availableCellSpace.GetWidth();
9457
9458 // Lay out cell
9459 cell->Invalidate(wxRICHTEXT_ALL);
9460 cell->Layout(dc, context, availableCellSpace, availableSpace, style);
9461
9462 // TODO: use GetCachedSize().x to compute 'natural' size
9463
9464 x += (availableCellSpace.GetWidth() + paddingX);
9465 if (cell->GetCachedSize().y > maxCellHeight)
9466 maxCellHeight = cell->GetCachedSize().y;
9467 }
9468 }
9469 }
9470
9471 maxCellHeight = wxMax(maxCellHeight, maxSpecifiedCellHeight);
9472
9473 for (i = 0; i < m_colCount; i++)
9474 {
9475 wxRichTextCell* cell = GetCell(j, i);
9476 if (cell->IsShown())
9477 {
9478 wxRect availableCellSpace = wxRect(cell->GetPosition(), wxSize(actualWidths[i], maxCellHeight));
9479 // Lay out cell with new height
9480 cell->Invalidate(wxRICHTEXT_ALL);
9481 cell->Layout(dc, context, availableCellSpace, availableSpace, style);
9482
9483 // Make sure the cell size really is the appropriate size,
9484 // not the calculated box size
9485 cell->SetCachedSize(wxSize(actualWidths[i], maxCellHeight));
9486
9487 maxRight = wxMax(maxRight, cell->GetPosition().x + cell->GetCachedSize().x);
9488 }
9489 }
9490
9491 y += maxCellHeight;
9492 if (j < (m_rowCount-1))
9493 y += paddingY;
9494 }
9495
9496 // We need to add back the margins etc.
9497 {
9498 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
9499 contentRect = wxRect(wxPoint(0, 0), wxSize(maxRight - availableSpace.x, y - availableSpace.y));
9500 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
9501 SetCachedSize(marginRect.GetSize());
9502 }
9503
9504 // TODO: calculate max size
9505 {
9506 SetMaxSize(GetCachedSize());
9507 }
9508
9509 // TODO: calculate min size
9510 {
9511 SetMinSize(GetCachedSize());
9512 }
9513
9514 // TODO: currently we use either a fixed table width or the parent's size.
9515 // We also want to be able to calculate the table width from its content,
9516 // whether using fixed column widths or cell content min/max width.
9517 // Probably need a boolean flag to say whether we need to stretch cells
9518 // to fit the table width, or to simply use min/max cell widths. The
9519 // trouble with this is that if cell widths are not specified, they
9520 // will be tiny; we could use arbitrary defaults but this seems unsatisfactory.
9521 // Anyway, ignoring that problem, we probably need to factor layout into a function
9522 // that can can calculate the maximum unconstrained layout in case table size is
9523 // not specified. Then LayoutToBestSize() can choose to use either parent size to
9524 // constrain Layout(), or the previously-calculated max size to constraint layout.
9525
9526 return true;
9527 }
9528
9529 // Finds the absolute position and row height for the given character position
9530 bool wxRichTextTable::FindPosition(wxDC& dc, wxRichTextDrawingContext& context, long index, wxPoint& pt, int* height, bool forceLineStart)
9531 {
9532 wxRichTextCell* child = GetCell(index+1);
9533 if (child)
9534 {
9535 // Find the position at the start of the child cell, since the table doesn't
9536 // have any caret position of its own.
9537 return child->FindPosition(dc, context, -1, pt, height, forceLineStart);
9538 }
9539 else
9540 return false;
9541 }
9542
9543 // Get the cell at the given character position (in the range of the table).
9544 wxRichTextCell* wxRichTextTable::GetCell(long pos) const
9545 {
9546 int row = 0, col = 0;
9547 if (GetCellRowColumnPosition(pos, row, col))
9548 {
9549 return GetCell(row, col);
9550 }
9551 else
9552 return NULL;
9553 }
9554
9555 // Get the row/column for a given character position
9556 bool wxRichTextTable::GetCellRowColumnPosition(long pos, int& row, int& col) const
9557 {
9558 if (m_colCount == 0 || m_rowCount == 0)
9559 return false;
9560
9561 row = (int) (pos / m_colCount);
9562 col = pos - (row * m_colCount);
9563
9564 wxASSERT(row < m_rowCount && col < m_colCount);
9565
9566 if (row < m_rowCount && col < m_colCount)
9567 return true;
9568 else
9569 return false;
9570 }
9571
9572 // Calculate range, taking row/cell ordering into account instead of relying
9573 // on list ordering.
9574 void wxRichTextTable::CalculateRange(long start, long& end)
9575 {
9576 long current = start;
9577 long lastEnd = current;
9578
9579 if (IsTopLevel())
9580 {
9581 current = 0;
9582 lastEnd = 0;
9583 }
9584
9585 int i, j;
9586 for (i = 0; i < m_rowCount; i++)
9587 {
9588 for (j = 0; j < m_colCount; j++)
9589 {
9590 wxRichTextCell* child = GetCell(i, j);
9591 if (child)
9592 {
9593 long childEnd = 0;
9594
9595 child->CalculateRange(current, childEnd);
9596
9597 lastEnd = childEnd;
9598 current = childEnd + 1;
9599 }
9600 }
9601 }
9602
9603 // A top-level object always has a range of size 1,
9604 // because its children don't count at this level.
9605 end = start;
9606 m_range.SetRange(start, start);
9607
9608 // An object with no children has zero length
9609 if (m_children.GetCount() == 0)
9610 lastEnd --;
9611 m_ownRange.SetRange(0, lastEnd);
9612 }
9613
9614 // Gets the range size.
9615 bool wxRichTextTable::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, wxPoint position, wxArrayInt* partialExtents) const
9616 {
9617 return wxRichTextBox::GetRangeSize(range, size, descent, dc, context, flags, position, partialExtents);
9618 }
9619
9620 // Deletes content in the given range.
9621 bool wxRichTextTable::DeleteRange(const wxRichTextRange& WXUNUSED(range))
9622 {
9623 // TODO: implement deletion of cells
9624 return true;
9625 }
9626
9627 // Gets any text in this object for the given range.
9628 wxString wxRichTextTable::GetTextForRange(const wxRichTextRange& range) const
9629 {
9630 return wxRichTextBox::GetTextForRange(range);
9631 }
9632
9633 // Copies this object.
9634 void wxRichTextTable::Copy(const wxRichTextTable& obj)
9635 {
9636 wxRichTextBox::Copy(obj);
9637
9638 ClearTable();
9639
9640 m_rowCount = obj.m_rowCount;
9641 m_colCount = obj.m_colCount;
9642
9643 m_cells.Add(wxRichTextObjectPtrArray(), m_rowCount);
9644
9645 int i, j;
9646 for (i = 0; i < m_rowCount; i++)
9647 {
9648 wxRichTextObjectPtrArray& colArray = m_cells[i];
9649 for (j = 0; j < m_colCount; j++)
9650 {
9651 wxRichTextCell* cell = wxDynamicCast(obj.GetCell(i, j)->Clone(), wxRichTextCell);
9652 AppendChild(cell);
9653
9654 colArray.Add(cell);
9655 }
9656 }
9657 }
9658
9659 void wxRichTextTable::ClearTable()
9660 {
9661 m_cells.Clear();
9662 DeleteChildren();
9663 }
9664
9665 bool wxRichTextTable::CreateTable(int rows, int cols)
9666 {
9667 ClearTable();
9668
9669 m_rowCount = rows;
9670 m_colCount = cols;
9671
9672 m_cells.Add(wxRichTextObjectPtrArray(), rows);
9673
9674 int i, j;
9675 for (i = 0; i < rows; i++)
9676 {
9677 wxRichTextObjectPtrArray& colArray = m_cells[i];
9678 for (j = 0; j < cols; j++)
9679 {
9680 wxRichTextCell* cell = new wxRichTextCell;
9681 AppendChild(cell);
9682 cell->AddParagraph(wxEmptyString);
9683
9684 colArray.Add(cell);
9685 }
9686 }
9687
9688 return true;
9689 }
9690
9691 wxRichTextCell* wxRichTextTable::GetCell(int row, int col) const
9692 {
9693 wxASSERT(row < m_rowCount);
9694 wxASSERT(col < m_colCount);
9695
9696 if (row < m_rowCount && col < m_colCount)
9697 {
9698 wxRichTextObjectPtrArray& colArray = m_cells[row];
9699 wxRichTextObject* obj = colArray[col];
9700 return wxDynamicCast(obj, wxRichTextCell);
9701 }
9702 else
9703 return NULL;
9704 }
9705
9706 // Returns a selection object specifying the selections between start and end character positions.
9707 // For example, a table would deduce what cells (of range length 1) are selected when dragging across the table.
9708 wxRichTextSelection wxRichTextTable::GetSelection(long start, long end) const
9709 {
9710 wxRichTextSelection selection;
9711 selection.SetContainer((wxRichTextTable*) this);
9712
9713 if (start > end)
9714 {
9715 long tmp = end;
9716 end = start;
9717 start = tmp;
9718 }
9719
9720 wxASSERT( start >= 0 && end < (m_colCount * m_rowCount));
9721
9722 if (end >= (m_colCount * m_rowCount))
9723 return selection;
9724
9725 // We need to find the rectangle of cells that is described by the rectangle
9726 // with start, end as the diagonal. Make sure we don't add cells that are
9727 // not currenty visible because they are overlapped by spanning cells.
9728 /*
9729 --------------------------
9730 | 0 | 1 | 2 | 3 | 4 |
9731 --------------------------
9732 | 5 | 6 | 7 | 8 | 9 |
9733 --------------------------
9734 | 10 | 11 | 12 | 13 | 14 |
9735 --------------------------
9736 | 15 | 16 | 17 | 18 | 19 |
9737 --------------------------
9738
9739 Let's say we select 6 -> 18.
9740
9741 Left and right edge cols of rectangle are 1 and 3 inclusive. Find least/greatest to find
9742 which is left and which is right.
9743
9744 Top and bottom edge rows are 1 and 3 inclusive. Again, find least/greatest to find top and bottom.
9745
9746 Now go through rows from 1 to 3 and only add cells that are (a) within above column range
9747 and (b) shown.
9748
9749
9750 */
9751
9752 int leftCol = start - m_colCount * int(start/m_colCount);
9753 int rightCol = end - m_colCount * int(end/m_colCount);
9754
9755 int topRow = int(start/m_colCount);
9756 int bottomRow = int(end/m_colCount);
9757
9758 if (leftCol > rightCol)
9759 {
9760 int tmp = rightCol;
9761 rightCol = leftCol;
9762 leftCol = tmp;
9763 }
9764
9765 if (topRow > bottomRow)
9766 {
9767 int tmp = bottomRow;
9768 bottomRow = topRow;
9769 topRow = tmp;
9770 }
9771
9772 int i, j;
9773 for (i = topRow; i <= bottomRow; i++)
9774 {
9775 for (j = leftCol; j <= rightCol; j++)
9776 {
9777 wxRichTextCell* cell = GetCell(i, j);
9778 if (cell && cell->IsShown())
9779 selection.Add(cell->GetRange());
9780 }
9781 }
9782
9783 return selection;
9784 }
9785
9786 // Sets the attributes for the cells specified by the selection.
9787 bool wxRichTextTable::SetCellStyle(const wxRichTextSelection& selection, const wxRichTextAttr& style, int flags)
9788 {
9789 if (selection.GetContainer() != this)
9790 return false;
9791
9792 wxRichTextBuffer* buffer = GetBuffer();
9793 bool haveControl = (buffer && buffer->GetRichTextCtrl() != NULL);
9794 bool withUndo = haveControl && ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0);
9795
9796 if (withUndo)
9797 buffer->BeginBatchUndo(_("Set Cell Style"));
9798
9799 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
9800 while (node)
9801 {
9802 wxRichTextCell* cell = wxDynamicCast(node->GetData(), wxRichTextCell);
9803 if (cell && selection.WithinSelection(cell->GetRange().GetStart()))
9804 SetStyle(cell, style, flags);
9805 node = node->GetNext();
9806 }
9807
9808 // Do action, or delay it until end of batch.
9809 if (withUndo)
9810 buffer->EndBatchUndo();
9811
9812 return true;
9813 }
9814
9815 bool wxRichTextTable::DeleteRows(int startRow, int noRows)
9816 {
9817 wxASSERT((startRow + noRows) < m_rowCount);
9818 if ((startRow + noRows) >= m_rowCount)
9819 return false;
9820
9821 int i, j;
9822 for (i = startRow; i < (startRow+noRows); i++)
9823 {
9824 wxRichTextObjectPtrArray& colArray = m_cells[startRow];
9825 for (j = 0; j < (int) colArray.GetCount(); j++)
9826 {
9827 wxRichTextObject* cell = colArray[j];
9828 RemoveChild(cell, true);
9829 }
9830
9831 // Keep deleting at the same position, since we move all
9832 // the others up
9833 m_cells.RemoveAt(startRow);
9834 }
9835
9836 m_rowCount = m_rowCount - noRows;
9837
9838 return true;
9839 }
9840
9841 bool wxRichTextTable::DeleteColumns(int startCol, int noCols)
9842 {
9843 wxASSERT((startCol + noCols) < m_colCount);
9844 if ((startCol + noCols) >= m_colCount)
9845 return false;
9846
9847 bool deleteRows = (noCols == m_colCount);
9848
9849 int i, j;
9850 for (i = 0; i < m_rowCount; i++)
9851 {
9852 wxRichTextObjectPtrArray& colArray = m_cells[deleteRows ? 0 : i];
9853 for (j = startCol; j < (startCol+noCols); j++)
9854 {
9855 wxRichTextObject* cell = colArray[j];
9856 RemoveChild(cell, true);
9857 }
9858
9859 if (deleteRows)
9860 m_cells.RemoveAt(0);
9861 }
9862
9863 if (deleteRows)
9864 m_rowCount = 0;
9865 m_colCount = m_colCount - noCols;
9866
9867 return true;
9868 }
9869
9870 bool wxRichTextTable::AddRows(int startRow, int noRows, const wxRichTextAttr& attr)
9871 {
9872 wxASSERT(startRow <= m_rowCount);
9873 if (startRow > m_rowCount)
9874 return false;
9875
9876 int i, j;
9877 for (i = 0; i < noRows; i++)
9878 {
9879 int idx;
9880 if (startRow == m_rowCount)
9881 {
9882 m_cells.Add(wxRichTextObjectPtrArray());
9883 idx = m_cells.GetCount() - 1;
9884 }
9885 else
9886 {
9887 m_cells.Insert(wxRichTextObjectPtrArray(), startRow+i);
9888 idx = startRow+i;
9889 }
9890
9891 wxRichTextObjectPtrArray& colArray = m_cells[idx];
9892 for (j = 0; j < m_colCount; j++)
9893 {
9894 wxRichTextCell* cell = new wxRichTextCell;
9895 cell->GetAttributes() = attr;
9896
9897 AppendChild(cell);
9898 colArray.Add(cell);
9899 }
9900 }
9901
9902 m_rowCount = m_rowCount + noRows;
9903 return true;
9904 }
9905
9906 bool wxRichTextTable::AddColumns(int startCol, int noCols, const wxRichTextAttr& attr)
9907 {
9908 wxASSERT(startCol <= m_colCount);
9909 if (startCol > m_colCount)
9910 return false;
9911
9912 int i, j;
9913 for (i = 0; i < m_rowCount; i++)
9914 {
9915 wxRichTextObjectPtrArray& colArray = m_cells[i];
9916 for (j = 0; j < noCols; j++)
9917 {
9918 wxRichTextCell* cell = new wxRichTextCell;
9919 cell->GetAttributes() = attr;
9920
9921 AppendChild(cell);
9922
9923 if (startCol == m_colCount)
9924 colArray.Add(cell);
9925 else
9926 colArray.Insert(cell, startCol+j);
9927 }
9928 }
9929
9930 m_colCount = m_colCount + noCols;
9931
9932 return true;
9933 }
9934
9935 // Edit properties via a GUI
9936 bool wxRichTextTable::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer)
9937 {
9938 wxRichTextObjectPropertiesDialog boxDlg(this, wxGetTopLevelParent(parent), wxID_ANY, _("Table Properties"));
9939 boxDlg.SetAttributes(GetAttributes());
9940
9941 if (boxDlg.ShowModal() == wxID_OK)
9942 {
9943 boxDlg.ApplyStyle(buffer->GetRichTextCtrl(), wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_RESET);
9944 return true;
9945 }
9946 else
9947 return false;
9948 }
9949
9950 /*
9951 * Module to initialise and clean up handlers
9952 */
9953
9954 class wxRichTextModule: public wxModule
9955 {
9956 DECLARE_DYNAMIC_CLASS(wxRichTextModule)
9957 public:
9958 wxRichTextModule() {}
9959 bool OnInit()
9960 {
9961 wxRichTextBuffer::SetRenderer(new wxRichTextStdRenderer);
9962 wxRichTextBuffer::InitStandardHandlers();
9963 wxRichTextParagraph::InitDefaultTabs();
9964
9965 wxRichTextXMLHandler::RegisterNodeName(wxT("text"), wxT("wxRichTextPlainText"));
9966 wxRichTextXMLHandler::RegisterNodeName(wxT("symbol"), wxT("wxRichTextPlainText"));
9967 wxRichTextXMLHandler::RegisterNodeName(wxT("image"), wxT("wxRichTextImage"));
9968 wxRichTextXMLHandler::RegisterNodeName(wxT("paragraph"), wxT("wxRichTextParagraph"));
9969 wxRichTextXMLHandler::RegisterNodeName(wxT("paragraphlayout"), wxT("wxRichTextParagraphLayoutBox"));
9970 wxRichTextXMLHandler::RegisterNodeName(wxT("textbox"), wxT("wxRichTextBox"));
9971 wxRichTextXMLHandler::RegisterNodeName(wxT("cell"), wxT("wxRichTextCell"));
9972 wxRichTextXMLHandler::RegisterNodeName(wxT("table"), wxT("wxRichTextTable"));
9973 wxRichTextXMLHandler::RegisterNodeName(wxT("field"), wxT("wxRichTextField"));
9974
9975 return true;
9976 }
9977 void OnExit()
9978 {
9979 wxRichTextBuffer::CleanUpHandlers();
9980 wxRichTextBuffer::CleanUpDrawingHandlers();
9981 wxRichTextBuffer::CleanUpFieldTypes();
9982 wxRichTextXMLHandler::ClearNodeToClassMap();
9983 wxRichTextDecimalToRoman(-1);
9984 wxRichTextParagraph::ClearDefaultTabs();
9985 wxRichTextCtrl::ClearAvailableFontNames();
9986 wxRichTextBuffer::SetRenderer(NULL);
9987 }
9988 };
9989
9990 IMPLEMENT_DYNAMIC_CLASS(wxRichTextModule, wxModule)
9991
9992
9993 // If the richtext lib is dynamically loaded after the app has already started
9994 // (such as from wxPython) then the built-in module system will not init this
9995 // module. Provide this function to do it manually.
9996 void wxRichTextModuleInit()
9997 {
9998 wxModule* module = new wxRichTextModule;
9999 module->Init();
10000 wxModule::RegisterModule(module);
10001 }
10002
10003
10004 /*!
10005 * Commands for undo/redo
10006 *
10007 */
10008
10009 wxRichTextCommand::wxRichTextCommand(const wxString& name, wxRichTextCommandId id, wxRichTextBuffer* buffer,
10010 wxRichTextParagraphLayoutBox* container, wxRichTextCtrl* ctrl, bool ignoreFirstTime): wxCommand(true, name)
10011 {
10012 /* wxRichTextAction* action = */ new wxRichTextAction(this, name, id, buffer, container, ctrl, ignoreFirstTime);
10013 }
10014
10015 wxRichTextCommand::wxRichTextCommand(const wxString& name): wxCommand(true, name)
10016 {
10017 }
10018
10019 wxRichTextCommand::~wxRichTextCommand()
10020 {
10021 ClearActions();
10022 }
10023
10024 void wxRichTextCommand::AddAction(wxRichTextAction* action)
10025 {
10026 if (!m_actions.Member(action))
10027 m_actions.Append(action);
10028 }
10029
10030 bool wxRichTextCommand::Do()
10031 {
10032 for (wxList::compatibility_iterator node = m_actions.GetFirst(); node; node = node->GetNext())
10033 {
10034 wxRichTextAction* action = (wxRichTextAction*) node->GetData();
10035 action->Do();
10036 }
10037
10038 return true;
10039 }
10040
10041 bool wxRichTextCommand::Undo()
10042 {
10043 for (wxList::compatibility_iterator node = m_actions.GetLast(); node; node = node->GetPrevious())
10044 {
10045 wxRichTextAction* action = (wxRichTextAction*) node->GetData();
10046 action->Undo();
10047 }
10048
10049 return true;
10050 }
10051
10052 void wxRichTextCommand::ClearActions()
10053 {
10054 WX_CLEAR_LIST(wxList, m_actions);
10055 }
10056
10057 /*!
10058 * Individual action
10059 *
10060 */
10061
10062 wxRichTextAction::wxRichTextAction(wxRichTextCommand* cmd, const wxString& name, wxRichTextCommandId id,
10063 wxRichTextBuffer* buffer, wxRichTextParagraphLayoutBox* container,
10064 wxRichTextCtrl* ctrl, bool ignoreFirstTime)
10065 {
10066 m_buffer = buffer;
10067 m_object = NULL;
10068 m_containerAddress.Create(buffer, container);
10069 m_ignoreThis = ignoreFirstTime;
10070 m_cmdId = id;
10071 m_position = -1;
10072 m_ctrl = ctrl;
10073 m_name = name;
10074 m_newParagraphs.SetDefaultStyle(buffer->GetDefaultStyle());
10075 m_newParagraphs.SetBasicStyle(buffer->GetBasicStyle());
10076 if (cmd)
10077 cmd->AddAction(this);
10078 }
10079
10080 wxRichTextAction::~wxRichTextAction()
10081 {
10082 if (m_object)
10083 delete m_object;
10084 }
10085
10086 // Returns the container that this action refers to, using the container address and top-level buffer.
10087 wxRichTextParagraphLayoutBox* wxRichTextAction::GetContainer() const
10088 {
10089 wxRichTextParagraphLayoutBox* container = wxDynamicCast(GetContainerAddress().GetObject(m_buffer), wxRichTextParagraphLayoutBox);
10090 return container;
10091 }
10092
10093
10094 void wxRichTextAction::CalculateRefreshOptimizations(wxArrayInt& optimizationLineCharPositions, wxArrayInt& optimizationLineYPositions)
10095 {
10096 // Store a list of line start character and y positions so we can figure out which area
10097 // we need to refresh
10098
10099 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
10100 wxRichTextParagraphLayoutBox* container = GetContainer();
10101 wxASSERT(container != NULL);
10102 if (!container)
10103 return;
10104
10105 // NOTE: we're assuming that the buffer is laid out correctly at this point.
10106 // If we had several actions, which only invalidate and leave layout until the
10107 // paint handler is called, then this might not be true. So we may need to switch
10108 // optimisation on only when we're simply adding text and not simultaneously
10109 // deleting a selection, for example. Or, we make sure the buffer is laid out correctly
10110 // first, but of course this means we'll be doing it twice.
10111 if (!m_buffer->IsDirty() && m_ctrl) // can only do optimisation if the buffer is already laid out correctly
10112 {
10113 wxSize clientSize = m_ctrl->GetUnscaledSize(m_ctrl->GetClientSize());
10114 wxPoint firstVisiblePt = m_ctrl->GetUnscaledPoint(m_ctrl->GetFirstVisiblePoint());
10115 int lastY = firstVisiblePt.y + clientSize.y;
10116
10117 wxRichTextParagraph* para = container->GetParagraphAtPosition(GetRange().GetStart());
10118 wxRichTextObjectList::compatibility_iterator node = container->GetChildren().Find(para);
10119 while (node)
10120 {
10121 wxRichTextParagraph* child = (wxRichTextParagraph*) node->GetData();
10122 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
10123 while (node2)
10124 {
10125 wxRichTextLine* line = node2->GetData();
10126 wxPoint pt = line->GetAbsolutePosition();
10127 wxRichTextRange range = line->GetAbsoluteRange();
10128
10129 if (pt.y > lastY)
10130 {
10131 node2 = wxRichTextLineList::compatibility_iterator();
10132 node = wxRichTextObjectList::compatibility_iterator();
10133 }
10134 else if (range.GetStart() > GetPosition() && pt.y >= firstVisiblePt.y)
10135 {
10136 optimizationLineCharPositions.Add(range.GetStart());
10137 optimizationLineYPositions.Add(pt.y);
10138 }
10139
10140 if (node2)
10141 node2 = node2->GetNext();
10142 }
10143
10144 if (node)
10145 node = node->GetNext();
10146 }
10147 }
10148 #endif
10149 }
10150
10151 bool wxRichTextAction::Do()
10152 {
10153 m_buffer->Modify(true);
10154
10155 wxRichTextParagraphLayoutBox* container = GetContainer();
10156 wxASSERT(container != NULL);
10157 if (!container)
10158 return false;
10159
10160 switch (m_cmdId)
10161 {
10162 case wxRICHTEXT_INSERT:
10163 {
10164 // Store a list of line start character and y positions so we can figure out which area
10165 // we need to refresh
10166 wxArrayInt optimizationLineCharPositions;
10167 wxArrayInt optimizationLineYPositions;
10168
10169 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
10170 CalculateRefreshOptimizations(optimizationLineCharPositions, optimizationLineYPositions);
10171 #endif
10172
10173 container->InsertFragment(GetRange().GetStart(), m_newParagraphs);
10174 container->UpdateRanges();
10175
10176 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
10177 // Layout() would stop prematurely at the top level.
10178 container->InvalidateHierarchy(wxRichTextRange(wxMax(0, GetRange().GetStart()-1), GetRange().GetEnd()));
10179
10180 long newCaretPosition = GetPosition() + m_newParagraphs.GetOwnRange().GetLength();
10181
10182 // Character position to caret position
10183 newCaretPosition --;
10184
10185 // Don't take into account the last newline
10186 if (m_newParagraphs.GetPartialParagraph())
10187 newCaretPosition --;
10188 else
10189 if (m_newParagraphs.GetChildren().GetCount() > 1)
10190 {
10191 wxRichTextObject* p = (wxRichTextObject*) m_newParagraphs.GetChildren().GetLast()->GetData();
10192 if (p->GetRange().GetLength() == 1)
10193 newCaretPosition --;
10194 }
10195
10196 newCaretPosition = wxMin(newCaretPosition, (container->GetOwnRange().GetEnd()-1));
10197
10198 UpdateAppearance(newCaretPosition, true /* send update event */, & optimizationLineCharPositions, & optimizationLineYPositions, true /* do */);
10199
10200 wxRichTextEvent cmdEvent(
10201 wxEVT_COMMAND_RICHTEXT_CONTENT_INSERTED,
10202 m_ctrl ? m_ctrl->GetId() : -1);
10203 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
10204 cmdEvent.SetRange(GetRange());
10205 cmdEvent.SetPosition(GetRange().GetStart());
10206 cmdEvent.SetContainer(container);
10207
10208 m_buffer->SendEvent(cmdEvent);
10209
10210 break;
10211 }
10212 case wxRICHTEXT_DELETE:
10213 {
10214 wxArrayInt optimizationLineCharPositions;
10215 wxArrayInt optimizationLineYPositions;
10216
10217 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
10218 CalculateRefreshOptimizations(optimizationLineCharPositions, optimizationLineYPositions);
10219 #endif
10220
10221 container->DeleteRange(GetRange());
10222 container->UpdateRanges();
10223 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
10224 // Layout() would stop prematurely at the top level.
10225 container->InvalidateHierarchy(wxRichTextRange(GetRange().GetStart(), GetRange().GetStart()));
10226
10227 long caretPos = GetRange().GetStart()-1;
10228 if (caretPos >= container->GetOwnRange().GetEnd())
10229 caretPos --;
10230
10231 UpdateAppearance(caretPos, true /* send update event */, & optimizationLineCharPositions, & optimizationLineYPositions, true /* do */);
10232
10233 wxRichTextEvent cmdEvent(
10234 wxEVT_COMMAND_RICHTEXT_CONTENT_DELETED,
10235 m_ctrl ? m_ctrl->GetId() : -1);
10236 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
10237 cmdEvent.SetRange(GetRange());
10238 cmdEvent.SetPosition(GetRange().GetStart());
10239 cmdEvent.SetContainer(container);
10240
10241 m_buffer->SendEvent(cmdEvent);
10242
10243 break;
10244 }
10245 case wxRICHTEXT_CHANGE_STYLE:
10246 case wxRICHTEXT_CHANGE_PROPERTIES:
10247 {
10248 ApplyParagraphs(GetNewParagraphs());
10249
10250 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
10251 // Layout() would stop prematurely at the top level.
10252 container->InvalidateHierarchy(GetRange());
10253
10254 UpdateAppearance(GetPosition());
10255
10256 wxRichTextEvent cmdEvent(
10257 m_cmdId == wxRICHTEXT_CHANGE_STYLE ? wxEVT_COMMAND_RICHTEXT_STYLE_CHANGED : wxEVT_COMMAND_RICHTEXT_PROPERTIES_CHANGED,
10258 m_ctrl ? m_ctrl->GetId() : -1);
10259 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
10260 cmdEvent.SetRange(GetRange());
10261 cmdEvent.SetPosition(GetRange().GetStart());
10262 cmdEvent.SetContainer(container);
10263
10264 m_buffer->SendEvent(cmdEvent);
10265
10266 break;
10267 }
10268 case wxRICHTEXT_CHANGE_ATTRIBUTES:
10269 {
10270 wxRichTextObject* obj = m_objectAddress.GetObject(m_buffer); // container->GetChildAtPosition(GetRange().GetStart());
10271 if (obj)
10272 {
10273 wxRichTextAttr oldAttr = obj->GetAttributes();
10274 obj->GetAttributes() = m_attributes;
10275 m_attributes = oldAttr;
10276 }
10277
10278 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
10279 // Layout() would stop prematurely at the top level.
10280 container->InvalidateHierarchy(GetRange());
10281
10282 UpdateAppearance(GetPosition());
10283
10284 wxRichTextEvent cmdEvent(
10285 wxEVT_COMMAND_RICHTEXT_STYLE_CHANGED,
10286 m_ctrl ? m_ctrl->GetId() : -1);
10287 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
10288 cmdEvent.SetRange(GetRange());
10289 cmdEvent.SetPosition(GetRange().GetStart());
10290 cmdEvent.SetContainer(container);
10291
10292 m_buffer->SendEvent(cmdEvent);
10293
10294 break;
10295 }
10296 case wxRICHTEXT_CHANGE_OBJECT:
10297 {
10298 wxRichTextObject* obj = m_objectAddress.GetObject(m_buffer);
10299 // wxRichTextObject* obj = container->GetChildAtPosition(GetRange().GetStart());
10300 if (obj && m_object)
10301 {
10302 wxRichTextObjectList::compatibility_iterator node = container->GetChildren().Find(obj);
10303 if (node)
10304 {
10305 wxRichTextObject* obj = node->GetData();
10306 node->SetData(m_object);
10307 m_object = obj;
10308 }
10309 }
10310
10311 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
10312 // Layout() would stop prematurely at the top level.
10313 container->InvalidateHierarchy(GetRange());
10314
10315 UpdateAppearance(GetPosition());
10316
10317 // TODO: send new kind of modification event
10318
10319 break;
10320 }
10321 default:
10322 break;
10323 }
10324
10325 return true;
10326 }
10327
10328 bool wxRichTextAction::Undo()
10329 {
10330 m_buffer->Modify(true);
10331
10332 wxRichTextParagraphLayoutBox* container = GetContainer();
10333 wxASSERT(container != NULL);
10334 if (!container)
10335 return false;
10336
10337 switch (m_cmdId)
10338 {
10339 case wxRICHTEXT_INSERT:
10340 {
10341 wxArrayInt optimizationLineCharPositions;
10342 wxArrayInt optimizationLineYPositions;
10343
10344 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
10345 CalculateRefreshOptimizations(optimizationLineCharPositions, optimizationLineYPositions);
10346 #endif
10347
10348 container->DeleteRange(GetRange());
10349 container->UpdateRanges();
10350
10351 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
10352 // Layout() would stop prematurely at the top level.
10353 container->InvalidateHierarchy(wxRichTextRange(GetRange().GetStart(), GetRange().GetStart()));
10354
10355 long newCaretPosition = GetPosition() - 1;
10356
10357 UpdateAppearance(newCaretPosition, true, /* send update event */ & optimizationLineCharPositions, & optimizationLineYPositions, false /* undo */);
10358
10359 wxRichTextEvent cmdEvent(
10360 wxEVT_COMMAND_RICHTEXT_CONTENT_DELETED,
10361 m_ctrl ? m_ctrl->GetId() : -1);
10362 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
10363 cmdEvent.SetRange(GetRange());
10364 cmdEvent.SetPosition(GetRange().GetStart());
10365 cmdEvent.SetContainer(container);
10366
10367 m_buffer->SendEvent(cmdEvent);
10368
10369 break;
10370 }
10371 case wxRICHTEXT_DELETE:
10372 {
10373 wxArrayInt optimizationLineCharPositions;
10374 wxArrayInt optimizationLineYPositions;
10375
10376 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
10377 CalculateRefreshOptimizations(optimizationLineCharPositions, optimizationLineYPositions);
10378 #endif
10379
10380 container->InsertFragment(GetRange().GetStart(), m_oldParagraphs);
10381 container->UpdateRanges();
10382
10383 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
10384 // Layout() would stop prematurely at the top level.
10385 container->InvalidateHierarchy(GetRange());
10386
10387 UpdateAppearance(GetPosition(), true, /* send update event */ & optimizationLineCharPositions, & optimizationLineYPositions, false /* undo */);
10388
10389 wxRichTextEvent cmdEvent(
10390 wxEVT_COMMAND_RICHTEXT_CONTENT_INSERTED,
10391 m_ctrl ? m_ctrl->GetId() : -1);
10392 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
10393 cmdEvent.SetRange(GetRange());
10394 cmdEvent.SetPosition(GetRange().GetStart());
10395 cmdEvent.SetContainer(container);
10396
10397 m_buffer->SendEvent(cmdEvent);
10398
10399 break;
10400 }
10401 case wxRICHTEXT_CHANGE_STYLE:
10402 case wxRICHTEXT_CHANGE_PROPERTIES:
10403 {
10404 ApplyParagraphs(GetOldParagraphs());
10405 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
10406 // Layout() would stop prematurely at the top level.
10407 container->InvalidateHierarchy(GetRange());
10408
10409 UpdateAppearance(GetPosition());
10410
10411 wxRichTextEvent cmdEvent(
10412 m_cmdId == wxRICHTEXT_CHANGE_STYLE ? wxEVT_COMMAND_RICHTEXT_STYLE_CHANGED : wxEVT_COMMAND_RICHTEXT_PROPERTIES_CHANGED,
10413 m_ctrl ? m_ctrl->GetId() : -1);
10414 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
10415 cmdEvent.SetRange(GetRange());
10416 cmdEvent.SetPosition(GetRange().GetStart());
10417 cmdEvent.SetContainer(container);
10418
10419 m_buffer->SendEvent(cmdEvent);
10420
10421 break;
10422 }
10423 case wxRICHTEXT_CHANGE_ATTRIBUTES:
10424 case wxRICHTEXT_CHANGE_OBJECT:
10425 {
10426 return Do();
10427 }
10428 default:
10429 break;
10430 }
10431
10432 return true;
10433 }
10434
10435 /// Update the control appearance
10436 void wxRichTextAction::UpdateAppearance(long caretPosition, bool sendUpdateEvent, wxArrayInt* optimizationLineCharPositions, wxArrayInt* optimizationLineYPositions, bool isDoCmd)
10437 {
10438 wxRichTextParagraphLayoutBox* container = GetContainer();
10439 wxASSERT(container != NULL);
10440 if (!container)
10441 return;
10442
10443 if (m_ctrl)
10444 {
10445 m_ctrl->SetFocusObject(container);
10446 m_ctrl->SetCaretPosition(caretPosition);
10447
10448 if (!m_ctrl->IsFrozen())
10449 {
10450 wxRect containerRect = container->GetRect();
10451
10452 m_ctrl->LayoutContent();
10453
10454 // Refresh everything if there were floating objects or the container changed size
10455 // (we can't yet optimize in these cases, since more complex interaction with other content occurs)
10456 if (container->GetFloatingObjectCount() > 0 || (container->GetParent() && containerRect != container->GetRect()))
10457 {
10458 m_ctrl->Refresh(false);
10459 }
10460 else
10461
10462 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
10463 // Find refresh rectangle if we are in a position to optimise refresh
10464 if ((m_cmdId == wxRICHTEXT_INSERT || m_cmdId == wxRICHTEXT_DELETE) && optimizationLineCharPositions)
10465 {
10466 size_t i;
10467
10468 wxSize clientSize = m_ctrl->GetUnscaledSize(m_ctrl->GetClientSize());
10469 wxPoint firstVisiblePt = m_ctrl->GetUnscaledPoint(m_ctrl->GetFirstVisiblePoint());
10470
10471 // Start/end positions
10472 int firstY = 0;
10473 int lastY = firstVisiblePt.y + clientSize.y;
10474
10475 bool foundEnd = false;
10476
10477 // position offset - how many characters were inserted
10478 int positionOffset = GetRange().GetLength();
10479
10480 // Determine whether this is Do or Undo, and adjust positionOffset accordingly
10481 if ((m_cmdId == wxRICHTEXT_DELETE && isDoCmd) || (m_cmdId == wxRICHTEXT_INSERT && !isDoCmd))
10482 positionOffset = - positionOffset;
10483
10484 // find the first line which is being drawn at the same position as it was
10485 // before. Since we're talking about a simple insertion, we can assume
10486 // that the rest of the window does not need to be redrawn.
10487
10488 wxRichTextParagraph* para = container->GetParagraphAtPosition(GetPosition());
10489 // Since we support floating layout, we should redraw the whole para instead of just
10490 // the first line touching the invalid range.
10491 if (para)
10492 {
10493 firstY = para->GetPosition().y;
10494 }
10495
10496 wxRichTextObjectList::compatibility_iterator node = container->GetChildren().Find(para);
10497 while (node)
10498 {
10499 wxRichTextParagraph* child = (wxRichTextParagraph*) node->GetData();
10500 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
10501 while (node2)
10502 {
10503 wxRichTextLine* line = node2->GetData();
10504 wxPoint pt = line->GetAbsolutePosition();
10505 wxRichTextRange range = line->GetAbsoluteRange();
10506
10507 // we want to find the first line that is in the same position
10508 // as before. This will mean we're at the end of the changed text.
10509
10510 if (pt.y > lastY) // going past the end of the window, no more info
10511 {
10512 node2 = wxRichTextLineList::compatibility_iterator();
10513 node = wxRichTextObjectList::compatibility_iterator();
10514 }
10515 // Detect last line in the buffer
10516 else if (!node2->GetNext() && para->GetRange().Contains(container->GetOwnRange().GetEnd()))
10517 {
10518 // If deleting text, make sure we refresh below as well as above
10519 if (positionOffset >= 0)
10520 {
10521 foundEnd = true;
10522 lastY = pt.y + line->GetSize().y;
10523 }
10524
10525 node2 = wxRichTextLineList::compatibility_iterator();
10526 node = wxRichTextObjectList::compatibility_iterator();
10527
10528 break;
10529 }
10530 else
10531 {
10532 // search for this line being at the same position as before
10533 for (i = 0; i < optimizationLineCharPositions->GetCount(); i++)
10534 {
10535 if (((*optimizationLineCharPositions)[i] + positionOffset == range.GetStart()) &&
10536 ((*optimizationLineYPositions)[i] == pt.y))
10537 {
10538 // Stop, we're now the same as we were
10539 foundEnd = true;
10540
10541 lastY = pt.y;
10542
10543 node2 = wxRichTextLineList::compatibility_iterator();
10544 node = wxRichTextObjectList::compatibility_iterator();
10545
10546 break;
10547 }
10548 }
10549 }
10550
10551 if (node2)
10552 node2 = node2->GetNext();
10553 }
10554
10555 if (node)
10556 node = node->GetNext();
10557 }
10558
10559 firstY = wxMax(firstVisiblePt.y, firstY);
10560 if (!foundEnd)
10561 lastY = firstVisiblePt.y + clientSize.y;
10562
10563 // Convert to device coordinates
10564 wxRect rect(m_ctrl->GetPhysicalPoint(m_ctrl->GetScaledPoint(wxPoint(firstVisiblePt.x, firstY))), m_ctrl->GetScaledSize(wxSize(clientSize.x, lastY - firstY)));
10565 m_ctrl->RefreshRect(rect);
10566 }
10567 else
10568 #endif
10569 m_ctrl->Refresh(false);
10570
10571 m_ctrl->PositionCaret();
10572
10573 // This causes styles to persist when doing programmatic
10574 // content creation except when Freeze/Thaw is used, so
10575 // disable this and check for the consequences.
10576 // m_ctrl->SetDefaultStyleToCursorStyle();
10577
10578 if (sendUpdateEvent)
10579 wxTextCtrl::SendTextUpdatedEvent(m_ctrl);
10580 }
10581 }
10582 }
10583
10584 /// Replace the buffer paragraphs with the new ones.
10585 void wxRichTextAction::ApplyParagraphs(const wxRichTextParagraphLayoutBox& fragment)
10586 {
10587 wxRichTextParagraphLayoutBox* container = GetContainer();
10588 wxASSERT(container != NULL);
10589 if (!container)
10590 return;
10591
10592 wxRichTextObjectList::compatibility_iterator node = fragment.GetChildren().GetFirst();
10593 while (node)
10594 {
10595 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
10596 wxASSERT (para != NULL);
10597
10598 // We'll replace the existing paragraph by finding the paragraph at this position,
10599 // delete its node data, and setting a copy as the new node data.
10600 // TODO: make more efficient by simply swapping old and new paragraph objects.
10601
10602 wxRichTextParagraph* existingPara = container->GetParagraphAtPosition(para->GetRange().GetStart());
10603 if (existingPara)
10604 {
10605 wxRichTextObjectList::compatibility_iterator bufferParaNode = container->GetChildren().Find(existingPara);
10606 if (bufferParaNode)
10607 {
10608 wxRichTextParagraph* newPara = new wxRichTextParagraph(*para);
10609 newPara->SetParent(container);
10610
10611 bufferParaNode->SetData(newPara);
10612
10613 delete existingPara;
10614 }
10615 }
10616
10617 node = node->GetNext();
10618 }
10619 }
10620
10621
10622 /*!
10623 * wxRichTextRange
10624 * This stores beginning and end positions for a range of data.
10625 */
10626
10627 WX_DEFINE_OBJARRAY(wxRichTextRangeArray);
10628
10629 /// Limit this range to be within 'range'
10630 bool wxRichTextRange::LimitTo(const wxRichTextRange& range)
10631 {
10632 if (m_start < range.m_start)
10633 m_start = range.m_start;
10634
10635 if (m_end > range.m_end)
10636 m_end = range.m_end;
10637
10638 return true;
10639 }
10640
10641 /*!
10642 * wxRichTextImage implementation
10643 * This object represents an image.
10644 */
10645
10646 IMPLEMENT_DYNAMIC_CLASS(wxRichTextImage, wxRichTextObject)
10647
10648 wxRichTextImage::wxRichTextImage(const wxImage& image, wxRichTextObject* parent, wxRichTextAttr* charStyle):
10649 wxRichTextObject(parent)
10650 {
10651 Init();
10652 m_imageBlock.MakeImageBlockDefaultQuality(image, wxBITMAP_TYPE_PNG);
10653 if (charStyle)
10654 SetAttributes(*charStyle);
10655 }
10656
10657 wxRichTextImage::wxRichTextImage(const wxRichTextImageBlock& imageBlock, wxRichTextObject* parent, wxRichTextAttr* charStyle):
10658 wxRichTextObject(parent)
10659 {
10660 Init();
10661 m_imageBlock = imageBlock;
10662 if (charStyle)
10663 SetAttributes(*charStyle);
10664 }
10665
10666 void wxRichTextImage::Init()
10667 {
10668 m_originalImageSize = wxSize(-1, -1);
10669 }
10670
10671 /// Create a cached image at the required size
10672 bool wxRichTextImage::LoadImageCache(wxDC& dc, bool resetCache)
10673 {
10674 if (!m_imageBlock.IsOk())
10675 return false;
10676
10677 // If we have an original image size, use that to compute the cached bitmap size
10678 // instead of loading the image each time. This way we can avoid loading
10679 // the image so long as the new cached bitmap size hasn't changed.
10680
10681 wxImage image;
10682 if (resetCache || m_originalImageSize == wxSize(-1, -1))
10683 {
10684 m_imageCache = wxNullBitmap;
10685
10686 m_imageBlock.Load(image);
10687 if (!image.IsOk())
10688 return false;
10689
10690 m_originalImageSize = wxSize(image.GetWidth(), image.GetHeight());
10691 }
10692
10693 int width = m_originalImageSize.GetWidth();
10694 int height = m_originalImageSize.GetHeight();
10695
10696 int parentWidth = 0;
10697 int parentHeight = 0;
10698
10699 int maxWidth = -1;
10700 int maxHeight = -1;
10701
10702 wxRichTextBuffer* buffer = GetBuffer();
10703 if (buffer)
10704 {
10705 wxSize sz;
10706 if (buffer->GetRichTextCtrl())
10707 {
10708 // Subtract borders
10709 sz = buffer->GetRichTextCtrl()->GetClientSize();
10710
10711 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
10712 marginRect = wxRect(0, 0, sz.x, sz.y);
10713 buffer->GetBoxRects(dc, buffer, buffer->GetAttributes(), marginRect, borderRect, contentRect, paddingRect, outlineRect);
10714
10715 sz = contentRect.GetSize();
10716
10717 // Start with a maximum width of the control size, even if not specified by the content,
10718 // to minimize the amount of picture overlapping the right-hand side
10719 maxWidth = sz.x;
10720 }
10721 else
10722 sz = buffer->GetCachedSize();
10723 parentWidth = sz.GetWidth();
10724 parentHeight = sz.GetHeight();
10725 }
10726
10727 if (GetAttributes().GetTextBoxAttr().GetWidth().IsValid() && GetAttributes().GetTextBoxAttr().GetWidth().GetValue() > 0)
10728 {
10729 if (parentWidth > 0 && GetAttributes().GetTextBoxAttr().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE)
10730 width = (int) ((GetAttributes().GetTextBoxAttr().GetWidth().GetValue() * parentWidth)/100.0);
10731 else if (GetAttributes().GetTextBoxAttr().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
10732 width = ConvertTenthsMMToPixels(dc, GetAttributes().GetTextBoxAttr().GetWidth().GetValue());
10733 else if (GetAttributes().GetTextBoxAttr().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_PIXELS)
10734 width = GetAttributes().GetTextBoxAttr().GetWidth().GetValue();
10735 }
10736
10737 // Limit to max width
10738
10739 if (GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().IsValid() && GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetValue() > 0)
10740 {
10741 int mw = -1;
10742
10743 if (parentWidth > 0 && GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE)
10744 mw = (int) ((GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetValue() * parentWidth)/100.0);
10745 else if (GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
10746 mw = ConvertTenthsMMToPixels(dc, GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetValue());
10747 else if (GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_PIXELS)
10748 mw = GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetValue();
10749
10750 // If we already have a smaller max width due to the constraints of the control size,
10751 // don't use the larger max width.
10752 if (mw != -1 && ((maxWidth == -1) || (mw < maxWidth)))
10753 maxWidth = mw;
10754 }
10755
10756 if (maxWidth > 0 && width > maxWidth)
10757 width = maxWidth;
10758
10759 // Preserve the aspect ratio
10760 if (width != m_originalImageSize.GetWidth())
10761 height = (int) (float(m_originalImageSize.GetHeight()) * (float(width)/float(m_originalImageSize.GetWidth())));
10762
10763 if (GetAttributes().GetTextBoxAttr().GetHeight().IsValid() && GetAttributes().GetTextBoxAttr().GetHeight().GetValue() > 0)
10764 {
10765 if (parentHeight > 0 && GetAttributes().GetTextBoxAttr().GetHeight().GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE)
10766 height = (int) ((GetAttributes().GetTextBoxAttr().GetHeight().GetValue() * parentHeight)/100.0);
10767 else if (GetAttributes().GetTextBoxAttr().GetHeight().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
10768 height = ConvertTenthsMMToPixels(dc, GetAttributes().GetTextBoxAttr().GetHeight().GetValue());
10769 else if (GetAttributes().GetTextBoxAttr().GetHeight().GetUnits() == wxTEXT_ATTR_UNITS_PIXELS)
10770 height = GetAttributes().GetTextBoxAttr().GetHeight().GetValue();
10771
10772 // Preserve the aspect ratio
10773 if (height != m_originalImageSize.GetHeight())
10774 width = (int) (float(m_originalImageSize.GetWidth()) * (float(height)/float(m_originalImageSize.GetHeight())));
10775 }
10776
10777 // Limit to max height
10778
10779 if (GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().IsValid() && GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetValue() > 0)
10780 {
10781 if (parentHeight > 0 && GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE)
10782 maxHeight = (int) ((GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetValue() * parentHeight)/100.0);
10783 else if (GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
10784 maxHeight = ConvertTenthsMMToPixels(dc, GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetValue());
10785 else if (GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetUnits() == wxTEXT_ATTR_UNITS_PIXELS)
10786 maxHeight = GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetValue();
10787 }
10788
10789 if (maxHeight > 0 && height > maxHeight)
10790 {
10791 height = maxHeight;
10792
10793 // Preserve the aspect ratio
10794 if (height != m_originalImageSize.GetHeight())
10795 width = (int) (float(m_originalImageSize.GetWidth()) * (float(height)/float(m_originalImageSize.GetHeight())));
10796 }
10797
10798 if (m_imageCache.IsOk() && m_imageCache.GetWidth() == width && m_imageCache.GetHeight() == height)
10799 {
10800 // Do nothing, we didn't need to change the image cache
10801 }
10802 else
10803 {
10804 if (!image.IsOk())
10805 {
10806 m_imageBlock.Load(image);
10807 if (!image.IsOk())
10808 return false;
10809 }
10810
10811 if (image.GetWidth() == width && image.GetHeight() == height)
10812 m_imageCache = wxBitmap(image);
10813 else
10814 {
10815 // If the original width and height is small, e.g. 400 or below,
10816 // scale up and then down to improve image quality. This can make
10817 // a big difference, with not much performance hit.
10818 int upscaleThreshold = 400;
10819 wxImage img;
10820 if (image.GetWidth() <= upscaleThreshold || image.GetHeight() <= upscaleThreshold)
10821 {
10822 img = image.Scale(image.GetWidth()*2, image.GetHeight()*2);
10823 img.Rescale(width, height, wxIMAGE_QUALITY_HIGH);
10824 }
10825 else
10826 img = image.Scale(width, height, wxIMAGE_QUALITY_HIGH);
10827 m_imageCache = wxBitmap(img);
10828 }
10829 }
10830
10831 return m_imageCache.IsOk();
10832 }
10833
10834 /// Draw the item
10835 bool wxRichTextImage::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& WXUNUSED(range), const wxRichTextSelection& selection, const wxRect& rect, int WXUNUSED(descent), int WXUNUSED(style))
10836 {
10837 if (!IsShown())
10838 return true;
10839
10840 // Don't need cached size AFAIK
10841 // wxSize size = GetCachedSize();
10842 if (!LoadImageCache(dc))
10843 return false;
10844
10845 wxRichTextAttr attr(GetAttributes());
10846 context.ApplyVirtualAttributes(attr, this);
10847
10848 DrawBoxAttributes(dc, GetBuffer(), attr, wxRect(rect.GetPosition(), GetCachedSize()));
10849
10850 wxSize imageSize(m_imageCache.GetWidth(), m_imageCache.GetHeight());
10851 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
10852 marginRect = rect; // outer rectangle, will calculate contentRect
10853 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
10854
10855 dc.DrawBitmap(m_imageCache, contentRect.x, contentRect.y, true);
10856
10857 if (selection.WithinSelection(GetRange().GetStart(), this))
10858 {
10859 wxCheckSetBrush(dc, *wxBLACK_BRUSH);
10860 wxCheckSetPen(dc, *wxBLACK_PEN);
10861 dc.SetLogicalFunction(wxINVERT);
10862 dc.DrawRectangle(contentRect);
10863 dc.SetLogicalFunction(wxCOPY);
10864 }
10865
10866 return true;
10867 }
10868
10869 /// Lay the item out
10870 bool wxRichTextImage::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& WXUNUSED(parentRect), int WXUNUSED(style))
10871 {
10872 if (!LoadImageCache(dc))
10873 return false;
10874
10875 wxSize imageSize(m_imageCache.GetWidth(), m_imageCache.GetHeight());
10876 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
10877 contentRect = wxRect(wxPoint(0,0), imageSize);
10878
10879 wxRichTextAttr attr(GetAttributes());
10880 context.ApplyVirtualAttributes(attr, this);
10881
10882 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
10883
10884 wxSize overallSize = marginRect.GetSize();
10885
10886 SetCachedSize(overallSize);
10887 SetMaxSize(overallSize);
10888 SetMinSize(overallSize);
10889 SetPosition(rect.GetPosition());
10890
10891 return true;
10892 }
10893
10894 /// Get/set the object size for the given range. Returns false if the range
10895 /// is invalid for this object.
10896 bool wxRichTextImage::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& WXUNUSED(descent), wxDC& dc, wxRichTextDrawingContext& context, int WXUNUSED(flags), wxPoint WXUNUSED(position), wxArrayInt* partialExtents) const
10897 {
10898 if (!range.IsWithin(GetRange()))
10899 return false;
10900
10901 if (!((wxRichTextImage*)this)->LoadImageCache(dc))
10902 {
10903 size.x = 0; size.y = 0;
10904 if (partialExtents)
10905 partialExtents->Add(0);
10906 return false;
10907 }
10908
10909 wxRichTextAttr attr(GetAttributes());
10910 context.ApplyVirtualAttributes(attr, (wxRichTextObject*) this);
10911
10912 wxSize imageSize(m_imageCache.GetWidth(), m_imageCache.GetHeight());
10913 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
10914 contentRect = wxRect(wxPoint(0,0), imageSize);
10915 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
10916
10917 wxSize overallSize = marginRect.GetSize();
10918
10919 if (partialExtents)
10920 partialExtents->Add(overallSize.x);
10921
10922 size = overallSize;
10923
10924 return true;
10925 }
10926
10927 // Get the 'natural' size for an object. For an image, it would be the
10928 // image size.
10929 wxTextAttrSize wxRichTextImage::GetNaturalSize() const
10930 {
10931 wxTextAttrSize size;
10932 if (GetImageCache().IsOk())
10933 {
10934 size.SetWidth(GetImageCache().GetWidth(), wxTEXT_ATTR_UNITS_PIXELS);
10935 size.SetHeight(GetImageCache().GetHeight(), wxTEXT_ATTR_UNITS_PIXELS);
10936 }
10937 return size;
10938 }
10939
10940
10941 /// Copy
10942 void wxRichTextImage::Copy(const wxRichTextImage& obj)
10943 {
10944 wxRichTextObject::Copy(obj);
10945
10946 m_imageBlock = obj.m_imageBlock;
10947 m_originalImageSize = obj.m_originalImageSize;
10948 }
10949
10950 /// Edit properties via a GUI
10951 bool wxRichTextImage::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer)
10952 {
10953 wxRichTextObjectPropertiesDialog imageDlg(this, wxGetTopLevelParent(parent), wxID_ANY, _("Picture Properties"));
10954 imageDlg.SetAttributes(GetAttributes());
10955
10956 if (imageDlg.ShowModal() == wxID_OK)
10957 {
10958 // By passing wxRICHTEXT_SETSTYLE_RESET, indeterminate attributes set by the user will be set as
10959 // indeterminate in the object.
10960 imageDlg.ApplyStyle(buffer->GetRichTextCtrl(), wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_RESET);
10961 return true;
10962 }
10963 else
10964 return false;
10965 }
10966
10967 /*!
10968 * Utilities
10969 *
10970 */
10971
10972 /// Compare two attribute objects
10973 bool wxTextAttrEq(const wxRichTextAttr& attr1, const wxRichTextAttr& attr2)
10974 {
10975 return (attr1 == attr2);
10976 }
10977
10978 /// Compare tabs
10979 bool wxRichTextTabsEq(const wxArrayInt& tabs1, const wxArrayInt& tabs2)
10980 {
10981 if (tabs1.GetCount() != tabs2.GetCount())
10982 return false;
10983
10984 size_t i;
10985 for (i = 0; i < tabs1.GetCount(); i++)
10986 {
10987 if (tabs1[i] != tabs2[i])
10988 return false;
10989 }
10990 return true;
10991 }
10992
10993 bool wxRichTextApplyStyle(wxRichTextAttr& destStyle, const wxRichTextAttr& style, wxRichTextAttr* compareWith)
10994 {
10995 return destStyle.Apply(style, compareWith);
10996 }
10997
10998 // Remove attributes
10999 bool wxRichTextRemoveStyle(wxRichTextAttr& destStyle, const wxRichTextAttr& style)
11000 {
11001 return destStyle.RemoveStyle(style);
11002 }
11003
11004 /// Combine two bitlists, specifying the bits of interest with separate flags.
11005 bool wxRichTextCombineBitlists(int& valueA, int valueB, int& flagsA, int flagsB)
11006 {
11007 return wxRichTextAttr::CombineBitlists(valueA, valueB, flagsA, flagsB);
11008 }
11009
11010 /// Compare two bitlists
11011 bool wxRichTextBitlistsEqPartial(int valueA, int valueB, int flags)
11012 {
11013 return wxRichTextAttr::BitlistsEqPartial(valueA, valueB, flags);
11014 }
11015
11016 /// Split into paragraph and character styles
11017 bool wxRichTextSplitParaCharStyles(const wxRichTextAttr& style, wxRichTextAttr& parStyle, wxRichTextAttr& charStyle)
11018 {
11019 return wxRichTextAttr::SplitParaCharStyles(style, parStyle, charStyle);
11020 }
11021
11022 /// Convert a decimal to Roman numerals
11023 wxString wxRichTextDecimalToRoman(long n)
11024 {
11025 static wxArrayInt decimalNumbers;
11026 static wxArrayString romanNumbers;
11027
11028 // Clean up arrays
11029 if (n == -1)
11030 {
11031 decimalNumbers.Clear();
11032 romanNumbers.Clear();
11033 return wxEmptyString;
11034 }
11035
11036 if (decimalNumbers.GetCount() == 0)
11037 {
11038 #define wxRichTextAddDecRom(n, r) decimalNumbers.Add(n); romanNumbers.Add(r);
11039
11040 wxRichTextAddDecRom(1000, wxT("M"));
11041 wxRichTextAddDecRom(900, wxT("CM"));
11042 wxRichTextAddDecRom(500, wxT("D"));
11043 wxRichTextAddDecRom(400, wxT("CD"));
11044 wxRichTextAddDecRom(100, wxT("C"));
11045 wxRichTextAddDecRom(90, wxT("XC"));
11046 wxRichTextAddDecRom(50, wxT("L"));
11047 wxRichTextAddDecRom(40, wxT("XL"));
11048 wxRichTextAddDecRom(10, wxT("X"));
11049 wxRichTextAddDecRom(9, wxT("IX"));
11050 wxRichTextAddDecRom(5, wxT("V"));
11051 wxRichTextAddDecRom(4, wxT("IV"));
11052 wxRichTextAddDecRom(1, wxT("I"));
11053 }
11054
11055 int i = 0;
11056 wxString roman;
11057
11058 while (n > 0 && i < 13)
11059 {
11060 if (n >= decimalNumbers[i])
11061 {
11062 n -= decimalNumbers[i];
11063 roman += romanNumbers[i];
11064 }
11065 else
11066 {
11067 i ++;
11068 }
11069 }
11070 if (roman.IsEmpty())
11071 roman = wxT("0");
11072 return roman;
11073 }
11074
11075 /*!
11076 * wxRichTextFileHandler
11077 * Base class for file handlers
11078 */
11079
11080 IMPLEMENT_CLASS(wxRichTextFileHandler, wxObject)
11081
11082 #if wxUSE_FFILE && wxUSE_STREAMS
11083 bool wxRichTextFileHandler::LoadFile(wxRichTextBuffer *buffer, const wxString& filename)
11084 {
11085 wxFFileInputStream stream(filename);
11086 if (stream.IsOk())
11087 return LoadFile(buffer, stream);
11088
11089 return false;
11090 }
11091
11092 bool wxRichTextFileHandler::SaveFile(wxRichTextBuffer *buffer, const wxString& filename)
11093 {
11094 wxFFileOutputStream stream(filename);
11095 if (stream.IsOk())
11096 return SaveFile(buffer, stream);
11097
11098 return false;
11099 }
11100 #endif // wxUSE_FFILE && wxUSE_STREAMS
11101
11102 /// Can we handle this filename (if using files)? By default, checks the extension.
11103 bool wxRichTextFileHandler::CanHandle(const wxString& filename) const
11104 {
11105 wxString path, file, ext;
11106 wxFileName::SplitPath(filename, & path, & file, & ext);
11107
11108 return (ext.Lower() == GetExtension());
11109 }
11110
11111 /*!
11112 * wxRichTextTextHandler
11113 * Plain text handler
11114 */
11115
11116 IMPLEMENT_CLASS(wxRichTextPlainTextHandler, wxRichTextFileHandler)
11117
11118 #if wxUSE_STREAMS
11119 bool wxRichTextPlainTextHandler::DoLoadFile(wxRichTextBuffer *buffer, wxInputStream& stream)
11120 {
11121 if (!stream.IsOk())
11122 return false;
11123
11124 wxString str;
11125 int lastCh = 0;
11126
11127 while (!stream.Eof())
11128 {
11129 int ch = stream.GetC();
11130
11131 if (!stream.Eof())
11132 {
11133 if (ch == 10 && lastCh != 13)
11134 str += wxT('\n');
11135
11136 if (ch > 0 && ch != 10)
11137 str += wxChar(ch);
11138
11139 lastCh = ch;
11140 }
11141 }
11142
11143 buffer->ResetAndClearCommands();
11144 buffer->Clear();
11145 buffer->AddParagraphs(str);
11146 buffer->UpdateRanges();
11147
11148 return true;
11149 }
11150
11151 bool wxRichTextPlainTextHandler::DoSaveFile(wxRichTextBuffer *buffer, wxOutputStream& stream)
11152 {
11153 if (!stream.IsOk())
11154 return false;
11155
11156 wxString text = buffer->GetText();
11157
11158 wxString newLine = wxRichTextLineBreakChar;
11159 text.Replace(newLine, wxT("\n"));
11160
11161 wxCharBuffer buf = text.ToAscii();
11162
11163 stream.Write((const char*) buf, text.length());
11164 return true;
11165 }
11166 #endif // wxUSE_STREAMS
11167
11168 /*
11169 * Stores information about an image, in binary in-memory form
11170 */
11171
11172 wxRichTextImageBlock::wxRichTextImageBlock()
11173 {
11174 Init();
11175 }
11176
11177 wxRichTextImageBlock::wxRichTextImageBlock(const wxRichTextImageBlock& block):wxObject()
11178 {
11179 Init();
11180 Copy(block);
11181 }
11182
11183 wxRichTextImageBlock::~wxRichTextImageBlock()
11184 {
11185 wxDELETEA(m_data);
11186 }
11187
11188 void wxRichTextImageBlock::Init()
11189 {
11190 m_data = NULL;
11191 m_dataSize = 0;
11192 m_imageType = wxBITMAP_TYPE_INVALID;
11193 }
11194
11195 void wxRichTextImageBlock::Clear()
11196 {
11197 wxDELETEA(m_data);
11198 m_dataSize = 0;
11199 m_imageType = wxBITMAP_TYPE_INVALID;
11200 }
11201
11202
11203 // Load the original image into a memory block.
11204 // If the image is not a JPEG, we must convert it into a JPEG
11205 // to conserve space.
11206 // If it's not a JPEG we can make use of 'image', already scaled, so we don't have to
11207 // load the image a 2nd time.
11208
11209 bool wxRichTextImageBlock::MakeImageBlock(const wxString& filename, wxBitmapType imageType,
11210 wxImage& image, bool convertToJPEG)
11211 {
11212 m_imageType = imageType;
11213
11214 wxString filenameToRead(filename);
11215 bool removeFile = false;
11216
11217 if (imageType == wxBITMAP_TYPE_INVALID)
11218 return false; // Could not determine image type
11219
11220 if ((imageType != wxBITMAP_TYPE_JPEG) && convertToJPEG)
11221 {
11222 wxString tempFile =
11223 wxFileName::CreateTempFileName(_("image"));
11224
11225 wxASSERT(!tempFile.IsEmpty());
11226
11227 image.SaveFile(tempFile, wxBITMAP_TYPE_JPEG);
11228 filenameToRead = tempFile;
11229 removeFile = true;
11230
11231 m_imageType = wxBITMAP_TYPE_JPEG;
11232 }
11233 wxFile file;
11234 if (!file.Open(filenameToRead))
11235 return false;
11236
11237 m_dataSize = (size_t) file.Length();
11238 file.Close();
11239
11240 if (m_data)
11241 delete[] m_data;
11242 m_data = ReadBlock(filenameToRead, m_dataSize);
11243
11244 if (removeFile)
11245 wxRemoveFile(filenameToRead);
11246
11247 return (m_data != NULL);
11248 }
11249
11250 // Make an image block from the wxImage in the given
11251 // format.
11252 bool wxRichTextImageBlock::MakeImageBlock(wxImage& image, wxBitmapType imageType, int quality)
11253 {
11254 image.SetOption(wxT("quality"), quality);
11255
11256 if (imageType == wxBITMAP_TYPE_INVALID)
11257 return false; // Could not determine image type
11258
11259 return DoMakeImageBlock(image, imageType);
11260 }
11261
11262 // Uses a const wxImage for efficiency, but can't set quality (only relevant for JPEG)
11263 bool wxRichTextImageBlock::MakeImageBlockDefaultQuality(const wxImage& image, wxBitmapType imageType)
11264 {
11265 if (imageType == wxBITMAP_TYPE_INVALID)
11266 return false; // Could not determine image type
11267
11268 return DoMakeImageBlock(image, imageType);
11269 }
11270
11271 // Makes the image block
11272 bool wxRichTextImageBlock::DoMakeImageBlock(const wxImage& image, wxBitmapType imageType)
11273 {
11274 wxMemoryOutputStream memStream;
11275 if (!image.SaveFile(memStream, imageType))
11276 {
11277 return false;
11278 }
11279
11280 unsigned char* block = new unsigned char[memStream.GetSize()];
11281 if (!block)
11282 return false;
11283
11284 if (m_data)
11285 delete[] m_data;
11286 m_data = block;
11287
11288 m_imageType = imageType;
11289 m_dataSize = memStream.GetSize();
11290
11291 memStream.CopyTo(m_data, m_dataSize);
11292
11293 return (m_data != NULL);
11294 }
11295
11296 // Write to a file
11297 bool wxRichTextImageBlock::Write(const wxString& filename)
11298 {
11299 return WriteBlock(filename, m_data, m_dataSize);
11300 }
11301
11302 void wxRichTextImageBlock::Copy(const wxRichTextImageBlock& block)
11303 {
11304 m_imageType = block.m_imageType;
11305 wxDELETEA(m_data);
11306 m_dataSize = block.m_dataSize;
11307 if (m_dataSize == 0)
11308 return;
11309
11310 m_data = new unsigned char[m_dataSize];
11311 unsigned int i;
11312 for (i = 0; i < m_dataSize; i++)
11313 m_data[i] = block.m_data[i];
11314 }
11315
11316 //// Operators
11317 void wxRichTextImageBlock::operator=(const wxRichTextImageBlock& block)
11318 {
11319 Copy(block);
11320 }
11321
11322 // Load a wxImage from the block
11323 bool wxRichTextImageBlock::Load(wxImage& image)
11324 {
11325 if (!m_data)
11326 return false;
11327
11328 // Read in the image.
11329 #if wxUSE_STREAMS
11330 wxMemoryInputStream mstream(m_data, m_dataSize);
11331 bool success = image.LoadFile(mstream, GetImageType());
11332 #else
11333 wxString tempFile = wxFileName::CreateTempFileName(_("image"));
11334 wxASSERT(!tempFile.IsEmpty());
11335
11336 if (!WriteBlock(tempFile, m_data, m_dataSize))
11337 {
11338 return false;
11339 }
11340 success = image.LoadFile(tempFile, GetImageType());
11341 wxRemoveFile(tempFile);
11342 #endif
11343
11344 return success;
11345 }
11346
11347 // Write data in hex to a stream
11348 bool wxRichTextImageBlock::WriteHex(wxOutputStream& stream)
11349 {
11350 if (m_dataSize == 0)
11351 return true;
11352
11353 int bufSize = 100000;
11354 if (int(2*m_dataSize) < bufSize)
11355 bufSize = 2*m_dataSize;
11356 char* buf = new char[bufSize+1];
11357
11358 int left = m_dataSize;
11359 int n, i, j;
11360 j = 0;
11361 while (left > 0)
11362 {
11363 if (left*2 > bufSize)
11364 {
11365 n = bufSize; left -= (bufSize/2);
11366 }
11367 else
11368 {
11369 n = left*2; left = 0;
11370 }
11371
11372 char* b = buf;
11373 for (i = 0; i < (n/2); i++)
11374 {
11375 wxDecToHex(m_data[j], b, b+1);
11376 b += 2; j ++;
11377 }
11378
11379 buf[n] = 0;
11380 stream.Write((const char*) buf, n);
11381 }
11382 delete[] buf;
11383 return true;
11384 }
11385
11386 // Read data in hex from a stream
11387 bool wxRichTextImageBlock::ReadHex(wxInputStream& stream, int length, wxBitmapType imageType)
11388 {
11389 int dataSize = length/2;
11390
11391 if (m_data)
11392 delete[] m_data;
11393
11394 // create a null terminated temporary string:
11395 char str[3];
11396 str[2] = '\0';
11397
11398 m_data = new unsigned char[dataSize];
11399 int i;
11400 for (i = 0; i < dataSize; i ++)
11401 {
11402 str[0] = (char)stream.GetC();
11403 str[1] = (char)stream.GetC();
11404
11405 m_data[i] = (unsigned char)wxHexToDec(str);
11406 }
11407
11408 m_dataSize = dataSize;
11409 m_imageType = imageType;
11410
11411 return true;
11412 }
11413
11414 // Allocate and read from stream as a block of memory
11415 unsigned char* wxRichTextImageBlock::ReadBlock(wxInputStream& stream, size_t size)
11416 {
11417 unsigned char* block = new unsigned char[size];
11418 if (!block)
11419 return NULL;
11420
11421 stream.Read(block, size);
11422
11423 return block;
11424 }
11425
11426 unsigned char* wxRichTextImageBlock::ReadBlock(const wxString& filename, size_t size)
11427 {
11428 wxFileInputStream stream(filename);
11429 if (!stream.IsOk())
11430 return NULL;
11431
11432 return ReadBlock(stream, size);
11433 }
11434
11435 // Write memory block to stream
11436 bool wxRichTextImageBlock::WriteBlock(wxOutputStream& stream, unsigned char* block, size_t size)
11437 {
11438 stream.Write((void*) block, size);
11439 return stream.IsOk();
11440
11441 }
11442
11443 // Write memory block to file
11444 bool wxRichTextImageBlock::WriteBlock(const wxString& filename, unsigned char* block, size_t size)
11445 {
11446 wxFileOutputStream outStream(filename);
11447 if (!outStream.IsOk())
11448 return false;
11449
11450 return WriteBlock(outStream, block, size);
11451 }
11452
11453 // Gets the extension for the block's type
11454 wxString wxRichTextImageBlock::GetExtension() const
11455 {
11456 wxImageHandler* handler = wxImage::FindHandler(GetImageType());
11457 if (handler)
11458 return handler->GetExtension();
11459 else
11460 return wxEmptyString;
11461 }
11462
11463 #if wxUSE_DATAOBJ
11464
11465 /*!
11466 * The data object for a wxRichTextBuffer
11467 */
11468
11469 const wxChar *wxRichTextBufferDataObject::ms_richTextBufferFormatId = wxT("wxShape");
11470
11471 wxRichTextBufferDataObject::wxRichTextBufferDataObject(wxRichTextBuffer* richTextBuffer)
11472 {
11473 m_richTextBuffer = richTextBuffer;
11474
11475 // this string should uniquely identify our format, but is otherwise
11476 // arbitrary
11477 m_formatRichTextBuffer.SetId(GetRichTextBufferFormatId());
11478
11479 SetFormat(m_formatRichTextBuffer);
11480 }
11481
11482 wxRichTextBufferDataObject::~wxRichTextBufferDataObject()
11483 {
11484 delete m_richTextBuffer;
11485 }
11486
11487 // after a call to this function, the richTextBuffer is owned by the caller and it
11488 // is responsible for deleting it!
11489 wxRichTextBuffer* wxRichTextBufferDataObject::GetRichTextBuffer()
11490 {
11491 wxRichTextBuffer* richTextBuffer = m_richTextBuffer;
11492 m_richTextBuffer = NULL;
11493
11494 return richTextBuffer;
11495 }
11496
11497 wxDataFormat wxRichTextBufferDataObject::GetPreferredFormat(Direction WXUNUSED(dir)) const
11498 {
11499 return m_formatRichTextBuffer;
11500 }
11501
11502 size_t wxRichTextBufferDataObject::GetDataSize() const
11503 {
11504 if (!m_richTextBuffer)
11505 return 0;
11506
11507 wxString bufXML;
11508
11509 {
11510 wxStringOutputStream stream(& bufXML);
11511 if (!m_richTextBuffer->SaveFile(stream, wxRICHTEXT_TYPE_XML))
11512 {
11513 wxLogError(wxT("Could not write the buffer to an XML stream.\nYou may have forgotten to add the XML file handler."));
11514 return 0;
11515 }
11516 }
11517
11518 #if wxUSE_UNICODE
11519 wxCharBuffer buffer = bufXML.mb_str(wxConvUTF8);
11520 return strlen(buffer) + 1;
11521 #else
11522 return bufXML.Length()+1;
11523 #endif
11524 }
11525
11526 bool wxRichTextBufferDataObject::GetDataHere(void *pBuf) const
11527 {
11528 if (!pBuf || !m_richTextBuffer)
11529 return false;
11530
11531 wxString bufXML;
11532
11533 {
11534 wxStringOutputStream stream(& bufXML);
11535 if (!m_richTextBuffer->SaveFile(stream, wxRICHTEXT_TYPE_XML))
11536 {
11537 wxLogError(wxT("Could not write the buffer to an XML stream.\nYou may have forgotten to add the XML file handler."));
11538 return 0;
11539 }
11540 }
11541
11542 #if wxUSE_UNICODE
11543 wxCharBuffer buffer = bufXML.mb_str(wxConvUTF8);
11544 size_t len = strlen(buffer);
11545 memcpy((char*) pBuf, (const char*) buffer, len);
11546 ((char*) pBuf)[len] = 0;
11547 #else
11548 size_t len = bufXML.Length();
11549 memcpy((char*) pBuf, (const char*) bufXML.c_str(), len);
11550 ((char*) pBuf)[len] = 0;
11551 #endif
11552
11553 return true;
11554 }
11555
11556 bool wxRichTextBufferDataObject::SetData(size_t WXUNUSED(len), const void *buf)
11557 {
11558 wxDELETE(m_richTextBuffer);
11559
11560 wxString bufXML((const char*) buf, wxConvUTF8);
11561
11562 m_richTextBuffer = new wxRichTextBuffer;
11563
11564 wxStringInputStream stream(bufXML);
11565 if (!m_richTextBuffer->LoadFile(stream, wxRICHTEXT_TYPE_XML))
11566 {
11567 wxLogError(wxT("Could not read the buffer from an XML stream.\nYou may have forgotten to add the XML file handler."));
11568
11569 wxDELETE(m_richTextBuffer);
11570
11571 return false;
11572 }
11573 return true;
11574 }
11575
11576 #endif
11577 // wxUSE_DATAOBJ
11578
11579
11580 /*
11581 * wxRichTextFontTable
11582 * Manages quick access to a pool of fonts for rendering rich text
11583 */
11584
11585 WX_DECLARE_STRING_HASH_MAP_WITH_DECL(wxFont, wxRichTextFontTableHashMap, class WXDLLIMPEXP_RICHTEXT);
11586
11587 class wxRichTextFontTableData: public wxObjectRefData
11588 {
11589 public:
11590 wxRichTextFontTableData() {}
11591
11592 wxFont FindFont(const wxRichTextAttr& fontSpec, double fontScale);
11593
11594 wxRichTextFontTableHashMap m_hashMap;
11595 };
11596
11597 wxFont wxRichTextFontTableData::FindFont(const wxRichTextAttr& fontSpec, double fontScale)
11598 {
11599 wxString facename(fontSpec.GetFontFaceName());
11600
11601 int fontSize = fontSpec.GetFontSize();
11602 if (fontScale != 1.0)
11603 fontSize = (int) ((double(fontSize) * fontScale) + 0.5);
11604
11605 wxString units;
11606 if (fontSpec.HasFontPixelSize() && !fontSpec.HasFontPointSize())
11607 units = wxT("px");
11608 else
11609 units = wxT("pt");
11610 wxString spec = wxString::Format(wxT("%d-%s-%d-%d-%d-%d-%s-%d"),
11611 fontSize, units.c_str(), fontSpec.GetFontStyle(), fontSpec.GetFontWeight(), (int) fontSpec.GetFontUnderlined(), (int) fontSpec.GetFontStrikethrough(),
11612 facename.c_str(), (int) fontSpec.GetFontEncoding());
11613
11614 wxRichTextFontTableHashMap::iterator entry = m_hashMap.find(spec);
11615 if ( entry == m_hashMap.end() )
11616 {
11617 if (fontSpec.HasFontPixelSize() && !fontSpec.HasFontPointSize())
11618 {
11619 wxFont font(wxSize(0, fontSize), wxFONTFAMILY_DEFAULT, fontSpec.GetFontStyle(), fontSpec.GetFontWeight(), fontSpec.GetFontUnderlined(), facename);
11620 if (fontSpec.HasFontStrikethrough() && fontSpec.GetFontStrikethrough())
11621 font.SetStrikethrough(true);
11622 m_hashMap[spec] = font;
11623 return font;
11624 }
11625 else
11626 {
11627 wxFont font(fontSize, wxFONTFAMILY_DEFAULT, fontSpec.GetFontStyle(), fontSpec.GetFontWeight(), fontSpec.GetFontUnderlined(), facename.c_str());
11628 if (fontSpec.HasFontStrikethrough() && fontSpec.GetFontStrikethrough())
11629 font.SetStrikethrough(true);
11630
11631 m_hashMap[spec] = font;
11632 return font;
11633 }
11634 }
11635 else
11636 {
11637 return entry->second;
11638 }
11639 }
11640
11641 IMPLEMENT_DYNAMIC_CLASS(wxRichTextFontTable, wxObject)
11642
11643 wxRichTextFontTable::wxRichTextFontTable()
11644 {
11645 m_refData = new wxRichTextFontTableData;
11646 m_fontScale = 1.0;
11647 }
11648
11649 wxRichTextFontTable::wxRichTextFontTable(const wxRichTextFontTable& table)
11650 : wxObject()
11651 {
11652 (*this) = table;
11653 }
11654
11655 wxRichTextFontTable::~wxRichTextFontTable()
11656 {
11657 UnRef();
11658 }
11659
11660 bool wxRichTextFontTable::operator == (const wxRichTextFontTable& table) const
11661 {
11662 return (m_refData == table.m_refData);
11663 }
11664
11665 void wxRichTextFontTable::operator= (const wxRichTextFontTable& table)
11666 {
11667 Ref(table);
11668 m_fontScale = table.m_fontScale;
11669 }
11670
11671 wxFont wxRichTextFontTable::FindFont(const wxRichTextAttr& fontSpec)
11672 {
11673 wxRichTextFontTableData* data = (wxRichTextFontTableData*) m_refData;
11674 if (data)
11675 return data->FindFont(fontSpec, m_fontScale);
11676 else
11677 return wxFont();
11678 }
11679
11680 void wxRichTextFontTable::Clear()
11681 {
11682 wxRichTextFontTableData* data = (wxRichTextFontTableData*) m_refData;
11683 if (data)
11684 data->m_hashMap.clear();
11685 }
11686
11687 void wxRichTextFontTable::SetFontScale(double fontScale)
11688 {
11689 if (fontScale != m_fontScale)
11690 Clear();
11691 m_fontScale = fontScale;
11692 }
11693
11694 // wxTextBoxAttr
11695
11696 void wxTextBoxAttr::Reset()
11697 {
11698 m_flags = 0;
11699 m_floatMode = wxTEXT_BOX_ATTR_FLOAT_NONE;
11700 m_clearMode = wxTEXT_BOX_ATTR_CLEAR_NONE;
11701 m_collapseMode = wxTEXT_BOX_ATTR_COLLAPSE_NONE;
11702 m_verticalAlignment = wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT_NONE;
11703 m_boxStyleName = wxEmptyString;
11704
11705 m_margins.Reset();
11706 m_padding.Reset();
11707 m_position.Reset();
11708
11709 m_size.Reset();
11710 m_minSize.Reset();
11711 m_maxSize.Reset();
11712
11713 m_border.Reset();
11714 m_outline.Reset();
11715 }
11716
11717 // Equality test
11718 bool wxTextBoxAttr::operator== (const wxTextBoxAttr& attr) const
11719 {
11720 return (
11721 m_flags == attr.m_flags &&
11722 m_floatMode == attr.m_floatMode &&
11723 m_clearMode == attr.m_clearMode &&
11724 m_collapseMode == attr.m_collapseMode &&
11725 m_verticalAlignment == attr.m_verticalAlignment &&
11726
11727 m_margins == attr.m_margins &&
11728 m_padding == attr.m_padding &&
11729 m_position == attr.m_position &&
11730
11731 m_size == attr.m_size &&
11732 m_minSize == attr.m_minSize &&
11733 m_maxSize == attr.m_maxSize &&
11734
11735 m_border == attr.m_border &&
11736 m_outline == attr.m_outline &&
11737
11738 m_boxStyleName == attr.m_boxStyleName
11739 );
11740 }
11741
11742 // Partial equality test
11743 bool wxTextBoxAttr::EqPartial(const wxTextBoxAttr& attr, bool weakTest) const
11744 {
11745 if (!weakTest &&
11746 ((!HasFloatMode() && attr.HasFloatMode()) ||
11747 (!HasClearMode() && attr.HasClearMode()) ||
11748 (!HasCollapseBorders() && attr.HasCollapseBorders()) ||
11749 (!HasVerticalAlignment() && attr.HasVerticalAlignment()) ||
11750 (!HasBoxStyleName() && attr.HasBoxStyleName())))
11751 {
11752 return false;
11753 }
11754 if (attr.HasFloatMode() && HasFloatMode() && (GetFloatMode() != attr.GetFloatMode()))
11755 return false;
11756
11757 if (attr.HasClearMode() && HasClearMode() && (GetClearMode() != attr.GetClearMode()))
11758 return false;
11759
11760 if (attr.HasCollapseBorders() && HasCollapseBorders() && (attr.GetCollapseBorders() != GetCollapseBorders()))
11761 return false;
11762
11763 if (attr.HasVerticalAlignment() && HasVerticalAlignment() && (attr.GetVerticalAlignment() != GetVerticalAlignment()))
11764 return false;
11765
11766 if (attr.HasBoxStyleName() && HasBoxStyleName() && (attr.GetBoxStyleName() != GetBoxStyleName()))
11767 return false;
11768
11769 // Position
11770
11771 if (!m_position.EqPartial(attr.m_position, weakTest))
11772 return false;
11773
11774 // Size
11775
11776 if (!m_size.EqPartial(attr.m_size, weakTest))
11777 return false;
11778 if (!m_minSize.EqPartial(attr.m_minSize, weakTest))
11779 return false;
11780 if (!m_maxSize.EqPartial(attr.m_maxSize, weakTest))
11781 return false;
11782
11783 // Margins
11784
11785 if (!m_margins.EqPartial(attr.m_margins, weakTest))
11786 return false;
11787
11788 // Padding
11789
11790 if (!m_padding.EqPartial(attr.m_padding, weakTest))
11791 return false;
11792
11793 // Border
11794
11795 if (!GetBorder().EqPartial(attr.GetBorder(), weakTest))
11796 return false;
11797
11798 // Outline
11799
11800 if (!GetOutline().EqPartial(attr.GetOutline(), weakTest))
11801 return false;
11802
11803 return true;
11804 }
11805
11806 // Merges the given attributes. If compareWith
11807 // is non-NULL, then it will be used to mask out those attributes that are the same in style
11808 // and compareWith, for situations where we don't want to explicitly set inherited attributes.
11809 bool wxTextBoxAttr::Apply(const wxTextBoxAttr& attr, const wxTextBoxAttr* compareWith)
11810 {
11811 if (attr.HasFloatMode())
11812 {
11813 if (!(compareWith && compareWith->HasFloatMode() && compareWith->GetFloatMode() == attr.GetFloatMode()))
11814 SetFloatMode(attr.GetFloatMode());
11815 }
11816
11817 if (attr.HasClearMode())
11818 {
11819 if (!(compareWith && compareWith->HasClearMode() && compareWith->GetClearMode() == attr.GetClearMode()))
11820 SetClearMode(attr.GetClearMode());
11821 }
11822
11823 if (attr.HasCollapseBorders())
11824 {
11825 if (!(compareWith && compareWith->HasCollapseBorders() && compareWith->GetCollapseBorders() == attr.GetCollapseBorders()))
11826 SetCollapseBorders(attr.GetCollapseBorders());
11827 }
11828
11829 if (attr.HasVerticalAlignment())
11830 {
11831 if (!(compareWith && compareWith->HasVerticalAlignment() && compareWith->GetVerticalAlignment() == attr.GetVerticalAlignment()))
11832 SetVerticalAlignment(attr.GetVerticalAlignment());
11833 }
11834
11835 if (attr.HasBoxStyleName())
11836 {
11837 if (!(compareWith && compareWith->HasBoxStyleName() && compareWith->GetBoxStyleName() == attr.GetBoxStyleName()))
11838 SetBoxStyleName(attr.GetBoxStyleName());
11839 }
11840
11841 m_margins.Apply(attr.m_margins, compareWith ? (& attr.m_margins) : (const wxTextAttrDimensions*) NULL);
11842 m_padding.Apply(attr.m_padding, compareWith ? (& attr.m_padding) : (const wxTextAttrDimensions*) NULL);
11843 m_position.Apply(attr.m_position, compareWith ? (& attr.m_position) : (const wxTextAttrDimensions*) NULL);
11844
11845 m_size.Apply(attr.m_size, compareWith ? (& attr.m_size) : (const wxTextAttrSize*) NULL);
11846 m_minSize.Apply(attr.m_minSize, compareWith ? (& attr.m_minSize) : (const wxTextAttrSize*) NULL);
11847 m_maxSize.Apply(attr.m_maxSize, compareWith ? (& attr.m_maxSize) : (const wxTextAttrSize*) NULL);
11848
11849 m_border.Apply(attr.m_border, compareWith ? (& attr.m_border) : (const wxTextAttrBorders*) NULL);
11850 m_outline.Apply(attr.m_outline, compareWith ? (& attr.m_outline) : (const wxTextAttrBorders*) NULL);
11851
11852 return true;
11853 }
11854
11855 // Remove specified attributes from this object
11856 bool wxTextBoxAttr::RemoveStyle(const wxTextBoxAttr& attr)
11857 {
11858 if (attr.HasFloatMode())
11859 RemoveFlag(wxTEXT_BOX_ATTR_FLOAT);
11860
11861 if (attr.HasClearMode())
11862 RemoveFlag(wxTEXT_BOX_ATTR_CLEAR);
11863
11864 if (attr.HasCollapseBorders())
11865 RemoveFlag(wxTEXT_BOX_ATTR_COLLAPSE_BORDERS);
11866
11867 if (attr.HasVerticalAlignment())
11868 RemoveFlag(wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT);
11869
11870 if (attr.HasBoxStyleName())
11871 {
11872 SetBoxStyleName(wxEmptyString);
11873 RemoveFlag(wxTEXT_BOX_ATTR_BOX_STYLE_NAME);
11874 }
11875
11876 m_margins.RemoveStyle(attr.m_margins);
11877 m_padding.RemoveStyle(attr.m_padding);
11878 m_position.RemoveStyle(attr.m_position);
11879
11880 m_size.RemoveStyle(attr.m_size);
11881 m_minSize.RemoveStyle(attr.m_minSize);
11882 m_maxSize.RemoveStyle(attr.m_maxSize);
11883
11884 m_border.RemoveStyle(attr.m_border);
11885 m_outline.RemoveStyle(attr.m_outline);
11886
11887 return true;
11888 }
11889
11890 // Collects the attributes that are common to a range of content, building up a note of
11891 // which attributes are absent in some objects and which clash in some objects.
11892 void wxTextBoxAttr::CollectCommonAttributes(const wxTextBoxAttr& attr, wxTextBoxAttr& clashingAttr, wxTextBoxAttr& absentAttr)
11893 {
11894 if (attr.HasFloatMode())
11895 {
11896 if (!clashingAttr.HasFloatMode() && !absentAttr.HasFloatMode())
11897 {
11898 if (HasFloatMode())
11899 {
11900 if (GetFloatMode() != attr.GetFloatMode())
11901 {
11902 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_FLOAT);
11903 RemoveFlag(wxTEXT_BOX_ATTR_FLOAT);
11904 }
11905 }
11906 else
11907 SetFloatMode(attr.GetFloatMode());
11908 }
11909 }
11910 else
11911 absentAttr.AddFlag(wxTEXT_BOX_ATTR_FLOAT);
11912
11913 if (attr.HasClearMode())
11914 {
11915 if (!clashingAttr.HasClearMode() && !absentAttr.HasClearMode())
11916 {
11917 if (HasClearMode())
11918 {
11919 if (GetClearMode() != attr.GetClearMode())
11920 {
11921 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_CLEAR);
11922 RemoveFlag(wxTEXT_BOX_ATTR_CLEAR);
11923 }
11924 }
11925 else
11926 SetClearMode(attr.GetClearMode());
11927 }
11928 }
11929 else
11930 absentAttr.AddFlag(wxTEXT_BOX_ATTR_CLEAR);
11931
11932 if (attr.HasCollapseBorders())
11933 {
11934 if (!clashingAttr.HasCollapseBorders() && !absentAttr.HasCollapseBorders())
11935 {
11936 if (HasCollapseBorders())
11937 {
11938 if (GetCollapseBorders() != attr.GetCollapseBorders())
11939 {
11940 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_COLLAPSE_BORDERS);
11941 RemoveFlag(wxTEXT_BOX_ATTR_COLLAPSE_BORDERS);
11942 }
11943 }
11944 else
11945 SetCollapseBorders(attr.GetCollapseBorders());
11946 }
11947 }
11948 else
11949 absentAttr.AddFlag(wxTEXT_BOX_ATTR_COLLAPSE_BORDERS);
11950
11951 if (attr.HasVerticalAlignment())
11952 {
11953 if (!clashingAttr.HasVerticalAlignment() && !absentAttr.HasVerticalAlignment())
11954 {
11955 if (HasVerticalAlignment())
11956 {
11957 if (GetVerticalAlignment() != attr.GetVerticalAlignment())
11958 {
11959 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT);
11960 RemoveFlag(wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT);
11961 }
11962 }
11963 else
11964 SetVerticalAlignment(attr.GetVerticalAlignment());
11965 }
11966 }
11967 else
11968 absentAttr.AddFlag(wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT);
11969
11970 if (attr.HasBoxStyleName())
11971 {
11972 if (!clashingAttr.HasBoxStyleName() && !absentAttr.HasBoxStyleName())
11973 {
11974 if (HasBoxStyleName())
11975 {
11976 if (GetBoxStyleName() != attr.GetBoxStyleName())
11977 {
11978 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_BOX_STYLE_NAME);
11979 RemoveFlag(wxTEXT_BOX_ATTR_BOX_STYLE_NAME);
11980 }
11981 }
11982 else
11983 SetBoxStyleName(attr.GetBoxStyleName());
11984 }
11985 }
11986 else
11987 absentAttr.AddFlag(wxTEXT_BOX_ATTR_BOX_STYLE_NAME);
11988
11989 m_margins.CollectCommonAttributes(attr.m_margins, clashingAttr.m_margins, absentAttr.m_margins);
11990 m_padding.CollectCommonAttributes(attr.m_padding, clashingAttr.m_padding, absentAttr.m_padding);
11991 m_position.CollectCommonAttributes(attr.m_position, clashingAttr.m_position, absentAttr.m_position);
11992
11993 m_size.CollectCommonAttributes(attr.m_size, clashingAttr.m_size, absentAttr.m_size);
11994 m_minSize.CollectCommonAttributes(attr.m_minSize, clashingAttr.m_minSize, absentAttr.m_minSize);
11995 m_maxSize.CollectCommonAttributes(attr.m_maxSize, clashingAttr.m_maxSize, absentAttr.m_maxSize);
11996
11997 m_border.CollectCommonAttributes(attr.m_border, clashingAttr.m_border, absentAttr.m_border);
11998 m_outline.CollectCommonAttributes(attr.m_outline, clashingAttr.m_outline, absentAttr.m_outline);
11999 }
12000
12001 bool wxTextBoxAttr::IsDefault() const
12002 {
12003 return GetFlags() == 0 && !m_border.IsValid() && !m_outline.IsValid() &&
12004 !m_size.IsValid() && !m_minSize.IsValid() && !m_maxSize.IsValid() &&
12005 !m_position.IsValid() && !m_padding.IsValid() && !m_margins.IsValid();
12006 }
12007
12008 // wxRichTextAttr
12009
12010 void wxRichTextAttr::Copy(const wxRichTextAttr& attr)
12011 {
12012 wxTextAttr::Copy(attr);
12013
12014 m_textBoxAttr = attr.m_textBoxAttr;
12015 }
12016
12017 bool wxRichTextAttr::operator==(const wxRichTextAttr& attr) const
12018 {
12019 if (!(wxTextAttr::operator==(attr)))
12020 return false;
12021
12022 return (m_textBoxAttr == attr.m_textBoxAttr);
12023 }
12024
12025 // Partial equality test
12026 bool wxRichTextAttr::EqPartial(const wxRichTextAttr& attr, bool weakTest) const
12027 {
12028 if (!(wxTextAttr::EqPartial(attr, weakTest)))
12029 return false;
12030
12031 return m_textBoxAttr.EqPartial(attr.m_textBoxAttr, weakTest);
12032 }
12033
12034 // Merges the given attributes. If compareWith
12035 // is non-NULL, then it will be used to mask out those attributes that are the same in style
12036 // and compareWith, for situations where we don't want to explicitly set inherited attributes.
12037 bool wxRichTextAttr::Apply(const wxRichTextAttr& style, const wxRichTextAttr* compareWith)
12038 {
12039 wxTextAttr::Apply(style, compareWith);
12040
12041 return m_textBoxAttr.Apply(style.m_textBoxAttr, compareWith ? (& compareWith->m_textBoxAttr) : (const wxTextBoxAttr*) NULL);
12042 }
12043
12044 // Remove specified attributes from this object
12045 bool wxRichTextAttr::RemoveStyle(const wxRichTextAttr& attr)
12046 {
12047 wxTextAttr::RemoveStyle(*this, attr);
12048
12049 return m_textBoxAttr.RemoveStyle(attr.m_textBoxAttr);
12050 }
12051
12052 // Collects the attributes that are common to a range of content, building up a note of
12053 // which attributes are absent in some objects and which clash in some objects.
12054 void wxRichTextAttr::CollectCommonAttributes(const wxRichTextAttr& attr, wxRichTextAttr& clashingAttr, wxRichTextAttr& absentAttr)
12055 {
12056 wxTextAttrCollectCommonAttributes(*this, attr, clashingAttr, absentAttr);
12057
12058 m_textBoxAttr.CollectCommonAttributes(attr.m_textBoxAttr, clashingAttr.m_textBoxAttr, absentAttr.m_textBoxAttr);
12059 }
12060
12061 // Partial equality test
12062 bool wxTextAttrBorder::EqPartial(const wxTextAttrBorder& border, bool weakTest) const
12063 {
12064 if (!weakTest &&
12065 ((!HasStyle() && border.HasStyle()) ||
12066 (!HasColour() && border.HasColour()) ||
12067 (!HasWidth() && border.HasWidth())))
12068 {
12069 return false;
12070 }
12071
12072 if (border.HasStyle() && HasStyle() && (border.GetStyle() != GetStyle()))
12073 return false;
12074
12075 if (border.HasColour() && HasColour() && (border.GetColourLong() != GetColourLong()))
12076 return false;
12077
12078 if (border.HasWidth() && HasWidth() && !(border.GetWidth() == GetWidth()))
12079 return false;
12080
12081 return true;
12082 }
12083
12084 // Apply border to 'this', but not if the same as compareWith
12085 bool wxTextAttrBorder::Apply(const wxTextAttrBorder& border, const wxTextAttrBorder* compareWith)
12086 {
12087 if (border.HasStyle())
12088 {
12089 if (!(compareWith && (border.GetStyle() == compareWith->GetStyle())))
12090 SetStyle(border.GetStyle());
12091 }
12092 if (border.HasColour())
12093 {
12094 if (!(compareWith && (border.GetColourLong() == compareWith->GetColourLong())))
12095 SetColour(border.GetColourLong());
12096 }
12097 if (border.HasWidth())
12098 {
12099 if (!(compareWith && (border.GetWidth() == compareWith->GetWidth())))
12100 SetWidth(border.GetWidth());
12101 }
12102
12103 return true;
12104 }
12105
12106 // Remove specified attributes from this object
12107 bool wxTextAttrBorder::RemoveStyle(const wxTextAttrBorder& attr)
12108 {
12109 if (attr.HasStyle() && HasStyle())
12110 SetFlags(GetFlags() & ~wxTEXT_BOX_ATTR_BORDER_STYLE);
12111 if (attr.HasColour() && HasColour())
12112 SetFlags(GetFlags() & ~wxTEXT_BOX_ATTR_BORDER_COLOUR);
12113 if (attr.HasWidth() && HasWidth())
12114 m_borderWidth.Reset();
12115
12116 return true;
12117 }
12118
12119 // Collects the attributes that are common to a range of content, building up a note of
12120 // which attributes are absent in some objects and which clash in some objects.
12121 void wxTextAttrBorder::CollectCommonAttributes(const wxTextAttrBorder& attr, wxTextAttrBorder& clashingAttr, wxTextAttrBorder& absentAttr)
12122 {
12123 if (attr.HasStyle())
12124 {
12125 if (!clashingAttr.HasStyle() && !absentAttr.HasStyle())
12126 {
12127 if (HasStyle())
12128 {
12129 if (GetStyle() != attr.GetStyle())
12130 {
12131 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_BORDER_STYLE);
12132 RemoveFlag(wxTEXT_BOX_ATTR_BORDER_STYLE);
12133 }
12134 }
12135 else
12136 SetStyle(attr.GetStyle());
12137 }
12138 }
12139 else
12140 absentAttr.AddFlag(wxTEXT_BOX_ATTR_BORDER_STYLE);
12141
12142 if (attr.HasColour())
12143 {
12144 if (!clashingAttr.HasColour() && !absentAttr.HasColour())
12145 {
12146 if (HasColour())
12147 {
12148 if (GetColour() != attr.GetColour())
12149 {
12150 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_BORDER_COLOUR);
12151 RemoveFlag(wxTEXT_BOX_ATTR_BORDER_COLOUR);
12152 }
12153 }
12154 else
12155 SetColour(attr.GetColourLong());
12156 }
12157 }
12158 else
12159 absentAttr.AddFlag(wxTEXT_BOX_ATTR_BORDER_COLOUR);
12160
12161 m_borderWidth.CollectCommonAttributes(attr.m_borderWidth, clashingAttr.m_borderWidth, absentAttr.m_borderWidth);
12162 }
12163
12164 // Partial equality test
12165 bool wxTextAttrBorders::EqPartial(const wxTextAttrBorders& borders, bool weakTest) const
12166 {
12167 return m_left.EqPartial(borders.m_left, weakTest) && m_right.EqPartial(borders.m_right, weakTest) &&
12168 m_top.EqPartial(borders.m_top, weakTest) && m_bottom.EqPartial(borders.m_bottom, weakTest);
12169 }
12170
12171 // Apply border to 'this', but not if the same as compareWith
12172 bool wxTextAttrBorders::Apply(const wxTextAttrBorders& borders, const wxTextAttrBorders* compareWith)
12173 {
12174 m_left.Apply(borders.m_left, compareWith ? (& compareWith->m_left) : (const wxTextAttrBorder*) NULL);
12175 m_right.Apply(borders.m_right, compareWith ? (& compareWith->m_right) : (const wxTextAttrBorder*) NULL);
12176 m_top.Apply(borders.m_top, compareWith ? (& compareWith->m_top) : (const wxTextAttrBorder*) NULL);
12177 m_bottom.Apply(borders.m_bottom, compareWith ? (& compareWith->m_bottom) : (const wxTextAttrBorder*) NULL);
12178 return true;
12179 }
12180
12181 // Remove specified attributes from this object
12182 bool wxTextAttrBorders::RemoveStyle(const wxTextAttrBorders& attr)
12183 {
12184 m_left.RemoveStyle(attr.m_left);
12185 m_right.RemoveStyle(attr.m_right);
12186 m_top.RemoveStyle(attr.m_top);
12187 m_bottom.RemoveStyle(attr.m_bottom);
12188 return true;
12189 }
12190
12191 // Collects the attributes that are common to a range of content, building up a note of
12192 // which attributes are absent in some objects and which clash in some objects.
12193 void wxTextAttrBorders::CollectCommonAttributes(const wxTextAttrBorders& attr, wxTextAttrBorders& clashingAttr, wxTextAttrBorders& absentAttr)
12194 {
12195 m_left.CollectCommonAttributes(attr.m_left, clashingAttr.m_left, absentAttr.m_left);
12196 m_right.CollectCommonAttributes(attr.m_right, clashingAttr.m_right, absentAttr.m_right);
12197 m_top.CollectCommonAttributes(attr.m_top, clashingAttr.m_top, absentAttr.m_top);
12198 m_bottom.CollectCommonAttributes(attr.m_bottom, clashingAttr.m_bottom, absentAttr.m_bottom);
12199 }
12200
12201 // Set style of all borders
12202 void wxTextAttrBorders::SetStyle(int style)
12203 {
12204 m_left.SetStyle(style);
12205 m_right.SetStyle(style);
12206 m_top.SetStyle(style);
12207 m_bottom.SetStyle(style);
12208 }
12209
12210 // Set colour of all borders
12211 void wxTextAttrBorders::SetColour(unsigned long colour)
12212 {
12213 m_left.SetColour(colour);
12214 m_right.SetColour(colour);
12215 m_top.SetColour(colour);
12216 m_bottom.SetColour(colour);
12217 }
12218
12219 void wxTextAttrBorders::SetColour(const wxColour& colour)
12220 {
12221 m_left.SetColour(colour);
12222 m_right.SetColour(colour);
12223 m_top.SetColour(colour);
12224 m_bottom.SetColour(colour);
12225 }
12226
12227 // Set width of all borders
12228 void wxTextAttrBorders::SetWidth(const wxTextAttrDimension& width)
12229 {
12230 m_left.SetWidth(width);
12231 m_right.SetWidth(width);
12232 m_top.SetWidth(width);
12233 m_bottom.SetWidth(width);
12234 }
12235
12236 // Partial equality test
12237 bool wxTextAttrDimension::EqPartial(const wxTextAttrDimension& dim, bool weakTest) const
12238 {
12239 if (!weakTest && !IsValid() && dim.IsValid())
12240 return false;
12241
12242 if (dim.IsValid() && IsValid() && !((*this) == dim))
12243 return false;
12244 else
12245 return true;
12246 }
12247
12248 bool wxTextAttrDimension::Apply(const wxTextAttrDimension& dim, const wxTextAttrDimension* compareWith)
12249 {
12250 if (dim.IsValid())
12251 {
12252 if (!(compareWith && dim == (*compareWith)))
12253 (*this) = dim;
12254 }
12255
12256 return true;
12257 }
12258
12259 // Collects the attributes that are common to a range of content, building up a note of
12260 // which attributes are absent in some objects and which clash in some objects.
12261 void wxTextAttrDimension::CollectCommonAttributes(const wxTextAttrDimension& attr, wxTextAttrDimension& clashingAttr, wxTextAttrDimension& absentAttr)
12262 {
12263 if (attr.IsValid())
12264 {
12265 if (!clashingAttr.IsValid() && !absentAttr.IsValid())
12266 {
12267 if (IsValid())
12268 {
12269 if (!((*this) == attr))
12270 {
12271 clashingAttr.SetValid(true);
12272 SetValid(false);
12273 }
12274 }
12275 else
12276 (*this) = attr;
12277 }
12278 }
12279 else
12280 absentAttr.SetValid(true);
12281 }
12282
12283 wxTextAttrDimensionConverter::wxTextAttrDimensionConverter(wxDC& dc, double scale, const wxSize& parentSize)
12284 {
12285 m_ppi = dc.GetPPI().x; m_scale = scale; m_parentSize = parentSize;
12286 }
12287
12288 wxTextAttrDimensionConverter::wxTextAttrDimensionConverter(int ppi, double scale, const wxSize& parentSize)
12289 {
12290 m_ppi = ppi; m_scale = scale; m_parentSize = parentSize;
12291 }
12292
12293 int wxTextAttrDimensionConverter::ConvertTenthsMMToPixels(int units) const
12294 {
12295 return wxRichTextObject::ConvertTenthsMMToPixels(m_ppi, units, m_scale);
12296 }
12297
12298 int wxTextAttrDimensionConverter::ConvertPixelsToTenthsMM(int pixels) const
12299 {
12300 return wxRichTextObject::ConvertPixelsToTenthsMM(m_ppi, pixels, m_scale);
12301 }
12302
12303 int wxTextAttrDimensionConverter::GetPixels(const wxTextAttrDimension& dim, int direction) const
12304 {
12305 if (dim.GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
12306 return ConvertTenthsMMToPixels(dim.GetValue());
12307 else if (dim.GetUnits() == wxTEXT_ATTR_UNITS_PIXELS)
12308 return dim.GetValue();
12309 else if (dim.GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE)
12310 {
12311 wxASSERT(m_parentSize != wxDefaultSize);
12312 if (direction == wxHORIZONTAL)
12313 return (int) (double(m_parentSize.x) * double(dim.GetValue()) / 100.0);
12314 else
12315 return (int) (double(m_parentSize.y) * double(dim.GetValue()) / 100.0);
12316 }
12317 else
12318 {
12319 wxASSERT(false);
12320 return 0;
12321 }
12322 }
12323
12324 int wxTextAttrDimensionConverter::GetTenthsMM(const wxTextAttrDimension& dim) const
12325 {
12326 if (dim.GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
12327 return dim.GetValue();
12328 else if (dim.GetUnits() == wxTEXT_ATTR_UNITS_PIXELS)
12329 return ConvertPixelsToTenthsMM(dim.GetValue());
12330 else
12331 {
12332 wxASSERT(false);
12333 return 0;
12334 }
12335 }
12336
12337 // Partial equality test
12338 bool wxTextAttrDimensions::EqPartial(const wxTextAttrDimensions& dims, bool weakTest) const
12339 {
12340 if (!m_left.EqPartial(dims.m_left, weakTest))
12341 return false;
12342
12343 if (!m_right.EqPartial(dims.m_right, weakTest))
12344 return false;
12345
12346 if (!m_top.EqPartial(dims.m_top, weakTest))
12347 return false;
12348
12349 if (!m_bottom.EqPartial(dims.m_bottom, weakTest))
12350 return false;
12351
12352 return true;
12353 }
12354
12355 // Apply border to 'this', but not if the same as compareWith
12356 bool wxTextAttrDimensions::Apply(const wxTextAttrDimensions& dims, const wxTextAttrDimensions* compareWith)
12357 {
12358 m_left.Apply(dims.m_left, compareWith ? (& compareWith->m_left) : (const wxTextAttrDimension*) NULL);
12359 m_right.Apply(dims.m_right, compareWith ? (& compareWith->m_right): (const wxTextAttrDimension*) NULL);
12360 m_top.Apply(dims.m_top, compareWith ? (& compareWith->m_top): (const wxTextAttrDimension*) NULL);
12361 m_bottom.Apply(dims.m_bottom, compareWith ? (& compareWith->m_bottom): (const wxTextAttrDimension*) NULL);
12362
12363 return true;
12364 }
12365
12366 // Remove specified attributes from this object
12367 bool wxTextAttrDimensions::RemoveStyle(const wxTextAttrDimensions& attr)
12368 {
12369 if (attr.m_left.IsValid())
12370 m_left.Reset();
12371 if (attr.m_right.IsValid())
12372 m_right.Reset();
12373 if (attr.m_top.IsValid())
12374 m_top.Reset();
12375 if (attr.m_bottom.IsValid())
12376 m_bottom.Reset();
12377
12378 return true;
12379 }
12380
12381 // Collects the attributes that are common to a range of content, building up a note of
12382 // which attributes are absent in some objects and which clash in some objects.
12383 void wxTextAttrDimensions::CollectCommonAttributes(const wxTextAttrDimensions& attr, wxTextAttrDimensions& clashingAttr, wxTextAttrDimensions& absentAttr)
12384 {
12385 m_left.CollectCommonAttributes(attr.m_left, clashingAttr.m_left, absentAttr.m_left);
12386 m_right.CollectCommonAttributes(attr.m_right, clashingAttr.m_right, absentAttr.m_right);
12387 m_top.CollectCommonAttributes(attr.m_top, clashingAttr.m_top, absentAttr.m_top);
12388 m_bottom.CollectCommonAttributes(attr.m_bottom, clashingAttr.m_bottom, absentAttr.m_bottom);
12389 }
12390
12391 // Partial equality test
12392 bool wxTextAttrSize::EqPartial(const wxTextAttrSize& size, bool weakTest) const
12393 {
12394 if (!m_width.EqPartial(size.m_width, weakTest))
12395 return false;
12396
12397 if (!m_height.EqPartial(size.m_height, weakTest))
12398 return false;
12399
12400 return true;
12401 }
12402
12403 // Apply border to 'this', but not if the same as compareWith
12404 bool wxTextAttrSize::Apply(const wxTextAttrSize& size, const wxTextAttrSize* compareWith)
12405 {
12406 m_width.Apply(size.m_width, compareWith ? (& compareWith->m_width) : (const wxTextAttrDimension*) NULL);
12407 m_height.Apply(size.m_height, compareWith ? (& compareWith->m_height): (const wxTextAttrDimension*) NULL);
12408
12409 return true;
12410 }
12411
12412 // Remove specified attributes from this object
12413 bool wxTextAttrSize::RemoveStyle(const wxTextAttrSize& attr)
12414 {
12415 if (attr.m_width.IsValid())
12416 m_width.Reset();
12417 if (attr.m_height.IsValid())
12418 m_height.Reset();
12419
12420 return true;
12421 }
12422
12423 // Collects the attributes that are common to a range of content, building up a note of
12424 // which attributes are absent in some objects and which clash in some objects.
12425 void wxTextAttrSize::CollectCommonAttributes(const wxTextAttrSize& attr, wxTextAttrSize& clashingAttr, wxTextAttrSize& absentAttr)
12426 {
12427 m_width.CollectCommonAttributes(attr.m_width, clashingAttr.m_width, absentAttr.m_width);
12428 m_height.CollectCommonAttributes(attr.m_height, clashingAttr.m_height, absentAttr.m_height);
12429 }
12430
12431 // Collects the attributes that are common to a range of content, building up a note of
12432 // which attributes are absent in some objects and which clash in some objects.
12433 void wxTextAttrCollectCommonAttributes(wxTextAttr& currentStyle, const wxTextAttr& attr, wxTextAttr& clashingAttr, wxTextAttr& absentAttr)
12434 {
12435 absentAttr.SetFlags(absentAttr.GetFlags() | (~attr.GetFlags() & wxTEXT_ATTR_ALL));
12436 absentAttr.SetTextEffectFlags(absentAttr.GetTextEffectFlags() | (~attr.GetTextEffectFlags() & 0xFFFF));
12437
12438 long forbiddenFlags = clashingAttr.GetFlags()|absentAttr.GetFlags();
12439
12440 if (attr.HasFont())
12441 {
12442 // If different font size units are being used, this is a clash.
12443 if (((attr.GetFlags() & wxTEXT_ATTR_FONT_SIZE) | (currentStyle.GetFlags() & wxTEXT_ATTR_FONT_SIZE)) == wxTEXT_ATTR_FONT_SIZE)
12444 {
12445 currentStyle.SetFontSize(0);
12446 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_SIZE);
12447 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_SIZE);
12448 }
12449 else
12450 {
12451 if (attr.HasFontPointSize() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_POINT_SIZE))
12452 {
12453 if (currentStyle.HasFontPointSize())
12454 {
12455 if (currentStyle.GetFontSize() != attr.GetFontSize())
12456 {
12457 // Clash of attr - mark as such
12458 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_POINT_SIZE);
12459 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_POINT_SIZE);
12460 }
12461 }
12462 else
12463 currentStyle.SetFontSize(attr.GetFontSize());
12464 }
12465
12466 if (attr.HasFontPixelSize() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_PIXEL_SIZE))
12467 {
12468 if (currentStyle.HasFontPixelSize())
12469 {
12470 if (currentStyle.GetFontSize() != attr.GetFontSize())
12471 {
12472 // Clash of attr - mark as such
12473 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_PIXEL_SIZE);
12474 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_PIXEL_SIZE);
12475 }
12476 }
12477 else
12478 currentStyle.SetFontPixelSize(attr.GetFontSize());
12479 }
12480 }
12481
12482 if (attr.HasFontItalic() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_ITALIC))
12483 {
12484 if (currentStyle.HasFontItalic())
12485 {
12486 if (currentStyle.GetFontStyle() != attr.GetFontStyle())
12487 {
12488 // Clash of attr - mark as such
12489 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_ITALIC);
12490 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_ITALIC);
12491 }
12492 }
12493 else
12494 currentStyle.SetFontStyle(attr.GetFontStyle());
12495 }
12496
12497 if (attr.HasFontFamily() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_FAMILY))
12498 {
12499 if (currentStyle.HasFontFamily())
12500 {
12501 if (currentStyle.GetFontFamily() != attr.GetFontFamily())
12502 {
12503 // Clash of attr - mark as such
12504 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_FAMILY);
12505 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_FAMILY);
12506 }
12507 }
12508 else
12509 currentStyle.SetFontFamily(attr.GetFontFamily());
12510 }
12511
12512 if (attr.HasFontWeight() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_WEIGHT))
12513 {
12514 if (currentStyle.HasFontWeight())
12515 {
12516 if (currentStyle.GetFontWeight() != attr.GetFontWeight())
12517 {
12518 // Clash of attr - mark as such
12519 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_WEIGHT);
12520 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_WEIGHT);
12521 }
12522 }
12523 else
12524 currentStyle.SetFontWeight(attr.GetFontWeight());
12525 }
12526
12527 if (attr.HasFontFaceName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_FACE))
12528 {
12529 if (currentStyle.HasFontFaceName())
12530 {
12531 wxString faceName1(currentStyle.GetFontFaceName());
12532 wxString faceName2(attr.GetFontFaceName());
12533
12534 if (faceName1 != faceName2)
12535 {
12536 // Clash of attr - mark as such
12537 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_FACE);
12538 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_FACE);
12539 }
12540 }
12541 else
12542 currentStyle.SetFontFaceName(attr.GetFontFaceName());
12543 }
12544
12545 if (attr.HasFontUnderlined() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_UNDERLINE))
12546 {
12547 if (currentStyle.HasFontUnderlined())
12548 {
12549 if (currentStyle.GetFontUnderlined() != attr.GetFontUnderlined())
12550 {
12551 // Clash of attr - mark as such
12552 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_UNDERLINE);
12553 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_UNDERLINE);
12554 }
12555 }
12556 else
12557 currentStyle.SetFontUnderlined(attr.GetFontUnderlined());
12558 }
12559
12560 if (attr.HasFontStrikethrough() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_STRIKETHROUGH))
12561 {
12562 if (currentStyle.HasFontStrikethrough())
12563 {
12564 if (currentStyle.GetFontStrikethrough() != attr.GetFontStrikethrough())
12565 {
12566 // Clash of attr - mark as such
12567 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_STRIKETHROUGH);
12568 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_STRIKETHROUGH);
12569 }
12570 }
12571 else
12572 currentStyle.SetFontStrikethrough(attr.GetFontStrikethrough());
12573 }
12574 }
12575
12576 if (attr.HasTextColour() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_TEXT_COLOUR))
12577 {
12578 if (currentStyle.HasTextColour())
12579 {
12580 if (currentStyle.GetTextColour() != attr.GetTextColour())
12581 {
12582 // Clash of attr - mark as such
12583 clashingAttr.AddFlag(wxTEXT_ATTR_TEXT_COLOUR);
12584 currentStyle.RemoveFlag(wxTEXT_ATTR_TEXT_COLOUR);
12585 }
12586 }
12587 else
12588 currentStyle.SetTextColour(attr.GetTextColour());
12589 }
12590
12591 if (attr.HasBackgroundColour() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BACKGROUND_COLOUR))
12592 {
12593 if (currentStyle.HasBackgroundColour())
12594 {
12595 if (currentStyle.GetBackgroundColour() != attr.GetBackgroundColour())
12596 {
12597 // Clash of attr - mark as such
12598 clashingAttr.AddFlag(wxTEXT_ATTR_BACKGROUND_COLOUR);
12599 currentStyle.RemoveFlag(wxTEXT_ATTR_BACKGROUND_COLOUR);
12600 }
12601 }
12602 else
12603 currentStyle.SetBackgroundColour(attr.GetBackgroundColour());
12604 }
12605
12606 if (attr.HasAlignment() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_ALIGNMENT))
12607 {
12608 if (currentStyle.HasAlignment())
12609 {
12610 if (currentStyle.GetAlignment() != attr.GetAlignment())
12611 {
12612 // Clash of attr - mark as such
12613 clashingAttr.AddFlag(wxTEXT_ATTR_ALIGNMENT);
12614 currentStyle.RemoveFlag(wxTEXT_ATTR_ALIGNMENT);
12615 }
12616 }
12617 else
12618 currentStyle.SetAlignment(attr.GetAlignment());
12619 }
12620
12621 if (attr.HasTabs() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_TABS))
12622 {
12623 if (currentStyle.HasTabs())
12624 {
12625 if (!wxRichTextTabsEq(currentStyle.GetTabs(), attr.GetTabs()))
12626 {
12627 // Clash of attr - mark as such
12628 clashingAttr.AddFlag(wxTEXT_ATTR_TABS);
12629 currentStyle.RemoveFlag(wxTEXT_ATTR_TABS);
12630 }
12631 }
12632 else
12633 currentStyle.SetTabs(attr.GetTabs());
12634 }
12635
12636 if (attr.HasLeftIndent() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_LEFT_INDENT))
12637 {
12638 if (currentStyle.HasLeftIndent())
12639 {
12640 if (currentStyle.GetLeftIndent() != attr.GetLeftIndent() || currentStyle.GetLeftSubIndent() != attr.GetLeftSubIndent())
12641 {
12642 // Clash of attr - mark as such
12643 clashingAttr.AddFlag(wxTEXT_ATTR_LEFT_INDENT);
12644 currentStyle.RemoveFlag(wxTEXT_ATTR_LEFT_INDENT);
12645 }
12646 }
12647 else
12648 currentStyle.SetLeftIndent(attr.GetLeftIndent(), attr.GetLeftSubIndent());
12649 }
12650
12651 if (attr.HasRightIndent() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_RIGHT_INDENT))
12652 {
12653 if (currentStyle.HasRightIndent())
12654 {
12655 if (currentStyle.GetRightIndent() != attr.GetRightIndent())
12656 {
12657 // Clash of attr - mark as such
12658 clashingAttr.AddFlag(wxTEXT_ATTR_RIGHT_INDENT);
12659 currentStyle.RemoveFlag(wxTEXT_ATTR_RIGHT_INDENT);
12660 }
12661 }
12662 else
12663 currentStyle.SetRightIndent(attr.GetRightIndent());
12664 }
12665
12666 if (attr.HasParagraphSpacingAfter() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_PARA_SPACING_AFTER))
12667 {
12668 if (currentStyle.HasParagraphSpacingAfter())
12669 {
12670 if (currentStyle.GetParagraphSpacingAfter() != attr.GetParagraphSpacingAfter())
12671 {
12672 // Clash of attr - mark as such
12673 clashingAttr.AddFlag(wxTEXT_ATTR_PARA_SPACING_AFTER);
12674 currentStyle.RemoveFlag(wxTEXT_ATTR_PARA_SPACING_AFTER);
12675 }
12676 }
12677 else
12678 currentStyle.SetParagraphSpacingAfter(attr.GetParagraphSpacingAfter());
12679 }
12680
12681 if (attr.HasParagraphSpacingBefore() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_PARA_SPACING_BEFORE))
12682 {
12683 if (currentStyle.HasParagraphSpacingBefore())
12684 {
12685 if (currentStyle.GetParagraphSpacingBefore() != attr.GetParagraphSpacingBefore())
12686 {
12687 // Clash of attr - mark as such
12688 clashingAttr.AddFlag(wxTEXT_ATTR_PARA_SPACING_BEFORE);
12689 currentStyle.RemoveFlag(wxTEXT_ATTR_PARA_SPACING_BEFORE);
12690 }
12691 }
12692 else
12693 currentStyle.SetParagraphSpacingBefore(attr.GetParagraphSpacingBefore());
12694 }
12695
12696 if (attr.HasLineSpacing() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_LINE_SPACING))
12697 {
12698 if (currentStyle.HasLineSpacing())
12699 {
12700 if (currentStyle.GetLineSpacing() != attr.GetLineSpacing())
12701 {
12702 // Clash of attr - mark as such
12703 clashingAttr.AddFlag(wxTEXT_ATTR_LINE_SPACING);
12704 currentStyle.RemoveFlag(wxTEXT_ATTR_LINE_SPACING);
12705 }
12706 }
12707 else
12708 currentStyle.SetLineSpacing(attr.GetLineSpacing());
12709 }
12710
12711 if (attr.HasCharacterStyleName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_CHARACTER_STYLE_NAME))
12712 {
12713 if (currentStyle.HasCharacterStyleName())
12714 {
12715 if (currentStyle.GetCharacterStyleName() != attr.GetCharacterStyleName())
12716 {
12717 // Clash of attr - mark as such
12718 clashingAttr.AddFlag(wxTEXT_ATTR_CHARACTER_STYLE_NAME);
12719 currentStyle.RemoveFlag(wxTEXT_ATTR_CHARACTER_STYLE_NAME);
12720 }
12721 }
12722 else
12723 currentStyle.SetCharacterStyleName(attr.GetCharacterStyleName());
12724 }
12725
12726 if (attr.HasParagraphStyleName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_PARAGRAPH_STYLE_NAME))
12727 {
12728 if (currentStyle.HasParagraphStyleName())
12729 {
12730 if (currentStyle.GetParagraphStyleName() != attr.GetParagraphStyleName())
12731 {
12732 // Clash of attr - mark as such
12733 clashingAttr.AddFlag(wxTEXT_ATTR_PARAGRAPH_STYLE_NAME);
12734 currentStyle.RemoveFlag(wxTEXT_ATTR_PARAGRAPH_STYLE_NAME);
12735 }
12736 }
12737 else
12738 currentStyle.SetParagraphStyleName(attr.GetParagraphStyleName());
12739 }
12740
12741 if (attr.HasListStyleName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_LIST_STYLE_NAME))
12742 {
12743 if (currentStyle.HasListStyleName())
12744 {
12745 if (currentStyle.GetListStyleName() != attr.GetListStyleName())
12746 {
12747 // Clash of attr - mark as such
12748 clashingAttr.AddFlag(wxTEXT_ATTR_LIST_STYLE_NAME);
12749 currentStyle.RemoveFlag(wxTEXT_ATTR_LIST_STYLE_NAME);
12750 }
12751 }
12752 else
12753 currentStyle.SetListStyleName(attr.GetListStyleName());
12754 }
12755
12756 if (attr.HasBulletStyle() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BULLET_STYLE))
12757 {
12758 if (currentStyle.HasBulletStyle())
12759 {
12760 if (currentStyle.GetBulletStyle() != attr.GetBulletStyle())
12761 {
12762 // Clash of attr - mark as such
12763 clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_STYLE);
12764 currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_STYLE);
12765 }
12766 }
12767 else
12768 currentStyle.SetBulletStyle(attr.GetBulletStyle());
12769 }
12770
12771 if (attr.HasBulletNumber() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BULLET_NUMBER))
12772 {
12773 if (currentStyle.HasBulletNumber())
12774 {
12775 if (currentStyle.GetBulletNumber() != attr.GetBulletNumber())
12776 {
12777 // Clash of attr - mark as such
12778 clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_NUMBER);
12779 currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_NUMBER);
12780 }
12781 }
12782 else
12783 currentStyle.SetBulletNumber(attr.GetBulletNumber());
12784 }
12785
12786 if (attr.HasBulletText() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BULLET_TEXT))
12787 {
12788 if (currentStyle.HasBulletText())
12789 {
12790 if (currentStyle.GetBulletText() != attr.GetBulletText())
12791 {
12792 // Clash of attr - mark as such
12793 clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_TEXT);
12794 currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_TEXT);
12795 }
12796 }
12797 else
12798 {
12799 currentStyle.SetBulletText(attr.GetBulletText());
12800 currentStyle.SetBulletFont(attr.GetBulletFont());
12801 }
12802 }
12803
12804 if (attr.HasBulletName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BULLET_NAME))
12805 {
12806 if (currentStyle.HasBulletName())
12807 {
12808 if (currentStyle.GetBulletName() != attr.GetBulletName())
12809 {
12810 // Clash of attr - mark as such
12811 clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_NAME);
12812 currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_NAME);
12813 }
12814 }
12815 else
12816 {
12817 currentStyle.SetBulletName(attr.GetBulletName());
12818 }
12819 }
12820
12821 if (attr.HasURL() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_URL))
12822 {
12823 if (currentStyle.HasURL())
12824 {
12825 if (currentStyle.GetURL() != attr.GetURL())
12826 {
12827 // Clash of attr - mark as such
12828 clashingAttr.AddFlag(wxTEXT_ATTR_URL);
12829 currentStyle.RemoveFlag(wxTEXT_ATTR_URL);
12830 }
12831 }
12832 else
12833 {
12834 currentStyle.SetURL(attr.GetURL());
12835 }
12836 }
12837
12838 if (attr.HasTextEffects() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_EFFECTS))
12839 {
12840 if (currentStyle.HasTextEffects())
12841 {
12842 // We need to find the bits in the new attr that are different:
12843 // just look at those bits that are specified by the new attr.
12844
12845 // We need to remove the bits and flags that are not common between current attr
12846 // and new attr. In so doing we need to take account of the styles absent from one or more of the
12847 // previous styles.
12848
12849 int currentRelevantTextEffects = currentStyle.GetTextEffects() & attr.GetTextEffectFlags();
12850 int newRelevantTextEffects = attr.GetTextEffects() & attr.GetTextEffectFlags();
12851
12852 if (currentRelevantTextEffects != newRelevantTextEffects)
12853 {
12854 // Find the text effects that were different, using XOR
12855 int differentEffects = currentRelevantTextEffects ^ newRelevantTextEffects;
12856
12857 // Clash of attr - mark as such
12858 clashingAttr.SetTextEffectFlags(clashingAttr.GetTextEffectFlags() | differentEffects);
12859 currentStyle.SetTextEffectFlags(currentStyle.GetTextEffectFlags() & ~differentEffects);
12860 }
12861 }
12862 else
12863 {
12864 currentStyle.SetTextEffects(attr.GetTextEffects());
12865 currentStyle.SetTextEffectFlags(attr.GetTextEffectFlags());
12866 }
12867
12868 // Mask out the flags and values that cannot be common because they were absent in one or more objecrs
12869 // that we've looked at so far
12870 currentStyle.SetTextEffects(currentStyle.GetTextEffects() & ~absentAttr.GetTextEffectFlags());
12871 currentStyle.SetTextEffectFlags(currentStyle.GetTextEffectFlags() & ~absentAttr.GetTextEffectFlags());
12872
12873 if (currentStyle.GetTextEffectFlags() == 0)
12874 currentStyle.RemoveFlag(wxTEXT_ATTR_EFFECTS);
12875 }
12876
12877 if (attr.HasOutlineLevel() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_OUTLINE_LEVEL))
12878 {
12879 if (currentStyle.HasOutlineLevel())
12880 {
12881 if (currentStyle.GetOutlineLevel() != attr.GetOutlineLevel())
12882 {
12883 // Clash of attr - mark as such
12884 clashingAttr.AddFlag(wxTEXT_ATTR_OUTLINE_LEVEL);
12885 currentStyle.RemoveFlag(wxTEXT_ATTR_OUTLINE_LEVEL);
12886 }
12887 }
12888 else
12889 currentStyle.SetOutlineLevel(attr.GetOutlineLevel());
12890 }
12891 }
12892
12893 WX_DEFINE_OBJARRAY(wxRichTextVariantArray);
12894
12895 IMPLEMENT_DYNAMIC_CLASS(wxRichTextProperties, wxObject)
12896
12897 bool wxRichTextProperties::operator==(const wxRichTextProperties& props) const
12898 {
12899 if (m_properties.GetCount() != props.GetCount())
12900 return false;
12901
12902 size_t i;
12903 for (i = 0; i < m_properties.GetCount(); i++)
12904 {
12905 const wxVariant& var1 = m_properties[i];
12906 int idx = props.Find(var1.GetName());
12907 if (idx == -1)
12908 return false;
12909 const wxVariant& var2 = props.m_properties[idx];
12910 if (!(var1 == var2))
12911 return false;
12912 }
12913
12914 return true;
12915 }
12916
12917 wxArrayString wxRichTextProperties::GetPropertyNames() const
12918 {
12919 wxArrayString arr;
12920 size_t i;
12921 for (i = 0; i < m_properties.GetCount(); i++)
12922 {
12923 arr.Add(m_properties[i].GetName());
12924 }
12925 return arr;
12926 }
12927
12928 int wxRichTextProperties::Find(const wxString& name) const
12929 {
12930 size_t i;
12931 for (i = 0; i < m_properties.GetCount(); i++)
12932 {
12933 if (m_properties[i].GetName() == name)
12934 return (int) i;
12935 }
12936 return -1;
12937 }
12938
12939 bool wxRichTextProperties::Remove(const wxString& name)
12940 {
12941 int idx = Find(name);
12942 if (idx != -1)
12943 {
12944 m_properties.RemoveAt(idx);
12945 return true;
12946 }
12947 else
12948 return false;
12949 }
12950
12951 wxVariant* wxRichTextProperties::FindOrCreateProperty(const wxString& name)
12952 {
12953 int idx = Find(name);
12954 if (idx == wxNOT_FOUND)
12955 SetProperty(name, wxString());
12956 idx = Find(name);
12957 if (idx != wxNOT_FOUND)
12958 {
12959 return & (*this)[idx];
12960 }
12961 else
12962 return NULL;
12963 }
12964
12965 const wxVariant& wxRichTextProperties::GetProperty(const wxString& name) const
12966 {
12967 static const wxVariant nullVariant;
12968 int idx = Find(name);
12969 if (idx != -1)
12970 return m_properties[idx];
12971 else
12972 return nullVariant;
12973 }
12974
12975 wxString wxRichTextProperties::GetPropertyString(const wxString& name) const
12976 {
12977 return GetProperty(name).GetString();
12978 }
12979
12980 long wxRichTextProperties::GetPropertyLong(const wxString& name) const
12981 {
12982 return GetProperty(name).GetLong();
12983 }
12984
12985 bool wxRichTextProperties::GetPropertyBool(const wxString& name) const
12986 {
12987 return GetProperty(name).GetBool();
12988 }
12989
12990 double wxRichTextProperties::GetPropertyDouble(const wxString& name) const
12991 {
12992 return GetProperty(name).GetDouble();
12993 }
12994
12995 void wxRichTextProperties::SetProperty(const wxVariant& variant)
12996 {
12997 wxASSERT(!variant.GetName().IsEmpty());
12998
12999 int idx = Find(variant.GetName());
13000
13001 if (idx == -1)
13002 m_properties.Add(variant);
13003 else
13004 m_properties[idx] = variant;
13005 }
13006
13007 void wxRichTextProperties::SetProperty(const wxString& name, const wxVariant& variant)
13008 {
13009 int idx = Find(name);
13010 wxVariant var(variant);
13011 var.SetName(name);
13012
13013 if (idx == -1)
13014 m_properties.Add(var);
13015 else
13016 m_properties[idx] = var;
13017 }
13018
13019 void wxRichTextProperties::SetProperty(const wxString& name, const wxString& value)
13020 {
13021 SetProperty(name, wxVariant(value, name));
13022 }
13023
13024 void wxRichTextProperties::SetProperty(const wxString& name, long value)
13025 {
13026 SetProperty(name, wxVariant(value, name));
13027 }
13028
13029 void wxRichTextProperties::SetProperty(const wxString& name, double value)
13030 {
13031 SetProperty(name, wxVariant(value, name));
13032 }
13033
13034 void wxRichTextProperties::SetProperty(const wxString& name, bool value)
13035 {
13036 SetProperty(name, wxVariant(value, name));
13037 }
13038
13039 void wxRichTextProperties::RemoveProperties(const wxRichTextProperties& properties)
13040 {
13041 size_t i;
13042 for (i = 0; i < properties.GetCount(); i++)
13043 {
13044 wxString name = properties.GetProperties()[i].GetName();
13045 if (HasProperty(name))
13046 Remove(name);
13047 }
13048 }
13049
13050 void wxRichTextProperties::MergeProperties(const wxRichTextProperties& properties)
13051 {
13052 size_t i;
13053 for (i = 0; i < properties.GetCount(); i++)
13054 {
13055 SetProperty(properties.GetProperties()[i]);
13056 }
13057 }
13058
13059 wxRichTextObject* wxRichTextObjectAddress::GetObject(wxRichTextParagraphLayoutBox* topLevelContainer) const
13060 {
13061 if (m_address.GetCount() == 0)
13062 return topLevelContainer;
13063
13064 wxRichTextCompositeObject* p = topLevelContainer;
13065 size_t i = 0;
13066 while (p && i < m_address.GetCount())
13067 {
13068 int pos = m_address[i];
13069 wxASSERT(pos >= 0 && pos < (int) p->GetChildren().GetCount());
13070 if (pos < 0 || pos >= (int) p->GetChildren().GetCount())
13071 return NULL;
13072
13073 wxRichTextObject* p1 = p->GetChild(pos);
13074 if (i == (m_address.GetCount()-1))
13075 return p1;
13076
13077 p = wxDynamicCast(p1, wxRichTextCompositeObject);
13078 i ++;
13079 }
13080 return NULL;
13081 }
13082
13083 bool wxRichTextObjectAddress::Create(wxRichTextParagraphLayoutBox* topLevelContainer, wxRichTextObject* obj)
13084 {
13085 m_address.Clear();
13086
13087 if (topLevelContainer == obj)
13088 return true;
13089
13090 wxRichTextObject* o = obj;
13091 while (o)
13092 {
13093 wxRichTextCompositeObject* p = wxDynamicCast(o->GetParent(), wxRichTextCompositeObject);
13094 if (!p)
13095 return false;
13096
13097 int pos = p->GetChildren().IndexOf(o);
13098 if (pos == -1)
13099 return false;
13100
13101 m_address.Insert(pos, 0);
13102
13103 if (p == topLevelContainer)
13104 return true;
13105
13106 o = p;
13107 }
13108 return false;
13109 }
13110
13111 // Equality test
13112 bool wxRichTextSelection::operator==(const wxRichTextSelection& sel) const
13113 {
13114 if (m_container != sel.m_container)
13115 return false;
13116 if (m_ranges.GetCount() != sel.m_ranges.GetCount())
13117 return false;
13118 size_t i;
13119 for (i = 0; i < m_ranges.GetCount(); i++)
13120 if (!(m_ranges[i] == sel.m_ranges[i]))
13121 return false;
13122 return true;
13123 }
13124
13125 // Get the selections appropriate to the specified object, if any; returns wxRICHTEXT_NO_SELECTION if none
13126 // or none at the level of the object's container.
13127 wxRichTextRangeArray wxRichTextSelection::GetSelectionForObject(wxRichTextObject* obj) const
13128 {
13129 if (IsValid())
13130 {
13131 wxRichTextParagraphLayoutBox* container = obj->GetParentContainer();
13132
13133 if (container == m_container)
13134 return m_ranges;
13135
13136 container = obj->GetContainer();
13137 while (container)
13138 {
13139 if (container->GetParent())
13140 {
13141 // If we found that our object's container is within the range of
13142 // a selection higher up, then assume the whole original object
13143 // is also selected.
13144 wxRichTextParagraphLayoutBox* parentContainer = container->GetParentContainer();
13145 if (parentContainer == m_container)
13146 {
13147 if (WithinSelection(container->GetRange().GetStart(), m_ranges))
13148 {
13149 wxRichTextRangeArray ranges;
13150 ranges.Add(obj->GetRange());
13151 return ranges;
13152 }
13153 }
13154
13155 container = parentContainer;
13156 }
13157 else
13158 {
13159 container = NULL;
13160 break;
13161 }
13162 }
13163 }
13164 return wxRichTextRangeArray();
13165 }
13166
13167 // Is the given position within the selection?
13168 bool wxRichTextSelection::WithinSelection(long pos, wxRichTextObject* obj) const
13169 {
13170 if (!IsValid())
13171 return false;
13172 else
13173 {
13174 wxRichTextRangeArray selectionRanges = GetSelectionForObject(obj);
13175 return WithinSelection(pos, selectionRanges);
13176 }
13177 }
13178
13179 // Is the given position within the selection range?
13180 bool wxRichTextSelection::WithinSelection(long pos, const wxRichTextRangeArray& ranges)
13181 {
13182 size_t i;
13183 for (i = 0; i < ranges.GetCount(); i++)
13184 {
13185 const wxRichTextRange& range = ranges[i];
13186 if (pos >= range.GetStart() && pos <= range.GetEnd())
13187 return true;
13188 }
13189 return false;
13190 }
13191
13192 // Is the given range completely within the selection range?
13193 bool wxRichTextSelection::WithinSelection(const wxRichTextRange& range, const wxRichTextRangeArray& ranges)
13194 {
13195 size_t i;
13196 for (i = 0; i < ranges.GetCount(); i++)
13197 {
13198 const wxRichTextRange& eachRange = ranges[i];
13199 if (range.IsWithin(eachRange))
13200 return true;
13201 }
13202 return false;
13203 }
13204
13205 IMPLEMENT_CLASS(wxRichTextDrawingHandler, wxObject)
13206 IMPLEMENT_CLASS(wxRichTextDrawingContext, wxObject)
13207
13208 bool wxRichTextDrawingContext::HasVirtualAttributes(wxRichTextObject* obj) const
13209 {
13210 wxList::compatibility_iterator node = m_buffer->GetDrawingHandlers().GetFirst();
13211 while (node)
13212 {
13213 wxRichTextDrawingHandler *handler = (wxRichTextDrawingHandler*)node->GetData();
13214 if (handler->HasVirtualAttributes(obj))
13215 return true;
13216
13217 node = node->GetNext();
13218 }
13219 return false;
13220 }
13221
13222 wxRichTextAttr wxRichTextDrawingContext::GetVirtualAttributes(wxRichTextObject* obj) const
13223 {
13224 wxRichTextAttr attr;
13225 // We apply all handlers, so we can may combine several different attributes
13226 wxList::compatibility_iterator node = m_buffer->GetDrawingHandlers().GetFirst();
13227 while (node)
13228 {
13229 wxRichTextDrawingHandler *handler = (wxRichTextDrawingHandler*)node->GetData();
13230 if (handler->HasVirtualAttributes(obj))
13231 {
13232 bool success = handler->GetVirtualAttributes(attr, obj);
13233 wxASSERT(success);
13234 wxUnusedVar(success);
13235 }
13236
13237 node = node->GetNext();
13238 }
13239 return attr;
13240 }
13241
13242 bool wxRichTextDrawingContext::ApplyVirtualAttributes(wxRichTextAttr& attr, wxRichTextObject* obj) const
13243 {
13244 if (HasVirtualAttributes(obj))
13245 {
13246 wxRichTextAttr a(GetVirtualAttributes(obj));
13247 attr.Apply(a);
13248 return true;
13249 }
13250 else
13251 return false;
13252 }
13253
13254 /// Adds a handler to the end
13255 void wxRichTextBuffer::AddDrawingHandler(wxRichTextDrawingHandler *handler)
13256 {
13257 sm_drawingHandlers.Append(handler);
13258 }
13259
13260 /// Inserts a handler at the front
13261 void wxRichTextBuffer::InsertDrawingHandler(wxRichTextDrawingHandler *handler)
13262 {
13263 sm_drawingHandlers.Insert( handler );
13264 }
13265
13266 /// Removes a handler
13267 bool wxRichTextBuffer::RemoveDrawingHandler(const wxString& name)
13268 {
13269 wxRichTextDrawingHandler *handler = FindDrawingHandler(name);
13270 if (handler)
13271 {
13272 sm_drawingHandlers.DeleteObject(handler);
13273 delete handler;
13274 return true;
13275 }
13276 else
13277 return false;
13278 }
13279
13280 wxRichTextDrawingHandler* wxRichTextBuffer::FindDrawingHandler(const wxString& name)
13281 {
13282 wxList::compatibility_iterator node = sm_drawingHandlers.GetFirst();
13283 while (node)
13284 {
13285 wxRichTextDrawingHandler *handler = (wxRichTextDrawingHandler*)node->GetData();
13286 if (handler->GetName().Lower() == name.Lower()) return handler;
13287
13288 node = node->GetNext();
13289 }
13290 return NULL;
13291 }
13292
13293 void wxRichTextBuffer::CleanUpDrawingHandlers()
13294 {
13295 wxList::compatibility_iterator node = sm_drawingHandlers.GetFirst();
13296 while (node)
13297 {
13298 wxRichTextDrawingHandler* handler = (wxRichTextDrawingHandler*)node->GetData();
13299 wxList::compatibility_iterator next = node->GetNext();
13300 delete handler;
13301 node = next;
13302 }
13303
13304 sm_drawingHandlers.Clear();
13305 }
13306
13307 void wxRichTextBuffer::AddFieldType(wxRichTextFieldType *fieldType)
13308 {
13309 sm_fieldTypes[fieldType->GetName()] = fieldType;
13310 }
13311
13312 bool wxRichTextBuffer::RemoveFieldType(const wxString& name)
13313 {
13314 wxRichTextFieldTypeHashMap::iterator it = sm_fieldTypes.find(name);
13315 if (it == sm_fieldTypes.end())
13316 return false;
13317 else
13318 {
13319 wxRichTextFieldType* fieldType = it->second;
13320 sm_fieldTypes.erase(it);
13321 delete fieldType;
13322 return true;
13323 }
13324 }
13325
13326 wxRichTextFieldType *wxRichTextBuffer::FindFieldType(const wxString& name)
13327 {
13328 wxRichTextFieldTypeHashMap::iterator it = sm_fieldTypes.find(name);
13329 if (it == sm_fieldTypes.end())
13330 return NULL;
13331 else
13332 return it->second;
13333 }
13334
13335 void wxRichTextBuffer::CleanUpFieldTypes()
13336 {
13337 wxRichTextFieldTypeHashMap::iterator it;
13338 for( it = sm_fieldTypes.begin(); it != sm_fieldTypes.end(); ++it )
13339 {
13340 wxRichTextFieldType* fieldType = it->second;
13341 delete fieldType;
13342 }
13343
13344 sm_fieldTypes.clear();
13345 }
13346
13347 #endif
13348 // wxUSE_RICHTEXT