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