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