Fixed floating image layout when typing in subsequent paragraph
[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 lineDescent = wxMax(childDescent, maxDescent);
4733 lineAscent = wxMax(childSize.y-childDescent, maxAscent);
4734 lineHeight = lineDescent + lineAscent;
4735 wxRect floatAvailableRect = collector->GetAvailableRect(rect.y + currentPosition.y, rect.y + currentPosition.y + lineHeight);
4736
4737 // Adjust availableRect to the space that is available when taking floating objects into account.
4738
4739 if (floatAvailableRect.x + startOffset > availableRect.x)
4740 {
4741 int newX = floatAvailableRect.x + startOffset;
4742 int newW = availableRect.width - (newX - availableRect.x);
4743 availableRect.x = newX;
4744 availableRect.width = newW;
4745 }
4746
4747 if (floatAvailableRect.width < availableRect.width)
4748 availableRect.width = floatAvailableRect.width;
4749
4750 currentPosition.x = availableRect.x - rect.x;
4751
4752 if (child->IsTopLevel() && loopIterations <= 20)
4753 {
4754 if (availableRect != oldAvailableRect)
4755 {
4756 wxSize oldSize = child->GetCachedSize();
4757
4758 //child->SetCachedSize(wxDefaultSize);
4759 // Lays out the object first with a given amount of space, and then if no width was specified in attr,
4760 // lays out the object again using the minimum size
4761 child->Invalidate(wxRICHTEXT_ALL);
4762 child->LayoutToBestSize(dc, context, buffer,
4763 attr, child->GetAttributes(), availableRect, parentRect, style);
4764 childSize = child->GetCachedSize();
4765 childDescent = child->GetDescent();
4766 //child->SetPosition(availableRect.GetPosition());
4767
4768 if (oldSize != child->GetCachedSize())
4769 {
4770 partialExtents.Clear();
4771
4772 // Recalculate the partial text extents since the child object changed size
4773 GetRangeSize(GetRange(), paraSize, paraDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_CACHE_SIZE, wxPoint(0,0), & partialExtents);
4774 }
4775
4776 // Go around the loop finding the available rect for the given floating objects
4777 }
4778 else
4779 doLoop = false;
4780 }
4781 else
4782 doLoop = false;
4783 }
4784 while (doLoop);
4785
4786 // Cases:
4787 // 1) There was a line break BEFORE the natural break
4788 // 2) There was a line break AFTER the natural break
4789 // 3) It's the last line
4790 // 4) The child still fits (carry on) - 'else' clause
4791
4792 if ((lineBreakInThisObject && (childSize.x + currentWidth <= availableRect.width))
4793 ||
4794 (childSize.x + currentWidth > availableRect.width)
4795 ||
4796 ((childSize.x + currentWidth <= availableRect.width) && !node->GetNext())
4797
4798 )
4799 {
4800 if (child->IsTopLevel())
4801 {
4802 // We can move it to the correct position at this point
4803 child->Move(GetPosition() + wxPoint(currentWidth, currentPosition.y));
4804 }
4805
4806 long wrapPosition = 0;
4807 if ((childSize.x + currentWidth <= availableRect.width) && !node->GetNext() && !lineBreakInThisObject)
4808 wrapPosition = child->GetRange().GetEnd();
4809 else
4810
4811 // Find a place to wrap. This may walk back to previous children,
4812 // for example if a word spans several objects.
4813 // Note: one object must contains only one wxTextAtrr, so the line height will not
4814 // change inside one object. Thus, we can pass the remain line width to the
4815 // FindWrapPosition function.
4816 if (!FindWrapPosition(wxRichTextRange(lastCompletedEndPos+1, child->GetRange().GetEnd()), dc, context, availableRect.width, wrapPosition, & partialExtents))
4817 {
4818 // If the function failed, just cut it off at the end of this child.
4819 wrapPosition = child->GetRange().GetEnd();
4820 }
4821
4822 // FindWrapPosition can still return a value that will put us in an endless wrapping loop
4823 if (wrapPosition <= lastCompletedEndPos)
4824 wrapPosition = wxMax(lastCompletedEndPos+1,child->GetRange().GetEnd());
4825
4826 // Line end position shouldn't be the same as the end, or greater.
4827 if (wrapPosition >= GetRange().GetEnd())
4828 wrapPosition = GetRange().GetEnd()-1;
4829
4830 // wxLogDebug(wxT("Split at %ld"), wrapPosition);
4831
4832 // Let's find the actual size of the current line now
4833 wxSize actualSize;
4834 wxRichTextRange actualRange(lastCompletedEndPos+1, wrapPosition);
4835
4836 /// Use previous descent, not the wrapping descent we just found, since this may be too big
4837 /// for the fragment we're about to add.
4838 childDescent = maxDescent;
4839
4840 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
4841 if (!child->IsEmpty())
4842 {
4843 // Get height only, then the width using the partial extents
4844 GetRangeSize(actualRange, actualSize, childDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_HEIGHT_ONLY);
4845 actualSize.x = wxRichTextGetRangeWidth(*this, actualRange, partialExtents);
4846 }
4847 else
4848 #endif
4849 GetRangeSize(actualRange, actualSize, childDescent, dc, context, wxRICHTEXT_UNFORMATTED);
4850
4851 currentWidth = actualSize.x;
4852 maxDescent = wxMax(childDescent, maxDescent);
4853 maxAscent = wxMax(actualSize.y-childDescent, maxAscent);
4854 lineHeight = maxDescent + maxAscent;
4855
4856 if (lineHeight == 0 && buffer)
4857 {
4858 wxFont font(buffer->GetFontTable().FindFont(attr));
4859 wxCheckSetFont(dc, font);
4860 lineHeight = dc.GetCharHeight();
4861 }
4862
4863 if (maxDescent == 0)
4864 {
4865 int w, h;
4866 dc.GetTextExtent(wxT("X"), & w, &h, & maxDescent);
4867 }
4868
4869 // Add a new line
4870 wxRichTextLine* line = AllocateLine(lineCount);
4871
4872 // Set relative range so we won't have to change line ranges when paragraphs are moved
4873 line->SetRange(wxRichTextRange(actualRange.GetStart() - GetRange().GetStart(), actualRange.GetEnd() - GetRange().GetStart()));
4874 line->SetPosition(currentPosition);
4875 line->SetSize(wxSize(currentWidth, lineHeight));
4876 line->SetDescent(maxDescent);
4877
4878 maxHeight = currentPosition.y + lineHeight;
4879
4880 // Now move down a line. TODO: add margins, spacing
4881 currentPosition.y += lineHeight;
4882 currentPosition.y += lineSpacing;
4883 maxDescent = 0;
4884 maxAscent = 0;
4885 maxWidth = wxMax(maxWidth, currentWidth+startOffset);
4886 currentWidth = 0;
4887
4888 lineCount ++;
4889
4890 // TODO: account for zero-length objects, such as fields
4891 // wxASSERT(wrapPosition > lastCompletedEndPos);
4892
4893 lastEndPos = wrapPosition;
4894 lastCompletedEndPos = lastEndPos;
4895
4896 lineHeight = 0;
4897
4898 if (wrapPosition < GetRange().GetEnd()-1)
4899 {
4900 // May need to set the node back to a previous one, due to searching back in wrapping
4901 wxRichTextObject* childAfterWrapPosition = FindObjectAtPosition(wrapPosition+1);
4902 if (childAfterWrapPosition)
4903 node = m_children.Find(childAfterWrapPosition);
4904 else
4905 node = node->GetNext();
4906 }
4907 else
4908 node = node->GetNext();
4909
4910 // Apply paragraph styles such as alignment to the wrapped line
4911 ApplyParagraphStyle(line, attr, availableRect, dc);
4912 }
4913 else
4914 {
4915 // We still fit, so don't add a line, and keep going
4916 currentWidth += childSize.x;
4917 maxDescent = wxMax(childDescent, maxDescent);
4918 maxAscent = wxMax(childSize.y-childDescent, maxAscent);
4919 lineHeight = maxDescent + maxAscent;
4920
4921 maxWidth = wxMax(maxWidth, currentWidth+startOffset);
4922 lastEndPos = child->GetRange().GetEnd();
4923
4924 node = node->GetNext();
4925 }
4926 }
4927
4928 //wxASSERT(!(lastCompletedEndPos != -1 && lastCompletedEndPos < GetRange().GetEnd()-1));
4929
4930 // Remove remaining unused line objects, if any
4931 ClearUnusedLines(lineCount);
4932
4933 // We need to add back the margins etc.
4934 {
4935 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
4936 contentRect = wxRect(wxPoint(0, 0), wxSize(maxWidth, currentPosition.y + spaceAfterPara));
4937 GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
4938 SetCachedSize(marginRect.GetSize());
4939 }
4940
4941 // The maximum size is the length of the paragraph stretched out into a line.
4942 // So if there were a single word, or an image, or a fixed-size text box, the object could be shrunk around
4943 // this size. TODO: take into account line breaks.
4944 {
4945 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
4946 contentRect = wxRect(wxPoint(0, 0), wxSize(paraSize.x + wxMax(leftIndent, leftIndent + leftSubIndent) + rightIndent, currentPosition.y + spaceAfterPara));
4947 GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
4948 SetMaxSize(marginRect.GetSize());
4949 }
4950
4951 // Find the greatest minimum size. Currently we only look at non-text objects,
4952 // which isn't ideal but it would be slow to find the maximum word width to
4953 // use as the minimum.
4954 {
4955 int minWidth = 0;
4956 node = m_children.GetFirst();
4957 while (node)
4958 {
4959 wxRichTextObject* child = node->GetData();
4960
4961 // If floating, ignore. We already laid out floats.
4962 // Also ignore if empty object, except if we haven't got any
4963 // size yet.
4964 if (!child->IsFloating() && child->GetRange().GetLength() != 0 && !child->IsKindOf(CLASSINFO(wxRichTextPlainText)))
4965 {
4966 if (child->GetCachedSize().x > minWidth)
4967 minWidth = child->GetMinSize().x;
4968 }
4969 node = node->GetNext();
4970 }
4971
4972 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
4973 contentRect = wxRect(wxPoint(0, 0), wxSize(minWidth, currentPosition.y + spaceAfterPara));
4974 GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
4975 SetMinSize(marginRect.GetSize());
4976 }
4977
4978
4979 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
4980 #if wxRICHTEXT_USE_OPTIMIZED_LINE_DRAWING
4981 // Use the text extents to calculate the size of each fragment in each line
4982 wxRichTextLineList::compatibility_iterator lineNode = m_cachedLines.GetFirst();
4983 while (lineNode)
4984 {
4985 wxRichTextLine* line = lineNode->GetData();
4986 wxRichTextRange lineRange = line->GetAbsoluteRange();
4987
4988 // Loop through objects until we get to the one within range
4989 wxRichTextObjectList::compatibility_iterator node2 = m_children.GetFirst();
4990
4991 while (node2)
4992 {
4993 wxRichTextObject* child = node2->GetData();
4994
4995 if (child->GetRange().GetLength() > 0 && !child->GetRange().IsOutside(lineRange))
4996 {
4997 wxRichTextRange rangeToUse = lineRange;
4998 rangeToUse.LimitTo(child->GetRange());
4999
5000 // Find the size of the child from the text extents, and store in an array
5001 // for drawing later
5002 int left = 0;
5003 if (rangeToUse.GetStart() > GetRange().GetStart())
5004 left = partialExtents[(rangeToUse.GetStart()-1) - GetRange().GetStart()];
5005 int right = partialExtents[rangeToUse.GetEnd() - GetRange().GetStart()];
5006 int sz = right - left;
5007 line->GetObjectSizes().Add(sz);
5008 }
5009 else if (child->GetRange().GetStart() > lineRange.GetEnd())
5010 // Can break out of inner loop now since we've passed this line's range
5011 break;
5012
5013 node2 = node2->GetNext();
5014 }
5015
5016 lineNode = lineNode->GetNext();
5017 }
5018 #endif
5019 #endif
5020
5021 return true;
5022 }
5023
5024 /// Apply paragraph styles, such as centering, to wrapped lines
5025 /// TODO: take into account box attributes, possibly
5026 void wxRichTextParagraph::ApplyParagraphStyle(wxRichTextLine* line, const wxRichTextAttr& attr, const wxRect& rect, wxDC& dc)
5027 {
5028 if (!attr.HasAlignment())
5029 return;
5030
5031 wxPoint pos = line->GetPosition();
5032 wxSize size = line->GetSize();
5033
5034 // centering, right-justification
5035 if (attr.HasAlignment() && attr.GetAlignment() == wxTEXT_ALIGNMENT_CENTRE)
5036 {
5037 int rightIndent = ConvertTenthsMMToPixels(dc, attr.GetRightIndent());
5038 pos.x = (rect.GetWidth() - rightIndent - size.x)/2 + pos.x;
5039 line->SetPosition(pos);
5040 }
5041 else if (attr.HasAlignment() && attr.GetAlignment() == wxTEXT_ALIGNMENT_RIGHT)
5042 {
5043 int rightIndent = ConvertTenthsMMToPixels(dc, attr.GetRightIndent());
5044 pos.x = pos.x + rect.GetWidth() - size.x - rightIndent;
5045 line->SetPosition(pos);
5046 }
5047 }
5048
5049 /// Insert text at the given position
5050 bool wxRichTextParagraph::InsertText(long pos, const wxString& text)
5051 {
5052 wxRichTextObject* childToUse = NULL;
5053 wxRichTextObjectList::compatibility_iterator nodeToUse = wxRichTextObjectList::compatibility_iterator();
5054
5055 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
5056 while (node)
5057 {
5058 wxRichTextObject* child = node->GetData();
5059 if (child->GetRange().Contains(pos) && child->GetRange().GetLength() > 0)
5060 {
5061 childToUse = child;
5062 nodeToUse = node;
5063 break;
5064 }
5065
5066 node = node->GetNext();
5067 }
5068
5069 if (childToUse)
5070 {
5071 wxRichTextPlainText* textObject = wxDynamicCast(childToUse, wxRichTextPlainText);
5072 if (textObject)
5073 {
5074 int posInString = pos - textObject->GetRange().GetStart();
5075
5076 wxString newText = textObject->GetText().Mid(0, posInString) +
5077 text + textObject->GetText().Mid(posInString);
5078 textObject->SetText(newText);
5079
5080 int textLength = text.length();
5081
5082 textObject->SetRange(wxRichTextRange(textObject->GetRange().GetStart(),
5083 textObject->GetRange().GetEnd() + textLength));
5084
5085 // Increment the end range of subsequent fragments in this paragraph.
5086 // We'll set the paragraph range itself at a higher level.
5087
5088 wxRichTextObjectList::compatibility_iterator node = nodeToUse->GetNext();
5089 while (node)
5090 {
5091 wxRichTextObject* child = node->GetData();
5092 child->SetRange(wxRichTextRange(textObject->GetRange().GetStart() + textLength,
5093 textObject->GetRange().GetEnd() + textLength));
5094
5095 node = node->GetNext();
5096 }
5097
5098 return true;
5099 }
5100 else
5101 {
5102 // TODO: if not a text object, insert at closest position, e.g. in front of it
5103 }
5104 }
5105 else
5106 {
5107 // Add at end.
5108 // Don't pass parent initially to suppress auto-setting of parent range.
5109 // We'll do that at a higher level.
5110 wxRichTextPlainText* textObject = new wxRichTextPlainText(text, this);
5111
5112 AppendChild(textObject);
5113 return true;
5114 }
5115
5116 return false;
5117 }
5118
5119 void wxRichTextParagraph::Copy(const wxRichTextParagraph& obj)
5120 {
5121 wxRichTextCompositeObject::Copy(obj);
5122 }
5123
5124 /// Clear the cached lines
5125 void wxRichTextParagraph::ClearLines()
5126 {
5127 WX_CLEAR_LIST(wxRichTextLineList, m_cachedLines);
5128 }
5129
5130 /// Get/set the object size for the given range. Returns false if the range
5131 /// is invalid for this object.
5132 bool wxRichTextParagraph::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, wxPoint position, wxArrayInt* partialExtents) const
5133 {
5134 if (!range.IsWithin(GetRange()))
5135 return false;
5136
5137 if (flags & wxRICHTEXT_UNFORMATTED)
5138 {
5139 // Just use unformatted data, assume no line breaks
5140 // TODO: take into account line breaks
5141
5142 wxSize sz;
5143
5144 wxArrayInt childExtents;
5145 wxArrayInt* p;
5146 if (partialExtents)
5147 p = & childExtents;
5148 else
5149 p = NULL;
5150
5151 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
5152 while (node)
5153 {
5154
5155 wxRichTextObject* child = node->GetData();
5156 if (!child->GetRange().IsOutside(range))
5157 {
5158 // Floating objects have a zero size within the paragraph.
5159 if (child->IsFloating())
5160 {
5161 if (partialExtents)
5162 {
5163 int lastSize;
5164 if (partialExtents->GetCount() > 0)
5165 lastSize = (*partialExtents)[partialExtents->GetCount()-1];
5166 else
5167 lastSize = 0;
5168
5169 partialExtents->Add(0 /* zero size */ + lastSize);
5170 }
5171 }
5172 else
5173 {
5174 wxSize childSize;
5175
5176 wxRichTextRange rangeToUse = range;
5177 rangeToUse.LimitTo(child->GetRange());
5178 #if 0
5179 if (child->IsTopLevel())
5180 rangeToUse = child->GetOwnRange();
5181 #endif
5182 int childDescent = 0;
5183
5184 // At present wxRICHTEXT_HEIGHT_ONLY is only fast if we're already cached the size,
5185 // but it's only going to be used after caching has taken place.
5186 if ((flags & wxRICHTEXT_HEIGHT_ONLY) && child->GetCachedSize().y != 0)
5187 {
5188 childDescent = child->GetDescent();
5189 childSize = child->GetCachedSize();
5190
5191 sz.y = wxMax(sz.y, childSize.y);
5192 sz.x += childSize.x;
5193 descent = wxMax(descent, childDescent);
5194 }
5195 else if (child->IsTopLevel())
5196 {
5197 childDescent = child->GetDescent();
5198 childSize = child->GetCachedSize();
5199
5200 sz.y = wxMax(sz.y, childSize.y);
5201 sz.x += childSize.x;
5202 descent = wxMax(descent, childDescent);
5203 if ((flags & wxRICHTEXT_CACHE_SIZE) && (rangeToUse == child->GetRange()))
5204 {
5205 child->SetCachedSize(childSize);
5206 child->SetDescent(childDescent);
5207 }
5208
5209 if (partialExtents)
5210 {
5211 int lastSize;
5212 if (partialExtents->GetCount() > 0)
5213 lastSize = (*partialExtents)[partialExtents->GetCount()-1];
5214 else
5215 lastSize = 0;
5216
5217 partialExtents->Add(childSize.x + lastSize);
5218 }
5219 }
5220 else if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, context, flags, wxPoint(position.x + sz.x, position.y), p))
5221 {
5222 sz.y = wxMax(sz.y, childSize.y);
5223 sz.x += childSize.x;
5224 descent = wxMax(descent, childDescent);
5225
5226 if ((flags & wxRICHTEXT_CACHE_SIZE) && (rangeToUse == child->GetRange()))
5227 {
5228 child->SetCachedSize(childSize);
5229 child->SetDescent(childDescent);
5230 }
5231
5232 if (partialExtents)
5233 {
5234 int lastSize;
5235 if (partialExtents->GetCount() > 0)
5236 lastSize = (*partialExtents)[partialExtents->GetCount()-1];
5237 else
5238 lastSize = 0;
5239
5240 size_t i;
5241 for (i = 0; i < childExtents.GetCount(); i++)
5242 {
5243 partialExtents->Add(childExtents[i] + lastSize);
5244 }
5245 }
5246 }
5247 }
5248
5249 if (p)
5250 p->Clear();
5251 }
5252
5253 node = node->GetNext();
5254 }
5255 size = sz;
5256 }
5257 else
5258 {
5259 // Use formatted data, with line breaks
5260 wxSize sz;
5261
5262 // We're going to loop through each line, and then for each line,
5263 // call GetRangeSize for the fragment that comprises that line.
5264 // Only we have to do that multiple times within the line, because
5265 // the line may be broken into pieces. For now ignore line break commands
5266 // (so we can assume that getting the unformatted size for a fragment
5267 // within a line is the actual size)
5268
5269 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
5270 while (node)
5271 {
5272 wxRichTextLine* line = node->GetData();
5273 wxRichTextRange lineRange = line->GetAbsoluteRange();
5274 if (!lineRange.IsOutside(range))
5275 {
5276 wxSize lineSize;
5277
5278 wxRichTextObjectList::compatibility_iterator node2 = m_children.GetFirst();
5279 while (node2)
5280 {
5281 wxRichTextObject* child = node2->GetData();
5282
5283 if (!child->IsFloating() && !child->GetRange().IsOutside(lineRange))
5284 {
5285 wxRichTextRange rangeToUse = lineRange;
5286 rangeToUse.LimitTo(child->GetRange());
5287 if (child->IsTopLevel())
5288 rangeToUse = child->GetOwnRange();
5289
5290 wxSize childSize;
5291 int childDescent = 0;
5292 if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, context, flags, wxPoint(position.x + sz.x, position.y)))
5293 {
5294 lineSize.y = wxMax(lineSize.y, childSize.y);
5295 lineSize.x += childSize.x;
5296 }
5297 descent = wxMax(descent, childDescent);
5298 }
5299
5300 node2 = node2->GetNext();
5301 }
5302
5303 // Increase size by a line (TODO: paragraph spacing)
5304 sz.y += lineSize.y;
5305 sz.x = wxMax(sz.x, lineSize.x);
5306 }
5307 node = node->GetNext();
5308 }
5309 size = sz;
5310 }
5311 return true;
5312 }
5313
5314 /// Finds the absolute position and row height for the given character position
5315 bool wxRichTextParagraph::FindPosition(wxDC& dc, wxRichTextDrawingContext& context, long index, wxPoint& pt, int* height, bool forceLineStart)
5316 {
5317 if (index == -1)
5318 {
5319 wxRichTextLine* line = ((wxRichTextParagraphLayoutBox*)GetParent())->GetLineAtPosition(0);
5320 if (line)
5321 *height = line->GetSize().y;
5322 else
5323 *height = dc.GetCharHeight();
5324
5325 // -1 means 'the start of the buffer'.
5326 pt = GetPosition();
5327 if (line)
5328 pt = pt + line->GetPosition();
5329
5330 return true;
5331 }
5332
5333 // The final position in a paragraph is taken to mean the position
5334 // at the start of the next paragraph.
5335 if (index == GetRange().GetEnd())
5336 {
5337 wxRichTextParagraphLayoutBox* parent = wxDynamicCast(GetParent(), wxRichTextParagraphLayoutBox);
5338 wxASSERT( parent != NULL );
5339
5340 // Find the height at the next paragraph, if any
5341 wxRichTextLine* line = parent->GetLineAtPosition(index + 1);
5342 if (line)
5343 {
5344 *height = line->GetSize().y;
5345 pt = line->GetAbsolutePosition();
5346 }
5347 else
5348 {
5349 *height = dc.GetCharHeight();
5350 int indent = ConvertTenthsMMToPixels(dc, m_attributes.GetLeftIndent());
5351 pt = wxPoint(indent, GetCachedSize().y);
5352 }
5353
5354 return true;
5355 }
5356
5357 if (index < GetRange().GetStart() || index > GetRange().GetEnd())
5358 return false;
5359
5360 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
5361 while (node)
5362 {
5363 wxRichTextLine* line = node->GetData();
5364 wxRichTextRange lineRange = line->GetAbsoluteRange();
5365 if (index >= lineRange.GetStart() && index <= lineRange.GetEnd())
5366 {
5367 // If this is the last point in the line, and we're forcing the
5368 // returned value to be the start of the next line, do the required
5369 // thing.
5370 if (index == lineRange.GetEnd() && forceLineStart)
5371 {
5372 if (node->GetNext())
5373 {
5374 wxRichTextLine* nextLine = node->GetNext()->GetData();
5375 *height = nextLine->GetSize().y;
5376 pt = nextLine->GetAbsolutePosition();
5377 return true;
5378 }
5379 }
5380
5381 pt.y = line->GetPosition().y + GetPosition().y;
5382
5383 wxRichTextRange r(lineRange.GetStart(), index);
5384 wxSize rangeSize;
5385 int descent = 0;
5386
5387 // We find the size of the line up to this point,
5388 // then we can add this size to the line start position and
5389 // paragraph start position to find the actual position.
5390
5391 if (GetRangeSize(r, rangeSize, descent, dc, context, wxRICHTEXT_UNFORMATTED, line->GetPosition()+ GetPosition()))
5392 {
5393 pt.x = line->GetPosition().x + GetPosition().x + rangeSize.x;
5394 *height = line->GetSize().y;
5395
5396 return true;
5397 }
5398
5399 }
5400
5401 node = node->GetNext();
5402 }
5403
5404 return false;
5405 }
5406
5407 /// Hit-testing: returns a flag indicating hit test details, plus
5408 /// information about position
5409 int wxRichTextParagraph::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int flags)
5410 {
5411 if (!IsShown())
5412 return wxRICHTEXT_HITTEST_NONE;
5413
5414 // If we're in the top-level container, then we can return
5415 // a suitable hit test code even if the point is outside the container area,
5416 // so that we can position the caret sensibly even if we don't
5417 // click on valid content. If we're not at the top-level, and the point
5418 // is not within this paragraph object, then we don't want to stop more
5419 // precise hit-testing from working prematurely, so return immediately.
5420 // NEW STRATEGY: use the parent boundary to test whether we're in the
5421 // right region, not the paragraph, since the paragraph may be positioned
5422 // some way in from where the user clicks.
5423 {
5424 long tmpPos;
5425 wxRichTextObject* tempObj, *tempContextObj;
5426 if (GetParent() && GetParent()->wxRichTextObject::HitTest(dc, context, pt, tmpPos, & tempObj, & tempContextObj, flags) == wxRICHTEXT_HITTEST_NONE)
5427 return wxRICHTEXT_HITTEST_NONE;
5428 }
5429
5430 wxRichTextObjectList::compatibility_iterator objNode = m_children.GetFirst();
5431 while (objNode)
5432 {
5433 wxRichTextObject* child = objNode->GetData();
5434 if (child->IsTopLevel() && ((flags & wxRICHTEXT_HITTEST_NO_NESTED_OBJECTS) == 0))
5435 {
5436 {
5437 int hitTest = child->HitTest(dc, context, pt, textPosition, obj, contextObj);
5438 if (hitTest != wxRICHTEXT_HITTEST_NONE)
5439 return hitTest;
5440 }
5441 }
5442
5443 objNode = objNode->GetNext();
5444 }
5445
5446 wxPoint paraPos = GetPosition();
5447
5448 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
5449 while (node)
5450 {
5451 wxRichTextLine* line = node->GetData();
5452 wxPoint linePos = paraPos + line->GetPosition();
5453 wxSize lineSize = line->GetSize();
5454 wxRichTextRange lineRange = line->GetAbsoluteRange();
5455
5456 if (pt.y <= linePos.y + lineSize.y)
5457 {
5458 if (pt.x < linePos.x)
5459 {
5460 textPosition = lineRange.GetStart();
5461 *obj = FindObjectAtPosition(textPosition);
5462 *contextObj = GetContainer();
5463 return wxRICHTEXT_HITTEST_BEFORE|wxRICHTEXT_HITTEST_OUTSIDE;
5464 }
5465 else if (pt.x >= (linePos.x + lineSize.x))
5466 {
5467 textPosition = lineRange.GetEnd();
5468 *obj = FindObjectAtPosition(textPosition);
5469 *contextObj = GetContainer();
5470 return wxRICHTEXT_HITTEST_AFTER|wxRICHTEXT_HITTEST_OUTSIDE;
5471 }
5472 else
5473 {
5474 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
5475 wxArrayInt partialExtents;
5476
5477 wxSize paraSize;
5478 int paraDescent;
5479
5480 // This calculates the partial text extents
5481 GetRangeSize(lineRange, paraSize, paraDescent, dc, context, wxRICHTEXT_UNFORMATTED, linePos, & partialExtents);
5482
5483 int lastX = linePos.x;
5484 size_t i;
5485 for (i = 0; i < partialExtents.GetCount(); i++)
5486 {
5487 int nextX = partialExtents[i] + linePos.x;
5488
5489 if (pt.x >= lastX && pt.x <= nextX)
5490 {
5491 textPosition = i + lineRange.GetStart(); // minus 1?
5492
5493 *obj = FindObjectAtPosition(textPosition);
5494 *contextObj = GetContainer();
5495
5496 // So now we know it's between i-1 and i.
5497 // Let's see if we can be more precise about
5498 // which side of the position it's on.
5499
5500 int midPoint = (nextX + lastX)/2;
5501 if (pt.x >= midPoint)
5502 return wxRICHTEXT_HITTEST_AFTER;
5503 else
5504 return wxRICHTEXT_HITTEST_BEFORE;
5505 }
5506
5507 lastX = nextX;
5508 }
5509 #else
5510 long i;
5511 int lastX = linePos.x;
5512 for (i = lineRange.GetStart(); i <= lineRange.GetEnd(); i++)
5513 {
5514 wxSize childSize;
5515 int descent = 0;
5516
5517 wxRichTextRange rangeToUse(lineRange.GetStart(), i);
5518
5519 GetRangeSize(rangeToUse, childSize, descent, dc, context, wxRICHTEXT_UNFORMATTED, linePos);
5520
5521 int nextX = childSize.x + linePos.x;
5522
5523 if (pt.x >= lastX && pt.x <= nextX)
5524 {
5525 textPosition = i;
5526
5527 *obj = FindObjectAtPosition(textPosition);
5528 *contextObj = GetContainer();
5529
5530 // So now we know it's between i-1 and i.
5531 // Let's see if we can be more precise about
5532 // which side of the position it's on.
5533
5534 int midPoint = (nextX + lastX)/2;
5535 if (pt.x >= midPoint)
5536 return wxRICHTEXT_HITTEST_AFTER;
5537 else
5538 return wxRICHTEXT_HITTEST_BEFORE;
5539 }
5540 else
5541 {
5542 lastX = nextX;
5543 }
5544 }
5545 #endif
5546 }
5547 }
5548
5549 node = node->GetNext();
5550 }
5551
5552 return wxRICHTEXT_HITTEST_NONE;
5553 }
5554
5555 /// Split an object at this position if necessary, and return
5556 /// the previous object, or NULL if inserting at beginning.
5557 wxRichTextObject* wxRichTextParagraph::SplitAt(long pos, wxRichTextObject** previousObject)
5558 {
5559 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
5560 while (node)
5561 {
5562 wxRichTextObject* child = node->GetData();
5563
5564 if (pos == child->GetRange().GetStart())
5565 {
5566 if (previousObject)
5567 {
5568 if (node->GetPrevious())
5569 *previousObject = node->GetPrevious()->GetData();
5570 else
5571 *previousObject = NULL;
5572 }
5573
5574 return child;
5575 }
5576
5577 if (child->GetRange().Contains(pos))
5578 {
5579 // This should create a new object, transferring part of
5580 // the content to the old object and the rest to the new object.
5581 wxRichTextObject* newObject = child->DoSplit(pos);
5582
5583 // If we couldn't split this object, just insert in front of it.
5584 if (!newObject)
5585 {
5586 // Maybe this is an empty string, try the next one
5587 // return child;
5588 }
5589 else
5590 {
5591 // Insert the new object after 'child'
5592 if (node->GetNext())
5593 m_children.Insert(node->GetNext(), newObject);
5594 else
5595 m_children.Append(newObject);
5596 newObject->SetParent(this);
5597
5598 if (previousObject)
5599 *previousObject = child;
5600
5601 return newObject;
5602 }
5603 }
5604
5605 node = node->GetNext();
5606 }
5607 if (previousObject)
5608 *previousObject = NULL;
5609 return NULL;
5610 }
5611
5612 /// Move content to a list from obj on
5613 void wxRichTextParagraph::MoveToList(wxRichTextObject* obj, wxList& list)
5614 {
5615 wxRichTextObjectList::compatibility_iterator node = m_children.Find(obj);
5616 while (node)
5617 {
5618 wxRichTextObject* child = node->GetData();
5619 list.Append(child);
5620
5621 wxRichTextObjectList::compatibility_iterator oldNode = node;
5622
5623 node = node->GetNext();
5624
5625 m_children.DeleteNode(oldNode);
5626 }
5627 }
5628
5629 /// Add content back from list
5630 void wxRichTextParagraph::MoveFromList(wxList& list)
5631 {
5632 for (wxList::compatibility_iterator node = list.GetFirst(); node; node = node->GetNext())
5633 {
5634 AppendChild((wxRichTextObject*) node->GetData());
5635 }
5636 }
5637
5638 /// Calculate range
5639 void wxRichTextParagraph::CalculateRange(long start, long& end)
5640 {
5641 wxRichTextCompositeObject::CalculateRange(start, end);
5642
5643 // Add one for end of paragraph
5644 end ++;
5645
5646 m_range.SetRange(start, end);
5647 }
5648
5649 /// Find the object at the given position
5650 wxRichTextObject* wxRichTextParagraph::FindObjectAtPosition(long position)
5651 {
5652 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
5653 while (node)
5654 {
5655 wxRichTextObject* obj = node->GetData();
5656 if (obj->GetRange().Contains(position) ||
5657 obj->GetRange().GetStart() == position ||
5658 obj->GetRange().GetEnd() == position)
5659 return obj;
5660
5661 node = node->GetNext();
5662 }
5663 return NULL;
5664 }
5665
5666 /// Get the plain text searching from the start or end of the range.
5667 /// The resulting string may be shorter than the range given.
5668 bool wxRichTextParagraph::GetContiguousPlainText(wxString& text, const wxRichTextRange& range, bool fromStart)
5669 {
5670 text = wxEmptyString;
5671
5672 if (fromStart)
5673 {
5674 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
5675 while (node)
5676 {
5677 wxRichTextObject* obj = node->GetData();
5678 if (!obj->GetRange().IsOutside(range))
5679 {
5680 wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText);
5681 if (textObj)
5682 {
5683 text += textObj->GetTextForRange(range);
5684 }
5685 else
5686 {
5687 text += wxT(" ");
5688 }
5689 }
5690
5691 node = node->GetNext();
5692 }
5693 }
5694 else
5695 {
5696 wxRichTextObjectList::compatibility_iterator node = m_children.GetLast();
5697 while (node)
5698 {
5699 wxRichTextObject* obj = node->GetData();
5700 if (!obj->GetRange().IsOutside(range))
5701 {
5702 wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText);
5703 if (textObj)
5704 {
5705 text = textObj->GetTextForRange(range) + text;
5706 }
5707 else
5708 {
5709 text = wxT(" ") + text;
5710 }
5711 }
5712
5713 node = node->GetPrevious();
5714 }
5715 }
5716
5717 return true;
5718 }
5719
5720 /// Find a suitable wrap position.
5721 bool wxRichTextParagraph::FindWrapPosition(const wxRichTextRange& range, wxDC& dc, wxRichTextDrawingContext& context, int availableSpace, long& wrapPosition, wxArrayInt* partialExtents)
5722 {
5723 if (range.GetLength() <= 0)
5724 return false;
5725
5726 // Find the first position where the line exceeds the available space.
5727 wxSize sz;
5728 long breakPosition = range.GetEnd();
5729
5730 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
5731 if (partialExtents && partialExtents->GetCount() >= (size_t) (GetRange().GetLength()-1)) // the final position in a paragraph is the newline
5732 {
5733 int widthBefore;
5734
5735 if (range.GetStart() > GetRange().GetStart())
5736 widthBefore = (*partialExtents)[range.GetStart() - GetRange().GetStart() - 1];
5737 else
5738 widthBefore = 0;
5739
5740 size_t i;
5741 for (i = (size_t) range.GetStart(); i <= (size_t) range.GetEnd(); i++)
5742 {
5743 int widthFromStartOfThisRange = (*partialExtents)[i - GetRange().GetStart()] - widthBefore;
5744
5745 if (widthFromStartOfThisRange > availableSpace)
5746 {
5747 breakPosition = i-1;
5748 break;
5749 }
5750 }
5751 }
5752 else
5753 #endif
5754 {
5755 // Binary chop for speed
5756 long minPos = range.GetStart();
5757 long maxPos = range.GetEnd();
5758 while (true)
5759 {
5760 if (minPos == maxPos)
5761 {
5762 int descent = 0;
5763 GetRangeSize(wxRichTextRange(range.GetStart(), minPos), sz, descent, dc, context, wxRICHTEXT_UNFORMATTED);
5764
5765 if (sz.x > availableSpace)
5766 breakPosition = minPos - 1;
5767 break;
5768 }
5769 else if ((maxPos - minPos) == 1)
5770 {
5771 int descent = 0;
5772 GetRangeSize(wxRichTextRange(range.GetStart(), minPos), sz, descent, dc, context, wxRICHTEXT_UNFORMATTED);
5773
5774 if (sz.x > availableSpace)
5775 breakPosition = minPos - 1;
5776 else
5777 {
5778 GetRangeSize(wxRichTextRange(range.GetStart(), maxPos), sz, descent, dc, context, wxRICHTEXT_UNFORMATTED);
5779 if (sz.x > availableSpace)
5780 breakPosition = maxPos-1;
5781 }
5782 break;
5783 }
5784 else
5785 {
5786 long nextPos = minPos + ((maxPos - minPos) / 2);
5787
5788 int descent = 0;
5789 GetRangeSize(wxRichTextRange(range.GetStart(), nextPos), sz, descent, dc, context, wxRICHTEXT_UNFORMATTED);
5790
5791 if (sz.x > availableSpace)
5792 {
5793 maxPos = nextPos;
5794 }
5795 else
5796 {
5797 minPos = nextPos;
5798 }
5799 }
5800 }
5801 }
5802
5803 // Now we know the last position on the line.
5804 // Let's try to find a word break.
5805
5806 wxString plainText;
5807 if (GetContiguousPlainText(plainText, wxRichTextRange(range.GetStart(), breakPosition), false))
5808 {
5809 int newLinePos = plainText.Find(wxRichTextLineBreakChar);
5810 if (newLinePos != wxNOT_FOUND)
5811 {
5812 breakPosition = wxMax(0, range.GetStart() + newLinePos);
5813 }
5814 else
5815 {
5816 int spacePos = plainText.Find(wxT(' '), true);
5817 int tabPos = plainText.Find(wxT('\t'), true);
5818 int pos = wxMax(spacePos, tabPos);
5819 if (pos != wxNOT_FOUND)
5820 {
5821 int positionsFromEndOfString = plainText.length() - pos - 1;
5822 breakPosition = breakPosition - positionsFromEndOfString;
5823 }
5824 }
5825 }
5826
5827 wrapPosition = breakPosition;
5828
5829 return true;
5830 }
5831
5832 /// Get the bullet text for this paragraph.
5833 wxString wxRichTextParagraph::GetBulletText()
5834 {
5835 if (GetAttributes().GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_NONE ||
5836 (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_BITMAP))
5837 return wxEmptyString;
5838
5839 int number = GetAttributes().GetBulletNumber();
5840
5841 wxString text;
5842 if ((GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ARABIC) || (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE))
5843 {
5844 text.Printf(wxT("%d"), number);
5845 }
5846 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_LETTERS_UPPER)
5847 {
5848 // TODO: Unicode, and also check if number > 26
5849 text.Printf(wxT("%c"), (wxChar) (number+64));
5850 }
5851 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_LETTERS_LOWER)
5852 {
5853 // TODO: Unicode, and also check if number > 26
5854 text.Printf(wxT("%c"), (wxChar) (number+96));
5855 }
5856 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ROMAN_UPPER)
5857 {
5858 text = wxRichTextDecimalToRoman(number);
5859 }
5860 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ROMAN_LOWER)
5861 {
5862 text = wxRichTextDecimalToRoman(number);
5863 text.MakeLower();
5864 }
5865 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_SYMBOL)
5866 {
5867 text = GetAttributes().GetBulletText();
5868 }
5869
5870 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE)
5871 {
5872 // The outline style relies on the text being computed statically,
5873 // since it depends on other levels points (e.g. 1.2.1.1). So normally the bullet text
5874 // should be stored in the attributes; if not, just use the number for this
5875 // level, as previously computed.
5876 if (!GetAttributes().GetBulletText().IsEmpty())
5877 text = GetAttributes().GetBulletText();
5878 }
5879
5880 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_PARENTHESES)
5881 {
5882 text = wxT("(") + text + wxT(")");
5883 }
5884 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_RIGHT_PARENTHESIS)
5885 {
5886 text = text + wxT(")");
5887 }
5888
5889 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_PERIOD)
5890 {
5891 text += wxT(".");
5892 }
5893
5894 return text;
5895 }
5896
5897 /// Allocate or reuse a line object
5898 wxRichTextLine* wxRichTextParagraph::AllocateLine(int pos)
5899 {
5900 if (pos < (int) m_cachedLines.GetCount())
5901 {
5902 wxRichTextLine* line = m_cachedLines.Item(pos)->GetData();
5903 line->Init(this);
5904 return line;
5905 }
5906 else
5907 {
5908 wxRichTextLine* line = new wxRichTextLine(this);
5909 m_cachedLines.Append(line);
5910 return line;
5911 }
5912 }
5913
5914 /// Clear remaining unused line objects, if any
5915 bool wxRichTextParagraph::ClearUnusedLines(int lineCount)
5916 {
5917 int cachedLineCount = m_cachedLines.GetCount();
5918 if ((int) cachedLineCount > lineCount)
5919 {
5920 for (int i = 0; i < (int) (cachedLineCount - lineCount); i ++)
5921 {
5922 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetLast();
5923 wxRichTextLine* line = node->GetData();
5924 m_cachedLines.Erase(node);
5925 delete line;
5926 }
5927 }
5928 return true;
5929 }
5930
5931 /// Get combined attributes of the base style, paragraph style and character style. We use this to dynamically
5932 /// retrieve the actual style.
5933 wxRichTextAttr wxRichTextParagraph::GetCombinedAttributes(const wxRichTextAttr& contentStyle, bool includingBoxAttr) const
5934 {
5935 wxRichTextAttr attr;
5936 wxRichTextParagraphLayoutBox* buf = wxDynamicCast(GetParent(), wxRichTextParagraphLayoutBox);
5937 if (buf)
5938 {
5939 attr = buf->GetBasicStyle();
5940 if (!includingBoxAttr)
5941 {
5942 attr.GetTextBoxAttr().Reset();
5943 // The background colour will be painted by the container, and we don't
5944 // want to unnecessarily overwrite the background when we're drawing text
5945 // because this may erase the guideline (which appears just under the text
5946 // if there's no padding).
5947 attr.SetFlags(attr.GetFlags() & ~wxTEXT_ATTR_BACKGROUND_COLOUR);
5948 }
5949 wxRichTextApplyStyle(attr, GetAttributes());
5950 }
5951 else
5952 attr = GetAttributes();
5953
5954 wxRichTextApplyStyle(attr, contentStyle);
5955 return attr;
5956 }
5957
5958 /// Get combined attributes of the base style and paragraph style.
5959 wxRichTextAttr wxRichTextParagraph::GetCombinedAttributes(bool includingBoxAttr) const
5960 {
5961 wxRichTextAttr attr;
5962 wxRichTextParagraphLayoutBox* buf = wxDynamicCast(GetParent(), wxRichTextParagraphLayoutBox);
5963 if (buf)
5964 {
5965 attr = buf->GetBasicStyle();
5966 if (!includingBoxAttr)
5967 attr.GetTextBoxAttr().Reset();
5968 wxRichTextApplyStyle(attr, GetAttributes());
5969 }
5970 else
5971 attr = GetAttributes();
5972
5973 return attr;
5974 }
5975
5976 // Create default tabstop array
5977 void wxRichTextParagraph::InitDefaultTabs()
5978 {
5979 // create a default tab list at 10 mm each.
5980 for (int i = 0; i < 20; ++i)
5981 {
5982 sm_defaultTabs.Add(i*100);
5983 }
5984 }
5985
5986 // Clear default tabstop array
5987 void wxRichTextParagraph::ClearDefaultTabs()
5988 {
5989 sm_defaultTabs.Clear();
5990 }
5991
5992 void wxRichTextParagraph::LayoutFloat(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, int style, wxRichTextFloatCollector* floatCollector)
5993 {
5994 wxRichTextObjectList::compatibility_iterator node = GetChildren().GetFirst();
5995 while (node)
5996 {
5997 wxRichTextObject* anchored = node->GetData();
5998 if (anchored && anchored->IsFloating() && !floatCollector->HasFloat(anchored))
5999 {
6000 wxSize size;
6001 int descent, x = 0;
6002 anchored->GetRangeSize(anchored->GetRange(), size, descent, dc, context, style);
6003
6004 int offsetY = 0;
6005 if (anchored->GetAttributes().GetTextBoxAttr().GetTop().IsValid())
6006 {
6007 offsetY = anchored->GetAttributes().GetTextBoxAttr().GetTop().GetValue();
6008 if (anchored->GetAttributes().GetTextBoxAttr().GetTop().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
6009 {
6010 offsetY = ConvertTenthsMMToPixels(dc, offsetY);
6011 }
6012 }
6013
6014 int pos = floatCollector->GetFitPosition(anchored->GetAttributes().GetTextBoxAttr().GetFloatMode(), rect.y + offsetY, size.y);
6015
6016 /* Update the offset */
6017 int newOffsetY = pos - rect.y;
6018 if (newOffsetY != offsetY)
6019 {
6020 if (anchored->GetAttributes().GetTextBoxAttr().GetTop().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
6021 newOffsetY = ConvertPixelsToTenthsMM(dc, newOffsetY);
6022 anchored->GetAttributes().GetTextBoxAttr().GetTop().SetValue(newOffsetY);
6023 }
6024
6025 if (anchored->GetAttributes().GetTextBoxAttr().GetFloatMode() == wxTEXT_BOX_ATTR_FLOAT_LEFT)
6026 x = rect.x;
6027 else if (anchored->GetAttributes().GetTextBoxAttr().GetFloatMode() == wxTEXT_BOX_ATTR_FLOAT_RIGHT)
6028 x = rect.x + rect.width - size.x;
6029
6030 anchored->SetPosition(wxPoint(x, pos));
6031 anchored->SetCachedSize(size);
6032 floatCollector->CollectFloat(this, anchored);
6033 }
6034
6035 node = node->GetNext();
6036 }
6037 }
6038
6039 // Get the first position from pos that has a line break character.
6040 long wxRichTextParagraph::GetFirstLineBreakPosition(long pos)
6041 {
6042 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
6043 while (node)
6044 {
6045 wxRichTextObject* obj = node->GetData();
6046 if (pos >= obj->GetRange().GetStart() && pos <= obj->GetRange().GetEnd())
6047 {
6048 wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText);
6049 if (textObj)
6050 {
6051 long breakPos = textObj->GetFirstLineBreakPosition(pos);
6052 if (breakPos > -1)
6053 return breakPos;
6054 }
6055 }
6056 node = node->GetNext();
6057 }
6058 return -1;
6059 }
6060
6061 /*!
6062 * wxRichTextLine
6063 * This object represents a line in a paragraph, and stores
6064 * offsets from the start of the paragraph representing the
6065 * start and end positions of the line.
6066 */
6067
6068 wxRichTextLine::wxRichTextLine(wxRichTextParagraph* parent)
6069 {
6070 Init(parent);
6071 }
6072
6073 /// Initialisation
6074 void wxRichTextLine::Init(wxRichTextParagraph* parent)
6075 {
6076 m_parent = parent;
6077 m_range.SetRange(-1, -1);
6078 m_pos = wxPoint(0, 0);
6079 m_size = wxSize(0, 0);
6080 m_descent = 0;
6081 #if wxRICHTEXT_USE_OPTIMIZED_LINE_DRAWING
6082 m_objectSizes.Clear();
6083 #endif
6084 }
6085
6086 /// Copy
6087 void wxRichTextLine::Copy(const wxRichTextLine& obj)
6088 {
6089 m_range = obj.m_range;
6090 #if wxRICHTEXT_USE_OPTIMIZED_LINE_DRAWING
6091 m_objectSizes = obj.m_objectSizes;
6092 #endif
6093 }
6094
6095 /// Get the absolute object position
6096 wxPoint wxRichTextLine::GetAbsolutePosition() const
6097 {
6098 return m_parent->GetPosition() + m_pos;
6099 }
6100
6101 /// Get the absolute range
6102 wxRichTextRange wxRichTextLine::GetAbsoluteRange() const
6103 {
6104 wxRichTextRange range(m_range.GetStart() + m_parent->GetRange().GetStart(), 0);
6105 range.SetEnd(range.GetStart() + m_range.GetLength()-1);
6106 return range;
6107 }
6108
6109 /*!
6110 * wxRichTextPlainText
6111 * This object represents a single piece of text.
6112 */
6113
6114 IMPLEMENT_DYNAMIC_CLASS(wxRichTextPlainText, wxRichTextObject)
6115
6116 wxRichTextPlainText::wxRichTextPlainText(const wxString& text, wxRichTextObject* parent, wxRichTextAttr* style):
6117 wxRichTextObject(parent)
6118 {
6119 if (style)
6120 SetAttributes(*style);
6121
6122 m_text = text;
6123 }
6124
6125 #define USE_KERNING_FIX 1
6126
6127 // If insufficient tabs are defined, this is the tab width used
6128 #define WIDTH_FOR_DEFAULT_TABS 50
6129
6130 /// Draw the item
6131 bool wxRichTextPlainText::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int WXUNUSED(style))
6132 {
6133 wxRichTextParagraph* para = wxDynamicCast(GetParent(), wxRichTextParagraph);
6134 wxASSERT (para != NULL);
6135
6136 wxRichTextAttr textAttr(para ? para->GetCombinedAttributes(GetAttributes(), false /* no box attributes */) : GetAttributes());
6137 context.ApplyVirtualAttributes(textAttr, this);
6138
6139 // Let's make the assumption for now that for content in a paragraph, including
6140 // text, we never have a discontinuous selection. So we only deal with a
6141 // single range.
6142 wxRichTextRange selectionRange;
6143 if (selection.IsValid())
6144 {
6145 wxRichTextRangeArray selectionRanges = selection.GetSelectionForObject(this);
6146 if (selectionRanges.GetCount() > 0)
6147 selectionRange = selectionRanges[0];
6148 else
6149 selectionRange = wxRICHTEXT_NO_SELECTION;
6150 }
6151 else
6152 selectionRange = wxRICHTEXT_NO_SELECTION;
6153
6154 int offset = GetRange().GetStart();
6155
6156 // Replace line break characters with spaces
6157 wxString str = m_text;
6158 wxString toRemove = wxRichTextLineBreakChar;
6159 str.Replace(toRemove, wxT(" "));
6160 if (textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_CAPITALS))
6161 str.MakeUpper();
6162
6163 long len = range.GetLength();
6164 wxString stringChunk = str.Mid(range.GetStart() - offset, (size_t) len);
6165
6166 // Test for the optimized situations where all is selected, or none
6167 // is selected.
6168
6169 wxFont textFont(GetBuffer()->GetFontTable().FindFont(textAttr));
6170 wxCheckSetFont(dc, textFont);
6171 int charHeight = dc.GetCharHeight();
6172
6173 int x, y;
6174 if ( textFont.IsOk() )
6175 {
6176 if ( textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUPERSCRIPT) )
6177 {
6178 double size = static_cast<double>(textFont.GetPointSize()) / wxSCRIPT_MUL_FACTOR;
6179 textFont.SetPointSize( static_cast<int>(size) );
6180 x = rect.x;
6181 y = rect.y;
6182 wxCheckSetFont(dc, textFont);
6183 }
6184 else if ( textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUBSCRIPT) )
6185 {
6186 double size = static_cast<double>(textFont.GetPointSize()) / wxSCRIPT_MUL_FACTOR;
6187 textFont.SetPointSize( static_cast<int>(size) );
6188 x = rect.x;
6189 int sub_height = static_cast<int>( static_cast<double>(charHeight) / wxSCRIPT_MUL_FACTOR);
6190 y = rect.y + (rect.height - sub_height + (descent - m_descent));
6191 wxCheckSetFont(dc, textFont);
6192 }
6193 else
6194 {
6195 x = rect.x;
6196 y = rect.y + (rect.height - charHeight - (descent - m_descent));
6197 }
6198 }
6199 else
6200 {
6201 x = rect.x;
6202 y = rect.y + (rect.height - charHeight - (descent - m_descent));
6203 }
6204
6205 // TODO: new selection code
6206
6207 // (a) All selected.
6208 if (selectionRange.GetStart() <= range.GetStart() && selectionRange.GetEnd() >= range.GetEnd())
6209 {
6210 DrawTabbedString(dc, textAttr, rect, stringChunk, x, y, true);
6211 }
6212 // (b) None selected.
6213 else if (selectionRange.GetEnd() < range.GetStart() || selectionRange.GetStart() > range.GetEnd())
6214 {
6215 // Draw all unselected
6216 DrawTabbedString(dc, textAttr, rect, stringChunk, x, y, false);
6217 }
6218 else
6219 {
6220 // (c) Part selected, part not
6221 // Let's draw unselected chunk, selected chunk, then unselected chunk.
6222
6223 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
6224
6225 // 1. Initial unselected chunk, if any, up until start of selection.
6226 if (selectionRange.GetStart() > range.GetStart() && selectionRange.GetStart() <= range.GetEnd())
6227 {
6228 int r1 = range.GetStart();
6229 int s1 = selectionRange.GetStart()-1;
6230 int fragmentLen = s1 - r1 + 1;
6231 if (fragmentLen < 0)
6232 {
6233 wxLogDebug(wxT("Mid(%d, %d"), (int)(r1 - offset), (int)fragmentLen);
6234 }
6235 wxString stringFragment = str.Mid(r1 - offset, fragmentLen);
6236
6237 DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, false);
6238
6239 #if USE_KERNING_FIX
6240 if (stringChunk.Find(wxT("\t")) == wxNOT_FOUND)
6241 {
6242 // Compensate for kerning difference
6243 wxString stringFragment2(str.Mid(r1 - offset, fragmentLen+1));
6244 wxString stringFragment3(str.Mid(r1 - offset + fragmentLen, 1));
6245
6246 wxCoord w1, h1, w2, h2, w3, h3;
6247 dc.GetTextExtent(stringFragment, & w1, & h1);
6248 dc.GetTextExtent(stringFragment2, & w2, & h2);
6249 dc.GetTextExtent(stringFragment3, & w3, & h3);
6250
6251 int kerningDiff = (w1 + w3) - w2;
6252 x = x - kerningDiff;
6253 }
6254 #endif
6255 }
6256
6257 // 2. Selected chunk, if any.
6258 if (selectionRange.GetEnd() >= range.GetStart())
6259 {
6260 int s1 = wxMax(selectionRange.GetStart(), range.GetStart());
6261 int s2 = wxMin(selectionRange.GetEnd(), range.GetEnd());
6262
6263 int fragmentLen = s2 - s1 + 1;
6264 if (fragmentLen < 0)
6265 {
6266 wxLogDebug(wxT("Mid(%d, %d"), (int)(s1 - offset), (int)fragmentLen);
6267 }
6268 wxString stringFragment = str.Mid(s1 - offset, fragmentLen);
6269
6270 DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, true);
6271
6272 #if USE_KERNING_FIX
6273 if (stringChunk.Find(wxT("\t")) == wxNOT_FOUND)
6274 {
6275 // Compensate for kerning difference
6276 wxString stringFragment2(str.Mid(s1 - offset, fragmentLen+1));
6277 wxString stringFragment3(str.Mid(s1 - offset + fragmentLen, 1));
6278
6279 wxCoord w1, h1, w2, h2, w3, h3;
6280 dc.GetTextExtent(stringFragment, & w1, & h1);
6281 dc.GetTextExtent(stringFragment2, & w2, & h2);
6282 dc.GetTextExtent(stringFragment3, & w3, & h3);
6283
6284 int kerningDiff = (w1 + w3) - w2;
6285 x = x - kerningDiff;
6286 }
6287 #endif
6288 }
6289
6290 // 3. Remaining unselected chunk, if any
6291 if (selectionRange.GetEnd() < range.GetEnd())
6292 {
6293 int s2 = wxMin(selectionRange.GetEnd()+1, range.GetEnd());
6294 int r2 = range.GetEnd();
6295
6296 int fragmentLen = r2 - s2 + 1;
6297 if (fragmentLen < 0)
6298 {
6299 wxLogDebug(wxT("Mid(%d, %d"), (int)(s2 - offset), (int)fragmentLen);
6300 }
6301 wxString stringFragment = str.Mid(s2 - offset, fragmentLen);
6302
6303 DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, false);
6304 }
6305 }
6306
6307 return true;
6308 }
6309
6310 bool wxRichTextPlainText::DrawTabbedString(wxDC& dc, const wxRichTextAttr& attr, const wxRect& rect,wxString& str, wxCoord& x, wxCoord& y, bool selected)
6311 {
6312 bool hasTabs = (str.Find(wxT('\t')) != wxNOT_FOUND);
6313
6314 wxArrayInt tabArray;
6315 int tabCount;
6316 if (hasTabs)
6317 {
6318 if (attr.GetTabs().IsEmpty())
6319 tabArray = wxRichTextParagraph::GetDefaultTabs();
6320 else
6321 tabArray = attr.GetTabs();
6322 tabCount = tabArray.GetCount();
6323
6324 for (int i = 0; i < tabCount; ++i)
6325 {
6326 int pos = tabArray[i];
6327 pos = ConvertTenthsMMToPixels(dc, pos);
6328 tabArray[i] = pos;
6329 }
6330 }
6331 else
6332 tabCount = 0;
6333
6334 int nextTabPos = -1;
6335 int tabPos = -1;
6336 wxCoord w, h;
6337
6338 if (selected)
6339 {
6340 wxColour highlightColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT));
6341 wxColour highlightTextColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT));
6342
6343 wxCheckSetBrush(dc, wxBrush(highlightColour));
6344 wxCheckSetPen(dc, wxPen(highlightColour));
6345 dc.SetTextForeground(highlightTextColour);
6346 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
6347 }
6348 else
6349 {
6350 dc.SetTextForeground(attr.GetTextColour());
6351
6352 if (attr.HasFlag(wxTEXT_ATTR_BACKGROUND_COLOUR) && attr.GetBackgroundColour().IsOk())
6353 {
6354 dc.SetBackgroundMode(wxBRUSHSTYLE_SOLID);
6355 dc.SetTextBackground(attr.GetBackgroundColour());
6356 }
6357 else
6358 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
6359 }
6360
6361 wxCoord x_orig = GetParent()->GetPosition().x;
6362 while (hasTabs)
6363 {
6364 // the string has a tab
6365 // break up the string at the Tab
6366 wxString stringChunk = str.BeforeFirst(wxT('\t'));
6367 str = str.AfterFirst(wxT('\t'));
6368 dc.GetTextExtent(stringChunk, & w, & h);
6369 tabPos = x + w;
6370 bool not_found = true;
6371 for (int i = 0; i < tabCount && not_found; ++i)
6372 {
6373 nextTabPos = tabArray.Item(i) + x_orig;
6374
6375 // Find the next tab position.
6376 // Even if we're at the end of the tab array, we must still draw the chunk.
6377
6378 if (nextTabPos > tabPos || (i == (tabCount - 1)))
6379 {
6380 if (nextTabPos <= tabPos)
6381 {
6382 int defaultTabWidth = ConvertTenthsMMToPixels(dc, WIDTH_FOR_DEFAULT_TABS);
6383 nextTabPos = tabPos + defaultTabWidth;
6384 }
6385
6386 not_found = false;
6387 if (selected)
6388 {
6389 w = nextTabPos - x;
6390 wxRect selRect(x, rect.y, w, rect.GetHeight());
6391 dc.DrawRectangle(selRect);
6392 }
6393 dc.DrawText(stringChunk, x, y);
6394
6395 if (attr.HasTextEffects() && (attr.GetTextEffects() & wxTEXT_ATTR_EFFECT_STRIKETHROUGH))
6396 {
6397 wxPen oldPen = dc.GetPen();
6398 wxCheckSetPen(dc, wxPen(attr.GetTextColour(), 1));
6399 dc.DrawLine(x, (int) (y+(h/2)+0.5), x+w, (int) (y+(h/2)+0.5));
6400 wxCheckSetPen(dc, oldPen);
6401 }
6402
6403 x = nextTabPos;
6404 }
6405 }
6406 hasTabs = (str.Find(wxT('\t')) != wxNOT_FOUND);
6407 }
6408
6409 if (!str.IsEmpty())
6410 {
6411 dc.GetTextExtent(str, & w, & h);
6412 if (selected)
6413 {
6414 wxRect selRect(x, rect.y, w, rect.GetHeight());
6415 dc.DrawRectangle(selRect);
6416 }
6417 dc.DrawText(str, x, y);
6418
6419 if (attr.HasTextEffects() && (attr.GetTextEffects() & wxTEXT_ATTR_EFFECT_STRIKETHROUGH))
6420 {
6421 wxPen oldPen = dc.GetPen();
6422 wxCheckSetPen(dc, wxPen(attr.GetTextColour(), 1));
6423 dc.DrawLine(x, (int) (y+(h/2)+0.5), x+w, (int) (y+(h/2)+0.5));
6424 wxCheckSetPen(dc, oldPen);
6425 }
6426
6427 x += w;
6428 }
6429 return true;
6430
6431 }
6432
6433 /// Lay the item out
6434 bool wxRichTextPlainText::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& WXUNUSED(rect), const wxRect& WXUNUSED(parentRect), int WXUNUSED(style))
6435 {
6436 // Only lay out if we haven't already cached the size
6437 if (m_size.x == -1)
6438 GetRangeSize(GetRange(), m_size, m_descent, dc, context, 0, wxPoint(0, 0));
6439 m_maxSize = m_size;
6440 // Eventually we want to have a reasonable estimate of minimum size.
6441 m_minSize = wxSize(0, 0);
6442 return true;
6443 }
6444
6445 /// Copy
6446 void wxRichTextPlainText::Copy(const wxRichTextPlainText& obj)
6447 {
6448 wxRichTextObject::Copy(obj);
6449
6450 m_text = obj.m_text;
6451 }
6452
6453 /// Get/set the object size for the given range. Returns false if the range
6454 /// is invalid for this object.
6455 bool wxRichTextPlainText::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int WXUNUSED(flags), wxPoint position, wxArrayInt* partialExtents) const
6456 {
6457 if (!range.IsWithin(GetRange()))
6458 return false;
6459
6460 wxRichTextParagraph* para = wxDynamicCast(GetParent(), wxRichTextParagraph);
6461 wxASSERT (para != NULL);
6462
6463 int relativeX = position.x - GetParent()->GetPosition().x;
6464
6465 wxRichTextAttr textAttr(para ? para->GetCombinedAttributes(GetAttributes()) : GetAttributes());
6466 context.ApplyVirtualAttributes(textAttr, (wxRichTextObject*) this);
6467
6468 // Always assume unformatted text, since at this level we have no knowledge
6469 // of line breaks - and we don't need it, since we'll calculate size within
6470 // formatted text by doing it in chunks according to the line ranges
6471
6472 bool bScript(false);
6473 wxFont font(GetBuffer()->GetFontTable().FindFont(textAttr));
6474 if (font.IsOk())
6475 {
6476 if ( textAttr.HasTextEffects() && ( (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUPERSCRIPT)
6477 || (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUBSCRIPT) ) )
6478 {
6479 wxFont textFont = font;
6480 double size = static_cast<double>(textFont.GetPointSize()) / wxSCRIPT_MUL_FACTOR;
6481 textFont.SetPointSize( static_cast<int>(size) );
6482 wxCheckSetFont(dc, textFont);
6483 bScript = true;
6484 }
6485 else
6486 {
6487 wxCheckSetFont(dc, font);
6488 }
6489 }
6490
6491 bool haveDescent = false;
6492 int startPos = range.GetStart() - GetRange().GetStart();
6493 long len = range.GetLength();
6494
6495 wxString str(m_text);
6496 wxString toReplace = wxRichTextLineBreakChar;
6497 str.Replace(toReplace, wxT(" "));
6498
6499 wxString stringChunk = str.Mid(startPos, (size_t) len);
6500
6501 if (textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_CAPITALS))
6502 stringChunk.MakeUpper();
6503
6504 wxCoord w, h;
6505 int width = 0;
6506 if (stringChunk.Find(wxT('\t')) != wxNOT_FOUND)
6507 {
6508 // the string has a tab
6509 wxArrayInt tabArray;
6510 if (textAttr.GetTabs().IsEmpty())
6511 tabArray = wxRichTextParagraph::GetDefaultTabs();
6512 else
6513 tabArray = textAttr.GetTabs();
6514
6515 int tabCount = tabArray.GetCount();
6516
6517 for (int i = 0; i < tabCount; ++i)
6518 {
6519 int pos = tabArray[i];
6520 pos = ((wxRichTextPlainText*) this)->ConvertTenthsMMToPixels(dc, pos);
6521 tabArray[i] = pos;
6522 }
6523
6524 int nextTabPos = -1;
6525
6526 while (stringChunk.Find(wxT('\t')) >= 0)
6527 {
6528 int absoluteWidth = 0;
6529
6530 // the string has a tab
6531 // break up the string at the Tab
6532 wxString stringFragment = stringChunk.BeforeFirst(wxT('\t'));
6533 stringChunk = stringChunk.AfterFirst(wxT('\t'));
6534
6535 if (partialExtents)
6536 {
6537 int oldWidth;
6538 if (partialExtents->GetCount() > 0)
6539 oldWidth = (*partialExtents)[partialExtents->GetCount()-1];
6540 else
6541 oldWidth = 0;
6542
6543 // Add these partial extents
6544 wxArrayInt p;
6545 dc.GetPartialTextExtents(stringFragment, p);
6546 size_t j;
6547 for (j = 0; j < p.GetCount(); j++)
6548 partialExtents->Add(oldWidth + p[j]);
6549
6550 if (partialExtents->GetCount() > 0)
6551 absoluteWidth = (*partialExtents)[(*partialExtents).GetCount()-1] + relativeX;
6552 else
6553 absoluteWidth = relativeX;
6554 }
6555 else
6556 {
6557 dc.GetTextExtent(stringFragment, & w, & h);
6558 width += w;
6559 absoluteWidth = width + relativeX;
6560 haveDescent = true;
6561 }
6562
6563 bool notFound = true;
6564 for (int i = 0; i < tabCount && notFound; ++i)
6565 {
6566 nextTabPos = tabArray.Item(i);
6567
6568 // Find the next tab position.
6569 // Even if we're at the end of the tab array, we must still process the chunk.
6570
6571 if (nextTabPos > absoluteWidth || (i == (tabCount - 1)))
6572 {
6573 if (nextTabPos <= absoluteWidth)
6574 {
6575 int defaultTabWidth = ((wxRichTextPlainText*) this)->ConvertTenthsMMToPixels(dc, WIDTH_FOR_DEFAULT_TABS);
6576 nextTabPos = absoluteWidth + defaultTabWidth;
6577 }
6578
6579 notFound = false;
6580 width = nextTabPos - relativeX;
6581
6582 if (partialExtents)
6583 partialExtents->Add(width);
6584 }
6585 }
6586 }
6587 }
6588
6589 if (!stringChunk.IsEmpty())
6590 {
6591 if (partialExtents)
6592 {
6593 int oldWidth;
6594 if (partialExtents->GetCount() > 0)
6595 oldWidth = (*partialExtents)[partialExtents->GetCount()-1];
6596 else
6597 oldWidth = 0;
6598
6599 // Add these partial extents
6600 wxArrayInt p;
6601 dc.GetPartialTextExtents(stringChunk, p);
6602 size_t j;
6603 for (j = 0; j < p.GetCount(); j++)
6604 partialExtents->Add(oldWidth + p[j]);
6605 }
6606 else
6607 {
6608 dc.GetTextExtent(stringChunk, & w, & h, & descent);
6609 width += w;
6610 haveDescent = true;
6611 }
6612 }
6613
6614 if (partialExtents)
6615 {
6616 int charHeight = dc.GetCharHeight();
6617 if ((*partialExtents).GetCount() > 0)
6618 w = (*partialExtents)[partialExtents->GetCount()-1];
6619 else
6620 w = 0;
6621 size = wxSize(w, charHeight);
6622 }
6623 else
6624 {
6625 size = wxSize(width, dc.GetCharHeight());
6626 }
6627
6628 if (!haveDescent)
6629 dc.GetTextExtent(wxT("X"), & w, & h, & descent);
6630
6631 if ( bScript )
6632 dc.SetFont(font);
6633
6634 return true;
6635 }
6636
6637 /// Do a split, returning an object containing the second part, and setting
6638 /// the first part in 'this'.
6639 wxRichTextObject* wxRichTextPlainText::DoSplit(long pos)
6640 {
6641 long index = pos - GetRange().GetStart();
6642
6643 if (index < 0 || index >= (int) m_text.length())
6644 return NULL;
6645
6646 wxString firstPart = m_text.Mid(0, index);
6647 wxString secondPart = m_text.Mid(index);
6648
6649 m_text = firstPart;
6650
6651 wxRichTextPlainText* newObject = new wxRichTextPlainText(secondPart);
6652 newObject->SetAttributes(GetAttributes());
6653 newObject->SetProperties(GetProperties());
6654
6655 newObject->SetRange(wxRichTextRange(pos, GetRange().GetEnd()));
6656 GetRange().SetEnd(pos-1);
6657
6658 return newObject;
6659 }
6660
6661 /// Calculate range
6662 void wxRichTextPlainText::CalculateRange(long start, long& end)
6663 {
6664 end = start + m_text.length() - 1;
6665 m_range.SetRange(start, end);
6666 }
6667
6668 /// Delete range
6669 bool wxRichTextPlainText::DeleteRange(const wxRichTextRange& range)
6670 {
6671 wxRichTextRange r = range;
6672
6673 r.LimitTo(GetRange());
6674
6675 if (r.GetStart() == GetRange().GetStart() && r.GetEnd() == GetRange().GetEnd())
6676 {
6677 m_text.Empty();
6678 return true;
6679 }
6680
6681 long startIndex = r.GetStart() - GetRange().GetStart();
6682 long len = r.GetLength();
6683
6684 m_text = m_text.Mid(0, startIndex) + m_text.Mid(startIndex+len);
6685 return true;
6686 }
6687
6688 /// Get text for the given range.
6689 wxString wxRichTextPlainText::GetTextForRange(const wxRichTextRange& range) const
6690 {
6691 wxRichTextRange r = range;
6692
6693 r.LimitTo(GetRange());
6694
6695 long startIndex = r.GetStart() - GetRange().GetStart();
6696 long len = r.GetLength();
6697
6698 return m_text.Mid(startIndex, len);
6699 }
6700
6701 /// Returns true if this object can merge itself with the given one.
6702 bool wxRichTextPlainText::CanMerge(wxRichTextObject* object) const
6703 {
6704 return object->GetClassInfo() == CLASSINFO(wxRichTextPlainText) &&
6705 (m_text.empty() || (wxTextAttrEq(GetAttributes(), object->GetAttributes()) && m_properties == object->GetProperties()));
6706 }
6707
6708 /// Returns true if this object merged itself with the given one.
6709 /// The calling code will then delete the given object.
6710 bool wxRichTextPlainText::Merge(wxRichTextObject* object)
6711 {
6712 wxRichTextPlainText* textObject = wxDynamicCast(object, wxRichTextPlainText);
6713 wxASSERT( textObject != NULL );
6714
6715 if (textObject)
6716 {
6717 m_text += textObject->GetText();
6718 wxRichTextApplyStyle(m_attributes, textObject->GetAttributes());
6719 return true;
6720 }
6721 else
6722 return false;
6723 }
6724
6725 /// Dump to output stream for debugging
6726 void wxRichTextPlainText::Dump(wxTextOutputStream& stream)
6727 {
6728 wxRichTextObject::Dump(stream);
6729 stream << m_text << wxT("\n");
6730 }
6731
6732 /// Get the first position from pos that has a line break character.
6733 long wxRichTextPlainText::GetFirstLineBreakPosition(long pos)
6734 {
6735 int i;
6736 int len = m_text.length();
6737 int startPos = pos - m_range.GetStart();
6738 for (i = startPos; i < len; i++)
6739 {
6740 wxChar ch = m_text[i];
6741 if (ch == wxRichTextLineBreakChar)
6742 {
6743 return i + m_range.GetStart();
6744 }
6745 }
6746 return -1;
6747 }
6748
6749 /*!
6750 * wxRichTextBuffer
6751 * This is a kind of box, used to represent the whole buffer
6752 */
6753
6754 IMPLEMENT_DYNAMIC_CLASS(wxRichTextBuffer, wxRichTextParagraphLayoutBox)
6755
6756 wxList wxRichTextBuffer::sm_handlers;
6757 wxList wxRichTextBuffer::sm_drawingHandlers;
6758 wxRichTextRenderer* wxRichTextBuffer::sm_renderer = NULL;
6759 int wxRichTextBuffer::sm_bulletRightMargin = 20;
6760 float wxRichTextBuffer::sm_bulletProportion = (float) 0.3;
6761
6762 /// Initialisation
6763 void wxRichTextBuffer::Init()
6764 {
6765 m_commandProcessor = new wxCommandProcessor;
6766 m_styleSheet = NULL;
6767 m_modified = false;
6768 m_batchedCommandDepth = 0;
6769 m_batchedCommand = NULL;
6770 m_suppressUndo = 0;
6771 m_handlerFlags = 0;
6772 m_scale = 1.0;
6773 }
6774
6775 /// Initialisation
6776 wxRichTextBuffer::~wxRichTextBuffer()
6777 {
6778 delete m_commandProcessor;
6779 delete m_batchedCommand;
6780
6781 ClearStyleStack();
6782 ClearEventHandlers();
6783 }
6784
6785 void wxRichTextBuffer::ResetAndClearCommands()
6786 {
6787 Reset();
6788
6789 GetCommandProcessor()->ClearCommands();
6790
6791 Modify(false);
6792 Invalidate(wxRICHTEXT_ALL);
6793 }
6794
6795 void wxRichTextBuffer::Copy(const wxRichTextBuffer& obj)
6796 {
6797 wxRichTextParagraphLayoutBox::Copy(obj);
6798
6799 m_styleSheet = obj.m_styleSheet;
6800 m_modified = obj.m_modified;
6801 m_batchedCommandDepth = 0;
6802 if (m_batchedCommand)
6803 delete m_batchedCommand;
6804 m_batchedCommand = NULL;
6805 m_suppressUndo = obj.m_suppressUndo;
6806 m_invalidRange = obj.m_invalidRange;
6807 }
6808
6809 /// Push style sheet to top of stack
6810 bool wxRichTextBuffer::PushStyleSheet(wxRichTextStyleSheet* styleSheet)
6811 {
6812 if (m_styleSheet)
6813 styleSheet->InsertSheet(m_styleSheet);
6814
6815 SetStyleSheet(styleSheet);
6816
6817 return true;
6818 }
6819
6820 /// Pop style sheet from top of stack
6821 wxRichTextStyleSheet* wxRichTextBuffer::PopStyleSheet()
6822 {
6823 if (m_styleSheet)
6824 {
6825 wxRichTextStyleSheet* oldSheet = m_styleSheet;
6826 m_styleSheet = oldSheet->GetNextSheet();
6827 oldSheet->Unlink();
6828
6829 return oldSheet;
6830 }
6831 else
6832 return NULL;
6833 }
6834
6835 /// Submit command to insert paragraphs
6836 bool wxRichTextBuffer::InsertParagraphsWithUndo(long pos, const wxRichTextParagraphLayoutBox& paragraphs, wxRichTextCtrl* ctrl, int flags)
6837 {
6838 return ctrl->GetFocusObject()->InsertParagraphsWithUndo(this, pos, paragraphs, ctrl, flags);
6839 }
6840
6841 /// Submit command to insert paragraphs
6842 bool wxRichTextParagraphLayoutBox::InsertParagraphsWithUndo(wxRichTextBuffer* buffer, long pos, const wxRichTextParagraphLayoutBox& paragraphs, wxRichTextCtrl* ctrl, int WXUNUSED(flags))
6843 {
6844 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
6845
6846 action->GetNewParagraphs() = paragraphs;
6847
6848 action->SetPosition(pos);
6849
6850 wxRichTextRange range = wxRichTextRange(pos, pos + paragraphs.GetOwnRange().GetEnd() - 1);
6851 if (!paragraphs.GetPartialParagraph())
6852 range.SetEnd(range.GetEnd()+1);
6853
6854 // Set the range we'll need to delete in Undo
6855 action->SetRange(range);
6856
6857 buffer->SubmitAction(action);
6858
6859 return true;
6860 }
6861
6862 /// Submit command to insert the given text
6863 bool wxRichTextBuffer::InsertTextWithUndo(long pos, const wxString& text, wxRichTextCtrl* ctrl, int flags)
6864 {
6865 return ctrl->GetFocusObject()->InsertTextWithUndo(this, pos, text, ctrl, flags);
6866 }
6867
6868 /// Submit command to insert the given text
6869 bool wxRichTextParagraphLayoutBox::InsertTextWithUndo(wxRichTextBuffer* buffer, long pos, const wxString& text, wxRichTextCtrl* ctrl, int flags)
6870 {
6871 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
6872
6873 wxRichTextAttr* p = NULL;
6874 wxRichTextAttr paraAttr;
6875 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
6876 {
6877 // Get appropriate paragraph style
6878 paraAttr = GetStyleForNewParagraph(buffer, pos, false, false);
6879 if (!paraAttr.IsDefault())
6880 p = & paraAttr;
6881 }
6882
6883 action->GetNewParagraphs().AddParagraphs(text, p);
6884
6885 int length = action->GetNewParagraphs().GetOwnRange().GetLength();
6886
6887 if (!text.empty() && text.Last() != wxT('\n'))
6888 {
6889 // Don't count the newline when undoing
6890 length --;
6891 action->GetNewParagraphs().SetPartialParagraph(true);
6892 }
6893 else if (!text.empty() && text.Last() == wxT('\n'))
6894 length --;
6895
6896 action->SetPosition(pos);
6897
6898 // Set the range we'll need to delete in Undo
6899 action->SetRange(wxRichTextRange(pos, pos + length - 1));
6900
6901 buffer->SubmitAction(action);
6902
6903 return true;
6904 }
6905
6906 /// Submit command to insert the given text
6907 bool wxRichTextBuffer::InsertNewlineWithUndo(long pos, wxRichTextCtrl* ctrl, int flags)
6908 {
6909 return ctrl->GetFocusObject()->InsertNewlineWithUndo(this, pos, ctrl, flags);
6910 }
6911
6912 /// Submit command to insert the given text
6913 bool wxRichTextParagraphLayoutBox::InsertNewlineWithUndo(wxRichTextBuffer* buffer, long pos, wxRichTextCtrl* ctrl, int flags)
6914 {
6915 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
6916
6917 wxRichTextAttr* p = NULL;
6918 wxRichTextAttr paraAttr;
6919 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
6920 {
6921 paraAttr = GetStyleForNewParagraph(buffer, pos, false, true /* look for next paragraph style */);
6922 if (!paraAttr.IsDefault())
6923 p = & paraAttr;
6924 }
6925
6926 wxRichTextAttr attr(buffer->GetDefaultStyle());
6927
6928 wxRichTextParagraph* newPara = new wxRichTextParagraph(wxEmptyString, this, & attr);
6929 action->GetNewParagraphs().AppendChild(newPara);
6930 action->GetNewParagraphs().UpdateRanges();
6931 action->GetNewParagraphs().SetPartialParagraph(false);
6932 wxRichTextParagraph* para = GetParagraphAtPosition(pos, false);
6933 long pos1 = pos;
6934
6935 if (p)
6936 newPara->SetAttributes(*p);
6937
6938 if (flags & wxRICHTEXT_INSERT_INTERACTIVE)
6939 {
6940 if (para && para->GetRange().GetEnd() == pos)
6941 pos1 ++;
6942
6943 // Now see if we need to number the paragraph.
6944 if (newPara->GetAttributes().HasBulletNumber())
6945 {
6946 wxRichTextAttr numberingAttr;
6947 if (FindNextParagraphNumber(para, numberingAttr))
6948 wxRichTextApplyStyle(newPara->GetAttributes(), (const wxRichTextAttr&) numberingAttr);
6949 }
6950 }
6951
6952 action->SetPosition(pos);
6953
6954 // Use the default character style
6955 // Use the default character style
6956 if (!buffer->GetDefaultStyle().IsDefault() && newPara->GetChildren().GetFirst())
6957 {
6958 // Check whether the default style merely reflects the paragraph/basic style,
6959 // in which case don't apply it.
6960 wxRichTextAttr defaultStyle(buffer->GetDefaultStyle());
6961 wxRichTextAttr toApply;
6962 if (para)
6963 {
6964 wxRichTextAttr combinedAttr = para->GetCombinedAttributes(true /* include box attributes */);
6965 wxRichTextAttr newAttr;
6966 // This filters out attributes that are accounted for by the current
6967 // paragraph/basic style
6968 wxRichTextApplyStyle(toApply, defaultStyle, & combinedAttr);
6969 }
6970 else
6971 toApply = defaultStyle;
6972
6973 if (!toApply.IsDefault())
6974 newPara->GetChildren().GetFirst()->GetData()->SetAttributes(toApply);
6975 }
6976
6977 // Set the range we'll need to delete in Undo
6978 action->SetRange(wxRichTextRange(pos1, pos1));
6979
6980 buffer->SubmitAction(action);
6981
6982 return true;
6983 }
6984
6985 /// Submit command to insert the given image
6986 bool wxRichTextBuffer::InsertImageWithUndo(long pos, const wxRichTextImageBlock& imageBlock, wxRichTextCtrl* ctrl, int flags,
6987 const wxRichTextAttr& textAttr)
6988 {
6989 return ctrl->GetFocusObject()->InsertImageWithUndo(this, pos, imageBlock, ctrl, flags, textAttr);
6990 }
6991
6992 /// Submit command to insert the given image
6993 bool wxRichTextParagraphLayoutBox::InsertImageWithUndo(wxRichTextBuffer* buffer, long pos, const wxRichTextImageBlock& imageBlock,
6994 wxRichTextCtrl* ctrl, int flags,
6995 const wxRichTextAttr& textAttr)
6996 {
6997 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Image"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
6998
6999 wxRichTextAttr* p = NULL;
7000 wxRichTextAttr paraAttr;
7001 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
7002 {
7003 paraAttr = GetStyleForNewParagraph(buffer, pos);
7004 if (!paraAttr.IsDefault())
7005 p = & paraAttr;
7006 }
7007
7008 wxRichTextAttr attr(buffer->GetDefaultStyle());
7009
7010 wxRichTextParagraph* newPara = new wxRichTextParagraph(this, & attr);
7011 if (p)
7012 newPara->SetAttributes(*p);
7013
7014 wxRichTextImage* imageObject = new wxRichTextImage(imageBlock, newPara);
7015 newPara->AppendChild(imageObject);
7016 imageObject->SetAttributes(textAttr);
7017 action->GetNewParagraphs().AppendChild(newPara);
7018 action->GetNewParagraphs().UpdateRanges();
7019
7020 action->GetNewParagraphs().SetPartialParagraph(true);
7021
7022 action->SetPosition(pos);
7023
7024 // Set the range we'll need to delete in Undo
7025 action->SetRange(wxRichTextRange(pos, pos));
7026
7027 buffer->SubmitAction(action);
7028
7029 return true;
7030 }
7031
7032 // Insert an object with no change of it
7033 wxRichTextObject* wxRichTextBuffer::InsertObjectWithUndo(long pos, wxRichTextObject *object, wxRichTextCtrl* ctrl, int flags)
7034 {
7035 return ctrl->GetFocusObject()->InsertObjectWithUndo(this, pos, object, ctrl, flags);
7036 }
7037
7038 // Insert an object with no change of it
7039 wxRichTextObject* wxRichTextParagraphLayoutBox::InsertObjectWithUndo(wxRichTextBuffer* buffer, long pos, wxRichTextObject *object, wxRichTextCtrl* ctrl, int flags)
7040 {
7041 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Object"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
7042
7043 wxRichTextAttr* p = NULL;
7044 wxRichTextAttr paraAttr;
7045 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
7046 {
7047 paraAttr = GetStyleForNewParagraph(buffer, pos);
7048 if (!paraAttr.IsDefault())
7049 p = & paraAttr;
7050 }
7051
7052 wxRichTextAttr attr(buffer->GetDefaultStyle());
7053
7054 wxRichTextParagraph* newPara = new wxRichTextParagraph(this, & attr);
7055 if (p)
7056 newPara->SetAttributes(*p);
7057
7058 newPara->AppendChild(object);
7059 action->GetNewParagraphs().AppendChild(newPara);
7060 action->GetNewParagraphs().UpdateRanges();
7061
7062 action->GetNewParagraphs().SetPartialParagraph(true);
7063
7064 action->SetPosition(pos);
7065
7066 // Set the range we'll need to delete in Undo
7067 action->SetRange(wxRichTextRange(pos, pos));
7068
7069 buffer->SubmitAction(action);
7070
7071 wxRichTextObject* obj = GetLeafObjectAtPosition(pos);
7072 return obj;
7073 }
7074
7075 /// Get the style that is appropriate for a new paragraph at this position.
7076 /// If the previous paragraph has a paragraph style name, look up the next-paragraph
7077 /// style.
7078 wxRichTextAttr wxRichTextParagraphLayoutBox::GetStyleForNewParagraph(wxRichTextBuffer* buffer, long pos, bool caretPosition, bool lookUpNewParaStyle) const
7079 {
7080 wxRichTextParagraph* para = GetParagraphAtPosition(pos, caretPosition);
7081 if (para)
7082 {
7083 wxRichTextAttr attr;
7084 bool foundAttributes = false;
7085
7086 // Look for a matching paragraph style
7087 if (lookUpNewParaStyle && !para->GetAttributes().GetParagraphStyleName().IsEmpty() && buffer->GetStyleSheet())
7088 {
7089 wxRichTextParagraphStyleDefinition* paraDef = buffer->GetStyleSheet()->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName());
7090 if (paraDef)
7091 {
7092 // If we're not at the end of the paragraph, then we apply THIS style, and not the designated next style.
7093 if (para->GetRange().GetEnd() == pos && !paraDef->GetNextStyle().IsEmpty())
7094 {
7095 wxRichTextParagraphStyleDefinition* nextParaDef = buffer->GetStyleSheet()->FindParagraphStyle(paraDef->GetNextStyle());
7096 if (nextParaDef)
7097 {
7098 foundAttributes = true;
7099 attr = nextParaDef->GetStyleMergedWithBase(buffer->GetStyleSheet());
7100 }
7101 }
7102
7103 // If we didn't find the 'next style', use this style instead.
7104 if (!foundAttributes)
7105 {
7106 foundAttributes = true;
7107 attr = paraDef->GetStyleMergedWithBase(buffer->GetStyleSheet());
7108 }
7109 }
7110 }
7111
7112 // Also apply list style if present
7113 if (lookUpNewParaStyle && !para->GetAttributes().GetListStyleName().IsEmpty() && buffer->GetStyleSheet())
7114 {
7115 wxRichTextListStyleDefinition* listDef = buffer->GetStyleSheet()->FindListStyle(para->GetAttributes().GetListStyleName());
7116 if (listDef)
7117 {
7118 int thisIndent = para->GetAttributes().GetLeftIndent();
7119 int thisLevel = para->GetAttributes().HasOutlineLevel() ? para->GetAttributes().GetOutlineLevel() : listDef->FindLevelForIndent(thisIndent);
7120
7121 // Apply the overall list style, and item style for this level
7122 wxRichTextAttr listStyle(listDef->GetCombinedStyleForLevel(thisLevel, buffer->GetStyleSheet()));
7123 wxRichTextApplyStyle(attr, listStyle);
7124 attr.SetOutlineLevel(thisLevel);
7125 if (para->GetAttributes().HasBulletNumber())
7126 attr.SetBulletNumber(para->GetAttributes().GetBulletNumber());
7127 }
7128 }
7129
7130 if (!foundAttributes)
7131 {
7132 attr = para->GetAttributes();
7133 int flags = attr.GetFlags();
7134
7135 // Eliminate character styles
7136 flags &= ( (~ wxTEXT_ATTR_FONT) |
7137 (~ wxTEXT_ATTR_TEXT_COLOUR) |
7138 (~ wxTEXT_ATTR_BACKGROUND_COLOUR) );
7139 attr.SetFlags(flags);
7140 }
7141
7142 return attr;
7143 }
7144 else
7145 return wxRichTextAttr();
7146 }
7147
7148 /// Submit command to delete this range
7149 bool wxRichTextBuffer::DeleteRangeWithUndo(const wxRichTextRange& range, wxRichTextCtrl* ctrl)
7150 {
7151 return ctrl->GetFocusObject()->DeleteRangeWithUndo(range, ctrl, this);
7152 }
7153
7154 /// Submit command to delete this range
7155 bool wxRichTextParagraphLayoutBox::DeleteRangeWithUndo(const wxRichTextRange& range, wxRichTextCtrl* ctrl, wxRichTextBuffer* buffer)
7156 {
7157 wxRichTextAction* action = new wxRichTextAction(NULL, _("Delete"), wxRICHTEXT_DELETE, buffer, this, ctrl);
7158
7159 action->SetPosition(ctrl->GetCaretPosition());
7160
7161 // Set the range to delete
7162 action->SetRange(range);
7163
7164 // Copy the fragment that we'll need to restore in Undo
7165 CopyFragment(range, action->GetOldParagraphs());
7166
7167 // See if we're deleting a paragraph marker, in which case we need to
7168 // make a note not to copy the attributes from the 2nd paragraph to the 1st.
7169 if (range.GetStart() == range.GetEnd())
7170 {
7171 wxRichTextParagraph* para = GetParagraphAtPosition(range.GetStart());
7172 if (para && para->GetRange().GetEnd() == range.GetEnd())
7173 {
7174 wxRichTextParagraph* nextPara = GetParagraphAtPosition(range.GetStart()+1);
7175 if (nextPara && nextPara != para)
7176 {
7177 action->GetOldParagraphs().GetChildren().GetFirst()->GetData()->SetAttributes(nextPara->GetAttributes());
7178 action->GetOldParagraphs().GetAttributes().SetFlags(action->GetOldParagraphs().GetAttributes().GetFlags() | wxTEXT_ATTR_KEEP_FIRST_PARA_STYLE);
7179 }
7180 }
7181 }
7182
7183 buffer->SubmitAction(action);
7184
7185 return true;
7186 }
7187
7188 /// Collapse undo/redo commands
7189 bool wxRichTextBuffer::BeginBatchUndo(const wxString& cmdName)
7190 {
7191 if (m_batchedCommandDepth == 0)
7192 {
7193 wxASSERT(m_batchedCommand == NULL);
7194 if (m_batchedCommand)
7195 {
7196 GetCommandProcessor()->Store(m_batchedCommand);
7197 }
7198 m_batchedCommand = new wxRichTextCommand(cmdName);
7199 }
7200
7201 m_batchedCommandDepth ++;
7202
7203 return true;
7204 }
7205
7206 /// Collapse undo/redo commands
7207 bool wxRichTextBuffer::EndBatchUndo()
7208 {
7209 m_batchedCommandDepth --;
7210
7211 wxASSERT(m_batchedCommandDepth >= 0);
7212 wxASSERT(m_batchedCommand != NULL);
7213
7214 if (m_batchedCommandDepth == 0)
7215 {
7216 GetCommandProcessor()->Store(m_batchedCommand);
7217 m_batchedCommand = NULL;
7218 }
7219
7220 return true;
7221 }
7222
7223 /// Submit immediately, or delay according to whether collapsing is on
7224 bool wxRichTextBuffer::SubmitAction(wxRichTextAction* action)
7225 {
7226 if (action && !action->GetNewParagraphs().IsEmpty())
7227 PrepareContent(action->GetNewParagraphs());
7228
7229 if (BatchingUndo() && m_batchedCommand && !SuppressingUndo())
7230 {
7231 wxRichTextCommand* cmd = new wxRichTextCommand(action->GetName());
7232 cmd->AddAction(action);
7233 cmd->Do();
7234 cmd->GetActions().Clear();
7235 delete cmd;
7236
7237 m_batchedCommand->AddAction(action);
7238 }
7239 else
7240 {
7241 wxRichTextCommand* cmd = new wxRichTextCommand(action->GetName());
7242 cmd->AddAction(action);
7243
7244 // Only store it if we're not suppressing undo.
7245 return GetCommandProcessor()->Submit(cmd, !SuppressingUndo());
7246 }
7247
7248 return true;
7249 }
7250
7251 /// Begin suppressing undo/redo commands.
7252 bool wxRichTextBuffer::BeginSuppressUndo()
7253 {
7254 m_suppressUndo ++;
7255
7256 return true;
7257 }
7258
7259 /// End suppressing undo/redo commands.
7260 bool wxRichTextBuffer::EndSuppressUndo()
7261 {
7262 m_suppressUndo --;
7263
7264 return true;
7265 }
7266
7267 /// Begin using a style
7268 bool wxRichTextBuffer::BeginStyle(const wxRichTextAttr& style)
7269 {
7270 wxRichTextAttr newStyle(GetDefaultStyle());
7271
7272 // Save the old default style
7273 m_attributeStack.Append((wxObject*) new wxRichTextAttr(GetDefaultStyle()));
7274
7275 wxRichTextApplyStyle(newStyle, style);
7276 newStyle.SetFlags(style.GetFlags()|newStyle.GetFlags());
7277
7278 SetDefaultStyle(newStyle);
7279
7280 return true;
7281 }
7282
7283 /// End the style
7284 bool wxRichTextBuffer::EndStyle()
7285 {
7286 if (!m_attributeStack.GetFirst())
7287 {
7288 wxLogDebug(_("Too many EndStyle calls!"));
7289 return false;
7290 }
7291
7292 wxList::compatibility_iterator node = m_attributeStack.GetLast();
7293 wxRichTextAttr* attr = (wxRichTextAttr*)node->GetData();
7294 m_attributeStack.Erase(node);
7295
7296 SetDefaultStyle(*attr);
7297
7298 delete attr;
7299 return true;
7300 }
7301
7302 /// End all styles
7303 bool wxRichTextBuffer::EndAllStyles()
7304 {
7305 while (m_attributeStack.GetCount() != 0)
7306 EndStyle();
7307 return true;
7308 }
7309
7310 /// Clear the style stack
7311 void wxRichTextBuffer::ClearStyleStack()
7312 {
7313 for (wxList::compatibility_iterator node = m_attributeStack.GetFirst(); node; node = node->GetNext())
7314 delete (wxRichTextAttr*) node->GetData();
7315 m_attributeStack.Clear();
7316 }
7317
7318 /// Begin using bold
7319 bool wxRichTextBuffer::BeginBold()
7320 {
7321 wxRichTextAttr attr;
7322 attr.SetFontWeight(wxFONTWEIGHT_BOLD);
7323
7324 return BeginStyle(attr);
7325 }
7326
7327 /// Begin using italic
7328 bool wxRichTextBuffer::BeginItalic()
7329 {
7330 wxRichTextAttr attr;
7331 attr.SetFontStyle(wxFONTSTYLE_ITALIC);
7332
7333 return BeginStyle(attr);
7334 }
7335
7336 /// Begin using underline
7337 bool wxRichTextBuffer::BeginUnderline()
7338 {
7339 wxRichTextAttr attr;
7340 attr.SetFontUnderlined(true);
7341
7342 return BeginStyle(attr);
7343 }
7344
7345 /// Begin using point size
7346 bool wxRichTextBuffer::BeginFontSize(int pointSize)
7347 {
7348 wxRichTextAttr attr;
7349 attr.SetFontSize(pointSize);
7350
7351 return BeginStyle(attr);
7352 }
7353
7354 /// Begin using this font
7355 bool wxRichTextBuffer::BeginFont(const wxFont& font)
7356 {
7357 wxRichTextAttr attr;
7358 attr.SetFont(font);
7359
7360 return BeginStyle(attr);
7361 }
7362
7363 /// Begin using this colour
7364 bool wxRichTextBuffer::BeginTextColour(const wxColour& colour)
7365 {
7366 wxRichTextAttr attr;
7367 attr.SetFlags(wxTEXT_ATTR_TEXT_COLOUR);
7368 attr.SetTextColour(colour);
7369
7370 return BeginStyle(attr);
7371 }
7372
7373 /// Begin using alignment
7374 bool wxRichTextBuffer::BeginAlignment(wxTextAttrAlignment alignment)
7375 {
7376 wxRichTextAttr attr;
7377 attr.SetFlags(wxTEXT_ATTR_ALIGNMENT);
7378 attr.SetAlignment(alignment);
7379
7380 return BeginStyle(attr);
7381 }
7382
7383 /// Begin left indent
7384 bool wxRichTextBuffer::BeginLeftIndent(int leftIndent, int leftSubIndent)
7385 {
7386 wxRichTextAttr attr;
7387 attr.SetFlags(wxTEXT_ATTR_LEFT_INDENT);
7388 attr.SetLeftIndent(leftIndent, leftSubIndent);
7389
7390 return BeginStyle(attr);
7391 }
7392
7393 /// Begin right indent
7394 bool wxRichTextBuffer::BeginRightIndent(int rightIndent)
7395 {
7396 wxRichTextAttr attr;
7397 attr.SetFlags(wxTEXT_ATTR_RIGHT_INDENT);
7398 attr.SetRightIndent(rightIndent);
7399
7400 return BeginStyle(attr);
7401 }
7402
7403 /// Begin paragraph spacing
7404 bool wxRichTextBuffer::BeginParagraphSpacing(int before, int after)
7405 {
7406 long flags = 0;
7407 if (before != 0)
7408 flags |= wxTEXT_ATTR_PARA_SPACING_BEFORE;
7409 if (after != 0)
7410 flags |= wxTEXT_ATTR_PARA_SPACING_AFTER;
7411
7412 wxRichTextAttr attr;
7413 attr.SetFlags(flags);
7414 attr.SetParagraphSpacingBefore(before);
7415 attr.SetParagraphSpacingAfter(after);
7416
7417 return BeginStyle(attr);
7418 }
7419
7420 /// Begin line spacing
7421 bool wxRichTextBuffer::BeginLineSpacing(int lineSpacing)
7422 {
7423 wxRichTextAttr attr;
7424 attr.SetFlags(wxTEXT_ATTR_LINE_SPACING);
7425 attr.SetLineSpacing(lineSpacing);
7426
7427 return BeginStyle(attr);
7428 }
7429
7430 /// Begin numbered bullet
7431 bool wxRichTextBuffer::BeginNumberedBullet(int bulletNumber, int leftIndent, int leftSubIndent, int bulletStyle)
7432 {
7433 wxRichTextAttr attr;
7434 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT);
7435 attr.SetBulletStyle(bulletStyle);
7436 attr.SetBulletNumber(bulletNumber);
7437 attr.SetLeftIndent(leftIndent, leftSubIndent);
7438
7439 return BeginStyle(attr);
7440 }
7441
7442 /// Begin symbol bullet
7443 bool wxRichTextBuffer::BeginSymbolBullet(const wxString& symbol, int leftIndent, int leftSubIndent, int bulletStyle)
7444 {
7445 wxRichTextAttr attr;
7446 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT);
7447 attr.SetBulletStyle(bulletStyle);
7448 attr.SetLeftIndent(leftIndent, leftSubIndent);
7449 attr.SetBulletText(symbol);
7450
7451 return BeginStyle(attr);
7452 }
7453
7454 /// Begin standard bullet
7455 bool wxRichTextBuffer::BeginStandardBullet(const wxString& bulletName, int leftIndent, int leftSubIndent, int bulletStyle)
7456 {
7457 wxRichTextAttr attr;
7458 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT);
7459 attr.SetBulletStyle(bulletStyle);
7460 attr.SetLeftIndent(leftIndent, leftSubIndent);
7461 attr.SetBulletName(bulletName);
7462
7463 return BeginStyle(attr);
7464 }
7465
7466 /// Begin named character style
7467 bool wxRichTextBuffer::BeginCharacterStyle(const wxString& characterStyle)
7468 {
7469 if (GetStyleSheet())
7470 {
7471 wxRichTextCharacterStyleDefinition* def = GetStyleSheet()->FindCharacterStyle(characterStyle);
7472 if (def)
7473 {
7474 wxRichTextAttr attr = def->GetStyleMergedWithBase(GetStyleSheet());
7475 return BeginStyle(attr);
7476 }
7477 }
7478 return false;
7479 }
7480
7481 /// Begin named paragraph style
7482 bool wxRichTextBuffer::BeginParagraphStyle(const wxString& paragraphStyle)
7483 {
7484 if (GetStyleSheet())
7485 {
7486 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(paragraphStyle);
7487 if (def)
7488 {
7489 wxRichTextAttr attr = def->GetStyleMergedWithBase(GetStyleSheet());
7490 return BeginStyle(attr);
7491 }
7492 }
7493 return false;
7494 }
7495
7496 /// Begin named list style
7497 bool wxRichTextBuffer::BeginListStyle(const wxString& listStyle, int level, int number)
7498 {
7499 if (GetStyleSheet())
7500 {
7501 wxRichTextListStyleDefinition* def = GetStyleSheet()->FindListStyle(listStyle);
7502 if (def)
7503 {
7504 wxRichTextAttr attr(def->GetCombinedStyleForLevel(level));
7505
7506 attr.SetBulletNumber(number);
7507
7508 return BeginStyle(attr);
7509 }
7510 }
7511 return false;
7512 }
7513
7514 /// Begin URL
7515 bool wxRichTextBuffer::BeginURL(const wxString& url, const wxString& characterStyle)
7516 {
7517 wxRichTextAttr attr;
7518
7519 if (!characterStyle.IsEmpty() && GetStyleSheet())
7520 {
7521 wxRichTextCharacterStyleDefinition* def = GetStyleSheet()->FindCharacterStyle(characterStyle);
7522 if (def)
7523 {
7524 attr = def->GetStyleMergedWithBase(GetStyleSheet());
7525 }
7526 }
7527 attr.SetURL(url);
7528
7529 return BeginStyle(attr);
7530 }
7531
7532 /// Adds a handler to the end
7533 void wxRichTextBuffer::AddHandler(wxRichTextFileHandler *handler)
7534 {
7535 sm_handlers.Append(handler);
7536 }
7537
7538 /// Inserts a handler at the front
7539 void wxRichTextBuffer::InsertHandler(wxRichTextFileHandler *handler)
7540 {
7541 sm_handlers.Insert( handler );
7542 }
7543
7544 /// Removes a handler
7545 bool wxRichTextBuffer::RemoveHandler(const wxString& name)
7546 {
7547 wxRichTextFileHandler *handler = FindHandler(name);
7548 if (handler)
7549 {
7550 sm_handlers.DeleteObject(handler);
7551 delete handler;
7552 return true;
7553 }
7554 else
7555 return false;
7556 }
7557
7558 /// Finds a handler by filename or, if supplied, type
7559 wxRichTextFileHandler *wxRichTextBuffer::FindHandlerFilenameOrType(const wxString& filename,
7560 wxRichTextFileType imageType)
7561 {
7562 if (imageType != wxRICHTEXT_TYPE_ANY)
7563 return FindHandler(imageType);
7564 else if (!filename.IsEmpty())
7565 {
7566 wxString path, file, ext;
7567 wxFileName::SplitPath(filename, & path, & file, & ext);
7568 return FindHandler(ext, imageType);
7569 }
7570 else
7571 return NULL;
7572 }
7573
7574
7575 /// Finds a handler by name
7576 wxRichTextFileHandler* wxRichTextBuffer::FindHandler(const wxString& name)
7577 {
7578 wxList::compatibility_iterator node = sm_handlers.GetFirst();
7579 while (node)
7580 {
7581 wxRichTextFileHandler *handler = (wxRichTextFileHandler*)node->GetData();
7582 if (handler->GetName().Lower() == name.Lower()) return handler;
7583
7584 node = node->GetNext();
7585 }
7586 return NULL;
7587 }
7588
7589 /// Finds a handler by extension and type
7590 wxRichTextFileHandler* wxRichTextBuffer::FindHandler(const wxString& extension, wxRichTextFileType type)
7591 {
7592 wxList::compatibility_iterator node = sm_handlers.GetFirst();
7593 while (node)
7594 {
7595 wxRichTextFileHandler *handler = (wxRichTextFileHandler*)node->GetData();
7596 if ( handler->GetExtension().Lower() == extension.Lower() &&
7597 (type == wxRICHTEXT_TYPE_ANY || handler->GetType() == type) )
7598 return handler;
7599 node = node->GetNext();
7600 }
7601 return 0;
7602 }
7603
7604 /// Finds a handler by type
7605 wxRichTextFileHandler* wxRichTextBuffer::FindHandler(wxRichTextFileType type)
7606 {
7607 wxList::compatibility_iterator node = sm_handlers.GetFirst();
7608 while (node)
7609 {
7610 wxRichTextFileHandler *handler = (wxRichTextFileHandler *)node->GetData();
7611 if (handler->GetType() == type) return handler;
7612 node = node->GetNext();
7613 }
7614 return NULL;
7615 }
7616
7617 void wxRichTextBuffer::InitStandardHandlers()
7618 {
7619 if (!FindHandler(wxRICHTEXT_TYPE_TEXT))
7620 AddHandler(new wxRichTextPlainTextHandler);
7621 }
7622
7623 void wxRichTextBuffer::CleanUpHandlers()
7624 {
7625 wxList::compatibility_iterator node = sm_handlers.GetFirst();
7626 while (node)
7627 {
7628 wxRichTextFileHandler* handler = (wxRichTextFileHandler*)node->GetData();
7629 wxList::compatibility_iterator next = node->GetNext();
7630 delete handler;
7631 node = next;
7632 }
7633
7634 sm_handlers.Clear();
7635 }
7636
7637 wxString wxRichTextBuffer::GetExtWildcard(bool combine, bool save, wxArrayInt* types)
7638 {
7639 if (types)
7640 types->Clear();
7641
7642 wxString wildcard;
7643
7644 wxList::compatibility_iterator node = GetHandlers().GetFirst();
7645 int count = 0;
7646 while (node)
7647 {
7648 wxRichTextFileHandler* handler = (wxRichTextFileHandler*) node->GetData();
7649 if (handler->IsVisible() && ((save && handler->CanSave()) || (!save && handler->CanLoad())))
7650 {
7651 if (combine)
7652 {
7653 if (count > 0)
7654 wildcard += wxT(";");
7655 wildcard += wxT("*.") + handler->GetExtension();
7656 }
7657 else
7658 {
7659 if (count > 0)
7660 wildcard += wxT("|");
7661 wildcard += handler->GetName();
7662 wildcard += wxT(" ");
7663 wildcard += _("files");
7664 wildcard += wxT(" (*.");
7665 wildcard += handler->GetExtension();
7666 wildcard += wxT(")|*.");
7667 wildcard += handler->GetExtension();
7668 if (types)
7669 types->Add(handler->GetType());
7670 }
7671 count ++;
7672 }
7673
7674 node = node->GetNext();
7675 }
7676
7677 if (combine)
7678 wildcard = wxT("(") + wildcard + wxT(")|") + wildcard;
7679 return wildcard;
7680 }
7681
7682 /// Load a file
7683 bool wxRichTextBuffer::LoadFile(const wxString& filename, wxRichTextFileType type)
7684 {
7685 wxRichTextFileHandler* handler = FindHandlerFilenameOrType(filename, type);
7686 if (handler)
7687 {
7688 SetDefaultStyle(wxRichTextAttr());
7689 handler->SetFlags(GetHandlerFlags());
7690 bool success = handler->LoadFile(this, filename);
7691 Invalidate(wxRICHTEXT_ALL);
7692 return success;
7693 }
7694 else
7695 return false;
7696 }
7697
7698 /// Save a file
7699 bool wxRichTextBuffer::SaveFile(const wxString& filename, wxRichTextFileType type)
7700 {
7701 wxRichTextFileHandler* handler = FindHandlerFilenameOrType(filename, type);
7702 if (handler)
7703 {
7704 handler->SetFlags(GetHandlerFlags());
7705 return handler->SaveFile(this, filename);
7706 }
7707 else
7708 return false;
7709 }
7710
7711 /// Load from a stream
7712 bool wxRichTextBuffer::LoadFile(wxInputStream& stream, wxRichTextFileType type)
7713 {
7714 wxRichTextFileHandler* handler = FindHandler(type);
7715 if (handler)
7716 {
7717 SetDefaultStyle(wxRichTextAttr());
7718 handler->SetFlags(GetHandlerFlags());
7719 bool success = handler->LoadFile(this, stream);
7720 Invalidate(wxRICHTEXT_ALL);
7721 return success;
7722 }
7723 else
7724 return false;
7725 }
7726
7727 /// Save to a stream
7728 bool wxRichTextBuffer::SaveFile(wxOutputStream& stream, wxRichTextFileType type)
7729 {
7730 wxRichTextFileHandler* handler = FindHandler(type);
7731 if (handler)
7732 {
7733 handler->SetFlags(GetHandlerFlags());
7734 return handler->SaveFile(this, stream);
7735 }
7736 else
7737 return false;
7738 }
7739
7740 /// Copy the range to the clipboard
7741 bool wxRichTextBuffer::CopyToClipboard(const wxRichTextRange& range)
7742 {
7743 bool success = false;
7744 wxRichTextParagraphLayoutBox* container = this;
7745 if (GetRichTextCtrl())
7746 container = GetRichTextCtrl()->GetFocusObject();
7747
7748 #if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
7749
7750 if (!wxTheClipboard->IsOpened() && wxTheClipboard->Open())
7751 {
7752 wxTheClipboard->Clear();
7753
7754 // Add composite object
7755
7756 wxDataObjectComposite* compositeObject = new wxDataObjectComposite();
7757
7758 {
7759 wxString text = container->GetTextForRange(range);
7760
7761 #ifdef __WXMSW__
7762 text = wxTextFile::Translate(text, wxTextFileType_Dos);
7763 #endif
7764
7765 compositeObject->Add(new wxTextDataObject(text), false /* not preferred */);
7766 }
7767
7768 // Add rich text buffer data object. This needs the XML handler to be present.
7769
7770 if (FindHandler(wxRICHTEXT_TYPE_XML))
7771 {
7772 wxRichTextBuffer* richTextBuf = new wxRichTextBuffer;
7773 container->CopyFragment(range, *richTextBuf);
7774
7775 compositeObject->Add(new wxRichTextBufferDataObject(richTextBuf), true /* preferred */);
7776 }
7777
7778 if (wxTheClipboard->SetData(compositeObject))
7779 success = true;
7780
7781 wxTheClipboard->Close();
7782 }
7783
7784 #else
7785 wxUnusedVar(range);
7786 #endif
7787 return success;
7788 }
7789
7790 /// Paste the clipboard content to the buffer
7791 bool wxRichTextBuffer::PasteFromClipboard(long position)
7792 {
7793 bool success = false;
7794 wxRichTextParagraphLayoutBox* container = this;
7795 if (GetRichTextCtrl())
7796 container = GetRichTextCtrl()->GetFocusObject();
7797
7798 #if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
7799 if (CanPasteFromClipboard())
7800 {
7801 if (wxTheClipboard->Open())
7802 {
7803 if (wxTheClipboard->IsSupported(wxDataFormat(wxRichTextBufferDataObject::GetRichTextBufferFormatId())))
7804 {
7805 wxRichTextBufferDataObject data;
7806 wxTheClipboard->GetData(data);
7807 wxRichTextBuffer* richTextBuffer = data.GetRichTextBuffer();
7808 if (richTextBuffer)
7809 {
7810 container->InsertParagraphsWithUndo(this, position+1, *richTextBuffer, GetRichTextCtrl(), 0);
7811 if (GetRichTextCtrl())
7812 GetRichTextCtrl()->ShowPosition(position + richTextBuffer->GetOwnRange().GetEnd());
7813 delete richTextBuffer;
7814 }
7815 }
7816 else if (wxTheClipboard->IsSupported(wxDF_TEXT)
7817 #if wxUSE_UNICODE
7818 || wxTheClipboard->IsSupported(wxDF_UNICODETEXT)
7819 #endif
7820 )
7821 {
7822 wxTextDataObject data;
7823 wxTheClipboard->GetData(data);
7824 wxString text(data.GetText());
7825 #ifdef __WXMSW__
7826 wxString text2;
7827 text2.Alloc(text.Length()+1);
7828 size_t i;
7829 for (i = 0; i < text.Length(); i++)
7830 {
7831 wxChar ch = text[i];
7832 if (ch != wxT('\r'))
7833 text2 += ch;
7834 }
7835 #else
7836 wxString text2 = text;
7837 #endif
7838 container->InsertTextWithUndo(this, position+1, text2, GetRichTextCtrl(), wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE);
7839
7840 if (GetRichTextCtrl())
7841 GetRichTextCtrl()->ShowPosition(position + text2.Length());
7842
7843 success = true;
7844 }
7845 else if (wxTheClipboard->IsSupported(wxDF_BITMAP))
7846 {
7847 wxBitmapDataObject data;
7848 wxTheClipboard->GetData(data);
7849 wxBitmap bitmap(data.GetBitmap());
7850 wxImage image(bitmap.ConvertToImage());
7851
7852 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Image"), wxRICHTEXT_INSERT, this, container, GetRichTextCtrl(), false);
7853
7854 action->GetNewParagraphs().AddImage(image);
7855
7856 if (action->GetNewParagraphs().GetChildCount() == 1)
7857 action->GetNewParagraphs().SetPartialParagraph(true);
7858
7859 action->SetPosition(position+1);
7860
7861 // Set the range we'll need to delete in Undo
7862 action->SetRange(wxRichTextRange(position+1, position+1));
7863
7864 SubmitAction(action);
7865
7866 success = true;
7867 }
7868 wxTheClipboard->Close();
7869 }
7870 }
7871 #else
7872 wxUnusedVar(position);
7873 #endif
7874 return success;
7875 }
7876
7877 /// Can we paste from the clipboard?
7878 bool wxRichTextBuffer::CanPasteFromClipboard() const
7879 {
7880 bool canPaste = false;
7881 #if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
7882 if (!wxTheClipboard->IsOpened() && wxTheClipboard->Open())
7883 {
7884 if (wxTheClipboard->IsSupported(wxDF_TEXT)
7885 #if wxUSE_UNICODE
7886 || wxTheClipboard->IsSupported(wxDF_UNICODETEXT)
7887 #endif
7888 || wxTheClipboard->IsSupported(wxDataFormat(wxRichTextBufferDataObject::GetRichTextBufferFormatId())) ||
7889 wxTheClipboard->IsSupported(wxDF_BITMAP))
7890 {
7891 canPaste = true;
7892 }
7893 wxTheClipboard->Close();
7894 }
7895 #endif
7896 return canPaste;
7897 }
7898
7899 /// Dumps contents of buffer for debugging purposes
7900 void wxRichTextBuffer::Dump()
7901 {
7902 wxString text;
7903 {
7904 wxStringOutputStream stream(& text);
7905 wxTextOutputStream textStream(stream);
7906 Dump(textStream);
7907 }
7908
7909 wxLogDebug(text);
7910 }
7911
7912 /// Add an event handler
7913 bool wxRichTextBuffer::AddEventHandler(wxEvtHandler* handler)
7914 {
7915 m_eventHandlers.Append(handler);
7916 return true;
7917 }
7918
7919 /// Remove an event handler
7920 bool wxRichTextBuffer::RemoveEventHandler(wxEvtHandler* handler, bool deleteHandler)
7921 {
7922 wxList::compatibility_iterator node = m_eventHandlers.Find(handler);
7923 if (node)
7924 {
7925 m_eventHandlers.Erase(node);
7926 if (deleteHandler)
7927 delete handler;
7928
7929 return true;
7930 }
7931 else
7932 return false;
7933 }
7934
7935 /// Clear event handlers
7936 void wxRichTextBuffer::ClearEventHandlers()
7937 {
7938 m_eventHandlers.Clear();
7939 }
7940
7941 /// Send event to event handlers. If sendToAll is true, will send to all event handlers,
7942 /// otherwise will stop at the first successful one.
7943 bool wxRichTextBuffer::SendEvent(wxEvent& event, bool sendToAll)
7944 {
7945 bool success = false;
7946 for (wxList::compatibility_iterator node = m_eventHandlers.GetFirst(); node; node = node->GetNext())
7947 {
7948 wxEvtHandler* handler = (wxEvtHandler*) node->GetData();
7949 if (handler->ProcessEvent(event))
7950 {
7951 success = true;
7952 if (!sendToAll)
7953 return true;
7954 }
7955 }
7956 return success;
7957 }
7958
7959 /// Set style sheet and notify of the change
7960 bool wxRichTextBuffer::SetStyleSheetAndNotify(wxRichTextStyleSheet* sheet)
7961 {
7962 wxRichTextStyleSheet* oldSheet = GetStyleSheet();
7963
7964 wxWindowID winid = wxID_ANY;
7965 if (GetRichTextCtrl())
7966 winid = GetRichTextCtrl()->GetId();
7967
7968 wxRichTextEvent event(wxEVT_COMMAND_RICHTEXT_STYLESHEET_REPLACING, winid);
7969 event.SetEventObject(GetRichTextCtrl());
7970 event.SetContainer(GetRichTextCtrl()->GetFocusObject());
7971 event.SetOldStyleSheet(oldSheet);
7972 event.SetNewStyleSheet(sheet);
7973 event.Allow();
7974
7975 if (SendEvent(event) && !event.IsAllowed())
7976 {
7977 if (sheet != oldSheet)
7978 delete sheet;
7979
7980 return false;
7981 }
7982
7983 if (oldSheet && oldSheet != sheet)
7984 delete oldSheet;
7985
7986 SetStyleSheet(sheet);
7987
7988 event.SetEventType(wxEVT_COMMAND_RICHTEXT_STYLESHEET_REPLACED);
7989 event.SetOldStyleSheet(NULL);
7990 event.Allow();
7991
7992 return SendEvent(event);
7993 }
7994
7995 /// Set renderer, deleting old one
7996 void wxRichTextBuffer::SetRenderer(wxRichTextRenderer* renderer)
7997 {
7998 if (sm_renderer)
7999 delete sm_renderer;
8000 sm_renderer = renderer;
8001 }
8002
8003 /// Hit-testing: returns a flag indicating hit test details, plus
8004 /// information about position
8005 int wxRichTextBuffer::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int flags)
8006 {
8007 int ret = wxRichTextParagraphLayoutBox::HitTest(dc, context, pt, textPosition, obj, contextObj, flags);
8008 if (ret != wxRICHTEXT_HITTEST_NONE)
8009 {
8010 return ret;
8011 }
8012 else
8013 {
8014 textPosition = m_ownRange.GetEnd()-1;
8015 *obj = this;
8016 *contextObj = this;
8017 return wxRICHTEXT_HITTEST_AFTER|wxRICHTEXT_HITTEST_OUTSIDE;
8018 }
8019 }
8020
8021 bool wxRichTextStdRenderer::DrawStandardBullet(wxRichTextParagraph* paragraph, wxDC& dc, const wxRichTextAttr& bulletAttr, const wxRect& rect)
8022 {
8023 if (bulletAttr.GetTextColour().IsOk())
8024 {
8025 wxCheckSetPen(dc, wxPen(bulletAttr.GetTextColour()));
8026 wxCheckSetBrush(dc, wxBrush(bulletAttr.GetTextColour()));
8027 }
8028 else
8029 {
8030 wxCheckSetPen(dc, *wxBLACK_PEN);
8031 wxCheckSetBrush(dc, *wxBLACK_BRUSH);
8032 }
8033
8034 wxFont font;
8035 if (bulletAttr.HasFont())
8036 {
8037 font = paragraph->GetBuffer()->GetFontTable().FindFont(bulletAttr);
8038 }
8039 else
8040 font = (*wxNORMAL_FONT);
8041
8042 wxCheckSetFont(dc, font);
8043
8044 int charHeight = dc.GetCharHeight();
8045
8046 int bulletWidth = (int) (((float) charHeight) * wxRichTextBuffer::GetBulletProportion());
8047 int bulletHeight = bulletWidth;
8048
8049 int x = rect.x;
8050
8051 // Calculate the top position of the character (as opposed to the whole line height)
8052 int y = rect.y + (rect.height - charHeight);
8053
8054 // Calculate where the bullet should be positioned
8055 y = y + (charHeight+1)/2 - (bulletHeight+1)/2;
8056
8057 // The margin between a bullet and text.
8058 int margin = paragraph->ConvertTenthsMMToPixels(dc, wxRichTextBuffer::GetBulletRightMargin());
8059
8060 if (bulletAttr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_RIGHT)
8061 x = rect.x + rect.width - bulletWidth - margin;
8062 else if (bulletAttr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_CENTRE)
8063 x = x + (rect.width)/2 - bulletWidth/2;
8064
8065 if (bulletAttr.GetBulletName() == wxT("standard/square"))
8066 {
8067 dc.DrawRectangle(x, y, bulletWidth, bulletHeight);
8068 }
8069 else if (bulletAttr.GetBulletName() == wxT("standard/diamond"))
8070 {
8071 wxPoint pts[5];
8072 pts[0].x = x; pts[0].y = y + bulletHeight/2;
8073 pts[1].x = x + bulletWidth/2; pts[1].y = y;
8074 pts[2].x = x + bulletWidth; pts[2].y = y + bulletHeight/2;
8075 pts[3].x = x + bulletWidth/2; pts[3].y = y + bulletHeight;
8076
8077 dc.DrawPolygon(4, pts);
8078 }
8079 else if (bulletAttr.GetBulletName() == wxT("standard/triangle"))
8080 {
8081 wxPoint pts[3];
8082 pts[0].x = x; pts[0].y = y;
8083 pts[1].x = x + bulletWidth; pts[1].y = y + bulletHeight/2;
8084 pts[2].x = x; pts[2].y = y + bulletHeight;
8085
8086 dc.DrawPolygon(3, pts);
8087 }
8088 else if (bulletAttr.GetBulletName() == wxT("standard/circle-outline"))
8089 {
8090 wxCheckSetBrush(dc, *wxTRANSPARENT_BRUSH);
8091 dc.DrawEllipse(x, y, bulletWidth, bulletHeight);
8092 }
8093 else // "standard/circle", and catch-all
8094 {
8095 dc.DrawEllipse(x, y, bulletWidth, bulletHeight);
8096 }
8097
8098 return true;
8099 }
8100
8101 bool wxRichTextStdRenderer::DrawTextBullet(wxRichTextParagraph* paragraph, wxDC& dc, const wxRichTextAttr& attr, const wxRect& rect, const wxString& text)
8102 {
8103 if (!text.empty())
8104 {
8105 wxFont font;
8106 if ((attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_SYMBOL) && !attr.GetBulletFont().IsEmpty() && attr.HasFont())
8107 {
8108 wxRichTextAttr fontAttr;
8109 fontAttr.SetFontSize(attr.GetFontSize());
8110 fontAttr.SetFontStyle(attr.GetFontStyle());
8111 fontAttr.SetFontWeight(attr.GetFontWeight());
8112 fontAttr.SetFontUnderlined(attr.GetFontUnderlined());
8113 fontAttr.SetFontFaceName(attr.GetBulletFont());
8114 font = paragraph->GetBuffer()->GetFontTable().FindFont(fontAttr);
8115 }
8116 else if (attr.HasFont())
8117 font = paragraph->GetBuffer()->GetFontTable().FindFont(attr);
8118 else
8119 font = (*wxNORMAL_FONT);
8120
8121 wxCheckSetFont(dc, font);
8122
8123 if (attr.GetTextColour().IsOk())
8124 dc.SetTextForeground(attr.GetTextColour());
8125
8126 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
8127
8128 int charHeight = dc.GetCharHeight();
8129 wxCoord tw, th;
8130 dc.GetTextExtent(text, & tw, & th);
8131
8132 int x = rect.x;
8133
8134 // Calculate the top position of the character (as opposed to the whole line height)
8135 int y = rect.y + (rect.height - charHeight);
8136
8137 // The margin between a bullet and text.
8138 int margin = paragraph->ConvertTenthsMMToPixels(dc, wxRichTextBuffer::GetBulletRightMargin());
8139
8140 if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_RIGHT)
8141 x = (rect.x + rect.width) - tw - margin;
8142 else if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_CENTRE)
8143 x = x + (rect.width)/2 - tw/2;
8144
8145 dc.DrawText(text, x, y);
8146
8147 return true;
8148 }
8149 else
8150 return false;
8151 }
8152
8153 bool wxRichTextStdRenderer::DrawBitmapBullet(wxRichTextParagraph* WXUNUSED(paragraph), wxDC& WXUNUSED(dc), const wxRichTextAttr& WXUNUSED(attr), const wxRect& WXUNUSED(rect))
8154 {
8155 // Currently unimplemented. The intention is to store bitmaps by name in a media store associated
8156 // with the buffer. The store will allow retrieval from memory, disk or other means.
8157 return false;
8158 }
8159
8160 /// Enumerate the standard bullet names currently supported
8161 bool wxRichTextStdRenderer::EnumerateStandardBulletNames(wxArrayString& bulletNames)
8162 {
8163 bulletNames.Add(wxTRANSLATE("standard/circle"));
8164 bulletNames.Add(wxTRANSLATE("standard/circle-outline"));
8165 bulletNames.Add(wxTRANSLATE("standard/square"));
8166 bulletNames.Add(wxTRANSLATE("standard/diamond"));
8167 bulletNames.Add(wxTRANSLATE("standard/triangle"));
8168
8169 return true;
8170 }
8171
8172 /*!
8173 * wxRichTextBox
8174 */
8175
8176 IMPLEMENT_DYNAMIC_CLASS(wxRichTextBox, wxRichTextParagraphLayoutBox)
8177
8178 wxRichTextBox::wxRichTextBox(wxRichTextObject* parent):
8179 wxRichTextParagraphLayoutBox(parent)
8180 {
8181 }
8182
8183 /// Draw the item
8184 bool wxRichTextBox::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
8185 {
8186 if (!IsShown())
8187 return true;
8188
8189 // TODO: if the active object in the control, draw an indication.
8190 // We need to add the concept of active object, and not just focus object,
8191 // so we can apply commands (properties, delete, ...) to objects such as text boxes and images.
8192 // Ultimately we would like to be able to interactively resize an active object
8193 // using drag handles.
8194 return wxRichTextParagraphLayoutBox::Draw(dc, context, range, selection, rect, descent, style);
8195 }
8196
8197 /// Copy
8198 void wxRichTextBox::Copy(const wxRichTextBox& obj)
8199 {
8200 wxRichTextParagraphLayoutBox::Copy(obj);
8201 }
8202
8203 // Edit properties via a GUI
8204 bool wxRichTextBox::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer)
8205 {
8206 wxRichTextObjectPropertiesDialog boxDlg(this, wxGetTopLevelParent(parent), wxID_ANY, _("Box Properties"));
8207 boxDlg.SetAttributes(GetAttributes());
8208
8209 if (boxDlg.ShowModal() == wxID_OK)
8210 {
8211 // By passing wxRICHTEXT_SETSTYLE_RESET, indeterminate attributes set by the user will be set as
8212 // indeterminate in the object.
8213 boxDlg.ApplyStyle(buffer->GetRichTextCtrl(), wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_RESET);
8214 return true;
8215 }
8216 else
8217 return false;
8218 }
8219
8220 IMPLEMENT_DYNAMIC_CLASS(wxRichTextCell, wxRichTextBox)
8221
8222 wxRichTextCell::wxRichTextCell(wxRichTextObject* parent):
8223 wxRichTextBox(parent)
8224 {
8225 }
8226
8227 /// Draw the item
8228 bool wxRichTextCell::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
8229 {
8230 return wxRichTextBox::Draw(dc, context, range, selection, rect, descent, style);
8231 }
8232
8233 /// Copy
8234 void wxRichTextCell::Copy(const wxRichTextCell& obj)
8235 {
8236 wxRichTextBox::Copy(obj);
8237 }
8238
8239 // Edit properties via a GUI
8240 bool wxRichTextCell::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer)
8241 {
8242 // We need to gather common attributes for all selected cells.
8243
8244 wxRichTextTable* table = wxDynamicCast(GetParent(), wxRichTextTable);
8245 bool multipleCells = false;
8246 wxRichTextAttr attr;
8247
8248 if (table && buffer && buffer->GetRichTextCtrl() && buffer->GetRichTextCtrl()->GetSelection().IsValid() &&
8249 buffer->GetRichTextCtrl()->GetSelection().GetContainer() == GetParent())
8250 {
8251 wxRichTextAttr clashingAttr, absentAttr;
8252 const wxRichTextSelection& sel = buffer->GetRichTextCtrl()->GetSelection();
8253 size_t i;
8254 int selectedCellCount = 0;
8255 for (i = 0; i < sel.GetCount(); i++)
8256 {
8257 const wxRichTextRange& range = sel[i];
8258 wxRichTextCell* cell = table->GetCell(range.GetStart());
8259 if (cell)
8260 {
8261 wxRichTextAttr cellStyle = cell->GetAttributes();
8262
8263 CollectStyle(attr, cellStyle, clashingAttr, absentAttr);
8264
8265 selectedCellCount ++;
8266 }
8267 }
8268 multipleCells = selectedCellCount > 1;
8269 }
8270 else
8271 {
8272 attr = GetAttributes();
8273 }
8274
8275 wxString caption;
8276 if (multipleCells)
8277 caption = _("Multiple Cell Properties");
8278 else
8279 caption = _("Cell Properties");
8280
8281 wxRichTextObjectPropertiesDialog cellDlg(this, wxGetTopLevelParent(parent), wxID_ANY, caption);
8282 cellDlg.SetAttributes(attr);
8283
8284 wxRichTextSizePage* sizePage = wxDynamicCast(cellDlg.FindPage(CLASSINFO(wxRichTextSizePage)), wxRichTextSizePage);
8285 if (sizePage)
8286 {
8287 // We don't want position and floating controls for a cell.
8288 sizePage->ShowPositionControls(false);
8289 sizePage->ShowFloatingControls(false);
8290 }
8291
8292 if (cellDlg.ShowModal() == wxID_OK)
8293 {
8294 if (multipleCells)
8295 {
8296 const wxRichTextSelection& sel = buffer->GetRichTextCtrl()->GetSelection();
8297 // Apply the style; we interpret indeterminate attributes as 'don't touch this attribute'
8298 // since it may represent clashing attributes across multiple objects.
8299 table->SetCellStyle(sel, attr);
8300 }
8301 else
8302 // For a single object, indeterminate attributes set by the user should be reflected in the
8303 // actual object style, so pass the wxRICHTEXT_SETSTYLE_RESET flag to assign
8304 // the style directly instead of applying (which ignores indeterminate attributes,
8305 // leaving them as they were).
8306 cellDlg.ApplyStyle(buffer->GetRichTextCtrl(), wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_RESET);
8307 return true;
8308 }
8309 else
8310 return false;
8311 }
8312
8313 WX_DEFINE_OBJARRAY(wxRichTextObjectPtrArrayArray)
8314
8315 IMPLEMENT_DYNAMIC_CLASS(wxRichTextTable, wxRichTextBox)
8316
8317 wxRichTextTable::wxRichTextTable(wxRichTextObject* parent): wxRichTextBox(parent)
8318 {
8319 m_rowCount = 0;
8320 m_colCount = 0;
8321 }
8322
8323 // Draws the object.
8324 bool wxRichTextTable::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
8325 {
8326 return wxRichTextBox::Draw(dc, context, range, selection, rect, descent, style);
8327 }
8328
8329 WX_DECLARE_OBJARRAY(wxRect, wxRichTextRectArray);
8330 WX_DEFINE_OBJARRAY(wxRichTextRectArray);
8331
8332 // Lays the object out. rect is the space available for layout. Often it will
8333 // be the specified overall space for this object, if trying to constrain
8334 // layout to a particular size, or it could be the total space available in the
8335 // parent. rect is the overall size, so we must subtract margins and padding.
8336 // to get the actual available space.
8337 bool wxRichTextTable::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& WXUNUSED(parentRect), int style)
8338 {
8339 SetPosition(rect.GetPosition());
8340
8341 // TODO: the meaty bit. Calculate sizes of all cells and rows. Try to use
8342 // minimum size if within alloted size, then divide up remaining size
8343 // between rows/cols.
8344
8345 double scale = 1.0;
8346 wxRichTextBuffer* buffer = GetBuffer();
8347 if (buffer) scale = buffer->GetScale();
8348
8349 wxRect availableSpace = GetAvailableContentArea(dc, context, rect);
8350 wxTextAttrDimensionConverter converter(dc, scale, availableSpace.GetSize());
8351
8352 wxRichTextAttr attr(GetAttributes());
8353 context.ApplyVirtualAttributes(attr, this);
8354
8355 // If we have no fixed table size, and assuming we're not pushed for
8356 // space, then we don't have to try to stretch the table to fit the contents.
8357 bool stretchToFitTableWidth = false;
8358
8359 int tableWidth = rect.width;
8360 if (attr.GetTextBoxAttr().GetWidth().IsValid())
8361 {
8362 tableWidth = converter.GetPixels(attr.GetTextBoxAttr().GetWidth());
8363
8364 // Fixed table width, so we do want to stretch columns out if necessary.
8365 stretchToFitTableWidth = true;
8366
8367 // Shouldn't be able to exceed the size passed to this function
8368 tableWidth = wxMin(rect.width, tableWidth);
8369 }
8370
8371 // Get internal padding
8372 int paddingLeft = 0, paddingTop = 0;
8373 if (attr.GetTextBoxAttr().GetPadding().GetLeft().IsValid())
8374 paddingLeft = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetLeft());
8375 if (attr.GetTextBoxAttr().GetPadding().GetTop().IsValid())
8376 paddingTop = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetTop());
8377
8378 // Assume that left and top padding are also used for inter-cell padding.
8379 int paddingX = paddingLeft;
8380 int paddingY = paddingTop;
8381
8382 int totalLeftMargin = 0, totalRightMargin = 0, totalTopMargin = 0, totalBottomMargin = 0;
8383 GetTotalMargin(dc, buffer, attr, totalLeftMargin, totalRightMargin, totalTopMargin, totalBottomMargin);
8384
8385 // Internal table width - the area for content
8386 int internalTableWidth = tableWidth - totalLeftMargin - totalRightMargin;
8387
8388 int rowCount = m_cells.GetCount();
8389 if (m_colCount == 0 || rowCount == 0)
8390 {
8391 wxRect overallRect(rect.x, rect.y, totalLeftMargin + totalRightMargin, totalTopMargin + totalBottomMargin);
8392 SetCachedSize(overallRect.GetSize());
8393
8394 // Zero content size
8395 SetMinSize(overallRect.GetSize());
8396 SetMaxSize(GetMinSize());
8397 return true;
8398 }
8399
8400 // The final calculated widths
8401 wxArrayInt colWidths;
8402 colWidths.Add(0, m_colCount);
8403
8404 wxArrayInt absoluteColWidths;
8405 absoluteColWidths.Add(0, m_colCount);
8406 // wxArrayInt absoluteColWidthsSpanning(m_colCount);
8407 wxArrayInt percentageColWidths;
8408 percentageColWidths.Add(0, m_colCount);
8409 // wxArrayInt percentageColWidthsSpanning(m_colCount);
8410 // These are only relevant when the first column contains spanning information.
8411 // wxArrayInt columnSpans(m_colCount); // Each contains 1 for non-spanning cell, > 1 for spanning cell.
8412 wxArrayInt maxColWidths;
8413 maxColWidths.Add(0, m_colCount);
8414 wxArrayInt minColWidths;
8415 minColWidths.Add(0, m_colCount);
8416
8417 wxSize tableSize(tableWidth, 0);
8418
8419 int i, j, k;
8420
8421 for (i = 0; i < m_colCount; i++)
8422 {
8423 absoluteColWidths[i] = 0;
8424 // absoluteColWidthsSpanning[i] = 0;
8425 percentageColWidths[i] = -1;
8426 // percentageColWidthsSpanning[i] = -1;
8427 colWidths[i] = 0;
8428 maxColWidths[i] = 0;
8429 minColWidths[i] = 0;
8430 // columnSpans[i] = 1;
8431 }
8432
8433 // (0) Determine which cells are visible according to spans
8434 // 1 2 3 4 5
8435 // __________________
8436 // | | | | | 1
8437 // |------| |----|
8438 // |------| | | 2
8439 // |------| | | 3
8440 // |------------------|
8441 // |__________________| 4
8442
8443 // To calculate cell visibility:
8444 // First find all spanning cells. Build an array of span records with start x, y and end x, y.
8445 // Then for each cell, test whether we're within one of those cells, and unless we're at the start of
8446 // that cell, hide the cell.
8447
8448 // We can also use this array to match the size of spanning cells to the grid. Or just do
8449 // this when we iterate through all cells.
8450
8451 // 0.1: add spanning cells to an array
8452 wxRichTextRectArray rectArray;
8453 for (j = 0; j < m_rowCount; j++)
8454 {
8455 for (i = 0; i < m_colCount; i++)
8456 {
8457 wxRichTextBox* cell = GetCell(j, i);
8458 int colSpan = 1, rowSpan = 1;
8459 if (cell->GetProperties().HasProperty(wxT("colspan")))
8460 colSpan = cell->GetProperties().GetPropertyLong(wxT("colspan"));
8461 if (cell->GetProperties().HasProperty(wxT("rowspan")))
8462 rowSpan = cell->GetProperties().GetPropertyLong(wxT("rowspan"));
8463 if (colSpan > 1 || rowSpan > 1)
8464 {
8465 rectArray.Add(wxRect(i, j, colSpan, rowSpan));
8466 }
8467 }
8468 }
8469 // 0.2: find which cells are subsumed by a spanning cell
8470 for (j = 0; j < m_rowCount; j++)
8471 {
8472 for (i = 0; i < m_colCount; i++)
8473 {
8474 wxRichTextBox* cell = GetCell(j, i);
8475 if (rectArray.GetCount() == 0)
8476 {
8477 cell->Show(true);
8478 }
8479 else
8480 {
8481 int colSpan = 1, rowSpan = 1;
8482 if (cell->GetProperties().HasProperty(wxT("colspan")))
8483 colSpan = cell->GetProperties().GetPropertyLong(wxT("colspan"));
8484 if (cell->GetProperties().HasProperty(wxT("rowspan")))
8485 rowSpan = cell->GetProperties().GetPropertyLong(wxT("rowspan"));
8486 if (colSpan > 1 || rowSpan > 1)
8487 {
8488 // Assume all spanning cells are shown
8489 cell->Show(true);
8490 }
8491 else
8492 {
8493 bool shown = true;
8494 for (k = 0; k < (int) rectArray.GetCount(); k++)
8495 {
8496 if (rectArray[k].Contains(wxPoint(i, j)))
8497 {
8498 shown = false;
8499 break;
8500 }
8501 }
8502 cell->Show(shown);
8503 }
8504 }
8505 }
8506 }
8507
8508 // TODO: find the first spanned cell in each row that spans the most columns and doesn't
8509 // overlap with a spanned cell starting at a previous column position.
8510 // This means we need to keep an array of rects so we can check. However
8511 // it does also mean that some spans simply may not be taken into account
8512 // where there are different spans happening on different rows. In these cases,
8513 // they will simply be as wide as their constituent columns.
8514
8515 // (1) Do an initial layout for all cells to get minimum and maximum size, and get
8516 // the absolute or percentage width of each column.
8517
8518 for (j = 0; j < m_rowCount; j++)
8519 {
8520 // First get the overall margins so we can calculate percentage widths based on
8521 // the available content space for all cells on the row
8522
8523 int overallRowContentMargin = 0;
8524 int visibleCellCount = 0;
8525
8526 for (i = 0; i < m_colCount; i++)
8527 {
8528 wxRichTextBox* cell = GetCell(j, i);
8529 if (cell->IsShown())
8530 {
8531 int cellTotalLeftMargin = 0, cellTotalRightMargin = 0, cellTotalTopMargin = 0, cellTotalBottomMargin = 0;
8532 GetTotalMargin(dc, buffer, cell->GetAttributes(), cellTotalLeftMargin, cellTotalRightMargin, cellTotalTopMargin, cellTotalBottomMargin);
8533
8534 overallRowContentMargin += (cellTotalLeftMargin + cellTotalRightMargin);
8535 visibleCellCount ++;
8536 }
8537 }
8538
8539 // Add in inter-cell padding
8540 overallRowContentMargin += ((visibleCellCount-1) * paddingX);
8541
8542 int rowContentWidth = internalTableWidth - overallRowContentMargin;
8543 wxSize rowTableSize(rowContentWidth, 0);
8544 wxTextAttrDimensionConverter converter(dc, scale, rowTableSize);
8545
8546 for (i = 0; i < m_colCount; i++)
8547 {
8548 wxRichTextBox* cell = GetCell(j, i);
8549 if (cell->IsShown())
8550 {
8551 int colSpan = 1;
8552 if (cell->GetProperties().HasProperty(wxT("colspan")))
8553 colSpan = cell->GetProperties().GetPropertyLong(wxT("colspan"));
8554
8555 // Lay out cell to find min/max widths
8556 cell->Invalidate(wxRICHTEXT_ALL);
8557 cell->Layout(dc, context, availableSpace, availableSpace, style);
8558
8559 if (colSpan == 1)
8560 {
8561 int absoluteCellWidth = -1;
8562 int percentageCellWidth = -1;
8563
8564 // I think we need to calculate percentages from the internal table size,
8565 // minus the padding between cells which we'll need to calculate from the
8566 // (number of VISIBLE cells - 1)*paddingX. Then percentages that add up to 100%
8567 // will add up to 100%. In CSS, the width specifies the cell's content rect width,
8568 // so if we want to conform to that we'll need to add in the overall cell margins.
8569 // However, this will make it difficult to specify percentages that add up to
8570 // 100% and still fit within the table width.
8571 // Let's say two cells have 50% width. They have 10 pixels of overall margin each.
8572 // The table content rect is 500 pixels and the inter-cell padding is 20 pixels.
8573 // If we're using internal content size for the width, we would calculate the
8574 // the overall cell width for n cells as:
8575 // (500 - 20*(n-1) - overallCellMargin1 - overallCellMargin2 - ...) * percentage / 100
8576 // + thisOverallCellMargin
8577 // = 500 - 20 - 10 - 10) * 0.5 + 10 = 240 pixels overall cell width.
8578 // Adding this back, we get 240 + 240 + 20 = 500 pixels.
8579
8580 if (cell->GetAttributes().GetTextBoxAttr().GetWidth().IsValid())
8581 {
8582 int w = converter.GetPixels(cell->GetAttributes().GetTextBoxAttr().GetWidth());
8583 if (cell->GetAttributes().GetTextBoxAttr().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE)
8584 {
8585 percentageCellWidth = w;
8586 }
8587 else
8588 {
8589 absoluteCellWidth = w;
8590 }
8591 // Override absolute width with minimum width if necessary
8592 if (cell->GetMinSize().x > 0 && absoluteCellWidth !=1 && cell->GetMinSize().x > absoluteCellWidth)
8593 absoluteCellWidth = cell->GetMinSize().x;
8594 }
8595
8596 if (absoluteCellWidth != -1)
8597 {
8598 if (absoluteCellWidth > absoluteColWidths[i])
8599 absoluteColWidths[i] = absoluteCellWidth;
8600 }
8601
8602 if (percentageCellWidth != -1)
8603 {
8604 if (percentageCellWidth > percentageColWidths[i])
8605 percentageColWidths[i] = percentageCellWidth;
8606 }
8607
8608 if (colSpan == 1 && cell->GetMinSize().x && cell->GetMinSize().x > minColWidths[i])
8609 minColWidths[i] = cell->GetMinSize().x;
8610 if (colSpan == 1 && cell->GetMaxSize().x && cell->GetMaxSize().x > maxColWidths[i])
8611 maxColWidths[i] = cell->GetMaxSize().x;
8612 }
8613 }
8614 }
8615 }
8616
8617 // (2) Allocate initial column widths from minimum widths, absolute values and proportions
8618 // TODO: simply merge this into (1).
8619 for (i = 0; i < m_colCount; i++)
8620 {
8621 if (absoluteColWidths[i] > 0)
8622 {
8623 colWidths[i] = absoluteColWidths[i];
8624 }
8625 else if (percentageColWidths[i] > 0)
8626 {
8627 colWidths[i] = percentageColWidths[i];
8628
8629 // This is rubbish - we calculated the absolute widths from percentages, so
8630 // we can't do it again here.
8631 //colWidths[i] = (int) (double(percentageColWidths[i]) * double(tableWidth) / 100.0 + 0.5);
8632 }
8633 }
8634
8635 // (3) Process absolute or proportional widths of spanning columns,
8636 // now that we know what our fixed column widths are going to be.
8637 // Spanned cells will try to adjust columns so the span will fit.
8638 // Even existing fixed column widths can be expanded if necessary.
8639 // Actually, currently fixed columns widths aren't adjusted; instead,
8640 // the algorithm favours earlier rows and adjusts unspecified column widths
8641 // the first time only. After that, we can't know whether the column has been
8642 // specified explicitly or not. (We could make a note if necessary.)
8643 for (j = 0; j < m_rowCount; j++)
8644 {
8645 // First get the overall margins so we can calculate percentage widths based on
8646 // the available content space for all cells on the row
8647
8648 int overallRowContentMargin = 0;
8649 int visibleCellCount = 0;
8650
8651 for (i = 0; i < m_colCount; i++)
8652 {
8653 wxRichTextBox* cell = GetCell(j, i);
8654 if (cell->IsShown())
8655 {
8656 int cellTotalLeftMargin = 0, cellTotalRightMargin = 0, cellTotalTopMargin = 0, cellTotalBottomMargin = 0;
8657 GetTotalMargin(dc, buffer, cell->GetAttributes(), cellTotalLeftMargin, cellTotalRightMargin, cellTotalTopMargin, cellTotalBottomMargin);
8658
8659 overallRowContentMargin += (cellTotalLeftMargin + cellTotalRightMargin);
8660 visibleCellCount ++;
8661 }
8662 }
8663
8664 // Add in inter-cell padding
8665 overallRowContentMargin += ((visibleCellCount-1) * paddingX);
8666
8667 int rowContentWidth = internalTableWidth - overallRowContentMargin;
8668 wxSize rowTableSize(rowContentWidth, 0);
8669 wxTextAttrDimensionConverter converter(dc, scale, rowTableSize);
8670
8671 for (i = 0; i < m_colCount; i++)
8672 {
8673 wxRichTextBox* cell = GetCell(j, i);
8674 if (cell->IsShown())
8675 {
8676 int colSpan = 1;
8677 if (cell->GetProperties().HasProperty(wxT("colspan")))
8678 colSpan = cell->GetProperties().GetPropertyLong(wxT("colspan"));
8679
8680 if (colSpan > 1)
8681 {
8682 int spans = wxMin(colSpan, m_colCount - i);
8683 int cellWidth = 0;
8684 if (spans > 0)
8685 {
8686 if (cell->GetAttributes().GetTextBoxAttr().GetWidth().IsValid())
8687 {
8688 cellWidth = converter.GetPixels(cell->GetAttributes().GetTextBoxAttr().GetWidth());
8689 // Override absolute width with minimum width if necessary
8690 if (cell->GetMinSize().x > 0 && cellWidth !=1 && cell->GetMinSize().x > cellWidth)
8691 cellWidth = cell->GetMinSize().x;
8692 }
8693 else
8694 {
8695 // Do we want to do this? It's the only chance we get to
8696 // use the cell's min/max sizes, so we need to work out
8697 // how we're going to balance the unspecified spanning cell
8698 // width with the possibility more-constrained constituent cell widths.
8699 // Say there's a tiny bitmap giving it a max width of 10 pixels. We
8700 // don't want to constraint all the spanned columns to fit into this cell.
8701 // OK, let's say that if any of the constituent columns don't fit,
8702 // then we simply stop constraining the columns; instead, we'll just fit the spanning
8703 // cells to the columns later.
8704 cellWidth = cell->GetMinSize().x;
8705 if (cell->GetMaxSize().x > cellWidth)
8706 cellWidth = cell->GetMaxSize().x;
8707 }
8708
8709 // Subtract the padding between cells
8710 int spanningWidth = cellWidth;
8711 spanningWidth -= paddingX * (spans-1);
8712
8713 if (spanningWidth > 0)
8714 {
8715 // Now share the spanning width between columns within that span
8716 // TODO: take into account min widths of columns within the span
8717 int spanningWidthLeft = spanningWidth;
8718 int stretchColCount = 0;
8719 for (k = i; k < (i+spans); k++)
8720 {
8721 if (colWidths[k] > 0) // absolute or proportional width has been specified
8722 spanningWidthLeft -= colWidths[k];
8723 else
8724 stretchColCount ++;
8725 }
8726 // Now divide what's left between the remaining columns
8727 int colShare = 0;
8728 if (stretchColCount > 0)
8729 colShare = spanningWidthLeft / stretchColCount;
8730 int colShareRemainder = spanningWidthLeft - (colShare * stretchColCount);
8731
8732 // If fixed-width columns are currently too big, then we'll later
8733 // stretch the spanned cell to fit.
8734
8735 if (spanningWidthLeft > 0)
8736 {
8737 for (k = i; k < (i+spans); k++)
8738 {
8739 if (colWidths[k] <= 0) // absolute or proportional width has not been specified
8740 {
8741 int newWidth = colShare;
8742 if (k == (i+spans-1))
8743 newWidth += colShareRemainder; // ensure all pixels are filled
8744 colWidths[k] = newWidth;
8745 }
8746 }
8747 }
8748 }
8749 }
8750 }
8751 }
8752 }
8753 }
8754
8755 // (4) Next, share any remaining space out between columns that have not yet been calculated.
8756 // TODO: take into account min widths of columns within the span
8757 int tableWidthMinusPadding = internalTableWidth - (m_colCount-1)*paddingX;
8758 int widthLeft = tableWidthMinusPadding;
8759 int stretchColCount = 0;
8760 for (i = 0; i < m_colCount; i++)
8761 {
8762 // TODO: we need to take into account min widths.
8763 // Subtract min width from width left, then
8764 // add the colShare to the min width
8765 if (colWidths[i] > 0) // absolute or proportional width has been specified
8766 widthLeft -= colWidths[i];
8767 else
8768 {
8769 if (minColWidths[i] > 0)
8770 widthLeft -= minColWidths[i];
8771
8772 stretchColCount ++;
8773 }
8774 }
8775
8776 // Now divide what's left between the remaining columns
8777 int colShare = 0;
8778 if (stretchColCount > 0)
8779 colShare = widthLeft / stretchColCount;
8780 int colShareRemainder = widthLeft - (colShare * stretchColCount);
8781
8782 // Check we don't have enough space, in which case shrink all columns, overriding
8783 // any absolute/proportional widths
8784 // TODO: actually we would like to divide up the shrinkage according to size.
8785 // How do we calculate the proportions that will achieve this?
8786 // Could first choose an arbitrary value for stretching cells, and then calculate
8787 // factors to multiply each width by.
8788 // TODO: want to record this fact and pass to an iteration that tries e.g. min widths
8789 if (widthLeft < 0 || (stretchToFitTableWidth && (stretchColCount == 0)))
8790 {
8791 colShare = tableWidthMinusPadding / m_colCount;
8792 colShareRemainder = tableWidthMinusPadding - (colShare * m_colCount);
8793 for (i = 0; i < m_colCount; i++)
8794 {
8795 colWidths[i] = 0;
8796 minColWidths[i] = 0;
8797 }
8798 }
8799
8800 // We have to adjust the columns if either we need to shrink the
8801 // table to fit the parent/table width, or we explicitly set the
8802 // table width and need to stretch out the table.
8803 if (widthLeft < 0 || stretchToFitTableWidth)
8804 {
8805 for (i = 0; i < m_colCount; i++)
8806 {
8807 if (colWidths[i] <= 0) // absolute or proportional width has not been specified
8808 {
8809 if (minColWidths[i] > 0)
8810 colWidths[i] = minColWidths[i] + colShare;
8811 else
8812 colWidths[i] = colShare;
8813 if (i == (m_colCount-1))
8814 colWidths[i] += colShareRemainder; // ensure all pixels are filled
8815 }
8816 }
8817 }
8818
8819 // TODO: if spanned cells have no specified or max width, make them the
8820 // as big as the columns they span. Do this for all spanned cells in all
8821 // rows, of course. Size any spanned cells left over at the end - even if they
8822 // have width > 0, make sure they're limited to the appropriate column edge.
8823
8824
8825 /*
8826 Sort out confusion between content width
8827 and overall width later. For now, assume we specify overall width.
8828
8829 So, now we've laid out the table to fit into the given space
8830 and have used specified widths and minimum widths.
8831
8832 Now we need to consider how we will try to take maximum width into account.
8833
8834 */
8835
8836 // (??) TODO: take max width into account
8837
8838 // (6) Lay out all cells again with the current values
8839
8840 int maxRight = 0;
8841 int y = availableSpace.y;
8842 for (j = 0; j < m_rowCount; j++)
8843 {
8844 int x = availableSpace.x; // TODO: take into account centering etc.
8845 int maxCellHeight = 0;
8846 int maxSpecifiedCellHeight = 0;
8847
8848 wxArrayInt actualWidths;
8849 actualWidths.Add(0, m_colCount);
8850
8851 wxTextAttrDimensionConverter converter(dc, scale);
8852 for (i = 0; i < m_colCount; i++)
8853 {
8854 wxRichTextCell* cell = GetCell(j, i);
8855 if (cell->IsShown())
8856 {
8857 // Get max specified cell height
8858 // Don't handle percentages for height
8859 if (cell->GetAttributes().GetTextBoxAttr().GetHeight().IsValid() && cell->GetAttributes().GetTextBoxAttr().GetHeight().GetUnits() != wxTEXT_ATTR_UNITS_PERCENTAGE)
8860 {
8861 int h = converter.GetPixels(cell->GetAttributes().GetTextBoxAttr().GetHeight());
8862 if (h > maxSpecifiedCellHeight)
8863 maxSpecifiedCellHeight = h;
8864 }
8865
8866 if (colWidths[i] > 0) // absolute or proportional width has been specified
8867 {
8868 int colSpan = 1;
8869 if (cell->GetProperties().HasProperty(wxT("colspan")))
8870 colSpan = cell->GetProperties().GetPropertyLong(wxT("colspan"));
8871
8872 wxRect availableCellSpace;
8873
8874 // TODO: take into acount spans
8875 if (colSpan > 1)
8876 {
8877 // Calculate the size of this spanning cell from its constituent columns
8878 int xx = x;
8879 int spans = wxMin(colSpan, m_colCount - i);
8880 for (k = i; k < spans; k++)
8881 {
8882 if (k != i)
8883 xx += paddingX;
8884 xx += colWidths[k];
8885 }
8886 availableCellSpace = wxRect(x, y, xx, -1);
8887 }
8888 else
8889 availableCellSpace = wxRect(x, y, colWidths[i], -1);
8890
8891 // Store actual width so we can force cell to be the appropriate width on the final loop
8892 actualWidths[i] = availableCellSpace.GetWidth();
8893
8894 // Lay out cell
8895 cell->Invalidate(wxRICHTEXT_ALL);
8896 cell->Layout(dc, context, availableCellSpace, availableSpace, style);
8897
8898 // TODO: use GetCachedSize().x to compute 'natural' size
8899
8900 x += (availableCellSpace.GetWidth() + paddingX);
8901 if (cell->GetCachedSize().y > maxCellHeight)
8902 maxCellHeight = cell->GetCachedSize().y;
8903 }
8904 }
8905 }
8906
8907 maxCellHeight = wxMax(maxCellHeight, maxSpecifiedCellHeight);
8908
8909 for (i = 0; i < m_colCount; i++)
8910 {
8911 wxRichTextCell* cell = GetCell(j, i);
8912 if (cell->IsShown())
8913 {
8914 wxRect availableCellSpace = wxRect(cell->GetPosition(), wxSize(actualWidths[i], maxCellHeight));
8915 // Lay out cell with new height
8916 cell->Invalidate(wxRICHTEXT_ALL);
8917 cell->Layout(dc, context, availableCellSpace, availableSpace, style);
8918
8919 // Make sure the cell size really is the appropriate size,
8920 // not the calculated box size
8921 cell->SetCachedSize(wxSize(actualWidths[i], maxCellHeight));
8922
8923 maxRight = wxMax(maxRight, cell->GetPosition().x + cell->GetCachedSize().x);
8924 }
8925 }
8926
8927 y += maxCellHeight;
8928 if (j < (m_rowCount-1))
8929 y += paddingY;
8930 }
8931
8932 // We need to add back the margins etc.
8933 {
8934 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
8935 contentRect = wxRect(wxPoint(0, 0), wxSize(maxRight - availableSpace.x, y - availableSpace.y));
8936 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
8937 SetCachedSize(marginRect.GetSize());
8938 }
8939
8940 // TODO: calculate max size
8941 {
8942 SetMaxSize(GetCachedSize());
8943 }
8944
8945 // TODO: calculate min size
8946 {
8947 SetMinSize(GetCachedSize());
8948 }
8949
8950 // TODO: currently we use either a fixed table width or the parent's size.
8951 // We also want to be able to calculate the table width from its content,
8952 // whether using fixed column widths or cell content min/max width.
8953 // Probably need a boolean flag to say whether we need to stretch cells
8954 // to fit the table width, or to simply use min/max cell widths. The
8955 // trouble with this is that if cell widths are not specified, they
8956 // will be tiny; we could use arbitrary defaults but this seems unsatisfactory.
8957 // Anyway, ignoring that problem, we probably need to factor layout into a function
8958 // that can can calculate the maximum unconstrained layout in case table size is
8959 // not specified. Then LayoutToBestSize() can choose to use either parent size to
8960 // constrain Layout(), or the previously-calculated max size to constraint layout.
8961
8962 return true;
8963 }
8964
8965 // Finds the absolute position and row height for the given character position
8966 bool wxRichTextTable::FindPosition(wxDC& dc, wxRichTextDrawingContext& context, long index, wxPoint& pt, int* height, bool forceLineStart)
8967 {
8968 wxRichTextCell* child = GetCell(index+1);
8969 if (child)
8970 {
8971 // Find the position at the start of the child cell, since the table doesn't
8972 // have any caret position of its own.
8973 return child->FindPosition(dc, context, -1, pt, height, forceLineStart);
8974 }
8975 else
8976 return false;
8977 }
8978
8979 // Get the cell at the given character position (in the range of the table).
8980 wxRichTextCell* wxRichTextTable::GetCell(long pos) const
8981 {
8982 int row = 0, col = 0;
8983 if (GetCellRowColumnPosition(pos, row, col))
8984 {
8985 return GetCell(row, col);
8986 }
8987 else
8988 return NULL;
8989 }
8990
8991 // Get the row/column for a given character position
8992 bool wxRichTextTable::GetCellRowColumnPosition(long pos, int& row, int& col) const
8993 {
8994 if (m_colCount == 0 || m_rowCount == 0)
8995 return false;
8996
8997 row = (int) (pos / m_colCount);
8998 col = pos - (row * m_colCount);
8999
9000 wxASSERT(row < m_rowCount && col < m_colCount);
9001
9002 if (row < m_rowCount && col < m_colCount)
9003 return true;
9004 else
9005 return false;
9006 }
9007
9008 // Calculate range, taking row/cell ordering into account instead of relying
9009 // on list ordering.
9010 void wxRichTextTable::CalculateRange(long start, long& end)
9011 {
9012 long current = start;
9013 long lastEnd = current;
9014
9015 if (IsTopLevel())
9016 {
9017 current = 0;
9018 lastEnd = 0;
9019 }
9020
9021 int i, j;
9022 for (i = 0; i < m_rowCount; i++)
9023 {
9024 for (j = 0; j < m_colCount; j++)
9025 {
9026 wxRichTextCell* child = GetCell(i, j);
9027 if (child)
9028 {
9029 long childEnd = 0;
9030
9031 child->CalculateRange(current, childEnd);
9032
9033 lastEnd = childEnd;
9034 current = childEnd + 1;
9035 }
9036 }
9037 }
9038
9039 // A top-level object always has a range of size 1,
9040 // because its children don't count at this level.
9041 end = start;
9042 m_range.SetRange(start, start);
9043
9044 // An object with no children has zero length
9045 if (m_children.GetCount() == 0)
9046 lastEnd --;
9047 m_ownRange.SetRange(0, lastEnd);
9048 }
9049
9050 // Gets the range size.
9051 bool wxRichTextTable::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, wxPoint position, wxArrayInt* partialExtents) const
9052 {
9053 return wxRichTextBox::GetRangeSize(range, size, descent, dc, context, flags, position, partialExtents);
9054 }
9055
9056 // Deletes content in the given range.
9057 bool wxRichTextTable::DeleteRange(const wxRichTextRange& WXUNUSED(range))
9058 {
9059 // TODO: implement deletion of cells
9060 return true;
9061 }
9062
9063 // Gets any text in this object for the given range.
9064 wxString wxRichTextTable::GetTextForRange(const wxRichTextRange& range) const
9065 {
9066 return wxRichTextBox::GetTextForRange(range);
9067 }
9068
9069 // Copies this object.
9070 void wxRichTextTable::Copy(const wxRichTextTable& obj)
9071 {
9072 wxRichTextBox::Copy(obj);
9073
9074 ClearTable();
9075
9076 m_rowCount = obj.m_rowCount;
9077 m_colCount = obj.m_colCount;
9078
9079 m_cells.Add(wxRichTextObjectPtrArray(), m_rowCount);
9080
9081 int i, j;
9082 for (i = 0; i < m_rowCount; i++)
9083 {
9084 wxRichTextObjectPtrArray& colArray = m_cells[i];
9085 for (j = 0; j < m_colCount; j++)
9086 {
9087 wxRichTextCell* cell = wxDynamicCast(obj.GetCell(i, j)->Clone(), wxRichTextCell);
9088 AppendChild(cell);
9089
9090 colArray.Add(cell);
9091 }
9092 }
9093 }
9094
9095 void wxRichTextTable::ClearTable()
9096 {
9097 m_cells.Clear();
9098 DeleteChildren();
9099 }
9100
9101 bool wxRichTextTable::CreateTable(int rows, int cols)
9102 {
9103 ClearTable();
9104
9105 m_rowCount = rows;
9106 m_colCount = cols;
9107
9108 m_cells.Add(wxRichTextObjectPtrArray(), rows);
9109
9110 int i, j;
9111 for (i = 0; i < rows; i++)
9112 {
9113 wxRichTextObjectPtrArray& colArray = m_cells[i];
9114 for (j = 0; j < cols; j++)
9115 {
9116 wxRichTextCell* cell = new wxRichTextCell;
9117 AppendChild(cell);
9118 cell->AddParagraph(wxEmptyString);
9119
9120 colArray.Add(cell);
9121 }
9122 }
9123
9124 return true;
9125 }
9126
9127 wxRichTextCell* wxRichTextTable::GetCell(int row, int col) const
9128 {
9129 wxASSERT(row < m_rowCount);
9130 wxASSERT(col < m_colCount);
9131
9132 if (row < m_rowCount && col < m_colCount)
9133 {
9134 wxRichTextObjectPtrArray& colArray = m_cells[row];
9135 wxRichTextObject* obj = colArray[col];
9136 return wxDynamicCast(obj, wxRichTextCell);
9137 }
9138 else
9139 return NULL;
9140 }
9141
9142 // Returns a selection object specifying the selections between start and end character positions.
9143 // For example, a table would deduce what cells (of range length 1) are selected when dragging across the table.
9144 wxRichTextSelection wxRichTextTable::GetSelection(long start, long end) const
9145 {
9146 wxRichTextSelection selection;
9147 selection.SetContainer((wxRichTextTable*) this);
9148
9149 if (start > end)
9150 {
9151 long tmp = end;
9152 end = start;
9153 start = tmp;
9154 }
9155
9156 wxASSERT( start >= 0 && end < (m_colCount * m_rowCount));
9157
9158 if (end >= (m_colCount * m_rowCount))
9159 return selection;
9160
9161 // We need to find the rectangle of cells that is described by the rectangle
9162 // with start, end as the diagonal. Make sure we don't add cells that are
9163 // not currenty visible because they are overlapped by spanning cells.
9164 /*
9165 --------------------------
9166 | 0 | 1 | 2 | 3 | 4 |
9167 --------------------------
9168 | 5 | 6 | 7 | 8 | 9 |
9169 --------------------------
9170 | 10 | 11 | 12 | 13 | 14 |
9171 --------------------------
9172 | 15 | 16 | 17 | 18 | 19 |
9173 --------------------------
9174
9175 Let's say we select 6 -> 18.
9176
9177 Left and right edge cols of rectangle are 1 and 3 inclusive. Find least/greatest to find
9178 which is left and which is right.
9179
9180 Top and bottom edge rows are 1 and 3 inclusive. Again, find least/greatest to find top and bottom.
9181
9182 Now go through rows from 1 to 3 and only add cells that are (a) within above column range
9183 and (b) shown.
9184
9185
9186 */
9187
9188 int leftCol = start - m_colCount * int(start/m_colCount);
9189 int rightCol = end - m_colCount * int(end/m_colCount);
9190
9191 int topRow = int(start/m_colCount);
9192 int bottomRow = int(end/m_colCount);
9193
9194 if (leftCol > rightCol)
9195 {
9196 int tmp = rightCol;
9197 rightCol = leftCol;
9198 leftCol = tmp;
9199 }
9200
9201 if (topRow > bottomRow)
9202 {
9203 int tmp = bottomRow;
9204 bottomRow = topRow;
9205 topRow = tmp;
9206 }
9207
9208 int i, j;
9209 for (i = topRow; i <= bottomRow; i++)
9210 {
9211 for (j = leftCol; j <= rightCol; j++)
9212 {
9213 wxRichTextCell* cell = GetCell(i, j);
9214 if (cell && cell->IsShown())
9215 selection.Add(cell->GetRange());
9216 }
9217 }
9218
9219 return selection;
9220 }
9221
9222 // Sets the attributes for the cells specified by the selection.
9223 bool wxRichTextTable::SetCellStyle(const wxRichTextSelection& selection, const wxRichTextAttr& style, int flags)
9224 {
9225 if (selection.GetContainer() != this)
9226 return false;
9227
9228 wxRichTextBuffer* buffer = GetBuffer();
9229 bool haveControl = (buffer && buffer->GetRichTextCtrl() != NULL);
9230 bool withUndo = haveControl && ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0);
9231
9232 if (withUndo)
9233 buffer->BeginBatchUndo(_("Set Cell Style"));
9234
9235 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
9236 while (node)
9237 {
9238 wxRichTextCell* cell = wxDynamicCast(node->GetData(), wxRichTextCell);
9239 if (cell && selection.WithinSelection(cell->GetRange().GetStart()))
9240 SetStyle(cell, style, flags);
9241 node = node->GetNext();
9242 }
9243
9244 // Do action, or delay it until end of batch.
9245 if (withUndo)
9246 buffer->EndBatchUndo();
9247
9248 return true;
9249 }
9250
9251 bool wxRichTextTable::DeleteRows(int startRow, int noRows)
9252 {
9253 wxASSERT((startRow + noRows) < m_rowCount);
9254 if ((startRow + noRows) >= m_rowCount)
9255 return false;
9256
9257 int i, j;
9258 for (i = startRow; i < (startRow+noRows); i++)
9259 {
9260 wxRichTextObjectPtrArray& colArray = m_cells[startRow];
9261 for (j = 0; j < (int) colArray.GetCount(); j++)
9262 {
9263 wxRichTextObject* cell = colArray[j];
9264 RemoveChild(cell, true);
9265 }
9266
9267 // Keep deleting at the same position, since we move all
9268 // the others up
9269 m_cells.RemoveAt(startRow);
9270 }
9271
9272 m_rowCount = m_rowCount - noRows;
9273
9274 return true;
9275 }
9276
9277 bool wxRichTextTable::DeleteColumns(int startCol, int noCols)
9278 {
9279 wxASSERT((startCol + noCols) < m_colCount);
9280 if ((startCol + noCols) >= m_colCount)
9281 return false;
9282
9283 bool deleteRows = (noCols == m_colCount);
9284
9285 int i, j;
9286 for (i = 0; i < m_rowCount; i++)
9287 {
9288 wxRichTextObjectPtrArray& colArray = m_cells[deleteRows ? 0 : i];
9289 for (j = startCol; j < (startCol+noCols); j++)
9290 {
9291 wxRichTextObject* cell = colArray[j];
9292 RemoveChild(cell, true);
9293 }
9294
9295 if (deleteRows)
9296 m_cells.RemoveAt(0);
9297 }
9298
9299 if (deleteRows)
9300 m_rowCount = 0;
9301 m_colCount = m_colCount - noCols;
9302
9303 return true;
9304 }
9305
9306 bool wxRichTextTable::AddRows(int startRow, int noRows, const wxRichTextAttr& attr)
9307 {
9308 wxASSERT(startRow <= m_rowCount);
9309 if (startRow > m_rowCount)
9310 return false;
9311
9312 int i, j;
9313 for (i = 0; i < noRows; i++)
9314 {
9315 int idx;
9316 if (startRow == m_rowCount)
9317 {
9318 m_cells.Add(wxRichTextObjectPtrArray());
9319 idx = m_cells.GetCount() - 1;
9320 }
9321 else
9322 {
9323 m_cells.Insert(wxRichTextObjectPtrArray(), startRow+i);
9324 idx = startRow+i;
9325 }
9326
9327 wxRichTextObjectPtrArray& colArray = m_cells[idx];
9328 for (j = 0; j < m_colCount; j++)
9329 {
9330 wxRichTextCell* cell = new wxRichTextCell;
9331 cell->GetAttributes() = attr;
9332
9333 AppendChild(cell);
9334 colArray.Add(cell);
9335 }
9336 }
9337
9338 m_rowCount = m_rowCount + noRows;
9339 return true;
9340 }
9341
9342 bool wxRichTextTable::AddColumns(int startCol, int noCols, const wxRichTextAttr& attr)
9343 {
9344 wxASSERT(startCol <= m_colCount);
9345 if (startCol > m_colCount)
9346 return false;
9347
9348 int i, j;
9349 for (i = 0; i < m_rowCount; i++)
9350 {
9351 wxRichTextObjectPtrArray& colArray = m_cells[i];
9352 for (j = 0; j < noCols; j++)
9353 {
9354 wxRichTextCell* cell = new wxRichTextCell;
9355 cell->GetAttributes() = attr;
9356
9357 AppendChild(cell);
9358
9359 if (startCol == m_colCount)
9360 colArray.Add(cell);
9361 else
9362 colArray.Insert(cell, startCol+j);
9363 }
9364 }
9365
9366 m_colCount = m_colCount + noCols;
9367
9368 return true;
9369 }
9370
9371 // Edit properties via a GUI
9372 bool wxRichTextTable::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer)
9373 {
9374 wxRichTextObjectPropertiesDialog boxDlg(this, wxGetTopLevelParent(parent), wxID_ANY, _("Table Properties"));
9375 boxDlg.SetAttributes(GetAttributes());
9376
9377 if (boxDlg.ShowModal() == wxID_OK)
9378 {
9379 boxDlg.ApplyStyle(buffer->GetRichTextCtrl(), wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_RESET);
9380 return true;
9381 }
9382 else
9383 return false;
9384 }
9385
9386 /*
9387 * Module to initialise and clean up handlers
9388 */
9389
9390 class wxRichTextModule: public wxModule
9391 {
9392 DECLARE_DYNAMIC_CLASS(wxRichTextModule)
9393 public:
9394 wxRichTextModule() {}
9395 bool OnInit()
9396 {
9397 wxRichTextBuffer::SetRenderer(new wxRichTextStdRenderer);
9398 wxRichTextBuffer::InitStandardHandlers();
9399 wxRichTextParagraph::InitDefaultTabs();
9400 return true;
9401 }
9402 void OnExit()
9403 {
9404 wxRichTextBuffer::CleanUpHandlers();
9405 wxRichTextBuffer::CleanUpDrawingHandlers();
9406 wxRichTextDecimalToRoman(-1);
9407 wxRichTextParagraph::ClearDefaultTabs();
9408 wxRichTextCtrl::ClearAvailableFontNames();
9409 wxRichTextBuffer::SetRenderer(NULL);
9410 }
9411 };
9412
9413 IMPLEMENT_DYNAMIC_CLASS(wxRichTextModule, wxModule)
9414
9415
9416 // If the richtext lib is dynamically loaded after the app has already started
9417 // (such as from wxPython) then the built-in module system will not init this
9418 // module. Provide this function to do it manually.
9419 void wxRichTextModuleInit()
9420 {
9421 wxModule* module = new wxRichTextModule;
9422 module->Init();
9423 wxModule::RegisterModule(module);
9424 }
9425
9426
9427 /*!
9428 * Commands for undo/redo
9429 *
9430 */
9431
9432 wxRichTextCommand::wxRichTextCommand(const wxString& name, wxRichTextCommandId id, wxRichTextBuffer* buffer,
9433 wxRichTextParagraphLayoutBox* container, wxRichTextCtrl* ctrl, bool ignoreFirstTime): wxCommand(true, name)
9434 {
9435 /* wxRichTextAction* action = */ new wxRichTextAction(this, name, id, buffer, container, ctrl, ignoreFirstTime);
9436 }
9437
9438 wxRichTextCommand::wxRichTextCommand(const wxString& name): wxCommand(true, name)
9439 {
9440 }
9441
9442 wxRichTextCommand::~wxRichTextCommand()
9443 {
9444 ClearActions();
9445 }
9446
9447 void wxRichTextCommand::AddAction(wxRichTextAction* action)
9448 {
9449 if (!m_actions.Member(action))
9450 m_actions.Append(action);
9451 }
9452
9453 bool wxRichTextCommand::Do()
9454 {
9455 for (wxList::compatibility_iterator node = m_actions.GetFirst(); node; node = node->GetNext())
9456 {
9457 wxRichTextAction* action = (wxRichTextAction*) node->GetData();
9458 action->Do();
9459 }
9460
9461 return true;
9462 }
9463
9464 bool wxRichTextCommand::Undo()
9465 {
9466 for (wxList::compatibility_iterator node = m_actions.GetLast(); node; node = node->GetPrevious())
9467 {
9468 wxRichTextAction* action = (wxRichTextAction*) node->GetData();
9469 action->Undo();
9470 }
9471
9472 return true;
9473 }
9474
9475 void wxRichTextCommand::ClearActions()
9476 {
9477 WX_CLEAR_LIST(wxList, m_actions);
9478 }
9479
9480 /*!
9481 * Individual action
9482 *
9483 */
9484
9485 wxRichTextAction::wxRichTextAction(wxRichTextCommand* cmd, const wxString& name, wxRichTextCommandId id,
9486 wxRichTextBuffer* buffer, wxRichTextParagraphLayoutBox* container,
9487 wxRichTextCtrl* ctrl, bool ignoreFirstTime)
9488 {
9489 m_buffer = buffer;
9490 m_object = NULL;
9491 m_containerAddress.Create(buffer, container);
9492 m_ignoreThis = ignoreFirstTime;
9493 m_cmdId = id;
9494 m_position = -1;
9495 m_ctrl = ctrl;
9496 m_name = name;
9497 m_newParagraphs.SetDefaultStyle(buffer->GetDefaultStyle());
9498 m_newParagraphs.SetBasicStyle(buffer->GetBasicStyle());
9499 if (cmd)
9500 cmd->AddAction(this);
9501 }
9502
9503 wxRichTextAction::~wxRichTextAction()
9504 {
9505 if (m_object)
9506 delete m_object;
9507 }
9508
9509 // Returns the container that this action refers to, using the container address and top-level buffer.
9510 wxRichTextParagraphLayoutBox* wxRichTextAction::GetContainer() const
9511 {
9512 wxRichTextParagraphLayoutBox* container = wxDynamicCast(GetContainerAddress().GetObject(m_buffer), wxRichTextParagraphLayoutBox);
9513 return container;
9514 }
9515
9516
9517 void wxRichTextAction::CalculateRefreshOptimizations(wxArrayInt& optimizationLineCharPositions, wxArrayInt& optimizationLineYPositions)
9518 {
9519 // Store a list of line start character and y positions so we can figure out which area
9520 // we need to refresh
9521
9522 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
9523 wxRichTextParagraphLayoutBox* container = GetContainer();
9524 wxASSERT(container != NULL);
9525 if (!container)
9526 return;
9527
9528 // NOTE: we're assuming that the buffer is laid out correctly at this point.
9529 // If we had several actions, which only invalidate and leave layout until the
9530 // paint handler is called, then this might not be true. So we may need to switch
9531 // optimisation on only when we're simply adding text and not simultaneously
9532 // deleting a selection, for example. Or, we make sure the buffer is laid out correctly
9533 // first, but of course this means we'll be doing it twice.
9534 if (!m_buffer->IsDirty() && m_ctrl) // can only do optimisation if the buffer is already laid out correctly
9535 {
9536 wxSize clientSize = m_ctrl->GetClientSize();
9537 wxPoint firstVisiblePt = m_ctrl->GetFirstVisiblePoint();
9538 int lastY = firstVisiblePt.y + clientSize.y;
9539
9540 wxRichTextParagraph* para = container->GetParagraphAtPosition(GetRange().GetStart());
9541 wxRichTextObjectList::compatibility_iterator node = container->GetChildren().Find(para);
9542 while (node)
9543 {
9544 wxRichTextParagraph* child = (wxRichTextParagraph*) node->GetData();
9545 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
9546 while (node2)
9547 {
9548 wxRichTextLine* line = node2->GetData();
9549 wxPoint pt = line->GetAbsolutePosition();
9550 wxRichTextRange range = line->GetAbsoluteRange();
9551
9552 if (pt.y > lastY)
9553 {
9554 node2 = wxRichTextLineList::compatibility_iterator();
9555 node = wxRichTextObjectList::compatibility_iterator();
9556 }
9557 else if (range.GetStart() > GetPosition() && pt.y >= firstVisiblePt.y)
9558 {
9559 optimizationLineCharPositions.Add(range.GetStart());
9560 optimizationLineYPositions.Add(pt.y);
9561 }
9562
9563 if (node2)
9564 node2 = node2->GetNext();
9565 }
9566
9567 if (node)
9568 node = node->GetNext();
9569 }
9570 }
9571 #endif
9572 }
9573
9574 bool wxRichTextAction::Do()
9575 {
9576 m_buffer->Modify(true);
9577
9578 wxRichTextParagraphLayoutBox* container = GetContainer();
9579 wxASSERT(container != NULL);
9580 if (!container)
9581 return false;
9582
9583 switch (m_cmdId)
9584 {
9585 case wxRICHTEXT_INSERT:
9586 {
9587 // Store a list of line start character and y positions so we can figure out which area
9588 // we need to refresh
9589 wxArrayInt optimizationLineCharPositions;
9590 wxArrayInt optimizationLineYPositions;
9591
9592 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
9593 CalculateRefreshOptimizations(optimizationLineCharPositions, optimizationLineYPositions);
9594 #endif
9595
9596 container->InsertFragment(GetRange().GetStart(), m_newParagraphs);
9597 container->UpdateRanges();
9598
9599 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
9600 // Layout() would stop prematurely at the top level.
9601 container->InvalidateHierarchy(wxRichTextRange(wxMax(0, GetRange().GetStart()-1), GetRange().GetEnd()));
9602
9603 long newCaretPosition = GetPosition() + m_newParagraphs.GetOwnRange().GetLength();
9604
9605 // Character position to caret position
9606 newCaretPosition --;
9607
9608 // Don't take into account the last newline
9609 if (m_newParagraphs.GetPartialParagraph())
9610 newCaretPosition --;
9611 else
9612 if (m_newParagraphs.GetChildren().GetCount() > 1)
9613 {
9614 wxRichTextObject* p = (wxRichTextObject*) m_newParagraphs.GetChildren().GetLast()->GetData();
9615 if (p->GetRange().GetLength() == 1)
9616 newCaretPosition --;
9617 }
9618
9619 newCaretPosition = wxMin(newCaretPosition, (container->GetOwnRange().GetEnd()-1));
9620
9621 UpdateAppearance(newCaretPosition, true /* send update event */, & optimizationLineCharPositions, & optimizationLineYPositions, true /* do */);
9622
9623 wxRichTextEvent cmdEvent(
9624 wxEVT_COMMAND_RICHTEXT_CONTENT_INSERTED,
9625 m_ctrl ? m_ctrl->GetId() : -1);
9626 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
9627 cmdEvent.SetRange(GetRange());
9628 cmdEvent.SetPosition(GetRange().GetStart());
9629 cmdEvent.SetContainer(container);
9630
9631 m_buffer->SendEvent(cmdEvent);
9632
9633 break;
9634 }
9635 case wxRICHTEXT_DELETE:
9636 {
9637 wxArrayInt optimizationLineCharPositions;
9638 wxArrayInt optimizationLineYPositions;
9639
9640 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
9641 CalculateRefreshOptimizations(optimizationLineCharPositions, optimizationLineYPositions);
9642 #endif
9643
9644 container->DeleteRange(GetRange());
9645 container->UpdateRanges();
9646 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
9647 // Layout() would stop prematurely at the top level.
9648 container->InvalidateHierarchy(wxRichTextRange(GetRange().GetStart(), GetRange().GetStart()));
9649
9650 long caretPos = GetRange().GetStart()-1;
9651 if (caretPos >= container->GetOwnRange().GetEnd())
9652 caretPos --;
9653
9654 UpdateAppearance(caretPos, true /* send update event */, & optimizationLineCharPositions, & optimizationLineYPositions, true /* do */);
9655
9656 wxRichTextEvent cmdEvent(
9657 wxEVT_COMMAND_RICHTEXT_CONTENT_DELETED,
9658 m_ctrl ? m_ctrl->GetId() : -1);
9659 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
9660 cmdEvent.SetRange(GetRange());
9661 cmdEvent.SetPosition(GetRange().GetStart());
9662 cmdEvent.SetContainer(container);
9663
9664 m_buffer->SendEvent(cmdEvent);
9665
9666 break;
9667 }
9668 case wxRICHTEXT_CHANGE_STYLE:
9669 case wxRICHTEXT_CHANGE_PROPERTIES:
9670 {
9671 ApplyParagraphs(GetNewParagraphs());
9672
9673 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
9674 // Layout() would stop prematurely at the top level.
9675 container->InvalidateHierarchy(GetRange());
9676
9677 UpdateAppearance(GetPosition());
9678
9679 wxRichTextEvent cmdEvent(
9680 m_cmdId == wxRICHTEXT_CHANGE_STYLE ? wxEVT_COMMAND_RICHTEXT_STYLE_CHANGED : wxEVT_COMMAND_RICHTEXT_PROPERTIES_CHANGED,
9681 m_ctrl ? m_ctrl->GetId() : -1);
9682 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
9683 cmdEvent.SetRange(GetRange());
9684 cmdEvent.SetPosition(GetRange().GetStart());
9685 cmdEvent.SetContainer(container);
9686
9687 m_buffer->SendEvent(cmdEvent);
9688
9689 break;
9690 }
9691 case wxRICHTEXT_CHANGE_ATTRIBUTES:
9692 {
9693 wxRichTextObject* obj = m_objectAddress.GetObject(m_buffer); // container->GetChildAtPosition(GetRange().GetStart());
9694 if (obj)
9695 {
9696 wxRichTextAttr oldAttr = obj->GetAttributes();
9697 obj->GetAttributes() = m_attributes;
9698 m_attributes = oldAttr;
9699 }
9700
9701 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
9702 // Layout() would stop prematurely at the top level.
9703 container->InvalidateHierarchy(GetRange());
9704
9705 UpdateAppearance(GetPosition());
9706
9707 wxRichTextEvent cmdEvent(
9708 wxEVT_COMMAND_RICHTEXT_STYLE_CHANGED,
9709 m_ctrl ? m_ctrl->GetId() : -1);
9710 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
9711 cmdEvent.SetRange(GetRange());
9712 cmdEvent.SetPosition(GetRange().GetStart());
9713 cmdEvent.SetContainer(container);
9714
9715 m_buffer->SendEvent(cmdEvent);
9716
9717 break;
9718 }
9719 case wxRICHTEXT_CHANGE_OBJECT:
9720 {
9721 wxRichTextObject* obj = m_objectAddress.GetObject(m_buffer);
9722 // wxRichTextObject* obj = container->GetChildAtPosition(GetRange().GetStart());
9723 if (obj && m_object)
9724 {
9725 wxRichTextObjectList::compatibility_iterator node = container->GetChildren().Find(obj);
9726 if (node)
9727 {
9728 wxRichTextObject* obj = node->GetData();
9729 node->SetData(m_object);
9730 m_object = obj;
9731 }
9732 }
9733
9734 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
9735 // Layout() would stop prematurely at the top level.
9736 container->InvalidateHierarchy(GetRange());
9737
9738 UpdateAppearance(GetPosition());
9739
9740 // TODO: send new kind of modification event
9741
9742 break;
9743 }
9744 default:
9745 break;
9746 }
9747
9748 return true;
9749 }
9750
9751 bool wxRichTextAction::Undo()
9752 {
9753 m_buffer->Modify(true);
9754
9755 wxRichTextParagraphLayoutBox* container = GetContainer();
9756 wxASSERT(container != NULL);
9757 if (!container)
9758 return false;
9759
9760 switch (m_cmdId)
9761 {
9762 case wxRICHTEXT_INSERT:
9763 {
9764 wxArrayInt optimizationLineCharPositions;
9765 wxArrayInt optimizationLineYPositions;
9766
9767 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
9768 CalculateRefreshOptimizations(optimizationLineCharPositions, optimizationLineYPositions);
9769 #endif
9770
9771 container->DeleteRange(GetRange());
9772 container->UpdateRanges();
9773 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
9774 // Layout() would stop prematurely at the top level.
9775 container->InvalidateHierarchy(wxRichTextRange(GetRange().GetStart(), GetRange().GetStart()));
9776
9777 long newCaretPosition = GetPosition() - 1;
9778
9779 UpdateAppearance(newCaretPosition, true, /* send update event */ & optimizationLineCharPositions, & optimizationLineYPositions, false /* undo */);
9780
9781 wxRichTextEvent cmdEvent(
9782 wxEVT_COMMAND_RICHTEXT_CONTENT_DELETED,
9783 m_ctrl ? m_ctrl->GetId() : -1);
9784 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
9785 cmdEvent.SetRange(GetRange());
9786 cmdEvent.SetPosition(GetRange().GetStart());
9787 cmdEvent.SetContainer(container);
9788
9789 m_buffer->SendEvent(cmdEvent);
9790
9791 break;
9792 }
9793 case wxRICHTEXT_DELETE:
9794 {
9795 wxArrayInt optimizationLineCharPositions;
9796 wxArrayInt optimizationLineYPositions;
9797
9798 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
9799 CalculateRefreshOptimizations(optimizationLineCharPositions, optimizationLineYPositions);
9800 #endif
9801
9802 container->InsertFragment(GetRange().GetStart(), m_oldParagraphs);
9803 container->UpdateRanges();
9804 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
9805 // Layout() would stop prematurely at the top level.
9806 container->InvalidateHierarchy(GetRange());
9807
9808 UpdateAppearance(GetPosition(), true, /* send update event */ & optimizationLineCharPositions, & optimizationLineYPositions, false /* undo */);
9809
9810 wxRichTextEvent cmdEvent(
9811 wxEVT_COMMAND_RICHTEXT_CONTENT_INSERTED,
9812 m_ctrl ? m_ctrl->GetId() : -1);
9813 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
9814 cmdEvent.SetRange(GetRange());
9815 cmdEvent.SetPosition(GetRange().GetStart());
9816 cmdEvent.SetContainer(container);
9817
9818 m_buffer->SendEvent(cmdEvent);
9819
9820 break;
9821 }
9822 case wxRICHTEXT_CHANGE_STYLE:
9823 case wxRICHTEXT_CHANGE_PROPERTIES:
9824 {
9825 ApplyParagraphs(GetOldParagraphs());
9826 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
9827 // Layout() would stop prematurely at the top level.
9828 container->InvalidateHierarchy(GetRange());
9829
9830 UpdateAppearance(GetPosition());
9831
9832 wxRichTextEvent cmdEvent(
9833 m_cmdId == wxRICHTEXT_CHANGE_STYLE ? wxEVT_COMMAND_RICHTEXT_STYLE_CHANGED : wxEVT_COMMAND_RICHTEXT_PROPERTIES_CHANGED,
9834 m_ctrl ? m_ctrl->GetId() : -1);
9835 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
9836 cmdEvent.SetRange(GetRange());
9837 cmdEvent.SetPosition(GetRange().GetStart());
9838 cmdEvent.SetContainer(container);
9839
9840 m_buffer->SendEvent(cmdEvent);
9841
9842 break;
9843 }
9844 case wxRICHTEXT_CHANGE_ATTRIBUTES:
9845 case wxRICHTEXT_CHANGE_OBJECT:
9846 {
9847 return Do();
9848 }
9849 default:
9850 break;
9851 }
9852
9853 return true;
9854 }
9855
9856 /// Update the control appearance
9857 void wxRichTextAction::UpdateAppearance(long caretPosition, bool sendUpdateEvent, wxArrayInt* optimizationLineCharPositions, wxArrayInt* optimizationLineYPositions, bool isDoCmd)
9858 {
9859 wxRichTextParagraphLayoutBox* container = GetContainer();
9860 wxASSERT(container != NULL);
9861 if (!container)
9862 return;
9863
9864 if (m_ctrl)
9865 {
9866 m_ctrl->SetFocusObject(container);
9867 m_ctrl->SetCaretPosition(caretPosition);
9868
9869 if (!m_ctrl->IsFrozen())
9870 {
9871 wxRect containerRect = container->GetRect();
9872
9873 m_ctrl->LayoutContent();
9874
9875 // Refresh everything if there were floating objects or the container changed size
9876 // (we can't yet optimize in these cases, since more complex interaction with other content occurs)
9877 if (container->GetFloatingObjectCount() > 0 || (container->GetParent() && containerRect != container->GetRect()))
9878 {
9879 m_ctrl->Refresh(false);
9880 }
9881 else
9882
9883 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
9884 // Find refresh rectangle if we are in a position to optimise refresh
9885 if ((m_cmdId == wxRICHTEXT_INSERT || m_cmdId == wxRICHTEXT_DELETE) && optimizationLineCharPositions)
9886 {
9887 size_t i;
9888
9889 wxSize clientSize = m_ctrl->GetClientSize();
9890 wxPoint firstVisiblePt = m_ctrl->GetFirstVisiblePoint();
9891
9892 // Start/end positions
9893 int firstY = 0;
9894 int lastY = firstVisiblePt.y + clientSize.y;
9895
9896 bool foundEnd = false;
9897
9898 // position offset - how many characters were inserted
9899 int positionOffset = GetRange().GetLength();
9900
9901 // Determine whether this is Do or Undo, and adjust positionOffset accordingly
9902 if ((m_cmdId == wxRICHTEXT_DELETE && isDoCmd) || (m_cmdId == wxRICHTEXT_INSERT && !isDoCmd))
9903 positionOffset = - positionOffset;
9904
9905 // find the first line which is being drawn at the same position as it was
9906 // before. Since we're talking about a simple insertion, we can assume
9907 // that the rest of the window does not need to be redrawn.
9908
9909 wxRichTextParagraph* para = container->GetParagraphAtPosition(GetPosition());
9910 // Since we support floating layout, we should redraw the whole para instead of just
9911 // the first line touching the invalid range.
9912 if (para)
9913 {
9914 firstY = para->GetPosition().y;
9915 }
9916
9917 wxRichTextObjectList::compatibility_iterator node = container->GetChildren().Find(para);
9918 while (node)
9919 {
9920 wxRichTextParagraph* child = (wxRichTextParagraph*) node->GetData();
9921 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
9922 while (node2)
9923 {
9924 wxRichTextLine* line = node2->GetData();
9925 wxPoint pt = line->GetAbsolutePosition();
9926 wxRichTextRange range = line->GetAbsoluteRange();
9927
9928 // we want to find the first line that is in the same position
9929 // as before. This will mean we're at the end of the changed text.
9930
9931 if (pt.y > lastY) // going past the end of the window, no more info
9932 {
9933 node2 = wxRichTextLineList::compatibility_iterator();
9934 node = wxRichTextObjectList::compatibility_iterator();
9935 }
9936 // Detect last line in the buffer
9937 else if (!node2->GetNext() && para->GetRange().Contains(container->GetOwnRange().GetEnd()))
9938 {
9939 // If deleting text, make sure we refresh below as well as above
9940 if (positionOffset >= 0)
9941 {
9942 foundEnd = true;
9943 lastY = pt.y + line->GetSize().y;
9944 }
9945
9946 node2 = wxRichTextLineList::compatibility_iterator();
9947 node = wxRichTextObjectList::compatibility_iterator();
9948
9949 break;
9950 }
9951 else
9952 {
9953 // search for this line being at the same position as before
9954 for (i = 0; i < optimizationLineCharPositions->GetCount(); i++)
9955 {
9956 if (((*optimizationLineCharPositions)[i] + positionOffset == range.GetStart()) &&
9957 ((*optimizationLineYPositions)[i] == pt.y))
9958 {
9959 // Stop, we're now the same as we were
9960 foundEnd = true;
9961
9962 lastY = pt.y;
9963
9964 node2 = wxRichTextLineList::compatibility_iterator();
9965 node = wxRichTextObjectList::compatibility_iterator();
9966
9967 break;
9968 }
9969 }
9970 }
9971
9972 if (node2)
9973 node2 = node2->GetNext();
9974 }
9975
9976 if (node)
9977 node = node->GetNext();
9978 }
9979
9980 firstY = wxMax(firstVisiblePt.y, firstY);
9981 if (!foundEnd)
9982 lastY = firstVisiblePt.y + clientSize.y;
9983
9984 // Convert to device coordinates
9985 wxRect rect(m_ctrl->GetPhysicalPoint(wxPoint(firstVisiblePt.x, firstY)), wxSize(clientSize.x, lastY - firstY));
9986 m_ctrl->RefreshRect(rect);
9987 }
9988 else
9989 #endif
9990 m_ctrl->Refresh(false);
9991
9992 m_ctrl->PositionCaret();
9993
9994 // This causes styles to persist when doing programmatic
9995 // content creation except when Freeze/Thaw is used, so
9996 // disable this and check for the consequences.
9997 // m_ctrl->SetDefaultStyleToCursorStyle();
9998
9999 if (sendUpdateEvent)
10000 wxTextCtrl::SendTextUpdatedEvent(m_ctrl);
10001 }
10002 }
10003 }
10004
10005 /// Replace the buffer paragraphs with the new ones.
10006 void wxRichTextAction::ApplyParagraphs(const wxRichTextParagraphLayoutBox& fragment)
10007 {
10008 wxRichTextParagraphLayoutBox* container = GetContainer();
10009 wxASSERT(container != NULL);
10010 if (!container)
10011 return;
10012
10013 wxRichTextObjectList::compatibility_iterator node = fragment.GetChildren().GetFirst();
10014 while (node)
10015 {
10016 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
10017 wxASSERT (para != NULL);
10018
10019 // We'll replace the existing paragraph by finding the paragraph at this position,
10020 // delete its node data, and setting a copy as the new node data.
10021 // TODO: make more efficient by simply swapping old and new paragraph objects.
10022
10023 wxRichTextParagraph* existingPara = container->GetParagraphAtPosition(para->GetRange().GetStart());
10024 if (existingPara)
10025 {
10026 wxRichTextObjectList::compatibility_iterator bufferParaNode = container->GetChildren().Find(existingPara);
10027 if (bufferParaNode)
10028 {
10029 wxRichTextParagraph* newPara = new wxRichTextParagraph(*para);
10030 newPara->SetParent(container);
10031
10032 bufferParaNode->SetData(newPara);
10033
10034 delete existingPara;
10035 }
10036 }
10037
10038 node = node->GetNext();
10039 }
10040 }
10041
10042
10043 /*!
10044 * wxRichTextRange
10045 * This stores beginning and end positions for a range of data.
10046 */
10047
10048 WX_DEFINE_OBJARRAY(wxRichTextRangeArray);
10049
10050 /// Limit this range to be within 'range'
10051 bool wxRichTextRange::LimitTo(const wxRichTextRange& range)
10052 {
10053 if (m_start < range.m_start)
10054 m_start = range.m_start;
10055
10056 if (m_end > range.m_end)
10057 m_end = range.m_end;
10058
10059 return true;
10060 }
10061
10062 /*!
10063 * wxRichTextImage implementation
10064 * This object represents an image.
10065 */
10066
10067 IMPLEMENT_DYNAMIC_CLASS(wxRichTextImage, wxRichTextObject)
10068
10069 wxRichTextImage::wxRichTextImage(const wxImage& image, wxRichTextObject* parent, wxRichTextAttr* charStyle):
10070 wxRichTextObject(parent)
10071 {
10072 Init();
10073 m_imageBlock.MakeImageBlockDefaultQuality(image, wxBITMAP_TYPE_PNG);
10074 if (charStyle)
10075 SetAttributes(*charStyle);
10076 }
10077
10078 wxRichTextImage::wxRichTextImage(const wxRichTextImageBlock& imageBlock, wxRichTextObject* parent, wxRichTextAttr* charStyle):
10079 wxRichTextObject(parent)
10080 {
10081 Init();
10082 m_imageBlock = imageBlock;
10083 if (charStyle)
10084 SetAttributes(*charStyle);
10085 }
10086
10087 void wxRichTextImage::Init()
10088 {
10089 m_originalImageSize = wxSize(-1, -1);
10090 }
10091
10092 /// Create a cached image at the required size
10093 bool wxRichTextImage::LoadImageCache(wxDC& dc, bool resetCache)
10094 {
10095 if (!m_imageBlock.IsOk())
10096 return false;
10097
10098 // If we have an original image size, use that to compute the cached bitmap size
10099 // instead of loading the image each time. This way we can avoid loading
10100 // the image so long as the new cached bitmap size hasn't changed.
10101
10102 wxImage image;
10103 if (resetCache || m_originalImageSize == wxSize(-1, -1))
10104 {
10105 m_imageCache = wxNullBitmap;
10106
10107 m_imageBlock.Load(image);
10108 if (!image.IsOk())
10109 return false;
10110
10111 m_originalImageSize = wxSize(image.GetWidth(), image.GetHeight());
10112 }
10113
10114 int width = m_originalImageSize.GetWidth();
10115 int height = m_originalImageSize.GetHeight();
10116
10117 int parentWidth = 0;
10118 int parentHeight = 0;
10119
10120 int maxWidth = -1;
10121 int maxHeight = -1;
10122
10123 wxRichTextBuffer* buffer = GetBuffer();
10124 if (buffer)
10125 {
10126 wxSize sz;
10127 if (buffer->GetRichTextCtrl())
10128 {
10129 // Subtract borders
10130 sz = buffer->GetRichTextCtrl()->GetClientSize();
10131
10132 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
10133 marginRect = wxRect(0, 0, sz.x, sz.y);
10134 buffer->GetBoxRects(dc, buffer, buffer->GetAttributes(), marginRect, borderRect, contentRect, paddingRect, outlineRect);
10135
10136 sz = contentRect.GetSize();
10137
10138 // Start with a maximum width of the control size, even if not specified by the content,
10139 // to minimize the amount of picture overlapping the right-hand side
10140 maxWidth = sz.x;
10141 }
10142 else
10143 sz = buffer->GetCachedSize();
10144 parentWidth = sz.GetWidth();
10145 parentHeight = sz.GetHeight();
10146 }
10147
10148 if (GetAttributes().GetTextBoxAttr().GetWidth().IsValid() && GetAttributes().GetTextBoxAttr().GetWidth().GetValue() > 0)
10149 {
10150 if (parentWidth > 0 && GetAttributes().GetTextBoxAttr().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE)
10151 width = (int) ((GetAttributes().GetTextBoxAttr().GetWidth().GetValue() * parentWidth)/100.0);
10152 else if (GetAttributes().GetTextBoxAttr().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
10153 width = ConvertTenthsMMToPixels(dc, GetAttributes().GetTextBoxAttr().GetWidth().GetValue());
10154 else if (GetAttributes().GetTextBoxAttr().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_PIXELS)
10155 width = GetAttributes().GetTextBoxAttr().GetWidth().GetValue();
10156 }
10157
10158 // Limit to max width
10159
10160 if (GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().IsValid() && GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetValue() > 0)
10161 {
10162 int mw = -1;
10163
10164 if (parentWidth > 0 && GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE)
10165 mw = (int) ((GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetValue() * parentWidth)/100.0);
10166 else if (GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
10167 mw = ConvertTenthsMMToPixels(dc, GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetValue());
10168 else if (GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_PIXELS)
10169 mw = GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetValue();
10170
10171 // If we already have a smaller max width due to the constraints of the control size,
10172 // don't use the larger max width.
10173 if (mw != -1 && ((maxWidth == -1) || (mw < maxWidth)))
10174 maxWidth = mw;
10175 }
10176
10177 if (maxWidth > 0 && width > maxWidth)
10178 width = maxWidth;
10179
10180 // Preserve the aspect ratio
10181 if (width != m_originalImageSize.GetWidth())
10182 height = (int) (float(m_originalImageSize.GetHeight()) * (float(width)/float(m_originalImageSize.GetWidth())));
10183
10184 if (GetAttributes().GetTextBoxAttr().GetHeight().IsValid() && GetAttributes().GetTextBoxAttr().GetHeight().GetValue() > 0)
10185 {
10186 if (parentHeight > 0 && GetAttributes().GetTextBoxAttr().GetHeight().GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE)
10187 height = (int) ((GetAttributes().GetTextBoxAttr().GetHeight().GetValue() * parentHeight)/100.0);
10188 else if (GetAttributes().GetTextBoxAttr().GetHeight().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
10189 height = ConvertTenthsMMToPixels(dc, GetAttributes().GetTextBoxAttr().GetHeight().GetValue());
10190 else if (GetAttributes().GetTextBoxAttr().GetHeight().GetUnits() == wxTEXT_ATTR_UNITS_PIXELS)
10191 height = GetAttributes().GetTextBoxAttr().GetHeight().GetValue();
10192
10193 // Preserve the aspect ratio
10194 if (height != m_originalImageSize.GetHeight())
10195 width = (int) (float(m_originalImageSize.GetWidth()) * (float(height)/float(m_originalImageSize.GetHeight())));
10196 }
10197
10198 // Limit to max height
10199
10200 if (GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().IsValid() && GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetValue() > 0)
10201 {
10202 if (parentHeight > 0 && GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE)
10203 maxHeight = (int) ((GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetValue() * parentHeight)/100.0);
10204 else if (GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
10205 maxHeight = ConvertTenthsMMToPixels(dc, GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetValue());
10206 else if (GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetUnits() == wxTEXT_ATTR_UNITS_PIXELS)
10207 maxHeight = GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetValue();
10208 }
10209
10210 if (maxHeight > 0 && height > maxHeight)
10211 {
10212 height = maxHeight;
10213
10214 // Preserve the aspect ratio
10215 if (height != m_originalImageSize.GetHeight())
10216 width = (int) (float(m_originalImageSize.GetWidth()) * (float(height)/float(m_originalImageSize.GetHeight())));
10217 }
10218
10219 if (m_imageCache.IsOk() && m_imageCache.GetWidth() == width && m_imageCache.GetHeight() == height)
10220 {
10221 // Do nothing, we didn't need to change the image cache
10222 }
10223 else
10224 {
10225 if (!image.IsOk())
10226 {
10227 m_imageBlock.Load(image);
10228 if (!image.IsOk())
10229 return false;
10230 }
10231
10232 if (image.GetWidth() == width && image.GetHeight() == height)
10233 m_imageCache = wxBitmap(image);
10234 else
10235 {
10236 // If the original width and height is small, e.g. 400 or below,
10237 // scale up and then down to improve image quality. This can make
10238 // a big difference, with not much performance hit.
10239 int upscaleThreshold = 400;
10240 wxImage img;
10241 if (image.GetWidth() <= upscaleThreshold || image.GetHeight() <= upscaleThreshold)
10242 {
10243 img = image.Scale(image.GetWidth()*2, image.GetHeight()*2);
10244 img.Rescale(width, height, wxIMAGE_QUALITY_HIGH);
10245 }
10246 else
10247 img = image.Scale(width, height, wxIMAGE_QUALITY_HIGH);
10248 m_imageCache = wxBitmap(img);
10249 }
10250 }
10251
10252 return m_imageCache.IsOk();
10253 }
10254
10255 /// Draw the item
10256 bool wxRichTextImage::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int WXUNUSED(descent), int WXUNUSED(style))
10257 {
10258 if (!IsShown())
10259 return true;
10260
10261 // Don't need cached size AFAIK
10262 // wxSize size = GetCachedSize();
10263 if (!LoadImageCache(dc))
10264 return false;
10265
10266 wxRichTextAttr attr(GetAttributes());
10267 context.ApplyVirtualAttributes(attr, this);
10268
10269 DrawBoxAttributes(dc, GetBuffer(), attr, wxRect(rect.GetPosition(), GetCachedSize()));
10270
10271 #if 0
10272 int y = rect.y + (rect.height - m_imageCache.GetHeight());
10273
10274 dc.DrawBitmap(m_imageCache, rect.x, y, true);
10275 #endif
10276
10277 wxSize imageSize(m_imageCache.GetWidth(), m_imageCache.GetHeight());
10278 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
10279 marginRect = rect; // outer rectangle, will calculate contentRect
10280 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
10281
10282 dc.DrawBitmap(m_imageCache, contentRect.x, contentRect.y, true);
10283
10284 if (selection.WithinSelection(range.GetStart(), this))
10285 {
10286 wxCheckSetBrush(dc, *wxBLACK_BRUSH);
10287 wxCheckSetPen(dc, *wxBLACK_PEN);
10288 dc.SetLogicalFunction(wxINVERT);
10289 dc.DrawRectangle(contentRect);
10290 dc.SetLogicalFunction(wxCOPY);
10291 }
10292
10293 return true;
10294 }
10295
10296 /// Lay the item out
10297 bool wxRichTextImage::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& WXUNUSED(parentRect), int WXUNUSED(style))
10298 {
10299 if (!LoadImageCache(dc))
10300 return false;
10301
10302 wxSize imageSize(m_imageCache.GetWidth(), m_imageCache.GetHeight());
10303 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
10304 contentRect = wxRect(wxPoint(0,0), imageSize);
10305
10306 wxRichTextAttr attr(GetAttributes());
10307 context.ApplyVirtualAttributes(attr, this);
10308
10309 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
10310
10311 wxSize overallSize = marginRect.GetSize();
10312
10313 SetCachedSize(overallSize);
10314 SetMaxSize(overallSize);
10315 SetMinSize(overallSize);
10316 SetPosition(rect.GetPosition());
10317
10318 return true;
10319 }
10320
10321 /// Get/set the object size for the given range. Returns false if the range
10322 /// is invalid for this object.
10323 bool wxRichTextImage::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& WXUNUSED(descent), wxDC& dc, wxRichTextDrawingContext& context, int WXUNUSED(flags), wxPoint WXUNUSED(position), wxArrayInt* partialExtents) const
10324 {
10325 if (!range.IsWithin(GetRange()))
10326 return false;
10327
10328 if (!((wxRichTextImage*)this)->LoadImageCache(dc))
10329 {
10330 size.x = 0; size.y = 0;
10331 if (partialExtents)
10332 partialExtents->Add(0);
10333 return false;
10334 }
10335
10336 wxRichTextAttr attr(GetAttributes());
10337 context.ApplyVirtualAttributes(attr, (wxRichTextObject*) this);
10338
10339 wxSize imageSize(m_imageCache.GetWidth(), m_imageCache.GetHeight());
10340 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
10341 contentRect = wxRect(wxPoint(0,0), imageSize);
10342 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
10343
10344 wxSize overallSize = marginRect.GetSize();
10345
10346 if (partialExtents)
10347 partialExtents->Add(overallSize.x);
10348
10349 size = overallSize;
10350
10351 return true;
10352 }
10353
10354 // Get the 'natural' size for an object. For an image, it would be the
10355 // image size.
10356 wxTextAttrSize wxRichTextImage::GetNaturalSize() const
10357 {
10358 wxTextAttrSize size;
10359 if (GetImageCache().IsOk())
10360 {
10361 size.SetWidth(GetImageCache().GetWidth(), wxTEXT_ATTR_UNITS_PIXELS);
10362 size.SetHeight(GetImageCache().GetHeight(), wxTEXT_ATTR_UNITS_PIXELS);
10363 }
10364 return size;
10365 }
10366
10367
10368 /// Copy
10369 void wxRichTextImage::Copy(const wxRichTextImage& obj)
10370 {
10371 wxRichTextObject::Copy(obj);
10372
10373 m_imageBlock = obj.m_imageBlock;
10374 m_originalImageSize = obj.m_originalImageSize;
10375 }
10376
10377 /// Edit properties via a GUI
10378 bool wxRichTextImage::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer)
10379 {
10380 wxRichTextObjectPropertiesDialog imageDlg(this, wxGetTopLevelParent(parent), wxID_ANY, _("Picture Properties"));
10381 imageDlg.SetAttributes(GetAttributes());
10382
10383 if (imageDlg.ShowModal() == wxID_OK)
10384 {
10385 // By passing wxRICHTEXT_SETSTYLE_RESET, indeterminate attributes set by the user will be set as
10386 // indeterminate in the object.
10387 imageDlg.ApplyStyle(buffer->GetRichTextCtrl(), wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_RESET);
10388 return true;
10389 }
10390 else
10391 return false;
10392 }
10393
10394 /*!
10395 * Utilities
10396 *
10397 */
10398
10399 /// Compare two attribute objects
10400 bool wxTextAttrEq(const wxRichTextAttr& attr1, const wxRichTextAttr& attr2)
10401 {
10402 return (attr1 == attr2);
10403 }
10404
10405 // Partial equality test taking flags into account
10406 bool wxTextAttrEqPartial(const wxRichTextAttr& attr1, const wxRichTextAttr& attr2)
10407 {
10408 return attr1.EqPartial(attr2);
10409 }
10410
10411 /// Compare tabs
10412 bool wxRichTextTabsEq(const wxArrayInt& tabs1, const wxArrayInt& tabs2)
10413 {
10414 if (tabs1.GetCount() != tabs2.GetCount())
10415 return false;
10416
10417 size_t i;
10418 for (i = 0; i < tabs1.GetCount(); i++)
10419 {
10420 if (tabs1[i] != tabs2[i])
10421 return false;
10422 }
10423 return true;
10424 }
10425
10426 bool wxRichTextApplyStyle(wxRichTextAttr& destStyle, const wxRichTextAttr& style, wxRichTextAttr* compareWith)
10427 {
10428 return destStyle.Apply(style, compareWith);
10429 }
10430
10431 // Remove attributes
10432 bool wxRichTextRemoveStyle(wxRichTextAttr& destStyle, const wxRichTextAttr& style)
10433 {
10434 return destStyle.RemoveStyle(style);
10435 }
10436
10437 /// Combine two bitlists, specifying the bits of interest with separate flags.
10438 bool wxRichTextCombineBitlists(int& valueA, int valueB, int& flagsA, int flagsB)
10439 {
10440 return wxRichTextAttr::CombineBitlists(valueA, valueB, flagsA, flagsB);
10441 }
10442
10443 /// Compare two bitlists
10444 bool wxRichTextBitlistsEqPartial(int valueA, int valueB, int flags)
10445 {
10446 return wxRichTextAttr::BitlistsEqPartial(valueA, valueB, flags);
10447 }
10448
10449 /// Split into paragraph and character styles
10450 bool wxRichTextSplitParaCharStyles(const wxRichTextAttr& style, wxRichTextAttr& parStyle, wxRichTextAttr& charStyle)
10451 {
10452 return wxRichTextAttr::SplitParaCharStyles(style, parStyle, charStyle);
10453 }
10454
10455 /// Convert a decimal to Roman numerals
10456 wxString wxRichTextDecimalToRoman(long n)
10457 {
10458 static wxArrayInt decimalNumbers;
10459 static wxArrayString romanNumbers;
10460
10461 // Clean up arrays
10462 if (n == -1)
10463 {
10464 decimalNumbers.Clear();
10465 romanNumbers.Clear();
10466 return wxEmptyString;
10467 }
10468
10469 if (decimalNumbers.GetCount() == 0)
10470 {
10471 #define wxRichTextAddDecRom(n, r) decimalNumbers.Add(n); romanNumbers.Add(r);
10472
10473 wxRichTextAddDecRom(1000, wxT("M"));
10474 wxRichTextAddDecRom(900, wxT("CM"));
10475 wxRichTextAddDecRom(500, wxT("D"));
10476 wxRichTextAddDecRom(400, wxT("CD"));
10477 wxRichTextAddDecRom(100, wxT("C"));
10478 wxRichTextAddDecRom(90, wxT("XC"));
10479 wxRichTextAddDecRom(50, wxT("L"));
10480 wxRichTextAddDecRom(40, wxT("XL"));
10481 wxRichTextAddDecRom(10, wxT("X"));
10482 wxRichTextAddDecRom(9, wxT("IX"));
10483 wxRichTextAddDecRom(5, wxT("V"));
10484 wxRichTextAddDecRom(4, wxT("IV"));
10485 wxRichTextAddDecRom(1, wxT("I"));
10486 }
10487
10488 int i = 0;
10489 wxString roman;
10490
10491 while (n > 0 && i < 13)
10492 {
10493 if (n >= decimalNumbers[i])
10494 {
10495 n -= decimalNumbers[i];
10496 roman += romanNumbers[i];
10497 }
10498 else
10499 {
10500 i ++;
10501 }
10502 }
10503 if (roman.IsEmpty())
10504 roman = wxT("0");
10505 return roman;
10506 }
10507
10508 /*!
10509 * wxRichTextFileHandler
10510 * Base class for file handlers
10511 */
10512
10513 IMPLEMENT_CLASS(wxRichTextFileHandler, wxObject)
10514
10515 #if wxUSE_FFILE && wxUSE_STREAMS
10516 bool wxRichTextFileHandler::LoadFile(wxRichTextBuffer *buffer, const wxString& filename)
10517 {
10518 wxFFileInputStream stream(filename);
10519 if (stream.IsOk())
10520 return LoadFile(buffer, stream);
10521
10522 return false;
10523 }
10524
10525 bool wxRichTextFileHandler::SaveFile(wxRichTextBuffer *buffer, const wxString& filename)
10526 {
10527 wxFFileOutputStream stream(filename);
10528 if (stream.IsOk())
10529 return SaveFile(buffer, stream);
10530
10531 return false;
10532 }
10533 #endif // wxUSE_FFILE && wxUSE_STREAMS
10534
10535 /// Can we handle this filename (if using files)? By default, checks the extension.
10536 bool wxRichTextFileHandler::CanHandle(const wxString& filename) const
10537 {
10538 wxString path, file, ext;
10539 wxFileName::SplitPath(filename, & path, & file, & ext);
10540
10541 return (ext.Lower() == GetExtension());
10542 }
10543
10544 /*!
10545 * wxRichTextTextHandler
10546 * Plain text handler
10547 */
10548
10549 IMPLEMENT_CLASS(wxRichTextPlainTextHandler, wxRichTextFileHandler)
10550
10551 #if wxUSE_STREAMS
10552 bool wxRichTextPlainTextHandler::DoLoadFile(wxRichTextBuffer *buffer, wxInputStream& stream)
10553 {
10554 if (!stream.IsOk())
10555 return false;
10556
10557 wxString str;
10558 int lastCh = 0;
10559
10560 while (!stream.Eof())
10561 {
10562 int ch = stream.GetC();
10563
10564 if (!stream.Eof())
10565 {
10566 if (ch == 10 && lastCh != 13)
10567 str += wxT('\n');
10568
10569 if (ch > 0 && ch != 10)
10570 str += wxChar(ch);
10571
10572 lastCh = ch;
10573 }
10574 }
10575
10576 buffer->ResetAndClearCommands();
10577 buffer->Clear();
10578 buffer->AddParagraphs(str);
10579 buffer->UpdateRanges();
10580
10581 return true;
10582 }
10583
10584 bool wxRichTextPlainTextHandler::DoSaveFile(wxRichTextBuffer *buffer, wxOutputStream& stream)
10585 {
10586 if (!stream.IsOk())
10587 return false;
10588
10589 wxString text = buffer->GetText();
10590
10591 wxString newLine = wxRichTextLineBreakChar;
10592 text.Replace(newLine, wxT("\n"));
10593
10594 wxCharBuffer buf = text.ToAscii();
10595
10596 stream.Write((const char*) buf, text.length());
10597 return true;
10598 }
10599 #endif // wxUSE_STREAMS
10600
10601 /*
10602 * Stores information about an image, in binary in-memory form
10603 */
10604
10605 wxRichTextImageBlock::wxRichTextImageBlock()
10606 {
10607 Init();
10608 }
10609
10610 wxRichTextImageBlock::wxRichTextImageBlock(const wxRichTextImageBlock& block):wxObject()
10611 {
10612 Init();
10613 Copy(block);
10614 }
10615
10616 wxRichTextImageBlock::~wxRichTextImageBlock()
10617 {
10618 wxDELETEA(m_data);
10619 }
10620
10621 void wxRichTextImageBlock::Init()
10622 {
10623 m_data = NULL;
10624 m_dataSize = 0;
10625 m_imageType = wxBITMAP_TYPE_INVALID;
10626 }
10627
10628 void wxRichTextImageBlock::Clear()
10629 {
10630 wxDELETEA(m_data);
10631 m_dataSize = 0;
10632 m_imageType = wxBITMAP_TYPE_INVALID;
10633 }
10634
10635
10636 // Load the original image into a memory block.
10637 // If the image is not a JPEG, we must convert it into a JPEG
10638 // to conserve space.
10639 // If it's not a JPEG we can make use of 'image', already scaled, so we don't have to
10640 // load the image a 2nd time.
10641
10642 bool wxRichTextImageBlock::MakeImageBlock(const wxString& filename, wxBitmapType imageType,
10643 wxImage& image, bool convertToJPEG)
10644 {
10645 m_imageType = imageType;
10646
10647 wxString filenameToRead(filename);
10648 bool removeFile = false;
10649
10650 if (imageType == wxBITMAP_TYPE_INVALID)
10651 return false; // Could not determine image type
10652
10653 if ((imageType != wxBITMAP_TYPE_JPEG) && convertToJPEG)
10654 {
10655 wxString tempFile =
10656 wxFileName::CreateTempFileName(_("image"));
10657
10658 wxASSERT(!tempFile.IsEmpty());
10659
10660 image.SaveFile(tempFile, wxBITMAP_TYPE_JPEG);
10661 filenameToRead = tempFile;
10662 removeFile = true;
10663
10664 m_imageType = wxBITMAP_TYPE_JPEG;
10665 }
10666 wxFile file;
10667 if (!file.Open(filenameToRead))
10668 return false;
10669
10670 m_dataSize = (size_t) file.Length();
10671 file.Close();
10672
10673 if (m_data)
10674 delete[] m_data;
10675 m_data = ReadBlock(filenameToRead, m_dataSize);
10676
10677 if (removeFile)
10678 wxRemoveFile(filenameToRead);
10679
10680 return (m_data != NULL);
10681 }
10682
10683 // Make an image block from the wxImage in the given
10684 // format.
10685 bool wxRichTextImageBlock::MakeImageBlock(wxImage& image, wxBitmapType imageType, int quality)
10686 {
10687 image.SetOption(wxT("quality"), quality);
10688
10689 if (imageType == wxBITMAP_TYPE_INVALID)
10690 return false; // Could not determine image type
10691
10692 return DoMakeImageBlock(image, imageType);
10693 }
10694
10695 // Uses a const wxImage for efficiency, but can't set quality (only relevant for JPEG)
10696 bool wxRichTextImageBlock::MakeImageBlockDefaultQuality(const wxImage& image, wxBitmapType imageType)
10697 {
10698 if (imageType == wxBITMAP_TYPE_INVALID)
10699 return false; // Could not determine image type
10700
10701 return DoMakeImageBlock(image, imageType);
10702 }
10703
10704 // Makes the image block
10705 bool wxRichTextImageBlock::DoMakeImageBlock(const wxImage& image, wxBitmapType imageType)
10706 {
10707 wxMemoryOutputStream memStream;
10708 if (!image.SaveFile(memStream, imageType))
10709 {
10710 return false;
10711 }
10712
10713 unsigned char* block = new unsigned char[memStream.GetSize()];
10714 if (!block)
10715 return false;
10716
10717 if (m_data)
10718 delete[] m_data;
10719 m_data = block;
10720
10721 m_imageType = imageType;
10722 m_dataSize = memStream.GetSize();
10723
10724 memStream.CopyTo(m_data, m_dataSize);
10725
10726 return (m_data != NULL);
10727 }
10728
10729 // Write to a file
10730 bool wxRichTextImageBlock::Write(const wxString& filename)
10731 {
10732 return WriteBlock(filename, m_data, m_dataSize);
10733 }
10734
10735 void wxRichTextImageBlock::Copy(const wxRichTextImageBlock& block)
10736 {
10737 m_imageType = block.m_imageType;
10738 wxDELETEA(m_data);
10739 m_dataSize = block.m_dataSize;
10740 if (m_dataSize == 0)
10741 return;
10742
10743 m_data = new unsigned char[m_dataSize];
10744 unsigned int i;
10745 for (i = 0; i < m_dataSize; i++)
10746 m_data[i] = block.m_data[i];
10747 }
10748
10749 //// Operators
10750 void wxRichTextImageBlock::operator=(const wxRichTextImageBlock& block)
10751 {
10752 Copy(block);
10753 }
10754
10755 // Load a wxImage from the block
10756 bool wxRichTextImageBlock::Load(wxImage& image)
10757 {
10758 if (!m_data)
10759 return false;
10760
10761 // Read in the image.
10762 #if wxUSE_STREAMS
10763 wxMemoryInputStream mstream(m_data, m_dataSize);
10764 bool success = image.LoadFile(mstream, GetImageType());
10765 #else
10766 wxString tempFile = wxFileName::CreateTempFileName(_("image"));
10767 wxASSERT(!tempFile.IsEmpty());
10768
10769 if (!WriteBlock(tempFile, m_data, m_dataSize))
10770 {
10771 return false;
10772 }
10773 success = image.LoadFile(tempFile, GetImageType());
10774 wxRemoveFile(tempFile);
10775 #endif
10776
10777 return success;
10778 }
10779
10780 // Write data in hex to a stream
10781 bool wxRichTextImageBlock::WriteHex(wxOutputStream& stream)
10782 {
10783 if (m_dataSize == 0)
10784 return true;
10785
10786 int bufSize = 100000;
10787 if (int(2*m_dataSize) < bufSize)
10788 bufSize = 2*m_dataSize;
10789 char* buf = new char[bufSize+1];
10790
10791 int left = m_dataSize;
10792 int n, i, j;
10793 j = 0;
10794 while (left > 0)
10795 {
10796 if (left*2 > bufSize)
10797 {
10798 n = bufSize; left -= (bufSize/2);
10799 }
10800 else
10801 {
10802 n = left*2; left = 0;
10803 }
10804
10805 char* b = buf;
10806 for (i = 0; i < (n/2); i++)
10807 {
10808 wxDecToHex(m_data[j], b, b+1);
10809 b += 2; j ++;
10810 }
10811
10812 buf[n] = 0;
10813 stream.Write((const char*) buf, n);
10814 }
10815 delete[] buf;
10816 return true;
10817 }
10818
10819 // Read data in hex from a stream
10820 bool wxRichTextImageBlock::ReadHex(wxInputStream& stream, int length, wxBitmapType imageType)
10821 {
10822 int dataSize = length/2;
10823
10824 if (m_data)
10825 delete[] m_data;
10826
10827 // create a null terminated temporary string:
10828 char str[3];
10829 str[2] = '\0';
10830
10831 m_data = new unsigned char[dataSize];
10832 int i;
10833 for (i = 0; i < dataSize; i ++)
10834 {
10835 str[0] = (char)stream.GetC();
10836 str[1] = (char)stream.GetC();
10837
10838 m_data[i] = (unsigned char)wxHexToDec(str);
10839 }
10840
10841 m_dataSize = dataSize;
10842 m_imageType = imageType;
10843
10844 return true;
10845 }
10846
10847 // Allocate and read from stream as a block of memory
10848 unsigned char* wxRichTextImageBlock::ReadBlock(wxInputStream& stream, size_t size)
10849 {
10850 unsigned char* block = new unsigned char[size];
10851 if (!block)
10852 return NULL;
10853
10854 stream.Read(block, size);
10855
10856 return block;
10857 }
10858
10859 unsigned char* wxRichTextImageBlock::ReadBlock(const wxString& filename, size_t size)
10860 {
10861 wxFileInputStream stream(filename);
10862 if (!stream.IsOk())
10863 return NULL;
10864
10865 return ReadBlock(stream, size);
10866 }
10867
10868 // Write memory block to stream
10869 bool wxRichTextImageBlock::WriteBlock(wxOutputStream& stream, unsigned char* block, size_t size)
10870 {
10871 stream.Write((void*) block, size);
10872 return stream.IsOk();
10873
10874 }
10875
10876 // Write memory block to file
10877 bool wxRichTextImageBlock::WriteBlock(const wxString& filename, unsigned char* block, size_t size)
10878 {
10879 wxFileOutputStream outStream(filename);
10880 if (!outStream.IsOk())
10881 return false;
10882
10883 return WriteBlock(outStream, block, size);
10884 }
10885
10886 // Gets the extension for the block's type
10887 wxString wxRichTextImageBlock::GetExtension() const
10888 {
10889 wxImageHandler* handler = wxImage::FindHandler(GetImageType());
10890 if (handler)
10891 return handler->GetExtension();
10892 else
10893 return wxEmptyString;
10894 }
10895
10896 #if wxUSE_DATAOBJ
10897
10898 /*!
10899 * The data object for a wxRichTextBuffer
10900 */
10901
10902 const wxChar *wxRichTextBufferDataObject::ms_richTextBufferFormatId = wxT("wxShape");
10903
10904 wxRichTextBufferDataObject::wxRichTextBufferDataObject(wxRichTextBuffer* richTextBuffer)
10905 {
10906 m_richTextBuffer = richTextBuffer;
10907
10908 // this string should uniquely identify our format, but is otherwise
10909 // arbitrary
10910 m_formatRichTextBuffer.SetId(GetRichTextBufferFormatId());
10911
10912 SetFormat(m_formatRichTextBuffer);
10913 }
10914
10915 wxRichTextBufferDataObject::~wxRichTextBufferDataObject()
10916 {
10917 delete m_richTextBuffer;
10918 }
10919
10920 // after a call to this function, the richTextBuffer is owned by the caller and it
10921 // is responsible for deleting it!
10922 wxRichTextBuffer* wxRichTextBufferDataObject::GetRichTextBuffer()
10923 {
10924 wxRichTextBuffer* richTextBuffer = m_richTextBuffer;
10925 m_richTextBuffer = NULL;
10926
10927 return richTextBuffer;
10928 }
10929
10930 wxDataFormat wxRichTextBufferDataObject::GetPreferredFormat(Direction WXUNUSED(dir)) const
10931 {
10932 return m_formatRichTextBuffer;
10933 }
10934
10935 size_t wxRichTextBufferDataObject::GetDataSize() const
10936 {
10937 if (!m_richTextBuffer)
10938 return 0;
10939
10940 wxString bufXML;
10941
10942 {
10943 wxStringOutputStream stream(& bufXML);
10944 if (!m_richTextBuffer->SaveFile(stream, wxRICHTEXT_TYPE_XML))
10945 {
10946 wxLogError(wxT("Could not write the buffer to an XML stream.\nYou may have forgotten to add the XML file handler."));
10947 return 0;
10948 }
10949 }
10950
10951 #if wxUSE_UNICODE
10952 wxCharBuffer buffer = bufXML.mb_str(wxConvUTF8);
10953 return strlen(buffer) + 1;
10954 #else
10955 return bufXML.Length()+1;
10956 #endif
10957 }
10958
10959 bool wxRichTextBufferDataObject::GetDataHere(void *pBuf) const
10960 {
10961 if (!pBuf || !m_richTextBuffer)
10962 return false;
10963
10964 wxString bufXML;
10965
10966 {
10967 wxStringOutputStream stream(& bufXML);
10968 if (!m_richTextBuffer->SaveFile(stream, wxRICHTEXT_TYPE_XML))
10969 {
10970 wxLogError(wxT("Could not write the buffer to an XML stream.\nYou may have forgotten to add the XML file handler."));
10971 return 0;
10972 }
10973 }
10974
10975 #if wxUSE_UNICODE
10976 wxCharBuffer buffer = bufXML.mb_str(wxConvUTF8);
10977 size_t len = strlen(buffer);
10978 memcpy((char*) pBuf, (const char*) buffer, len);
10979 ((char*) pBuf)[len] = 0;
10980 #else
10981 size_t len = bufXML.Length();
10982 memcpy((char*) pBuf, (const char*) bufXML.c_str(), len);
10983 ((char*) pBuf)[len] = 0;
10984 #endif
10985
10986 return true;
10987 }
10988
10989 bool wxRichTextBufferDataObject::SetData(size_t WXUNUSED(len), const void *buf)
10990 {
10991 wxDELETE(m_richTextBuffer);
10992
10993 wxString bufXML((const char*) buf, wxConvUTF8);
10994
10995 m_richTextBuffer = new wxRichTextBuffer;
10996
10997 wxStringInputStream stream(bufXML);
10998 if (!m_richTextBuffer->LoadFile(stream, wxRICHTEXT_TYPE_XML))
10999 {
11000 wxLogError(wxT("Could not read the buffer from an XML stream.\nYou may have forgotten to add the XML file handler."));
11001
11002 wxDELETE(m_richTextBuffer);
11003
11004 return false;
11005 }
11006 return true;
11007 }
11008
11009 #endif
11010 // wxUSE_DATAOBJ
11011
11012
11013 /*
11014 * wxRichTextFontTable
11015 * Manages quick access to a pool of fonts for rendering rich text
11016 */
11017
11018 WX_DECLARE_STRING_HASH_MAP_WITH_DECL(wxFont, wxRichTextFontTableHashMap, class WXDLLIMPEXP_RICHTEXT);
11019
11020 class wxRichTextFontTableData: public wxObjectRefData
11021 {
11022 public:
11023 wxRichTextFontTableData() {}
11024
11025 wxFont FindFont(const wxRichTextAttr& fontSpec);
11026
11027 wxRichTextFontTableHashMap m_hashMap;
11028 };
11029
11030 wxFont wxRichTextFontTableData::FindFont(const wxRichTextAttr& fontSpec)
11031 {
11032 wxString facename(fontSpec.GetFontFaceName());
11033 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()));
11034 wxRichTextFontTableHashMap::iterator entry = m_hashMap.find(spec);
11035
11036 if ( entry == m_hashMap.end() )
11037 {
11038 wxFont font(fontSpec.GetFontSize(), wxDEFAULT, fontSpec.GetFontStyle(), fontSpec.GetFontWeight(), fontSpec.GetFontUnderlined(), facename.c_str());
11039 m_hashMap[spec] = font;
11040 return font;
11041 }
11042 else
11043 {
11044 return entry->second;
11045 }
11046 }
11047
11048 IMPLEMENT_DYNAMIC_CLASS(wxRichTextFontTable, wxObject)
11049
11050 wxRichTextFontTable::wxRichTextFontTable()
11051 {
11052 m_refData = new wxRichTextFontTableData;
11053 }
11054
11055 wxRichTextFontTable::wxRichTextFontTable(const wxRichTextFontTable& table)
11056 : wxObject()
11057 {
11058 (*this) = table;
11059 }
11060
11061 wxRichTextFontTable::~wxRichTextFontTable()
11062 {
11063 UnRef();
11064 }
11065
11066 bool wxRichTextFontTable::operator == (const wxRichTextFontTable& table) const
11067 {
11068 return (m_refData == table.m_refData);
11069 }
11070
11071 void wxRichTextFontTable::operator= (const wxRichTextFontTable& table)
11072 {
11073 Ref(table);
11074 }
11075
11076 wxFont wxRichTextFontTable::FindFont(const wxRichTextAttr& fontSpec)
11077 {
11078 wxRichTextFontTableData* data = (wxRichTextFontTableData*) m_refData;
11079 if (data)
11080 return data->FindFont(fontSpec);
11081 else
11082 return wxFont();
11083 }
11084
11085 void wxRichTextFontTable::Clear()
11086 {
11087 wxRichTextFontTableData* data = (wxRichTextFontTableData*) m_refData;
11088 if (data)
11089 data->m_hashMap.clear();
11090 }
11091
11092 // wxTextBoxAttr
11093
11094
11095 void wxTextBoxAttr::Reset()
11096 {
11097 m_flags = 0;
11098 m_floatMode = wxTEXT_BOX_ATTR_FLOAT_NONE;
11099 m_clearMode = wxTEXT_BOX_ATTR_CLEAR_NONE;
11100 m_collapseMode = wxTEXT_BOX_ATTR_COLLAPSE_NONE;
11101 m_verticalAlignment = wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT_NONE;
11102 m_boxStyleName = wxEmptyString;
11103
11104 m_margins.Reset();
11105 m_padding.Reset();
11106 m_position.Reset();
11107
11108 m_size.Reset();
11109 m_minSize.Reset();
11110 m_maxSize.Reset();
11111
11112 m_border.Reset();
11113 m_outline.Reset();
11114 }
11115
11116 // Equality test
11117 bool wxTextBoxAttr::operator== (const wxTextBoxAttr& attr) const
11118 {
11119 return (
11120 m_flags == attr.m_flags &&
11121 m_floatMode == attr.m_floatMode &&
11122 m_clearMode == attr.m_clearMode &&
11123 m_collapseMode == attr.m_collapseMode &&
11124 m_verticalAlignment == attr.m_verticalAlignment &&
11125
11126 m_margins == attr.m_margins &&
11127 m_padding == attr.m_padding &&
11128 m_position == attr.m_position &&
11129
11130 m_size == attr.m_size &&
11131 m_minSize == attr.m_minSize &&
11132 m_maxSize == attr.m_maxSize &&
11133
11134 m_border == attr.m_border &&
11135 m_outline == attr.m_outline &&
11136
11137 m_boxStyleName == attr.m_boxStyleName
11138 );
11139 }
11140
11141 // Partial equality test
11142 bool wxTextBoxAttr::EqPartial(const wxTextBoxAttr& attr) const
11143 {
11144 if (attr.HasFloatMode() && HasFloatMode() && (GetFloatMode() != attr.GetFloatMode()))
11145 return false;
11146
11147 if (attr.HasClearMode() && HasClearMode() && (GetClearMode() != attr.GetClearMode()))
11148 return false;
11149
11150 if (attr.HasCollapseBorders() && HasCollapseBorders() && (attr.GetCollapseBorders() != GetCollapseBorders()))
11151 return false;
11152
11153 if (attr.HasVerticalAlignment() && HasVerticalAlignment() && (attr.GetVerticalAlignment() != GetVerticalAlignment()))
11154 return false;
11155
11156 if (attr.HasBoxStyleName() && HasBoxStyleName() && (attr.GetBoxStyleName() != GetBoxStyleName()))
11157 return false;
11158
11159 // Position
11160
11161 if (!m_position.EqPartial(attr.m_position))
11162 return false;
11163
11164 // Size
11165
11166 if (!m_size.EqPartial(attr.m_size))
11167 return false;
11168 if (!m_minSize.EqPartial(attr.m_minSize))
11169 return false;
11170 if (!m_maxSize.EqPartial(attr.m_maxSize))
11171 return false;
11172
11173 // Margins
11174
11175 if (!m_margins.EqPartial(attr.m_margins))
11176 return false;
11177
11178 // Padding
11179
11180 if (!m_padding.EqPartial(attr.m_padding))
11181 return false;
11182
11183 // Border
11184
11185 if (!GetBorder().EqPartial(attr.GetBorder()))
11186 return false;
11187
11188 // Outline
11189
11190 if (!GetOutline().EqPartial(attr.GetOutline()))
11191 return false;
11192
11193 return true;
11194 }
11195
11196 // Merges the given attributes. If compareWith
11197 // is non-NULL, then it will be used to mask out those attributes that are the same in style
11198 // and compareWith, for situations where we don't want to explicitly set inherited attributes.
11199 bool wxTextBoxAttr::Apply(const wxTextBoxAttr& attr, const wxTextBoxAttr* compareWith)
11200 {
11201 if (attr.HasFloatMode())
11202 {
11203 if (!(compareWith && compareWith->HasFloatMode() && compareWith->GetFloatMode() == attr.GetFloatMode()))
11204 SetFloatMode(attr.GetFloatMode());
11205 }
11206
11207 if (attr.HasClearMode())
11208 {
11209 if (!(compareWith && compareWith->HasClearMode() && compareWith->GetClearMode() == attr.GetClearMode()))
11210 SetClearMode(attr.GetClearMode());
11211 }
11212
11213 if (attr.HasCollapseBorders())
11214 {
11215 if (!(compareWith && compareWith->HasCollapseBorders() && compareWith->GetCollapseBorders() == attr.GetCollapseBorders()))
11216 SetCollapseBorders(attr.GetCollapseBorders());
11217 }
11218
11219 if (attr.HasVerticalAlignment())
11220 {
11221 if (!(compareWith && compareWith->HasVerticalAlignment() && compareWith->GetVerticalAlignment() == attr.GetVerticalAlignment()))
11222 SetVerticalAlignment(attr.GetVerticalAlignment());
11223 }
11224
11225 if (attr.HasBoxStyleName())
11226 {
11227 if (!(compareWith && compareWith->HasBoxStyleName() && compareWith->GetBoxStyleName() == attr.GetBoxStyleName()))
11228 SetBoxStyleName(attr.GetBoxStyleName());
11229 }
11230
11231 m_margins.Apply(attr.m_margins, compareWith ? (& attr.m_margins) : (const wxTextAttrDimensions*) NULL);
11232 m_padding.Apply(attr.m_padding, compareWith ? (& attr.m_padding) : (const wxTextAttrDimensions*) NULL);
11233 m_position.Apply(attr.m_position, compareWith ? (& attr.m_position) : (const wxTextAttrDimensions*) NULL);
11234
11235 m_size.Apply(attr.m_size, compareWith ? (& attr.m_size) : (const wxTextAttrSize*) NULL);
11236 m_minSize.Apply(attr.m_minSize, compareWith ? (& attr.m_minSize) : (const wxTextAttrSize*) NULL);
11237 m_maxSize.Apply(attr.m_maxSize, compareWith ? (& attr.m_maxSize) : (const wxTextAttrSize*) NULL);
11238
11239 m_border.Apply(attr.m_border, compareWith ? (& attr.m_border) : (const wxTextAttrBorders*) NULL);
11240 m_outline.Apply(attr.m_outline, compareWith ? (& attr.m_outline) : (const wxTextAttrBorders*) NULL);
11241
11242 return true;
11243 }
11244
11245 // Remove specified attributes from this object
11246 bool wxTextBoxAttr::RemoveStyle(const wxTextBoxAttr& attr)
11247 {
11248 if (attr.HasFloatMode())
11249 RemoveFlag(wxTEXT_BOX_ATTR_FLOAT);
11250
11251 if (attr.HasClearMode())
11252 RemoveFlag(wxTEXT_BOX_ATTR_CLEAR);
11253
11254 if (attr.HasCollapseBorders())
11255 RemoveFlag(wxTEXT_BOX_ATTR_COLLAPSE_BORDERS);
11256
11257 if (attr.HasVerticalAlignment())
11258 RemoveFlag(wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT);
11259
11260 if (attr.HasBoxStyleName())
11261 {
11262 SetBoxStyleName(wxEmptyString);
11263 RemoveFlag(wxTEXT_BOX_ATTR_BOX_STYLE_NAME);
11264 }
11265
11266 m_margins.RemoveStyle(attr.m_margins);
11267 m_padding.RemoveStyle(attr.m_padding);
11268 m_position.RemoveStyle(attr.m_position);
11269
11270 m_size.RemoveStyle(attr.m_size);
11271 m_minSize.RemoveStyle(attr.m_minSize);
11272 m_maxSize.RemoveStyle(attr.m_maxSize);
11273
11274 m_border.RemoveStyle(attr.m_border);
11275 m_outline.RemoveStyle(attr.m_outline);
11276
11277 return true;
11278 }
11279
11280 // Collects the attributes that are common to a range of content, building up a note of
11281 // which attributes are absent in some objects and which clash in some objects.
11282 void wxTextBoxAttr::CollectCommonAttributes(const wxTextBoxAttr& attr, wxTextBoxAttr& clashingAttr, wxTextBoxAttr& absentAttr)
11283 {
11284 if (attr.HasFloatMode())
11285 {
11286 if (!clashingAttr.HasFloatMode() && !absentAttr.HasFloatMode())
11287 {
11288 if (HasFloatMode())
11289 {
11290 if (GetFloatMode() != attr.GetFloatMode())
11291 {
11292 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_FLOAT);
11293 RemoveFlag(wxTEXT_BOX_ATTR_FLOAT);
11294 }
11295 }
11296 else
11297 SetFloatMode(attr.GetFloatMode());
11298 }
11299 }
11300 else
11301 absentAttr.AddFlag(wxTEXT_BOX_ATTR_FLOAT);
11302
11303 if (attr.HasClearMode())
11304 {
11305 if (!clashingAttr.HasClearMode() && !absentAttr.HasClearMode())
11306 {
11307 if (HasClearMode())
11308 {
11309 if (GetClearMode() != attr.GetClearMode())
11310 {
11311 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_CLEAR);
11312 RemoveFlag(wxTEXT_BOX_ATTR_CLEAR);
11313 }
11314 }
11315 else
11316 SetClearMode(attr.GetClearMode());
11317 }
11318 }
11319 else
11320 absentAttr.AddFlag(wxTEXT_BOX_ATTR_CLEAR);
11321
11322 if (attr.HasCollapseBorders())
11323 {
11324 if (!clashingAttr.HasCollapseBorders() && !absentAttr.HasCollapseBorders())
11325 {
11326 if (HasCollapseBorders())
11327 {
11328 if (GetCollapseBorders() != attr.GetCollapseBorders())
11329 {
11330 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_COLLAPSE_BORDERS);
11331 RemoveFlag(wxTEXT_BOX_ATTR_COLLAPSE_BORDERS);
11332 }
11333 }
11334 else
11335 SetCollapseBorders(attr.GetCollapseBorders());
11336 }
11337 }
11338 else
11339 absentAttr.AddFlag(wxTEXT_BOX_ATTR_COLLAPSE_BORDERS);
11340
11341 if (attr.HasVerticalAlignment())
11342 {
11343 if (!clashingAttr.HasVerticalAlignment() && !absentAttr.HasVerticalAlignment())
11344 {
11345 if (HasVerticalAlignment())
11346 {
11347 if (GetVerticalAlignment() != attr.GetVerticalAlignment())
11348 {
11349 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT);
11350 RemoveFlag(wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT);
11351 }
11352 }
11353 else
11354 SetVerticalAlignment(attr.GetVerticalAlignment());
11355 }
11356 }
11357 else
11358 absentAttr.AddFlag(wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT);
11359
11360 if (attr.HasBoxStyleName())
11361 {
11362 if (!clashingAttr.HasBoxStyleName() && !absentAttr.HasBoxStyleName())
11363 {
11364 if (HasBoxStyleName())
11365 {
11366 if (GetBoxStyleName() != attr.GetBoxStyleName())
11367 {
11368 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_BOX_STYLE_NAME);
11369 RemoveFlag(wxTEXT_BOX_ATTR_BOX_STYLE_NAME);
11370 }
11371 }
11372 else
11373 SetBoxStyleName(attr.GetBoxStyleName());
11374 }
11375 }
11376 else
11377 absentAttr.AddFlag(wxTEXT_BOX_ATTR_BOX_STYLE_NAME);
11378
11379 m_margins.CollectCommonAttributes(attr.m_margins, clashingAttr.m_margins, absentAttr.m_margins);
11380 m_padding.CollectCommonAttributes(attr.m_padding, clashingAttr.m_padding, absentAttr.m_padding);
11381 m_position.CollectCommonAttributes(attr.m_position, clashingAttr.m_position, absentAttr.m_position);
11382
11383 m_size.CollectCommonAttributes(attr.m_size, clashingAttr.m_size, absentAttr.m_size);
11384 m_minSize.CollectCommonAttributes(attr.m_minSize, clashingAttr.m_minSize, absentAttr.m_minSize);
11385 m_maxSize.CollectCommonAttributes(attr.m_maxSize, clashingAttr.m_maxSize, absentAttr.m_maxSize);
11386
11387 m_border.CollectCommonAttributes(attr.m_border, clashingAttr.m_border, absentAttr.m_border);
11388 m_outline.CollectCommonAttributes(attr.m_outline, clashingAttr.m_outline, absentAttr.m_outline);
11389 }
11390
11391 bool wxTextBoxAttr::IsDefault() const
11392 {
11393 return GetFlags() == 0 && !m_border.IsValid() && !m_outline.IsValid() &&
11394 !m_size.IsValid() && !m_minSize.IsValid() && !m_maxSize.IsValid() &&
11395 !m_position.IsValid() && !m_padding.IsValid() && !m_margins.IsValid();
11396 }
11397
11398 // wxRichTextAttr
11399
11400 void wxRichTextAttr::Copy(const wxRichTextAttr& attr)
11401 {
11402 wxTextAttr::Copy(attr);
11403
11404 m_textBoxAttr = attr.m_textBoxAttr;
11405 }
11406
11407 bool wxRichTextAttr::operator==(const wxRichTextAttr& attr) const
11408 {
11409 if (!(wxTextAttr::operator==(attr)))
11410 return false;
11411
11412 return (m_textBoxAttr == attr.m_textBoxAttr);
11413 }
11414
11415 // Partial equality test taking comparison object into account
11416 bool wxRichTextAttr::EqPartial(const wxRichTextAttr& attr) const
11417 {
11418 if (!(wxTextAttr::EqPartial(attr)))
11419 return false;
11420
11421 return m_textBoxAttr.EqPartial(attr.m_textBoxAttr);
11422 }
11423
11424 // Merges the given attributes. If compareWith
11425 // is non-NULL, then it will be used to mask out those attributes that are the same in style
11426 // and compareWith, for situations where we don't want to explicitly set inherited attributes.
11427 bool wxRichTextAttr::Apply(const wxRichTextAttr& style, const wxRichTextAttr* compareWith)
11428 {
11429 wxTextAttr::Apply(style, compareWith);
11430
11431 return m_textBoxAttr.Apply(style.m_textBoxAttr, compareWith ? (& compareWith->m_textBoxAttr) : (const wxTextBoxAttr*) NULL);
11432 }
11433
11434 // Remove specified attributes from this object
11435 bool wxRichTextAttr::RemoveStyle(const wxRichTextAttr& attr)
11436 {
11437 wxTextAttr::RemoveStyle(*this, attr);
11438
11439 return m_textBoxAttr.RemoveStyle(attr.m_textBoxAttr);
11440 }
11441
11442 // Collects the attributes that are common to a range of content, building up a note of
11443 // which attributes are absent in some objects and which clash in some objects.
11444 void wxRichTextAttr::CollectCommonAttributes(const wxRichTextAttr& attr, wxRichTextAttr& clashingAttr, wxRichTextAttr& absentAttr)
11445 {
11446 wxTextAttrCollectCommonAttributes(*this, attr, clashingAttr, absentAttr);
11447
11448 m_textBoxAttr.CollectCommonAttributes(attr.m_textBoxAttr, clashingAttr.m_textBoxAttr, absentAttr.m_textBoxAttr);
11449 }
11450
11451 // Partial equality test
11452 bool wxTextAttrBorder::EqPartial(const wxTextAttrBorder& border) const
11453 {
11454 if (border.HasStyle() && !HasStyle() && (border.GetStyle() != GetStyle()))
11455 return false;
11456
11457 if (border.HasColour() && !HasColour() && (border.GetColourLong() != GetColourLong()))
11458 return false;
11459
11460 if (border.HasWidth() && !HasWidth() && !(border.GetWidth() == GetWidth()))
11461 return false;
11462
11463 return true;
11464 }
11465
11466 // Apply border to 'this', but not if the same as compareWith
11467 bool wxTextAttrBorder::Apply(const wxTextAttrBorder& border, const wxTextAttrBorder* compareWith)
11468 {
11469 if (border.HasStyle())
11470 {
11471 if (!(compareWith && (border.GetStyle() == compareWith->GetStyle())))
11472 SetStyle(border.GetStyle());
11473 }
11474 if (border.HasColour())
11475 {
11476 if (!(compareWith && (border.GetColourLong() == compareWith->GetColourLong())))
11477 SetColour(border.GetColourLong());
11478 }
11479 if (border.HasWidth())
11480 {
11481 if (!(compareWith && (border.GetWidth() == compareWith->GetWidth())))
11482 SetWidth(border.GetWidth());
11483 }
11484
11485 return true;
11486 }
11487
11488 // Remove specified attributes from this object
11489 bool wxTextAttrBorder::RemoveStyle(const wxTextAttrBorder& attr)
11490 {
11491 if (attr.HasStyle() && HasStyle())
11492 SetFlags(GetFlags() & ~wxTEXT_BOX_ATTR_BORDER_STYLE);
11493 if (attr.HasColour() && HasColour())
11494 SetFlags(GetFlags() & ~wxTEXT_BOX_ATTR_BORDER_COLOUR);
11495 if (attr.HasWidth() && HasWidth())
11496 m_borderWidth.Reset();
11497
11498 return true;
11499 }
11500
11501 // Collects the attributes that are common to a range of content, building up a note of
11502 // which attributes are absent in some objects and which clash in some objects.
11503 void wxTextAttrBorder::CollectCommonAttributes(const wxTextAttrBorder& attr, wxTextAttrBorder& clashingAttr, wxTextAttrBorder& absentAttr)
11504 {
11505 if (attr.HasStyle())
11506 {
11507 if (!clashingAttr.HasStyle() && !absentAttr.HasStyle())
11508 {
11509 if (HasStyle())
11510 {
11511 if (GetStyle() != attr.GetStyle())
11512 {
11513 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_BORDER_STYLE);
11514 RemoveFlag(wxTEXT_BOX_ATTR_BORDER_STYLE);
11515 }
11516 }
11517 else
11518 SetStyle(attr.GetStyle());
11519 }
11520 }
11521 else
11522 absentAttr.AddFlag(wxTEXT_BOX_ATTR_BORDER_STYLE);
11523
11524 if (attr.HasColour())
11525 {
11526 if (!clashingAttr.HasColour() && !absentAttr.HasColour())
11527 {
11528 if (HasColour())
11529 {
11530 if (GetColour() != attr.GetColour())
11531 {
11532 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_BORDER_COLOUR);
11533 RemoveFlag(wxTEXT_BOX_ATTR_BORDER_COLOUR);
11534 }
11535 }
11536 else
11537 SetColour(attr.GetColourLong());
11538 }
11539 }
11540 else
11541 absentAttr.AddFlag(wxTEXT_BOX_ATTR_BORDER_COLOUR);
11542
11543 m_borderWidth.CollectCommonAttributes(attr.m_borderWidth, clashingAttr.m_borderWidth, absentAttr.m_borderWidth);
11544 }
11545
11546 // Partial equality test
11547 bool wxTextAttrBorders::EqPartial(const wxTextAttrBorders& borders) const
11548 {
11549 return m_left.EqPartial(borders.m_left) && m_right.EqPartial(borders.m_right) &&
11550 m_top.EqPartial(borders.m_top) && m_bottom.EqPartial(borders.m_bottom);
11551 }
11552
11553 // Apply border to 'this', but not if the same as compareWith
11554 bool wxTextAttrBorders::Apply(const wxTextAttrBorders& borders, const wxTextAttrBorders* compareWith)
11555 {
11556 m_left.Apply(borders.m_left, compareWith ? (& compareWith->m_left) : (const wxTextAttrBorder*) NULL);
11557 m_right.Apply(borders.m_right, compareWith ? (& compareWith->m_right) : (const wxTextAttrBorder*) NULL);
11558 m_top.Apply(borders.m_top, compareWith ? (& compareWith->m_top) : (const wxTextAttrBorder*) NULL);
11559 m_bottom.Apply(borders.m_bottom, compareWith ? (& compareWith->m_bottom) : (const wxTextAttrBorder*) NULL);
11560 return true;
11561 }
11562
11563 // Remove specified attributes from this object
11564 bool wxTextAttrBorders::RemoveStyle(const wxTextAttrBorders& attr)
11565 {
11566 m_left.RemoveStyle(attr.m_left);
11567 m_right.RemoveStyle(attr.m_right);
11568 m_top.RemoveStyle(attr.m_top);
11569 m_bottom.RemoveStyle(attr.m_bottom);
11570 return true;
11571 }
11572
11573 // Collects the attributes that are common to a range of content, building up a note of
11574 // which attributes are absent in some objects and which clash in some objects.
11575 void wxTextAttrBorders::CollectCommonAttributes(const wxTextAttrBorders& attr, wxTextAttrBorders& clashingAttr, wxTextAttrBorders& absentAttr)
11576 {
11577 m_left.CollectCommonAttributes(attr.m_left, clashingAttr.m_left, absentAttr.m_left);
11578 m_right.CollectCommonAttributes(attr.m_right, clashingAttr.m_right, absentAttr.m_right);
11579 m_top.CollectCommonAttributes(attr.m_top, clashingAttr.m_top, absentAttr.m_top);
11580 m_bottom.CollectCommonAttributes(attr.m_bottom, clashingAttr.m_bottom, absentAttr.m_bottom);
11581 }
11582
11583 // Set style of all borders
11584 void wxTextAttrBorders::SetStyle(int style)
11585 {
11586 m_left.SetStyle(style);
11587 m_right.SetStyle(style);
11588 m_top.SetStyle(style);
11589 m_bottom.SetStyle(style);
11590 }
11591
11592 // Set colour of all borders
11593 void wxTextAttrBorders::SetColour(unsigned long colour)
11594 {
11595 m_left.SetColour(colour);
11596 m_right.SetColour(colour);
11597 m_top.SetColour(colour);
11598 m_bottom.SetColour(colour);
11599 }
11600
11601 void wxTextAttrBorders::SetColour(const wxColour& colour)
11602 {
11603 m_left.SetColour(colour);
11604 m_right.SetColour(colour);
11605 m_top.SetColour(colour);
11606 m_bottom.SetColour(colour);
11607 }
11608
11609 // Set width of all borders
11610 void wxTextAttrBorders::SetWidth(const wxTextAttrDimension& width)
11611 {
11612 m_left.SetWidth(width);
11613 m_right.SetWidth(width);
11614 m_top.SetWidth(width);
11615 m_bottom.SetWidth(width);
11616 }
11617
11618 // Partial equality test
11619 bool wxTextAttrDimension::EqPartial(const wxTextAttrDimension& dim) const
11620 {
11621 if (dim.IsValid() && IsValid() && !((*this) == dim))
11622 return false;
11623 else
11624 return true;
11625 }
11626
11627 bool wxTextAttrDimension::Apply(const wxTextAttrDimension& dim, const wxTextAttrDimension* compareWith)
11628 {
11629 if (dim.IsValid())
11630 {
11631 if (!(compareWith && dim == (*compareWith)))
11632 (*this) = dim;
11633 }
11634
11635 return true;
11636 }
11637
11638 // Collects the attributes that are common to a range of content, building up a note of
11639 // which attributes are absent in some objects and which clash in some objects.
11640 void wxTextAttrDimension::CollectCommonAttributes(const wxTextAttrDimension& attr, wxTextAttrDimension& clashingAttr, wxTextAttrDimension& absentAttr)
11641 {
11642 if (attr.IsValid())
11643 {
11644 if (!clashingAttr.IsValid() && !absentAttr.IsValid())
11645 {
11646 if (IsValid())
11647 {
11648 if (!((*this) == attr))
11649 {
11650 clashingAttr.SetValid(true);
11651 SetValid(false);
11652 }
11653 }
11654 else
11655 (*this) = attr;
11656 }
11657 }
11658 else
11659 absentAttr.SetValid(true);
11660 }
11661
11662 wxTextAttrDimensionConverter::wxTextAttrDimensionConverter(wxDC& dc, double scale, const wxSize& parentSize)
11663 {
11664 m_ppi = dc.GetPPI().x; m_scale = scale; m_parentSize = parentSize;
11665 }
11666
11667 wxTextAttrDimensionConverter::wxTextAttrDimensionConverter(int ppi, double scale, const wxSize& parentSize)
11668 {
11669 m_ppi = ppi; m_scale = scale; m_parentSize = parentSize;
11670 }
11671
11672 int wxTextAttrDimensionConverter::ConvertTenthsMMToPixels(int units) const
11673 {
11674 return wxRichTextObject::ConvertTenthsMMToPixels(m_ppi, units, m_scale);
11675 }
11676
11677 int wxTextAttrDimensionConverter::ConvertPixelsToTenthsMM(int pixels) const
11678 {
11679 return wxRichTextObject::ConvertPixelsToTenthsMM(m_ppi, pixels, m_scale);
11680 }
11681
11682 int wxTextAttrDimensionConverter::GetPixels(const wxTextAttrDimension& dim, int direction) const
11683 {
11684 if (dim.GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
11685 return ConvertTenthsMMToPixels(dim.GetValue());
11686 else if (dim.GetUnits() == wxTEXT_ATTR_UNITS_PIXELS)
11687 return dim.GetValue();
11688 else if (dim.GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE)
11689 {
11690 wxASSERT(m_parentSize != wxDefaultSize);
11691 if (direction == wxHORIZONTAL)
11692 return (int) (double(m_parentSize.x) * double(dim.GetValue()) / 100.0);
11693 else
11694 return (int) (double(m_parentSize.y) * double(dim.GetValue()) / 100.0);
11695 }
11696 else
11697 {
11698 wxASSERT(false);
11699 return 0;
11700 }
11701 }
11702
11703 int wxTextAttrDimensionConverter::GetTenthsMM(const wxTextAttrDimension& dim) const
11704 {
11705 if (dim.GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
11706 return dim.GetValue();
11707 else if (dim.GetUnits() == wxTEXT_ATTR_UNITS_PIXELS)
11708 return ConvertPixelsToTenthsMM(dim.GetValue());
11709 else
11710 {
11711 wxASSERT(false);
11712 return 0;
11713 }
11714 }
11715
11716 // Partial equality test
11717 bool wxTextAttrDimensions::EqPartial(const wxTextAttrDimensions& dims) const
11718 {
11719 if (!m_left.EqPartial(dims.m_left))
11720 return false;
11721
11722 if (!m_right.EqPartial(dims.m_right))
11723 return false;
11724
11725 if (!m_top.EqPartial(dims.m_top))
11726 return false;
11727
11728 if (!m_bottom.EqPartial(dims.m_bottom))
11729 return false;
11730
11731 return true;
11732 }
11733
11734 // Apply border to 'this', but not if the same as compareWith
11735 bool wxTextAttrDimensions::Apply(const wxTextAttrDimensions& dims, const wxTextAttrDimensions* compareWith)
11736 {
11737 m_left.Apply(dims.m_left, compareWith ? (& compareWith->m_left) : (const wxTextAttrDimension*) NULL);
11738 m_right.Apply(dims.m_right, compareWith ? (& compareWith->m_right): (const wxTextAttrDimension*) NULL);
11739 m_top.Apply(dims.m_top, compareWith ? (& compareWith->m_top): (const wxTextAttrDimension*) NULL);
11740 m_bottom.Apply(dims.m_bottom, compareWith ? (& compareWith->m_bottom): (const wxTextAttrDimension*) NULL);
11741
11742 return true;
11743 }
11744
11745 // Remove specified attributes from this object
11746 bool wxTextAttrDimensions::RemoveStyle(const wxTextAttrDimensions& attr)
11747 {
11748 if (attr.m_left.IsValid())
11749 m_left.Reset();
11750 if (attr.m_right.IsValid())
11751 m_right.Reset();
11752 if (attr.m_top.IsValid())
11753 m_top.Reset();
11754 if (attr.m_bottom.IsValid())
11755 m_bottom.Reset();
11756
11757 return true;
11758 }
11759
11760 // Collects the attributes that are common to a range of content, building up a note of
11761 // which attributes are absent in some objects and which clash in some objects.
11762 void wxTextAttrDimensions::CollectCommonAttributes(const wxTextAttrDimensions& attr, wxTextAttrDimensions& clashingAttr, wxTextAttrDimensions& absentAttr)
11763 {
11764 m_left.CollectCommonAttributes(attr.m_left, clashingAttr.m_left, absentAttr.m_left);
11765 m_right.CollectCommonAttributes(attr.m_right, clashingAttr.m_right, absentAttr.m_right);
11766 m_top.CollectCommonAttributes(attr.m_top, clashingAttr.m_top, absentAttr.m_top);
11767 m_bottom.CollectCommonAttributes(attr.m_bottom, clashingAttr.m_bottom, absentAttr.m_bottom);
11768 }
11769
11770 // Partial equality test
11771 bool wxTextAttrSize::EqPartial(const wxTextAttrSize& size) const
11772 {
11773 if (!m_width.EqPartial(size.m_width))
11774 return false;
11775
11776 if (!m_height.EqPartial(size.m_height))
11777 return false;
11778
11779 return true;
11780 }
11781
11782 // Apply border to 'this', but not if the same as compareWith
11783 bool wxTextAttrSize::Apply(const wxTextAttrSize& size, const wxTextAttrSize* compareWith)
11784 {
11785 m_width.Apply(size.m_width, compareWith ? (& compareWith->m_width) : (const wxTextAttrDimension*) NULL);
11786 m_height.Apply(size.m_height, compareWith ? (& compareWith->m_height): (const wxTextAttrDimension*) NULL);
11787
11788 return true;
11789 }
11790
11791 // Remove specified attributes from this object
11792 bool wxTextAttrSize::RemoveStyle(const wxTextAttrSize& attr)
11793 {
11794 if (attr.m_width.IsValid())
11795 m_width.Reset();
11796 if (attr.m_height.IsValid())
11797 m_height.Reset();
11798
11799 return true;
11800 }
11801
11802 // Collects the attributes that are common to a range of content, building up a note of
11803 // which attributes are absent in some objects and which clash in some objects.
11804 void wxTextAttrSize::CollectCommonAttributes(const wxTextAttrSize& attr, wxTextAttrSize& clashingAttr, wxTextAttrSize& absentAttr)
11805 {
11806 m_width.CollectCommonAttributes(attr.m_width, clashingAttr.m_width, absentAttr.m_width);
11807 m_height.CollectCommonAttributes(attr.m_height, clashingAttr.m_height, absentAttr.m_height);
11808 }
11809
11810 // Collects the attributes that are common to a range of content, building up a note of
11811 // which attributes are absent in some objects and which clash in some objects.
11812 void wxTextAttrCollectCommonAttributes(wxTextAttr& currentStyle, const wxTextAttr& attr, wxTextAttr& clashingAttr, wxTextAttr& absentAttr)
11813 {
11814 absentAttr.SetFlags(absentAttr.GetFlags() | (~attr.GetFlags() & wxTEXT_ATTR_ALL));
11815 absentAttr.SetTextEffectFlags(absentAttr.GetTextEffectFlags() | (~attr.GetTextEffectFlags() & 0xFFFF));
11816
11817 long forbiddenFlags = clashingAttr.GetFlags()|absentAttr.GetFlags();
11818
11819 if (attr.HasFont())
11820 {
11821 if (attr.HasFontSize() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_SIZE))
11822 {
11823 if (currentStyle.HasFontSize())
11824 {
11825 if (currentStyle.GetFontSize() != attr.GetFontSize())
11826 {
11827 // Clash of attr - mark as such
11828 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_SIZE);
11829 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_SIZE);
11830 }
11831 }
11832 else
11833 currentStyle.SetFontSize(attr.GetFontSize());
11834 }
11835
11836 if (attr.HasFontItalic() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_ITALIC))
11837 {
11838 if (currentStyle.HasFontItalic())
11839 {
11840 if (currentStyle.GetFontStyle() != attr.GetFontStyle())
11841 {
11842 // Clash of attr - mark as such
11843 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_ITALIC);
11844 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_ITALIC);
11845 }
11846 }
11847 else
11848 currentStyle.SetFontStyle(attr.GetFontStyle());
11849 }
11850
11851 if (attr.HasFontFamily() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_FAMILY))
11852 {
11853 if (currentStyle.HasFontFamily())
11854 {
11855 if (currentStyle.GetFontFamily() != attr.GetFontFamily())
11856 {
11857 // Clash of attr - mark as such
11858 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_FAMILY);
11859 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_FAMILY);
11860 }
11861 }
11862 else
11863 currentStyle.SetFontFamily(attr.GetFontFamily());
11864 }
11865
11866 if (attr.HasFontWeight() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_WEIGHT))
11867 {
11868 if (currentStyle.HasFontWeight())
11869 {
11870 if (currentStyle.GetFontWeight() != attr.GetFontWeight())
11871 {
11872 // Clash of attr - mark as such
11873 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_WEIGHT);
11874 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_WEIGHT);
11875 }
11876 }
11877 else
11878 currentStyle.SetFontWeight(attr.GetFontWeight());
11879 }
11880
11881 if (attr.HasFontFaceName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_FACE))
11882 {
11883 if (currentStyle.HasFontFaceName())
11884 {
11885 wxString faceName1(currentStyle.GetFontFaceName());
11886 wxString faceName2(attr.GetFontFaceName());
11887
11888 if (faceName1 != faceName2)
11889 {
11890 // Clash of attr - mark as such
11891 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_FACE);
11892 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_FACE);
11893 }
11894 }
11895 else
11896 currentStyle.SetFontFaceName(attr.GetFontFaceName());
11897 }
11898
11899 if (attr.HasFontUnderlined() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_UNDERLINE))
11900 {
11901 if (currentStyle.HasFontUnderlined())
11902 {
11903 if (currentStyle.GetFontUnderlined() != attr.GetFontUnderlined())
11904 {
11905 // Clash of attr - mark as such
11906 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_UNDERLINE);
11907 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_UNDERLINE);
11908 }
11909 }
11910 else
11911 currentStyle.SetFontUnderlined(attr.GetFontUnderlined());
11912 }
11913 }
11914
11915 if (attr.HasTextColour() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_TEXT_COLOUR))
11916 {
11917 if (currentStyle.HasTextColour())
11918 {
11919 if (currentStyle.GetTextColour() != attr.GetTextColour())
11920 {
11921 // Clash of attr - mark as such
11922 clashingAttr.AddFlag(wxTEXT_ATTR_TEXT_COLOUR);
11923 currentStyle.RemoveFlag(wxTEXT_ATTR_TEXT_COLOUR);
11924 }
11925 }
11926 else
11927 currentStyle.SetTextColour(attr.GetTextColour());
11928 }
11929
11930 if (attr.HasBackgroundColour() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BACKGROUND_COLOUR))
11931 {
11932 if (currentStyle.HasBackgroundColour())
11933 {
11934 if (currentStyle.GetBackgroundColour() != attr.GetBackgroundColour())
11935 {
11936 // Clash of attr - mark as such
11937 clashingAttr.AddFlag(wxTEXT_ATTR_BACKGROUND_COLOUR);
11938 currentStyle.RemoveFlag(wxTEXT_ATTR_BACKGROUND_COLOUR);
11939 }
11940 }
11941 else
11942 currentStyle.SetBackgroundColour(attr.GetBackgroundColour());
11943 }
11944
11945 if (attr.HasAlignment() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_ALIGNMENT))
11946 {
11947 if (currentStyle.HasAlignment())
11948 {
11949 if (currentStyle.GetAlignment() != attr.GetAlignment())
11950 {
11951 // Clash of attr - mark as such
11952 clashingAttr.AddFlag(wxTEXT_ATTR_ALIGNMENT);
11953 currentStyle.RemoveFlag(wxTEXT_ATTR_ALIGNMENT);
11954 }
11955 }
11956 else
11957 currentStyle.SetAlignment(attr.GetAlignment());
11958 }
11959
11960 if (attr.HasTabs() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_TABS))
11961 {
11962 if (currentStyle.HasTabs())
11963 {
11964 if (!wxRichTextTabsEq(currentStyle.GetTabs(), attr.GetTabs()))
11965 {
11966 // Clash of attr - mark as such
11967 clashingAttr.AddFlag(wxTEXT_ATTR_TABS);
11968 currentStyle.RemoveFlag(wxTEXT_ATTR_TABS);
11969 }
11970 }
11971 else
11972 currentStyle.SetTabs(attr.GetTabs());
11973 }
11974
11975 if (attr.HasLeftIndent() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_LEFT_INDENT))
11976 {
11977 if (currentStyle.HasLeftIndent())
11978 {
11979 if (currentStyle.GetLeftIndent() != attr.GetLeftIndent() || currentStyle.GetLeftSubIndent() != attr.GetLeftSubIndent())
11980 {
11981 // Clash of attr - mark as such
11982 clashingAttr.AddFlag(wxTEXT_ATTR_LEFT_INDENT);
11983 currentStyle.RemoveFlag(wxTEXT_ATTR_LEFT_INDENT);
11984 }
11985 }
11986 else
11987 currentStyle.SetLeftIndent(attr.GetLeftIndent(), attr.GetLeftSubIndent());
11988 }
11989
11990 if (attr.HasRightIndent() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_RIGHT_INDENT))
11991 {
11992 if (currentStyle.HasRightIndent())
11993 {
11994 if (currentStyle.GetRightIndent() != attr.GetRightIndent())
11995 {
11996 // Clash of attr - mark as such
11997 clashingAttr.AddFlag(wxTEXT_ATTR_RIGHT_INDENT);
11998 currentStyle.RemoveFlag(wxTEXT_ATTR_RIGHT_INDENT);
11999 }
12000 }
12001 else
12002 currentStyle.SetRightIndent(attr.GetRightIndent());
12003 }
12004
12005 if (attr.HasParagraphSpacingAfter() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_PARA_SPACING_AFTER))
12006 {
12007 if (currentStyle.HasParagraphSpacingAfter())
12008 {
12009 if (currentStyle.GetParagraphSpacingAfter() != attr.GetParagraphSpacingAfter())
12010 {
12011 // Clash of attr - mark as such
12012 clashingAttr.AddFlag(wxTEXT_ATTR_PARA_SPACING_AFTER);
12013 currentStyle.RemoveFlag(wxTEXT_ATTR_PARA_SPACING_AFTER);
12014 }
12015 }
12016 else
12017 currentStyle.SetParagraphSpacingAfter(attr.GetParagraphSpacingAfter());
12018 }
12019
12020 if (attr.HasParagraphSpacingBefore() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_PARA_SPACING_BEFORE))
12021 {
12022 if (currentStyle.HasParagraphSpacingBefore())
12023 {
12024 if (currentStyle.GetParagraphSpacingBefore() != attr.GetParagraphSpacingBefore())
12025 {
12026 // Clash of attr - mark as such
12027 clashingAttr.AddFlag(wxTEXT_ATTR_PARA_SPACING_BEFORE);
12028 currentStyle.RemoveFlag(wxTEXT_ATTR_PARA_SPACING_BEFORE);
12029 }
12030 }
12031 else
12032 currentStyle.SetParagraphSpacingBefore(attr.GetParagraphSpacingBefore());
12033 }
12034
12035 if (attr.HasLineSpacing() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_LINE_SPACING))
12036 {
12037 if (currentStyle.HasLineSpacing())
12038 {
12039 if (currentStyle.GetLineSpacing() != attr.GetLineSpacing())
12040 {
12041 // Clash of attr - mark as such
12042 clashingAttr.AddFlag(wxTEXT_ATTR_LINE_SPACING);
12043 currentStyle.RemoveFlag(wxTEXT_ATTR_LINE_SPACING);
12044 }
12045 }
12046 else
12047 currentStyle.SetLineSpacing(attr.GetLineSpacing());
12048 }
12049
12050 if (attr.HasCharacterStyleName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_CHARACTER_STYLE_NAME))
12051 {
12052 if (currentStyle.HasCharacterStyleName())
12053 {
12054 if (currentStyle.GetCharacterStyleName() != attr.GetCharacterStyleName())
12055 {
12056 // Clash of attr - mark as such
12057 clashingAttr.AddFlag(wxTEXT_ATTR_CHARACTER_STYLE_NAME);
12058 currentStyle.RemoveFlag(wxTEXT_ATTR_CHARACTER_STYLE_NAME);
12059 }
12060 }
12061 else
12062 currentStyle.SetCharacterStyleName(attr.GetCharacterStyleName());
12063 }
12064
12065 if (attr.HasParagraphStyleName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_PARAGRAPH_STYLE_NAME))
12066 {
12067 if (currentStyle.HasParagraphStyleName())
12068 {
12069 if (currentStyle.GetParagraphStyleName() != attr.GetParagraphStyleName())
12070 {
12071 // Clash of attr - mark as such
12072 clashingAttr.AddFlag(wxTEXT_ATTR_PARAGRAPH_STYLE_NAME);
12073 currentStyle.RemoveFlag(wxTEXT_ATTR_PARAGRAPH_STYLE_NAME);
12074 }
12075 }
12076 else
12077 currentStyle.SetParagraphStyleName(attr.GetParagraphStyleName());
12078 }
12079
12080 if (attr.HasListStyleName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_LIST_STYLE_NAME))
12081 {
12082 if (currentStyle.HasListStyleName())
12083 {
12084 if (currentStyle.GetListStyleName() != attr.GetListStyleName())
12085 {
12086 // Clash of attr - mark as such
12087 clashingAttr.AddFlag(wxTEXT_ATTR_LIST_STYLE_NAME);
12088 currentStyle.RemoveFlag(wxTEXT_ATTR_LIST_STYLE_NAME);
12089 }
12090 }
12091 else
12092 currentStyle.SetListStyleName(attr.GetListStyleName());
12093 }
12094
12095 if (attr.HasBulletStyle() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BULLET_STYLE))
12096 {
12097 if (currentStyle.HasBulletStyle())
12098 {
12099 if (currentStyle.GetBulletStyle() != attr.GetBulletStyle())
12100 {
12101 // Clash of attr - mark as such
12102 clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_STYLE);
12103 currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_STYLE);
12104 }
12105 }
12106 else
12107 currentStyle.SetBulletStyle(attr.GetBulletStyle());
12108 }
12109
12110 if (attr.HasBulletNumber() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BULLET_NUMBER))
12111 {
12112 if (currentStyle.HasBulletNumber())
12113 {
12114 if (currentStyle.GetBulletNumber() != attr.GetBulletNumber())
12115 {
12116 // Clash of attr - mark as such
12117 clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_NUMBER);
12118 currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_NUMBER);
12119 }
12120 }
12121 else
12122 currentStyle.SetBulletNumber(attr.GetBulletNumber());
12123 }
12124
12125 if (attr.HasBulletText() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BULLET_TEXT))
12126 {
12127 if (currentStyle.HasBulletText())
12128 {
12129 if (currentStyle.GetBulletText() != attr.GetBulletText())
12130 {
12131 // Clash of attr - mark as such
12132 clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_TEXT);
12133 currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_TEXT);
12134 }
12135 }
12136 else
12137 {
12138 currentStyle.SetBulletText(attr.GetBulletText());
12139 currentStyle.SetBulletFont(attr.GetBulletFont());
12140 }
12141 }
12142
12143 if (attr.HasBulletName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BULLET_NAME))
12144 {
12145 if (currentStyle.HasBulletName())
12146 {
12147 if (currentStyle.GetBulletName() != attr.GetBulletName())
12148 {
12149 // Clash of attr - mark as such
12150 clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_NAME);
12151 currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_NAME);
12152 }
12153 }
12154 else
12155 {
12156 currentStyle.SetBulletName(attr.GetBulletName());
12157 }
12158 }
12159
12160 if (attr.HasURL() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_URL))
12161 {
12162 if (currentStyle.HasURL())
12163 {
12164 if (currentStyle.GetURL() != attr.GetURL())
12165 {
12166 // Clash of attr - mark as such
12167 clashingAttr.AddFlag(wxTEXT_ATTR_URL);
12168 currentStyle.RemoveFlag(wxTEXT_ATTR_URL);
12169 }
12170 }
12171 else
12172 {
12173 currentStyle.SetURL(attr.GetURL());
12174 }
12175 }
12176
12177 if (attr.HasTextEffects() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_EFFECTS))
12178 {
12179 if (currentStyle.HasTextEffects())
12180 {
12181 // We need to find the bits in the new attr that are different:
12182 // just look at those bits that are specified by the new attr.
12183
12184 // We need to remove the bits and flags that are not common between current attr
12185 // and new attr. In so doing we need to take account of the styles absent from one or more of the
12186 // previous styles.
12187
12188 int currentRelevantTextEffects = currentStyle.GetTextEffects() & attr.GetTextEffectFlags();
12189 int newRelevantTextEffects = attr.GetTextEffects() & attr.GetTextEffectFlags();
12190
12191 if (currentRelevantTextEffects != newRelevantTextEffects)
12192 {
12193 // Find the text effects that were different, using XOR
12194 int differentEffects = currentRelevantTextEffects ^ newRelevantTextEffects;
12195
12196 // Clash of attr - mark as such
12197 clashingAttr.SetTextEffectFlags(clashingAttr.GetTextEffectFlags() | differentEffects);
12198 currentStyle.SetTextEffectFlags(currentStyle.GetTextEffectFlags() & ~differentEffects);
12199 }
12200 }
12201 else
12202 {
12203 currentStyle.SetTextEffects(attr.GetTextEffects());
12204 currentStyle.SetTextEffectFlags(attr.GetTextEffectFlags());
12205 }
12206
12207 // Mask out the flags and values that cannot be common because they were absent in one or more objecrs
12208 // that we've looked at so far
12209 currentStyle.SetTextEffects(currentStyle.GetTextEffects() & ~absentAttr.GetTextEffectFlags());
12210 currentStyle.SetTextEffectFlags(currentStyle.GetTextEffectFlags() & ~absentAttr.GetTextEffectFlags());
12211
12212 if (currentStyle.GetTextEffectFlags() == 0)
12213 currentStyle.RemoveFlag(wxTEXT_ATTR_EFFECTS);
12214 }
12215
12216 if (attr.HasOutlineLevel() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_OUTLINE_LEVEL))
12217 {
12218 if (currentStyle.HasOutlineLevel())
12219 {
12220 if (currentStyle.GetOutlineLevel() != attr.GetOutlineLevel())
12221 {
12222 // Clash of attr - mark as such
12223 clashingAttr.AddFlag(wxTEXT_ATTR_OUTLINE_LEVEL);
12224 currentStyle.RemoveFlag(wxTEXT_ATTR_OUTLINE_LEVEL);
12225 }
12226 }
12227 else
12228 currentStyle.SetOutlineLevel(attr.GetOutlineLevel());
12229 }
12230 }
12231
12232 WX_DEFINE_OBJARRAY(wxRichTextVariantArray);
12233
12234 IMPLEMENT_DYNAMIC_CLASS(wxRichTextProperties, wxObject)
12235
12236 bool wxRichTextProperties::operator==(const wxRichTextProperties& props) const
12237 {
12238 if (m_properties.GetCount() != props.GetCount())
12239 return false;
12240
12241 size_t i;
12242 for (i = 0; i < m_properties.GetCount(); i++)
12243 {
12244 const wxVariant& var1 = m_properties[i];
12245 int idx = props.Find(var1.GetName());
12246 if (idx == -1)
12247 return false;
12248 const wxVariant& var2 = props.m_properties[idx];
12249 if (!(var1 == var2))
12250 return false;
12251 }
12252
12253 return true;
12254 }
12255
12256 wxArrayString wxRichTextProperties::GetPropertyNames() const
12257 {
12258 wxArrayString arr;
12259 size_t i;
12260 for (i = 0; i < m_properties.GetCount(); i++)
12261 {
12262 arr.Add(m_properties[i].GetName());
12263 }
12264 return arr;
12265 }
12266
12267 int wxRichTextProperties::Find(const wxString& name) const
12268 {
12269 size_t i;
12270 for (i = 0; i < m_properties.GetCount(); i++)
12271 {
12272 if (m_properties[i].GetName() == name)
12273 return (int) i;
12274 }
12275 return -1;
12276 }
12277
12278 bool wxRichTextProperties::Remove(const wxString& name)
12279 {
12280 int idx = Find(name);
12281 if (idx != -1)
12282 {
12283 m_properties.RemoveAt(idx);
12284 return true;
12285 }
12286 else
12287 return false;
12288 }
12289
12290 wxVariant* wxRichTextProperties::FindOrCreateProperty(const wxString& name)
12291 {
12292 int idx = Find(name);
12293 if (idx == wxNOT_FOUND)
12294 SetProperty(name, wxString());
12295 idx = Find(name);
12296 if (idx != wxNOT_FOUND)
12297 {
12298 return & (*this)[idx];
12299 }
12300 else
12301 return NULL;
12302 }
12303
12304 const wxVariant& wxRichTextProperties::GetProperty(const wxString& name) const
12305 {
12306 static const wxVariant nullVariant;
12307 int idx = Find(name);
12308 if (idx != -1)
12309 return m_properties[idx];
12310 else
12311 return nullVariant;
12312 }
12313
12314 wxString wxRichTextProperties::GetPropertyString(const wxString& name) const
12315 {
12316 return GetProperty(name).GetString();
12317 }
12318
12319 long wxRichTextProperties::GetPropertyLong(const wxString& name) const
12320 {
12321 return GetProperty(name).GetLong();
12322 }
12323
12324 bool wxRichTextProperties::GetPropertyBool(const wxString& name) const
12325 {
12326 return GetProperty(name).GetBool();
12327 }
12328
12329 double wxRichTextProperties::GetPropertyDouble(const wxString& name) const
12330 {
12331 return GetProperty(name).GetDouble();
12332 }
12333
12334 void wxRichTextProperties::SetProperty(const wxVariant& variant)
12335 {
12336 wxASSERT(!variant.GetName().IsEmpty());
12337
12338 int idx = Find(variant.GetName());
12339
12340 if (idx == -1)
12341 m_properties.Add(variant);
12342 else
12343 m_properties[idx] = variant;
12344 }
12345
12346 void wxRichTextProperties::SetProperty(const wxString& name, const wxVariant& variant)
12347 {
12348 int idx = Find(name);
12349 wxVariant var(variant);
12350 var.SetName(name);
12351
12352 if (idx == -1)
12353 m_properties.Add(var);
12354 else
12355 m_properties[idx] = var;
12356 }
12357
12358 void wxRichTextProperties::SetProperty(const wxString& name, const wxString& value)
12359 {
12360 SetProperty(name, wxVariant(value, name));
12361 }
12362
12363 void wxRichTextProperties::SetProperty(const wxString& name, long value)
12364 {
12365 SetProperty(name, wxVariant(value, name));
12366 }
12367
12368 void wxRichTextProperties::SetProperty(const wxString& name, double value)
12369 {
12370 SetProperty(name, wxVariant(value, name));
12371 }
12372
12373 void wxRichTextProperties::SetProperty(const wxString& name, bool value)
12374 {
12375 SetProperty(name, wxVariant(value, name));
12376 }
12377
12378 void wxRichTextProperties::RemoveProperties(const wxRichTextProperties& properties)
12379 {
12380 size_t i;
12381 for (i = 0; i < properties.GetCount(); i++)
12382 {
12383 wxString name = properties.GetProperties()[i].GetName();
12384 if (HasProperty(name))
12385 Remove(name);
12386 }
12387 }
12388
12389 void wxRichTextProperties::MergeProperties(const wxRichTextProperties& properties)
12390 {
12391 size_t i;
12392 for (i = 0; i < properties.GetCount(); i++)
12393 {
12394 SetProperty(properties.GetProperties()[i]);
12395 }
12396 }
12397
12398 wxRichTextObject* wxRichTextObjectAddress::GetObject(wxRichTextParagraphLayoutBox* topLevelContainer) const
12399 {
12400 if (m_address.GetCount() == 0)
12401 return topLevelContainer;
12402
12403 wxRichTextCompositeObject* p = topLevelContainer;
12404 size_t i = 0;
12405 while (p && i < m_address.GetCount())
12406 {
12407 int pos = m_address[i];
12408 wxASSERT(pos >= 0 && pos < (int) p->GetChildren().GetCount());
12409 if (pos < 0 || pos >= (int) p->GetChildren().GetCount())
12410 return NULL;
12411
12412 wxRichTextObject* p1 = p->GetChild(pos);
12413 if (i == (m_address.GetCount()-1))
12414 return p1;
12415
12416 p = wxDynamicCast(p1, wxRichTextCompositeObject);
12417 i ++;
12418 }
12419 return NULL;
12420 }
12421
12422 bool wxRichTextObjectAddress::Create(wxRichTextParagraphLayoutBox* topLevelContainer, wxRichTextObject* obj)
12423 {
12424 m_address.Clear();
12425
12426 if (topLevelContainer == obj)
12427 return true;
12428
12429 wxRichTextObject* o = obj;
12430 while (o)
12431 {
12432 wxRichTextCompositeObject* p = wxDynamicCast(o->GetParent(), wxRichTextCompositeObject);
12433 if (!p)
12434 return false;
12435
12436 int pos = p->GetChildren().IndexOf(o);
12437 if (pos == -1)
12438 return false;
12439
12440 m_address.Insert(pos, 0);
12441
12442 if (p == topLevelContainer)
12443 return true;
12444
12445 o = p;
12446 }
12447 return false;
12448 }
12449
12450 // Equality test
12451 bool wxRichTextSelection::operator==(const wxRichTextSelection& sel) const
12452 {
12453 if (m_container != sel.m_container)
12454 return false;
12455 if (m_ranges.GetCount() != sel.m_ranges.GetCount())
12456 return false;
12457 size_t i;
12458 for (i = 0; i < m_ranges.GetCount(); i++)
12459 if (!(m_ranges[i] == sel.m_ranges[i]))
12460 return false;
12461 return true;
12462 }
12463
12464 // Get the selections appropriate to the specified object, if any; returns wxRICHTEXT_NO_SELECTION if none
12465 // or none at the level of the object's container.
12466 wxRichTextRangeArray wxRichTextSelection::GetSelectionForObject(wxRichTextObject* obj) const
12467 {
12468 if (IsValid())
12469 {
12470 wxRichTextParagraphLayoutBox* container = obj->GetParentContainer();
12471
12472 if (container == m_container)
12473 return m_ranges;
12474
12475 container = obj->GetContainer();
12476 while (container)
12477 {
12478 if (container->GetParent())
12479 {
12480 // If we found that our object's container is within the range of
12481 // a selection higher up, then assume the whole original object
12482 // is also selected.
12483 wxRichTextParagraphLayoutBox* parentContainer = container->GetParentContainer();
12484 if (parentContainer == m_container)
12485 {
12486 if (WithinSelection(container->GetRange().GetStart(), m_ranges))
12487 {
12488 wxRichTextRangeArray ranges;
12489 ranges.Add(obj->GetRange());
12490 return ranges;
12491 }
12492 }
12493
12494 container = parentContainer;
12495 }
12496 else
12497 {
12498 container = NULL;
12499 break;
12500 }
12501 }
12502 }
12503 return wxRichTextRangeArray();
12504 }
12505
12506 // Is the given position within the selection?
12507 bool wxRichTextSelection::WithinSelection(long pos, wxRichTextObject* obj) const
12508 {
12509 if (!IsValid())
12510 return false;
12511 else
12512 {
12513 wxRichTextRangeArray selectionRanges = GetSelectionForObject(obj);
12514 return WithinSelection(pos, selectionRanges);
12515 }
12516 }
12517
12518 // Is the given position within the selection range?
12519 bool wxRichTextSelection::WithinSelection(long pos, const wxRichTextRangeArray& ranges)
12520 {
12521 size_t i;
12522 for (i = 0; i < ranges.GetCount(); i++)
12523 {
12524 const wxRichTextRange& range = ranges[i];
12525 if (pos >= range.GetStart() && pos <= range.GetEnd())
12526 return true;
12527 }
12528 return false;
12529 }
12530
12531 // Is the given range completely within the selection range?
12532 bool wxRichTextSelection::WithinSelection(const wxRichTextRange& range, const wxRichTextRangeArray& ranges)
12533 {
12534 size_t i;
12535 for (i = 0; i < ranges.GetCount(); i++)
12536 {
12537 const wxRichTextRange& eachRange = ranges[i];
12538 if (range.IsWithin(eachRange))
12539 return true;
12540 }
12541 return false;
12542 }
12543
12544 IMPLEMENT_CLASS(wxRichTextDrawingHandler, wxObject)
12545 IMPLEMENT_CLASS(wxRichTextDrawingContext, wxObject)
12546
12547 bool wxRichTextDrawingContext::HasVirtualAttributes(wxRichTextObject* obj) const
12548 {
12549 wxList::compatibility_iterator node = m_buffer->GetDrawingHandlers().GetFirst();
12550 while (node)
12551 {
12552 wxRichTextDrawingHandler *handler = (wxRichTextDrawingHandler*)node->GetData();
12553 if (handler->HasVirtualAttributes(obj))
12554 return true;
12555
12556 node = node->GetNext();
12557 }
12558 return false;
12559 }
12560
12561 wxRichTextAttr wxRichTextDrawingContext::GetVirtualAttributes(wxRichTextObject* obj) const
12562 {
12563 wxRichTextAttr attr;
12564 // We apply all handlers, so we can may combine several different attributes
12565 wxList::compatibility_iterator node = m_buffer->GetDrawingHandlers().GetFirst();
12566 while (node)
12567 {
12568 wxRichTextDrawingHandler *handler = (wxRichTextDrawingHandler*)node->GetData();
12569 if (handler->HasVirtualAttributes(obj))
12570 {
12571 bool success = handler->GetVirtualAttributes(attr, obj);
12572 wxASSERT(success);
12573 wxUnusedVar(success);
12574 }
12575
12576 node = node->GetNext();
12577 }
12578 return attr;
12579 }
12580
12581 bool wxRichTextDrawingContext::ApplyVirtualAttributes(wxRichTextAttr& attr, wxRichTextObject* obj) const
12582 {
12583 if (HasVirtualAttributes(obj))
12584 {
12585 wxRichTextAttr a(GetVirtualAttributes(obj));
12586 attr.Apply(a);
12587 return true;
12588 }
12589 else
12590 return false;
12591 }
12592
12593 /// Adds a handler to the end
12594 void wxRichTextBuffer::AddDrawingHandler(wxRichTextDrawingHandler *handler)
12595 {
12596 sm_drawingHandlers.Append(handler);
12597 }
12598
12599 /// Inserts a handler at the front
12600 void wxRichTextBuffer::InsertDrawingHandler(wxRichTextDrawingHandler *handler)
12601 {
12602 sm_drawingHandlers.Insert( handler );
12603 }
12604
12605 /// Removes a handler
12606 bool wxRichTextBuffer::RemoveDrawingHandler(const wxString& name)
12607 {
12608 wxRichTextDrawingHandler *handler = FindDrawingHandler(name);
12609 if (handler)
12610 {
12611 sm_drawingHandlers.DeleteObject(handler);
12612 delete handler;
12613 return true;
12614 }
12615 else
12616 return false;
12617 }
12618
12619 wxRichTextDrawingHandler* wxRichTextBuffer::FindDrawingHandler(const wxString& name)
12620 {
12621 wxList::compatibility_iterator node = sm_drawingHandlers.GetFirst();
12622 while (node)
12623 {
12624 wxRichTextDrawingHandler *handler = (wxRichTextDrawingHandler*)node->GetData();
12625 if (handler->GetName().Lower() == name.Lower()) return handler;
12626
12627 node = node->GetNext();
12628 }
12629 return NULL;
12630 }
12631
12632 void wxRichTextBuffer::CleanUpDrawingHandlers()
12633 {
12634 wxList::compatibility_iterator node = sm_drawingHandlers.GetFirst();
12635 while (node)
12636 {
12637 wxRichTextDrawingHandler* handler = (wxRichTextDrawingHandler*)node->GetData();
12638 wxList::compatibility_iterator next = node->GetNext();
12639 delete handler;
12640 node = next;
12641 }
12642
12643 sm_drawingHandlers.Clear();
12644 }
12645
12646 #endif
12647 // wxUSE_RICHTEXT