Added wxRichTextXMLHandler::RegisterNodeName so custom content classes can be added...
[wxWidgets.git] / src / richtext / richtextbuffer.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/richtext/richtextbuffer.cpp
3 // Purpose: Buffer for wxRichTextCtrl
4 // Author: Julian Smart
5 // Modified by:
6 // Created: 2005-09-30
7 // RCS-ID: $Id$
8 // Copyright: (c) Julian Smart
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 // For compilers that support precompilation, includes "wx.h".
13 #include "wx/wxprec.h"
14
15 #ifdef __BORLANDC__
16 #pragma hdrstop
17 #endif
18
19 #if wxUSE_RICHTEXT
20
21 #include "wx/richtext/richtextbuffer.h"
22
23 #ifndef WX_PRECOMP
24 #include "wx/dc.h"
25 #include "wx/intl.h"
26 #include "wx/log.h"
27 #include "wx/dataobj.h"
28 #include "wx/module.h"
29 #endif
30
31 #include "wx/settings.h"
32 #include "wx/filename.h"
33 #include "wx/clipbrd.h"
34 #include "wx/wfstream.h"
35 #include "wx/mstream.h"
36 #include "wx/sstream.h"
37 #include "wx/textfile.h"
38 #include "wx/hashmap.h"
39 #include "wx/dynarray.h"
40
41 #include "wx/richtext/richtextctrl.h"
42 #include "wx/richtext/richtextstyles.h"
43 #include "wx/richtext/richtextimagedlg.h"
44 #include "wx/richtext/richtextsizepage.h"
45 #include "wx/richtext/richtextxml.h"
46
47 #include "wx/listimpl.cpp"
48 #include "wx/arrimpl.cpp"
49
50 WX_DEFINE_LIST(wxRichTextObjectList)
51 WX_DEFINE_LIST(wxRichTextLineList)
52
53 // Switch off if the platform doesn't like it for some reason
54 #define wxRICHTEXT_USE_OPTIMIZED_DRAWING 1
55
56 // Use GetPartialTextExtents for platforms that support it natively
57 #define wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS 1
58
59 const wxChar wxRichTextLineBreakChar = (wxChar) 29;
60
61 // Helper classes for floating layout
62 struct wxRichTextFloatRectMap
63 {
64 wxRichTextFloatRectMap(int sY, int eY, int w, wxRichTextObject* obj)
65 {
66 startY = sY;
67 endY = eY;
68 width = w;
69 anchor = obj;
70 }
71
72 int startY, endY;
73 int width;
74 wxRichTextObject* anchor;
75 };
76
77 WX_DEFINE_SORTED_ARRAY(wxRichTextFloatRectMap*, wxRichTextFloatRectMapArray);
78
79 int wxRichTextFloatRectMapCmp(wxRichTextFloatRectMap* r1, wxRichTextFloatRectMap* r2)
80 {
81 return r1->startY - r2->startY;
82 }
83
84 class wxRichTextFloatCollector
85 {
86 public:
87 wxRichTextFloatCollector(const wxRect& availableRect);
88 ~wxRichTextFloatCollector();
89
90 // Collect the floating objects info in the given paragraph
91 void CollectFloat(wxRichTextParagraph* para);
92 void CollectFloat(wxRichTextParagraph* para, wxRichTextObject* floating);
93
94 // Return the last paragraph we collected
95 wxRichTextParagraph* LastParagraph();
96
97 // Given the start y position and the height of the line,
98 // find out how wide the line can be
99 wxRect GetAvailableRect(int startY, int endY);
100
101 // Given a floating box, find its fit position
102 int GetFitPosition(int direction, int start, int height) const;
103 int GetFitPosition(const wxRichTextFloatRectMapArray& array, int start, int height) const;
104
105 // Find the last y position
106 int GetLastRectBottom();
107
108 // Draw the floats inside a rect
109 void Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style);
110
111 // HitTest the floats
112 int HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, int flags);
113
114 // Get floating object count
115 int GetFloatingObjectCount() const { return m_left.GetCount() + m_right.GetCount(); }
116
117 // Get floating objects
118 bool GetFloatingObjects(wxRichTextObjectList& objects) const;
119
120 // Delete a float
121 bool DeleteFloat(wxRichTextObject* obj);
122
123 // Do we have this float already?
124 bool HasFloat(wxRichTextObject* obj);
125
126 bool HasFloats() const { return m_left.GetCount() >0 || m_right.GetCount() > 0; }
127
128 static int SearchAdjacentRect(const wxRichTextFloatRectMapArray& array, int point);
129
130 static int GetWidthFromFloatRect(const wxRichTextFloatRectMapArray& array, int index, int startY, int endY);
131
132 static void FreeFloatRectMapArray(wxRichTextFloatRectMapArray& array);
133
134 static void DrawFloat(const wxRichTextFloatRectMapArray& array, wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style);
135
136 static int HitTestFloat(const wxRichTextFloatRectMapArray& array, wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, int flags);
137
138 private:
139 wxRichTextFloatRectMapArray m_left;
140 wxRichTextFloatRectMapArray m_right;
141 //int m_width;
142 wxRect m_availableRect;
143 wxRichTextParagraph* m_para;
144 };
145
146 // Delete a float
147 bool wxRichTextFloatCollector::DeleteFloat(wxRichTextObject* obj)
148 {
149 size_t i;
150 for (i = 0; i < m_left.GetCount(); i++)
151 {
152 if (m_left[i]->anchor == obj)
153 {
154 m_left.RemoveAt(i);
155 return true;
156 }
157 }
158 for (i = 0; i < m_right.GetCount(); i++)
159 {
160 if (m_right[i]->anchor == obj)
161 {
162 m_right.RemoveAt(i);
163 return true;
164 }
165 }
166 return false;
167 }
168
169 // Do we have this float already?
170 bool wxRichTextFloatCollector::HasFloat(wxRichTextObject* obj)
171 {
172 size_t i;
173 for (i = 0; i < m_left.GetCount(); i++)
174 {
175 if (m_left[i]->anchor == obj)
176 {
177 return true;
178 }
179 }
180 for (i = 0; i < m_right.GetCount(); i++)
181 {
182 if (m_right[i]->anchor == obj)
183 {
184 return true;
185 }
186 }
187 return false;
188 }
189
190 // Get floating objects
191 bool wxRichTextFloatCollector::GetFloatingObjects(wxRichTextObjectList& objects) const
192 {
193 size_t i;
194 for (i = 0; i < m_left.GetCount(); i++)
195 objects.Append(m_left[i]->anchor);
196 for (i = 0; i < m_right.GetCount(); i++)
197 objects.Append(m_right[i]->anchor);
198 return true;
199 }
200
201
202 /*
203 * Binary search helper function
204 * The argument point is the Y coordinate, and this fuction
205 * always return the floating rect that contain this coordinate
206 * or under this coordinate.
207 */
208 int wxRichTextFloatCollector::SearchAdjacentRect(const wxRichTextFloatRectMapArray& array, int point)
209 {
210 int end = array.GetCount() - 1;
211 int start = 0;
212 int ret = 0;
213
214 wxASSERT(end >= 0);
215
216 while (true)
217 {
218 if (start > end)
219 {
220 break;
221 }
222
223 int mid = (start + end) / 2;
224 if (array[mid]->startY <= point && array[mid]->endY >= point)
225 return mid;
226 else if (array[mid]->startY > point)
227 {
228 end = mid - 1;
229 ret = mid;
230 }
231 else if (array[mid]->endY < point)
232 {
233 start = mid + 1;
234 ret = start;
235 }
236 }
237
238 return ret;
239 }
240
241 int wxRichTextFloatCollector::GetWidthFromFloatRect(const wxRichTextFloatRectMapArray& array, int index, int startY, int endY)
242 {
243 int ret = 0;
244 int len = array.GetCount();
245
246 wxASSERT(index >= 0 && index < len);
247
248 if (array[index]->startY < startY && array[index]->endY > startY)
249 ret = ret < array[index]->width ? array[index]->width : ret;
250 while (index < len && array[index]->startY <= endY)
251 {
252 ret = ret < array[index]->width ? array[index]->width : ret;
253 index++;
254 }
255
256 return ret;
257 }
258
259 wxRichTextFloatCollector::wxRichTextFloatCollector(const wxRect& rect) : m_left(wxRichTextFloatRectMapCmp), m_right(wxRichTextFloatRectMapCmp)
260 {
261 m_availableRect = rect;
262 m_para = NULL;
263 }
264
265 void wxRichTextFloatCollector::FreeFloatRectMapArray(wxRichTextFloatRectMapArray& array)
266 {
267 int len = array.GetCount();
268 for (int i = 0; i < len; i++)
269 delete array[i];
270 }
271
272 wxRichTextFloatCollector::~wxRichTextFloatCollector()
273 {
274 FreeFloatRectMapArray(m_left);
275 FreeFloatRectMapArray(m_right);
276 }
277
278 int wxRichTextFloatCollector::GetFitPosition(const wxRichTextFloatRectMapArray& array, int start, int height) const
279 {
280 if (array.GetCount() == 0)
281 return start;
282
283 int i = SearchAdjacentRect(array, start);
284 int last = start;
285 while (i < (int) array.GetCount())
286 {
287 if (array[i]->startY - last >= height)
288 return last + 1;
289 last = array[i]->endY;
290 i++;
291 }
292
293 return last + 1;
294 }
295
296 int wxRichTextFloatCollector::GetFitPosition(int direction, int start, int height) const
297 {
298 if (direction == wxTEXT_BOX_ATTR_FLOAT_LEFT)
299 return GetFitPosition(m_left, start, height);
300 else if (direction == wxTEXT_BOX_ATTR_FLOAT_RIGHT)
301 return GetFitPosition(m_right, start, height);
302 else
303 {
304 wxASSERT("Never should be here");
305 return start;
306 }
307 }
308
309 // Adds a floating image to the float collector.
310 // The actual positioning is done by wxRichTextParagraph::LayoutFloat.
311 void wxRichTextFloatCollector::CollectFloat(wxRichTextParagraph* para, wxRichTextObject* floating)
312 {
313 int direction = floating->GetFloatDirection();
314
315 wxPoint pos = floating->GetPosition();
316 wxSize size = floating->GetCachedSize();
317 wxRichTextFloatRectMap *map = new wxRichTextFloatRectMap(pos.y, pos.y + size.y, size.x, floating);
318 switch (direction)
319 {
320 case wxTEXT_BOX_ATTR_FLOAT_NONE:
321 delete map;
322 break;
323 case wxTEXT_BOX_ATTR_FLOAT_LEFT:
324 // Just a not-enough simple assertion
325 wxASSERT (m_left.Index(map) == wxNOT_FOUND);
326 m_left.Add(map);
327 break;
328 case wxTEXT_BOX_ATTR_FLOAT_RIGHT:
329 wxASSERT (m_right.Index(map) == wxNOT_FOUND);
330 m_right.Add(map);
331 break;
332 default:
333 delete map;
334 wxASSERT("Unrecognised float attribute.");
335 }
336
337 m_para = para;
338 }
339
340 void wxRichTextFloatCollector::CollectFloat(wxRichTextParagraph* para)
341 {
342 wxRichTextObjectList::compatibility_iterator node = para->GetChildren().GetFirst();
343 while (node)
344 {
345 wxRichTextObject* floating = node->GetData();
346
347 if (floating->IsFloating())
348 {
349 CollectFloat(para, floating);
350 }
351
352 node = node->GetNext();
353 }
354
355 m_para = para;
356 }
357
358 wxRichTextParagraph* wxRichTextFloatCollector::LastParagraph()
359 {
360 return m_para;
361 }
362
363 wxRect wxRichTextFloatCollector::GetAvailableRect(int startY, int endY)
364 {
365 int widthLeft = 0, widthRight = 0;
366 if (m_left.GetCount() != 0)
367 {
368 int i = SearchAdjacentRect(m_left, startY);
369 if (i < (int) m_left.GetCount())
370 widthLeft = GetWidthFromFloatRect(m_left, i, startY, endY);
371 }
372 if (m_right.GetCount() != 0)
373 {
374 int j = SearchAdjacentRect(m_right, startY);
375 if (j < (int) m_right.GetCount())
376 widthRight = GetWidthFromFloatRect(m_right, j, startY, endY);
377 }
378
379 // TODO: actually we want to use the actual image positions to find the
380 // available remaining space, since the image might not be right up against
381 // the left or right edge of the container.
382 return wxRect(widthLeft + m_availableRect.x, 0, m_availableRect.width - widthLeft - widthRight, 0);
383 }
384
385 int wxRichTextFloatCollector::GetLastRectBottom()
386 {
387 int ret = 0;
388 int len = m_left.GetCount();
389 if (len) {
390 ret = ret > m_left[len-1]->endY ? ret : m_left[len-1]->endY;
391 }
392 len = m_right.GetCount();
393 if (len) {
394 ret = ret > m_right[len-1]->endY ? ret : m_right[len-1]->endY;
395 }
396
397 return ret;
398 }
399
400 void wxRichTextFloatCollector::DrawFloat(const wxRichTextFloatRectMapArray& array, wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& WXUNUSED(range), const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
401 {
402 int start = rect.y;
403 int end = rect.y + rect.height;
404 int i, j;
405 i = SearchAdjacentRect(array, start);
406 if (i < 0 || i >= (int) array.GetCount())
407 return;
408 j = SearchAdjacentRect(array, end);
409 if (j < 0 || j >= (int) array.GetCount())
410 j = array.GetCount() - 1;
411 while (i <= j)
412 {
413 wxRichTextObject* obj = array[i]->anchor;
414 wxRichTextRange r = obj->GetRange();
415 obj->Draw(dc, context, r, selection, wxRect(obj->GetPosition(), obj->GetCachedSize()), descent, style);
416 i++;
417 }
418 }
419
420 void wxRichTextFloatCollector::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
421 {
422 if (m_left.GetCount() > 0)
423 DrawFloat(m_left, dc, context, range, selection, rect, descent, style);
424 if (m_right.GetCount() > 0)
425 DrawFloat(m_right, dc, context, range, selection, rect, descent, style);
426 }
427
428 int wxRichTextFloatCollector::HitTestFloat(const wxRichTextFloatRectMapArray& array, wxDC& WXUNUSED(dc), wxRichTextDrawingContext& WXUNUSED(context), const wxPoint& pt, long& textPosition, wxRichTextObject** obj, int WXUNUSED(flags))
429 {
430 int i;
431 if (array.GetCount() == 0)
432 return wxRICHTEXT_HITTEST_NONE;
433 i = SearchAdjacentRect(array, pt.y);
434 if (i < 0 || i >= (int) array.GetCount())
435 return wxRICHTEXT_HITTEST_NONE;
436 if (!array[i]->anchor->IsShown())
437 return wxRICHTEXT_HITTEST_NONE;
438
439 wxPoint point = array[i]->anchor->GetPosition();
440 wxSize size = array[i]->anchor->GetCachedSize();
441 if (point.x <= pt.x && point.x + size.x >= pt.x
442 && point.y <= pt.y && point.y + size.y >= pt.y)
443 {
444 textPosition = array[i]->anchor->GetRange().GetStart();
445 * obj = array[i]->anchor;
446 if (pt.x > (pt.x + pt.x + size.x) / 2)
447 return wxRICHTEXT_HITTEST_BEFORE;
448 else
449 return wxRICHTEXT_HITTEST_AFTER;
450 }
451
452 return wxRICHTEXT_HITTEST_NONE;
453 }
454
455 int wxRichTextFloatCollector::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, int flags)
456 {
457 int ret = HitTestFloat(m_left, dc, context, pt, textPosition, obj, flags);
458 if (ret == wxRICHTEXT_HITTEST_NONE)
459 {
460 ret = HitTestFloat(m_right, dc, context, pt, textPosition, obj, flags);
461 }
462 return ret;
463 }
464
465 // Helpers for efficiency
466 inline void wxCheckSetFont(wxDC& dc, const wxFont& font)
467 {
468 // JACS: did I do this some time ago when testing? Should we re-enable it?
469 #if 0
470 const wxFont& font1 = dc.GetFont();
471 if (font1.IsOk() && font.IsOk())
472 {
473 if (font1.GetPointSize() == font.GetPointSize() &&
474 font1.GetFamily() == font.GetFamily() &&
475 font1.GetStyle() == font.GetStyle() &&
476 font1.GetWeight() == font.GetWeight() &&
477 font1.GetUnderlined() == font.GetUnderlined() &&
478 font1.GetFamily() == font.GetFamily() &&
479 font1.GetFaceName() == font.GetFaceName())
480 return;
481 }
482 #endif
483 dc.SetFont(font);
484 }
485
486 inline void wxCheckSetPen(wxDC& dc, const wxPen& pen)
487 {
488 const wxPen& pen1 = dc.GetPen();
489 if (pen1.IsOk() && pen.IsOk())
490 {
491 if (pen1.GetWidth() == pen.GetWidth() &&
492 pen1.GetStyle() == pen.GetStyle() &&
493 pen1.GetColour() == pen.GetColour())
494 return;
495 }
496 dc.SetPen(pen);
497 }
498
499 inline void wxCheckSetBrush(wxDC& dc, const wxBrush& brush)
500 {
501 const wxBrush& brush1 = dc.GetBrush();
502 if (brush1.IsOk() && brush.IsOk())
503 {
504 if (brush1.GetStyle() == brush.GetStyle() &&
505 brush1.GetColour() == brush.GetColour())
506 return;
507 }
508 dc.SetBrush(brush);
509 }
510
511 /*!
512 * wxRichTextObject
513 * This is the base for drawable objects.
514 */
515
516 IMPLEMENT_CLASS(wxRichTextObject, wxObject)
517
518 wxRichTextObject::wxRichTextObject(wxRichTextObject* parent)
519 {
520 m_refCount = 1;
521 m_parent = parent;
522 m_descent = 0;
523 m_show = true;
524 }
525
526 wxRichTextObject::~wxRichTextObject()
527 {
528 }
529
530 void wxRichTextObject::Dereference()
531 {
532 m_refCount --;
533 if (m_refCount <= 0)
534 delete this;
535 }
536
537 /// Copy
538 void wxRichTextObject::Copy(const wxRichTextObject& obj)
539 {
540 m_size = obj.m_size;
541 m_maxSize = obj.m_maxSize;
542 m_minSize = obj.m_minSize;
543 m_pos = obj.m_pos;
544 m_range = obj.m_range;
545 m_ownRange = obj.m_ownRange;
546 m_attributes = obj.m_attributes;
547 m_properties = obj.m_properties;
548 m_descent = obj.m_descent;
549 m_show = obj.m_show;
550 }
551
552 // Get/set the top-level container of this object.
553 wxRichTextParagraphLayoutBox* wxRichTextObject::GetContainer() const
554 {
555 const wxRichTextObject* p = this;
556 while (p)
557 {
558 if (p->IsTopLevel())
559 {
560 return wxDynamicCast(p, wxRichTextParagraphLayoutBox);
561 }
562 p = p->GetParent();
563 }
564 return NULL;
565 }
566
567 void wxRichTextObject::SetMargins(int margin)
568 {
569 SetMargins(margin, margin, margin, margin);
570 }
571
572 void wxRichTextObject::SetMargins(int leftMargin, int rightMargin, int topMargin, int bottomMargin)
573 {
574 GetAttributes().GetTextBoxAttr().GetMargins().GetLeft().SetValue(leftMargin, wxTEXT_ATTR_UNITS_PIXELS);
575 GetAttributes().GetTextBoxAttr().GetMargins().GetRight().SetValue(rightMargin, wxTEXT_ATTR_UNITS_PIXELS);
576 GetAttributes().GetTextBoxAttr().GetMargins().GetTop().SetValue(topMargin, wxTEXT_ATTR_UNITS_PIXELS);
577 GetAttributes().GetTextBoxAttr().GetMargins().GetBottom().SetValue(bottomMargin, wxTEXT_ATTR_UNITS_PIXELS);
578 }
579
580 int wxRichTextObject::GetLeftMargin() const
581 {
582 return GetAttributes().GetTextBoxAttr().GetMargins().GetLeft().GetValue();
583 }
584
585 int wxRichTextObject::GetRightMargin() const
586 {
587 return GetAttributes().GetTextBoxAttr().GetMargins().GetRight().GetValue();
588 }
589
590 int wxRichTextObject::GetTopMargin() const
591 {
592 return GetAttributes().GetTextBoxAttr().GetMargins().GetTop().GetValue();
593 }
594
595 int wxRichTextObject::GetBottomMargin() const
596 {
597 return GetAttributes().GetTextBoxAttr().GetMargins().GetBottom().GetValue();
598 }
599
600 // Calculate the available content space in the given rectangle, given the
601 // margins, border and padding specified in the object's attributes.
602 wxRect wxRichTextObject::GetAvailableContentArea(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& outerRect) const
603 {
604 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
605 marginRect = outerRect;
606 wxRichTextAttr attr(GetAttributes());
607 context.ApplyVirtualAttributes(attr, (wxRichTextObject*) this);
608 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
609 return contentRect;
610 }
611
612 // Invalidate the buffer. With no argument, invalidates whole buffer.
613 void wxRichTextObject::Invalidate(const wxRichTextRange& invalidRange)
614 {
615 if (invalidRange != wxRICHTEXT_NONE)
616 {
617 // If this is a floating object, size may not be recalculated
618 // after floats have been collected in an early stage of Layout.
619 // So avoid resetting the cache for floating objects during layout.
620 if (!IsFloating())
621 SetCachedSize(wxDefaultSize);
622 SetMaxSize(wxDefaultSize);
623 SetMinSize(wxDefaultSize);
624 }
625 }
626
627 // Convert units in tenths of a millimetre to device units
628 int wxRichTextObject::ConvertTenthsMMToPixels(wxDC& dc, int units) const
629 {
630 // Unscale
631 double scale = 1.0;
632 if (GetBuffer())
633 scale = GetBuffer()->GetScale();
634 int p = ConvertTenthsMMToPixels(dc.GetPPI().x, units, scale);
635
636 return p;
637 }
638
639 // Convert units in tenths of a millimetre to device units
640 int wxRichTextObject::ConvertTenthsMMToPixels(int ppi, int units, double scale)
641 {
642 // There are ppi pixels in 254.1 "1/10 mm"
643
644 double pixels = ((double) units * (double)ppi) / 254.1;
645 if (scale != 1.0)
646 pixels /= scale;
647
648 // If the result is very small, make it at least one pixel in size.
649 if (pixels == 0 && units > 0)
650 pixels = 1;
651
652 return (int) pixels;
653 }
654
655 // Convert units in pixels to tenths of a millimetre
656 int wxRichTextObject::ConvertPixelsToTenthsMM(wxDC& dc, int pixels) const
657 {
658 int p = pixels;
659 double scale = 1.0;
660 if (GetBuffer())
661 scale = GetBuffer()->GetScale();
662
663 return ConvertPixelsToTenthsMM(dc.GetPPI().x, p, scale);
664 }
665
666 int wxRichTextObject::ConvertPixelsToTenthsMM(int ppi, int pixels, double scale)
667 {
668 // There are ppi pixels in 254.1 "1/10 mm"
669
670 double p = double(pixels);
671
672 if (scale != 1.0)
673 p *= scale;
674
675 int units = int( p * 254.1 / (double) ppi );
676 return units;
677 }
678
679 // Draw the borders and background for the given rectangle and attributes.
680 // Width and height are taken to be the outer margin size, not the content.
681 bool wxRichTextObject::DrawBoxAttributes(wxDC& dc, wxRichTextBuffer* buffer, const wxRichTextAttr& attr, const wxRect& boxRect, int flags)
682 {
683 // Assume boxRect is the area around the content
684 wxRect marginRect = boxRect;
685 wxRect contentRect, borderRect, paddingRect, outlineRect;
686
687 GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
688
689 // Margin is transparent. Draw background from margin.
690 if (attr.HasBackgroundColour() || (flags & wxRICHTEXT_DRAW_SELECTED))
691 {
692 wxColour colour;
693 if (flags & wxRICHTEXT_DRAW_SELECTED)
694 {
695 // TODO: get selection colour from control?
696 colour = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT);
697 }
698 else
699 colour = attr.GetBackgroundColour();
700
701 wxPen pen(colour);
702 wxBrush brush(colour);
703
704 dc.SetPen(pen);
705 dc.SetBrush(brush);
706 dc.DrawRectangle(borderRect);
707 }
708
709 if (flags & wxRICHTEXT_DRAW_GUIDELINES)
710 {
711 wxRichTextAttr editBorderAttr = attr;
712 // TODO: make guideline colour configurable
713 editBorderAttr.GetTextBoxAttr().GetBorder().SetColour(*wxLIGHT_GREY);
714 editBorderAttr.GetTextBoxAttr().GetBorder().SetWidth(1, wxTEXT_ATTR_UNITS_PIXELS);
715 editBorderAttr.GetTextBoxAttr().GetBorder().SetStyle(wxTEXT_BOX_ATTR_BORDER_SOLID);
716
717 DrawBorder(dc, buffer, editBorderAttr.GetTextBoxAttr().GetBorder(), borderRect, flags);
718 }
719
720 if (attr.GetTextBoxAttr().GetBorder().IsValid())
721 DrawBorder(dc, buffer, attr.GetTextBoxAttr().GetBorder(), borderRect);
722
723 if (attr.GetTextBoxAttr().GetOutline().IsValid())
724 DrawBorder(dc, buffer, attr.GetTextBoxAttr().GetOutline(), outlineRect);
725
726 return true;
727 }
728
729 // Draw a border
730 bool wxRichTextObject::DrawBorder(wxDC& dc, wxRichTextBuffer* buffer, const wxTextAttrBorders& attr, const wxRect& rect, int WXUNUSED(flags))
731 {
732 int borderLeft = 0, borderRight = 0, borderTop = 0, borderBottom = 0;
733 wxTextAttrDimensionConverter converter(dc, buffer ? buffer->GetScale() : 1.0);
734
735 if (attr.GetLeft().IsValid() && attr.GetLeft().GetStyle() != wxTEXT_BOX_ATTR_BORDER_NONE)
736 {
737 borderLeft = converter.GetPixels(attr.GetLeft().GetWidth());
738 wxColour col(attr.GetLeft().GetColour());
739
740 // If pen width is > 1, resorts to a solid rectangle.
741 if (borderLeft == 1)
742 {
743 int penStyle = wxSOLID;
744 if (attr.GetLeft().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DOTTED)
745 penStyle = wxDOT;
746 else if (attr.GetLeft().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DASHED)
747 penStyle = wxLONG_DASH;
748 wxPen pen(col, 1, penStyle);
749 dc.SetPen(pen);
750 dc.DrawLine(rect.x, rect.y, rect.x, rect.y + rect.height);
751
752 }
753 else if (borderLeft > 1)
754 {
755 wxPen pen(col);
756 wxBrush brush(col);
757 dc.SetPen(pen);
758 dc.SetBrush(brush);
759 dc.DrawRectangle(rect.x, rect.y, borderLeft, rect.height);
760 }
761 }
762
763 if (attr.GetRight().IsValid() && attr.GetRight().GetStyle() != wxTEXT_BOX_ATTR_BORDER_NONE)
764 {
765 borderRight = converter.GetPixels(attr.GetRight().GetWidth());
766
767 wxColour col(attr.GetRight().GetColour());
768
769 // If pen width is > 1, resorts to a solid rectangle.
770 if (borderRight == 1)
771 {
772 int penStyle = wxSOLID;
773 if (attr.GetRight().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DOTTED)
774 penStyle = wxDOT;
775 else if (attr.GetRight().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DASHED)
776 penStyle = wxLONG_DASH;
777 wxPen pen(col, 1, penStyle);
778 dc.SetPen(pen);
779 dc.DrawLine(rect.x + rect.width, rect.y, rect.x + rect.width, rect.y + rect.height + 1);
780
781 }
782 else if (borderRight > 1)
783 {
784 wxPen pen(col);
785 wxBrush brush(col);
786 dc.SetPen(pen);
787 dc.SetBrush(brush);
788 dc.DrawRectangle(rect.x + rect.width - borderRight, rect.y, borderRight, rect.height);
789 }
790 }
791
792 if (attr.GetTop().IsValid() && attr.GetTop().GetStyle() != wxTEXT_BOX_ATTR_BORDER_NONE)
793 {
794 borderTop = converter.GetPixels(attr.GetTop().GetWidth());
795
796 wxColour col(attr.GetTop().GetColour());
797
798 // If pen width is > 1, resorts to a solid rectangle.
799 if (borderTop == 1)
800 {
801 int penStyle = wxSOLID;
802 if (attr.GetTop().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DOTTED)
803 penStyle = wxDOT;
804 else if (attr.GetTop().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DASHED)
805 penStyle = wxLONG_DASH;
806 wxPen pen(col, 1, penStyle);
807 dc.SetPen(pen);
808 dc.DrawLine(rect.x, rect.y, rect.x + rect.width, rect.y);
809
810 }
811 else if (borderTop > 1)
812 {
813 wxPen pen(col);
814 wxBrush brush(col);
815 dc.SetPen(pen);
816 dc.SetBrush(brush);
817 dc.DrawRectangle(rect.x, rect.y, rect.width, borderTop);
818 }
819 }
820
821 if (attr.GetBottom().IsValid() && attr.GetBottom().GetStyle() != wxTEXT_BOX_ATTR_BORDER_NONE)
822 {
823 borderBottom = converter.GetPixels(attr.GetBottom().GetWidth());
824 wxColour col(attr.GetTop().GetColour());
825
826 // If pen width is > 1, resorts to a solid rectangle.
827 if (borderBottom == 1)
828 {
829 int penStyle = wxSOLID;
830 if (attr.GetBottom().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DOTTED)
831 penStyle = wxDOT;
832 else if (attr.GetBottom().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DASHED)
833 penStyle = wxLONG_DASH;
834 wxPen pen(col, 1, penStyle);
835 dc.SetPen(pen);
836 dc.DrawLine(rect.x, rect.y + rect.height, rect.x + rect.width, rect.y + rect.height);
837
838 }
839 else if (borderBottom > 1)
840 {
841 wxPen pen(col);
842 wxBrush brush(col);
843 dc.SetPen(pen);
844 dc.SetBrush(brush);
845 dc.DrawRectangle(rect.x, rect.y + rect.height - borderBottom, rect.width, borderBottom);
846 }
847 }
848
849 return true;
850 }
851
852 // Get the various rectangles of the box model in pixels. You can either specify contentRect (inner)
853 // or marginRect (outer), and the other must be the default rectangle (no width or height).
854 // Note that the outline doesn't affect the position of the rectangle, it's drawn in whatever space
855 // is available.
856 //
857 // | Margin | Border | Padding | CONTENT | Padding | Border | Margin |
858
859 bool wxRichTextObject::GetBoxRects(wxDC& dc, wxRichTextBuffer* buffer, const wxRichTextAttr& attr, wxRect& marginRect, wxRect& borderRect, wxRect& contentRect, wxRect& paddingRect, wxRect& outlineRect)
860 {
861 int borderLeft = 0, borderRight = 0, borderTop = 0, borderBottom = 0;
862 int outlineLeft = 0, outlineRight = 0, outlineTop = 0, outlineBottom = 0;
863 int paddingLeft = 0, paddingRight = 0, paddingTop = 0, paddingBottom = 0;
864 int marginLeft = 0, marginRight = 0, marginTop = 0, marginBottom = 0;
865
866 wxTextAttrDimensionConverter converter(dc, buffer ? buffer->GetScale() : 1.0);
867
868 if (attr.GetTextBoxAttr().GetMargins().GetLeft().IsValid())
869 marginLeft = converter.GetPixels(attr.GetTextBoxAttr().GetMargins().GetLeft());
870 if (attr.GetTextBoxAttr().GetMargins().GetRight().IsValid())
871 marginRight = converter.GetPixels(attr.GetTextBoxAttr().GetMargins().GetRight());
872 if (attr.GetTextBoxAttr().GetMargins().GetTop().IsValid())
873 marginTop = converter.GetPixels(attr.GetTextBoxAttr().GetMargins().GetTop());
874 if (attr.GetTextBoxAttr().GetMargins().GetBottom().IsValid())
875 marginBottom = converter.GetPixels(attr.GetTextBoxAttr().GetMargins().GetBottom());
876
877 if (attr.GetTextBoxAttr().GetBorder().GetLeft().GetWidth().IsValid())
878 borderLeft = converter.GetPixels(attr.GetTextBoxAttr().GetBorder().GetLeft().GetWidth());
879 if (attr.GetTextBoxAttr().GetBorder().GetRight().GetWidth().IsValid())
880 borderRight = converter.GetPixels(attr.GetTextBoxAttr().GetBorder().GetRight().GetWidth());
881 if (attr.GetTextBoxAttr().GetBorder().GetTop().GetWidth().IsValid())
882 borderTop = converter.GetPixels(attr.GetTextBoxAttr().GetBorder().GetTop().GetWidth());
883 if (attr.GetTextBoxAttr().GetBorder().GetBottom().GetWidth().IsValid())
884 borderBottom = converter.GetPixels(attr.GetTextBoxAttr().GetBorder().GetBottom().GetWidth());
885
886 if (attr.GetTextBoxAttr().GetPadding().GetLeft().IsValid())
887 paddingLeft = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetLeft());
888 if (attr.GetTextBoxAttr().GetPadding().GetRight().IsValid())
889 paddingRight = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetRight());
890 if (attr.GetTextBoxAttr().GetPadding().GetTop().IsValid())
891 paddingTop = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetTop());
892 if (attr.GetTextBoxAttr().GetPadding().GetBottom().IsValid())
893 paddingBottom = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetBottom());
894
895 if (attr.GetTextBoxAttr().GetOutline().GetLeft().GetWidth().IsValid())
896 outlineLeft = converter.GetPixels(attr.GetTextBoxAttr().GetOutline().GetLeft().GetWidth());
897 if (attr.GetTextBoxAttr().GetOutline().GetRight().GetWidth().IsValid())
898 outlineRight = converter.GetPixels(attr.GetTextBoxAttr().GetOutline().GetRight().GetWidth());
899 if (attr.GetTextBoxAttr().GetOutline().GetTop().GetWidth().IsValid())
900 outlineTop = converter.GetPixels(attr.GetTextBoxAttr().GetOutline().GetTop().GetWidth());
901 if (attr.GetTextBoxAttr().GetOutline().GetBottom().GetWidth().IsValid())
902 outlineBottom = converter.GetPixels(attr.GetTextBoxAttr().GetOutline().GetBottom().GetWidth());
903
904 int leftTotal = marginLeft + borderLeft + paddingLeft;
905 int rightTotal = marginRight + borderRight + paddingRight;
906 int topTotal = marginTop + borderTop + paddingTop;
907 int bottomTotal = marginBottom + borderBottom + paddingBottom;
908
909 if (marginRect != wxRect())
910 {
911 contentRect.x = marginRect.x + leftTotal;
912 contentRect.y = marginRect.y + topTotal;
913 contentRect.width = marginRect.width - (leftTotal + rightTotal);
914 contentRect.height = marginRect.height - (topTotal + bottomTotal);
915 }
916 else
917 {
918 marginRect.x = contentRect.x - leftTotal;
919 marginRect.y = contentRect.y - topTotal;
920 marginRect.width = contentRect.width + (leftTotal + rightTotal);
921 marginRect.height = contentRect.height + (topTotal + bottomTotal);
922 }
923
924 borderRect.x = marginRect.x + marginLeft;
925 borderRect.y = marginRect.y + marginTop;
926 borderRect.width = marginRect.width - (marginLeft + marginRight);
927 borderRect.height = marginRect.height - (marginTop + marginBottom);
928
929 paddingRect.x = marginRect.x + marginLeft + borderLeft;
930 paddingRect.y = marginRect.y + marginTop + borderTop;
931 paddingRect.width = marginRect.width - (marginLeft + marginRight + borderLeft + borderRight);
932 paddingRect.height = marginRect.height - (marginTop + marginBottom + borderTop + borderBottom);
933
934 // The outline is outside the margin and doesn't influence the overall box position or content size.
935 outlineRect.x = marginRect.x - outlineLeft;
936 outlineRect.y = marginRect.y - outlineTop;
937 outlineRect.width = marginRect.width + (outlineLeft + outlineRight);
938 outlineRect.height = marginRect.height + (outlineTop + outlineBottom);
939
940 return true;
941 }
942
943 // Get the total margin for the object in pixels, taking into account margin, padding and border size
944 bool wxRichTextObject::GetTotalMargin(wxDC& dc, wxRichTextBuffer* buffer, const wxRichTextAttr& attr, int& leftMargin, int& rightMargin,
945 int& topMargin, int& bottomMargin)
946 {
947 // Assume boxRect is the area around the content
948 wxRect contentRect, marginRect, borderRect, paddingRect, outlineRect;
949 marginRect = wxRect(0, 0, 1000, 1000);
950
951 GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
952
953 leftMargin = contentRect.GetLeft() - marginRect.GetLeft();
954 rightMargin = marginRect.GetRight() - contentRect.GetRight();
955 topMargin = contentRect.GetTop() - marginRect.GetTop();
956 bottomMargin = marginRect.GetBottom() - contentRect.GetBottom();
957
958 return true;
959 }
960
961 // Returns the rectangle which the child has available to it given restrictions specified in the
962 // child attribute, e.g. 50% width of the parent, 400 pixels, x position 20% of the parent, etc.
963 // availableContainerSpace might be a parent that the cell has to compute its width relative to.
964 // E.g. a cell that's 50% of its parent.
965 wxRect wxRichTextObject::AdjustAvailableSpace(wxDC& dc, wxRichTextBuffer* buffer, const wxRichTextAttr& WXUNUSED(parentAttr), const wxRichTextAttr& childAttr, const wxRect& availableParentSpace, const wxRect& availableContainerSpace)
966 {
967 wxRect rect = availableParentSpace;
968 double scale = 1.0;
969 if (buffer)
970 scale = buffer->GetScale();
971
972 wxTextAttrDimensionConverter converter(dc, scale, availableContainerSpace.GetSize());
973
974 if (childAttr.GetTextBoxAttr().GetWidth().IsValid())
975 rect.width = converter.GetPixels(childAttr.GetTextBoxAttr().GetWidth());
976
977 if (childAttr.GetTextBoxAttr().GetHeight().IsValid())
978 rect.height = converter.GetPixels(childAttr.GetTextBoxAttr().GetHeight());
979
980 // Can specify either left or right for the position (we're assuming we can't
981 // set the left and right edges to effectively set the size. Would we want to do that?)
982 if (childAttr.GetTextBoxAttr().GetPosition().GetLeft().IsValid())
983 {
984 rect.x = rect.x + converter.GetPixels(childAttr.GetTextBoxAttr().GetPosition().GetLeft());
985 }
986 else if (childAttr.GetTextBoxAttr().GetPosition().GetRight().IsValid())
987 {
988 int x = converter.GetPixels(childAttr.GetTextBoxAttr().GetPosition().GetRight());
989 if (childAttr.GetTextBoxAttr().GetPosition().GetRight().GetPosition() == wxTEXT_BOX_ATTR_POSITION_RELATIVE)
990 rect.x = availableContainerSpace.x + availableContainerSpace.width - rect.width;
991 else
992 rect.x += x;
993 }
994
995 if (childAttr.GetTextBoxAttr().GetPosition().GetTop().IsValid())
996 {
997 rect.y = rect.y + converter.GetPixels(childAttr.GetTextBoxAttr().GetPosition().GetTop());
998 }
999 else if (childAttr.GetTextBoxAttr().GetPosition().GetBottom().IsValid())
1000 {
1001 int y = converter.GetPixels(childAttr.GetTextBoxAttr().GetPosition().GetBottom());
1002 if (childAttr.GetTextBoxAttr().GetPosition().GetBottom().GetPosition() == wxTEXT_BOX_ATTR_POSITION_RELATIVE)
1003 rect.y = availableContainerSpace.y + availableContainerSpace.height - rect.height;
1004 else
1005 rect.y += y;
1006 }
1007
1008 if (rect.GetWidth() > availableParentSpace.GetWidth())
1009 rect.SetWidth(availableParentSpace.GetWidth());
1010
1011 return rect;
1012 }
1013
1014 // Dump to output stream for debugging
1015 void wxRichTextObject::Dump(wxTextOutputStream& stream)
1016 {
1017 stream << GetClassInfo()->GetClassName() << wxT("\n");
1018 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");
1019 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");
1020 }
1021
1022 // Gets the containing buffer
1023 wxRichTextBuffer* wxRichTextObject::GetBuffer() const
1024 {
1025 const wxRichTextObject* obj = this;
1026 while (obj && !obj->IsKindOf(CLASSINFO(wxRichTextBuffer)))
1027 obj = obj->GetParent();
1028 return wxDynamicCast(obj, wxRichTextBuffer);
1029 }
1030
1031 // Get the absolute object position, by traversing up the child/parent hierarchy
1032 wxPoint wxRichTextObject::GetAbsolutePosition() const
1033 {
1034 wxPoint pt = GetPosition();
1035
1036 wxRichTextObject* p = GetParent();
1037 while (p)
1038 {
1039 pt = pt + p->GetPosition();
1040 p = p->GetParent();
1041 }
1042
1043 return pt;
1044 }
1045
1046 // Hit-testing: returns a flag indicating hit test details, plus
1047 // information about position
1048 int wxRichTextObject::HitTest(wxDC& WXUNUSED(dc), wxRichTextDrawingContext& WXUNUSED(context), const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int WXUNUSED(flags))
1049 {
1050 if (!IsShown())
1051 return wxRICHTEXT_HITTEST_NONE;
1052
1053 wxRect rect = GetRect();
1054 if (pt.x >= rect.x && pt.x < rect.x + rect.width &&
1055 pt.y >= rect.y && pt.y < rect.y + rect.height)
1056 {
1057 *obj = this;
1058 *contextObj = GetParentContainer();
1059 textPosition = GetRange().GetStart();
1060 return wxRICHTEXT_HITTEST_ON;
1061 }
1062 else
1063 return wxRICHTEXT_HITTEST_NONE;
1064 }
1065
1066 // Lays out the object first with a given amount of space, and then if no width was specified in attr,
1067 // lays out the object again using the maximum ('best') size
1068 bool wxRichTextObject::LayoutToBestSize(wxDC& dc, wxRichTextDrawingContext& context, wxRichTextBuffer* buffer,
1069 const wxRichTextAttr& parentAttr, const wxRichTextAttr& attr,
1070 const wxRect& availableParentSpace, const wxRect& availableContainerSpace,
1071 int style)
1072 {
1073 wxRect availableChildRect = AdjustAvailableSpace(dc, buffer, parentAttr, attr, availableParentSpace, availableContainerSpace);
1074 wxRect originalAvailableRect = availableChildRect;
1075 Layout(dc, context, availableChildRect, availableContainerSpace, style);
1076
1077 wxSize maxSize = GetMaxSize();
1078
1079 // Don't ignore if maxSize.x is zero, since we need to redo the paragraph's lines
1080 // on this basis
1081 if (!attr.GetTextBoxAttr().GetWidth().IsValid() && maxSize.x < availableChildRect.width)
1082 {
1083 // Redo the layout with a fixed, minimum size this time.
1084 Invalidate(wxRICHTEXT_ALL);
1085 wxRichTextAttr newAttr(attr);
1086 newAttr.GetTextBoxAttr().GetWidth().SetValue(maxSize.x, wxTEXT_ATTR_UNITS_PIXELS);
1087 newAttr.GetTextBoxAttr().GetWidth().SetPosition(wxTEXT_BOX_ATTR_POSITION_ABSOLUTE);
1088
1089 availableChildRect = AdjustAvailableSpace(dc, buffer, parentAttr, newAttr, availableParentSpace, availableContainerSpace);
1090
1091 // If a paragraph, align the whole paragraph.
1092 // Problem with this: if we're limited by a floating object, a line may be centered
1093 // w.r.t. the smaller resulting box rather than the actual available width.
1094 if (attr.HasAlignment() && !GetContainer()->GetFloatCollector()->HasFloats()) // FIXME: aligning whole paragraph not compatible with floating objects
1095 {
1096 // centering, right-justification
1097 if (attr.GetAlignment() == wxTEXT_ALIGNMENT_CENTRE)
1098 {
1099 availableChildRect.x = (originalAvailableRect.GetWidth() - availableChildRect.GetWidth())/2 + availableChildRect.x;
1100 }
1101 else if (attr.GetAlignment() == wxTEXT_ALIGNMENT_RIGHT)
1102 {
1103 availableChildRect.x = availableChildRect.x + originalAvailableRect.GetWidth() - availableChildRect.GetWidth();
1104 }
1105 }
1106
1107 Layout(dc, context, availableChildRect, availableContainerSpace, style);
1108 }
1109
1110 /*
1111 __________________
1112 | ____________ |
1113 | | | |
1114
1115
1116 */
1117
1118 return true;
1119 }
1120
1121 // Move the object recursively, by adding the offset from old to new
1122 void wxRichTextObject::Move(const wxPoint& pt)
1123 {
1124 SetPosition(pt);
1125 }
1126
1127
1128 /*!
1129 * wxRichTextCompositeObject
1130 * This is the base for drawable objects.
1131 */
1132
1133 IMPLEMENT_CLASS(wxRichTextCompositeObject, wxRichTextObject)
1134
1135 wxRichTextCompositeObject::wxRichTextCompositeObject(wxRichTextObject* parent):
1136 wxRichTextObject(parent)
1137 {
1138 }
1139
1140 wxRichTextCompositeObject::~wxRichTextCompositeObject()
1141 {
1142 DeleteChildren();
1143 }
1144
1145 /// Get the nth child
1146 wxRichTextObject* wxRichTextCompositeObject::GetChild(size_t n) const
1147 {
1148 wxASSERT ( n < m_children.GetCount() );
1149
1150 return m_children.Item(n)->GetData();
1151 }
1152
1153 /// Append a child, returning the position
1154 size_t wxRichTextCompositeObject::AppendChild(wxRichTextObject* child)
1155 {
1156 m_children.Append(child);
1157 child->SetParent(this);
1158 return m_children.GetCount() - 1;
1159 }
1160
1161 /// Insert the child in front of the given object, or at the beginning
1162 bool wxRichTextCompositeObject::InsertChild(wxRichTextObject* child, wxRichTextObject* inFrontOf)
1163 {
1164 if (inFrontOf)
1165 {
1166 wxRichTextObjectList::compatibility_iterator node = m_children.Find(inFrontOf);
1167 m_children.Insert(node, child);
1168 }
1169 else
1170 m_children.Insert(child);
1171 child->SetParent(this);
1172
1173 return true;
1174 }
1175
1176 /// Delete the child
1177 bool wxRichTextCompositeObject::RemoveChild(wxRichTextObject* child, bool deleteChild)
1178 {
1179 wxRichTextObjectList::compatibility_iterator node = m_children.Find(child);
1180 if (node)
1181 {
1182 wxRichTextObject* obj = node->GetData();
1183 m_children.Erase(node);
1184 if (deleteChild)
1185 delete obj;
1186
1187 return true;
1188 }
1189 return false;
1190 }
1191
1192 /// Delete all children
1193 bool wxRichTextCompositeObject::DeleteChildren()
1194 {
1195 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1196 while (node)
1197 {
1198 wxRichTextObjectList::compatibility_iterator oldNode = node;
1199
1200 wxRichTextObject* child = node->GetData();
1201 child->Dereference(); // Only delete if reference count is zero
1202
1203 node = node->GetNext();
1204 m_children.Erase(oldNode);
1205 }
1206
1207 return true;
1208 }
1209
1210 /// Get the child count
1211 size_t wxRichTextCompositeObject::GetChildCount() const
1212 {
1213 return m_children.GetCount();
1214 }
1215
1216 /// Copy
1217 void wxRichTextCompositeObject::Copy(const wxRichTextCompositeObject& obj)
1218 {
1219 wxRichTextObject::Copy(obj);
1220
1221 DeleteChildren();
1222
1223 wxRichTextObjectList::compatibility_iterator node = obj.m_children.GetFirst();
1224 while (node)
1225 {
1226 wxRichTextObject* child = node->GetData();
1227 wxRichTextObject* newChild = child->Clone();
1228 newChild->SetParent(this);
1229 m_children.Append(newChild);
1230
1231 node = node->GetNext();
1232 }
1233 }
1234
1235 /// Hit-testing: returns a flag indicating hit test details, plus
1236 /// information about position
1237 int wxRichTextCompositeObject::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int flags)
1238 {
1239 if (!IsShown())
1240 return wxRICHTEXT_HITTEST_NONE;
1241
1242 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1243 while (node)
1244 {
1245 wxRichTextObject* child = node->GetData();
1246
1247 if (child->IsShown() && child->IsTopLevel() && (flags & wxRICHTEXT_HITTEST_NO_NESTED_OBJECTS))
1248 {
1249 // Just check if we hit the overall object
1250 int ret = child->wxRichTextObject::HitTest(dc, context, pt, textPosition, obj, contextObj, flags);
1251 if (ret != wxRICHTEXT_HITTEST_NONE)
1252 return ret;
1253 }
1254 else if (child->IsShown())
1255 {
1256 int ret = child->HitTest(dc, context, pt, textPosition, obj, contextObj, flags);
1257 if (ret != wxRICHTEXT_HITTEST_NONE)
1258 return ret;
1259 }
1260
1261 node = node->GetNext();
1262 }
1263
1264 return wxRICHTEXT_HITTEST_NONE;
1265 }
1266
1267 /// Finds the absolute position and row height for the given character position
1268 bool wxRichTextCompositeObject::FindPosition(wxDC& dc, wxRichTextDrawingContext& context, long index, wxPoint& pt, int* height, bool forceLineStart)
1269 {
1270 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1271 while (node)
1272 {
1273 wxRichTextObject* child = node->GetData();
1274
1275 // Don't recurse if the child is a top-level object,
1276 // such as a text box, because the character position will no longer
1277 // apply. By definition, a top-level object has its own range of
1278 // character positions.
1279 if (!child->IsTopLevel() && child->FindPosition(dc, context, index, pt, height, forceLineStart))
1280 return true;
1281
1282 node = node->GetNext();
1283 }
1284
1285 return false;
1286 }
1287
1288 /// Calculate range
1289 void wxRichTextCompositeObject::CalculateRange(long start, long& end)
1290 {
1291 long current = start;
1292 long lastEnd = current;
1293
1294 if (IsTopLevel())
1295 {
1296 current = 0;
1297 lastEnd = 0;
1298 }
1299
1300 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1301 while (node)
1302 {
1303 wxRichTextObject* child = node->GetData();
1304 long childEnd = 0;
1305
1306 child->CalculateRange(current, childEnd);
1307 lastEnd = childEnd;
1308
1309 current = childEnd + 1;
1310
1311 node = node->GetNext();
1312 }
1313
1314 if (IsTopLevel())
1315 {
1316 // A top-level object always has a range of size 1,
1317 // because its children don't count at this level.
1318 end = start;
1319 m_range.SetRange(start, start);
1320
1321 // An object with no children has zero length
1322 if (m_children.GetCount() == 0)
1323 lastEnd --;
1324 m_ownRange.SetRange(0, lastEnd);
1325 }
1326 else
1327 {
1328 end = lastEnd;
1329
1330 // An object with no children has zero length
1331 if (m_children.GetCount() == 0)
1332 end --;
1333
1334 m_range.SetRange(start, end);
1335 }
1336 }
1337
1338 /// Delete range from layout.
1339 bool wxRichTextCompositeObject::DeleteRange(const wxRichTextRange& range)
1340 {
1341 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1342
1343 while (node)
1344 {
1345 wxRichTextObject* obj = (wxRichTextObject*) node->GetData();
1346 wxRichTextObjectList::compatibility_iterator next = node->GetNext();
1347
1348 // Delete the range in each paragraph
1349
1350 // When a chunk has been deleted, internally the content does not
1351 // now match the ranges.
1352 // However, so long as deletion is not done on the same object twice this is OK.
1353 // If you may delete content from the same object twice, recalculate
1354 // the ranges inbetween DeleteRange calls by calling CalculateRanges, and
1355 // adjust the range you're deleting accordingly.
1356
1357 if (!obj->GetRange().IsOutside(range))
1358 {
1359 // No need to delete within a top-level object; just removing this object will do fine
1360 if (!obj->IsTopLevel())
1361 obj->DeleteRange(range);
1362
1363 // Delete an empty object, or paragraph within this range.
1364 if (obj->IsEmpty() ||
1365 (range.GetStart() <= obj->GetRange().GetStart() && range.GetEnd() >= obj->GetRange().GetEnd()))
1366 {
1367 // An empty paragraph has length 1, so won't be deleted unless the
1368 // whole range is deleted.
1369 RemoveChild(obj, true);
1370 }
1371 }
1372
1373 node = next;
1374 }
1375
1376 return true;
1377 }
1378
1379 /// Get any text in this object for the given range
1380 wxString wxRichTextCompositeObject::GetTextForRange(const wxRichTextRange& range) const
1381 {
1382 wxString text;
1383 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1384 while (node)
1385 {
1386 wxRichTextObject* child = node->GetData();
1387 wxRichTextRange childRange = range;
1388 if (!child->GetRange().IsOutside(range))
1389 {
1390 childRange.LimitTo(child->GetRange());
1391
1392 wxString childText = child->GetTextForRange(childRange);
1393
1394 text += childText;
1395 }
1396 node = node->GetNext();
1397 }
1398
1399 return text;
1400 }
1401
1402 /// Get the child object at the given character position
1403 wxRichTextObject* wxRichTextCompositeObject::GetChildAtPosition(long pos) const
1404 {
1405 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1406 while (node)
1407 {
1408 wxRichTextObject* child = node->GetData();
1409 if (child->GetRange().GetStart() == pos)
1410 return child;
1411 node = node->GetNext();
1412 }
1413 return NULL;
1414 }
1415
1416 /// Recursively merge all pieces that can be merged.
1417 bool wxRichTextCompositeObject::Defragment(const wxRichTextRange& range)
1418 {
1419 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1420 while (node)
1421 {
1422 wxRichTextObject* child = node->GetData();
1423 if (range == wxRICHTEXT_ALL || !child->GetRange().IsOutside(range))
1424 {
1425 wxRichTextCompositeObject* composite = wxDynamicCast(child, wxRichTextCompositeObject);
1426 if (composite)
1427 composite->Defragment();
1428
1429 if (node->GetNext())
1430 {
1431 wxRichTextObject* nextChild = node->GetNext()->GetData();
1432 if (child->CanMerge(nextChild) && child->Merge(nextChild))
1433 {
1434 nextChild->Dereference();
1435 m_children.Erase(node->GetNext());
1436
1437 // Don't set node -- we'll see if we can merge again with the next
1438 // child.
1439 }
1440 else
1441 node = node->GetNext();
1442 }
1443 else
1444 node = node->GetNext();
1445 }
1446 else
1447 node = node->GetNext();
1448 }
1449
1450 // Delete any remaining empty objects, but leave at least one empty object per composite object.
1451 if (GetChildCount() > 1)
1452 {
1453 node = m_children.GetFirst();
1454 while (node)
1455 {
1456 wxRichTextObjectList::compatibility_iterator next = node->GetNext();
1457 wxRichTextObject* child = node->GetData();
1458 if (range == wxRICHTEXT_ALL || !child->GetRange().IsOutside(range))
1459 {
1460 if (child->IsEmpty())
1461 {
1462 child->Dereference();
1463 m_children.Erase(node);
1464 }
1465 node = next;
1466 }
1467 else
1468 node = node->GetNext();
1469 }
1470 }
1471
1472 return true;
1473 }
1474
1475 /// Dump to output stream for debugging
1476 void wxRichTextCompositeObject::Dump(wxTextOutputStream& stream)
1477 {
1478 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1479 while (node)
1480 {
1481 wxRichTextObject* child = node->GetData();
1482 child->Dump(stream);
1483 node = node->GetNext();
1484 }
1485 }
1486
1487 /// Get/set the object size for the given range. Returns false if the range
1488 /// is invalid for this object.
1489 bool wxRichTextCompositeObject::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, wxPoint position, wxArrayInt* partialExtents) const
1490 {
1491 if (!range.IsWithin(GetRange()))
1492 return false;
1493
1494 wxSize sz;
1495
1496 wxArrayInt childExtents;
1497 wxArrayInt* p;
1498 if (partialExtents)
1499 p = & childExtents;
1500 else
1501 p = NULL;
1502
1503 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1504 while (node)
1505 {
1506 wxRichTextObject* child = node->GetData();
1507 if (!child->GetRange().IsOutside(range))
1508 {
1509 // Floating objects have a zero size within the paragraph.
1510 if (child->IsFloating())
1511 {
1512 if (partialExtents)
1513 {
1514 int lastSize;
1515 if (partialExtents->GetCount() > 0)
1516 lastSize = (*partialExtents)[partialExtents->GetCount()-1];
1517 else
1518 lastSize = 0;
1519
1520 partialExtents->Add(0 /* zero size */ + lastSize);
1521 }
1522 }
1523 else
1524 {
1525 wxSize childSize;
1526
1527 wxRichTextRange rangeToUse = range;
1528 rangeToUse.LimitTo(child->GetRange());
1529 if (child->IsTopLevel())
1530 rangeToUse = child->GetOwnRange();
1531
1532 int childDescent = 0;
1533
1534 // At present wxRICHTEXT_HEIGHT_ONLY is only fast if we're already cached the size,
1535 // but it's only going to be used after caching has taken place.
1536 if ((flags & wxRICHTEXT_HEIGHT_ONLY) && child->GetCachedSize().y != 0)
1537 {
1538 childDescent = child->GetDescent();
1539 childSize = child->GetCachedSize();
1540
1541 sz.y = wxMax(sz.y, childSize.y);
1542 sz.x += childSize.x;
1543 descent = wxMax(descent, childDescent);
1544 }
1545 else if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, context, flags, wxPoint(position.x + sz.x, position.y), p))
1546 {
1547 sz.y = wxMax(sz.y, childSize.y);
1548 sz.x += childSize.x;
1549 descent = wxMax(descent, childDescent);
1550
1551 if ((flags & wxRICHTEXT_CACHE_SIZE) && (rangeToUse == child->GetRange() || child->IsTopLevel()))
1552 {
1553 child->SetCachedSize(childSize);
1554 child->SetDescent(childDescent);
1555 }
1556
1557 if (partialExtents)
1558 {
1559 int lastSize;
1560 if (partialExtents->GetCount() > 0)
1561 lastSize = (*partialExtents)[partialExtents->GetCount()-1];
1562 else
1563 lastSize = 0;
1564
1565 size_t i;
1566 for (i = 0; i < childExtents.GetCount(); i++)
1567 {
1568 partialExtents->Add(childExtents[i] + lastSize);
1569 }
1570 }
1571 }
1572 }
1573
1574 if (p)
1575 p->Clear();
1576 }
1577
1578 node = node->GetNext();
1579 }
1580 size = sz;
1581 return true;
1582 }
1583
1584 // Invalidate the buffer. With no argument, invalidates whole buffer.
1585 void wxRichTextCompositeObject::Invalidate(const wxRichTextRange& invalidRange)
1586 {
1587 wxRichTextObject::Invalidate(invalidRange);
1588
1589 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1590 while (node)
1591 {
1592 wxRichTextObject* child = node->GetData();
1593 if (invalidRange != wxRICHTEXT_ALL && invalidRange != wxRICHTEXT_NONE && child->GetRange().IsOutside(invalidRange))
1594 {
1595 // Skip
1596 }
1597 else if (child->IsTopLevel())
1598 {
1599 if (invalidRange == wxRICHTEXT_NONE)
1600 child->Invalidate(wxRICHTEXT_NONE);
1601 else
1602 child->Invalidate(wxRICHTEXT_ALL); // All children must be invalidated if within parent range
1603 }
1604 else
1605 child->Invalidate(invalidRange);
1606 node = node->GetNext();
1607 }
1608 }
1609
1610 // Move the object recursively, by adding the offset from old to new
1611 void wxRichTextCompositeObject::Move(const wxPoint& pt)
1612 {
1613 wxPoint oldPos = GetPosition();
1614 SetPosition(pt);
1615 wxPoint offset = pt - oldPos;
1616
1617 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1618 while (node)
1619 {
1620 wxRichTextObject* child = node->GetData();
1621 wxPoint childPos = child->GetPosition() + offset;
1622 child->Move(childPos);
1623 node = node->GetNext();
1624 }
1625 }
1626
1627
1628 /*!
1629 * wxRichTextParagraphLayoutBox
1630 * This box knows how to lay out paragraphs.
1631 */
1632
1633 IMPLEMENT_DYNAMIC_CLASS(wxRichTextParagraphLayoutBox, wxRichTextCompositeObject)
1634
1635 wxRichTextParagraphLayoutBox::wxRichTextParagraphLayoutBox(wxRichTextObject* parent):
1636 wxRichTextCompositeObject(parent)
1637 {
1638 Init();
1639 }
1640
1641 wxRichTextParagraphLayoutBox::~wxRichTextParagraphLayoutBox()
1642 {
1643 if (m_floatCollector)
1644 {
1645 delete m_floatCollector;
1646 m_floatCollector = NULL;
1647 }
1648 }
1649
1650 /// Initialize the object.
1651 void wxRichTextParagraphLayoutBox::Init()
1652 {
1653 m_ctrl = NULL;
1654
1655 // For now, assume is the only box and has no initial size.
1656 m_range = wxRichTextRange(0, -1);
1657 m_ownRange = wxRichTextRange(0, -1);
1658
1659 m_invalidRange = wxRICHTEXT_ALL;
1660
1661 m_partialParagraph = false;
1662 m_floatCollector = NULL;
1663 }
1664
1665 void wxRichTextParagraphLayoutBox::Clear()
1666 {
1667 DeleteChildren();
1668
1669 if (m_floatCollector)
1670 delete m_floatCollector;
1671 m_floatCollector = NULL;
1672 m_partialParagraph = false;
1673 }
1674
1675 /// Copy
1676 void wxRichTextParagraphLayoutBox::Copy(const wxRichTextParagraphLayoutBox& obj)
1677 {
1678 Clear();
1679
1680 wxRichTextCompositeObject::Copy(obj);
1681
1682 m_partialParagraph = obj.m_partialParagraph;
1683 m_defaultAttributes = obj.m_defaultAttributes;
1684 }
1685
1686 // Gather information about floating objects; only gather floats for those paragraphs that
1687 // will not be formatted again due to optimization, after which floats will be gathered per-paragraph
1688 // during layout.
1689 bool wxRichTextParagraphLayoutBox::UpdateFloatingObjects(const wxRect& availableRect, wxRichTextObject* untilObj)
1690 {
1691 if (m_floatCollector != NULL)
1692 delete m_floatCollector;
1693 m_floatCollector = new wxRichTextFloatCollector(availableRect);
1694 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1695 // Only gather floats up to the point we'll start formatting paragraphs.
1696 while (untilObj && node && node->GetData() != untilObj)
1697 {
1698 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
1699 wxASSERT (child != NULL);
1700 if (child)
1701 m_floatCollector->CollectFloat(child);
1702 node = node->GetNext();
1703 }
1704
1705 return true;
1706 }
1707
1708 // Returns the style sheet associated with the overall buffer.
1709 wxRichTextStyleSheet* wxRichTextParagraphLayoutBox::GetStyleSheet() const
1710 {
1711 return GetBuffer() ? GetBuffer()->GetStyleSheet() : (wxRichTextStyleSheet*) NULL;
1712 }
1713
1714 // Get the number of floating objects at this level
1715 int wxRichTextParagraphLayoutBox::GetFloatingObjectCount() const
1716 {
1717 if (m_floatCollector)
1718 return m_floatCollector->GetFloatingObjectCount();
1719 else
1720 return 0;
1721 }
1722
1723 // Get a list of floating objects
1724 bool wxRichTextParagraphLayoutBox::GetFloatingObjects(wxRichTextObjectList& objects) const
1725 {
1726 if (m_floatCollector)
1727 {
1728 return m_floatCollector->GetFloatingObjects(objects);
1729 }
1730 else
1731 return false;
1732 }
1733
1734 // Calculate ranges
1735 void wxRichTextParagraphLayoutBox::UpdateRanges()
1736 {
1737 long start = 0;
1738 if (GetParent())
1739 start = GetRange().GetStart();
1740 long end;
1741 CalculateRange(start, end);
1742 }
1743
1744 // HitTest
1745 int wxRichTextParagraphLayoutBox::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int flags)
1746 {
1747 if (!IsShown())
1748 return wxRICHTEXT_HITTEST_NONE;
1749
1750 int ret = wxRICHTEXT_HITTEST_NONE;
1751 if (m_floatCollector && (flags & wxRICHTEXT_HITTEST_NO_FLOATING_OBJECTS) == 0)
1752 ret = m_floatCollector->HitTest(dc, context, pt, textPosition, obj, flags);
1753
1754 if (ret == wxRICHTEXT_HITTEST_NONE)
1755 return wxRichTextCompositeObject::HitTest(dc, context, pt, textPosition, obj, contextObj, flags);
1756 else
1757 {
1758 *contextObj = this;
1759 return ret;
1760 }
1761 }
1762
1763 /// Draw the floating objects
1764 void wxRichTextParagraphLayoutBox::DrawFloats(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
1765 {
1766 if (m_floatCollector)
1767 m_floatCollector->Draw(dc, context, range, selection, rect, descent, style);
1768 }
1769
1770 void wxRichTextParagraphLayoutBox::MoveAnchoredObjectToParagraph(wxRichTextParagraph* from, wxRichTextParagraph* to, wxRichTextObject* obj)
1771 {
1772 if (from == to)
1773 return;
1774
1775 from->RemoveChild(obj);
1776 to->AppendChild(obj);
1777 }
1778
1779 /// Draw the item
1780 bool wxRichTextParagraphLayoutBox::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
1781 {
1782 if (!IsShown())
1783 return true;
1784
1785 wxRect thisRect(GetPosition(), GetCachedSize());
1786
1787 wxRichTextAttr attr(GetAttributes());
1788 context.ApplyVirtualAttributes(attr, this);
1789
1790 int flags = style;
1791 if (selection.IsValid() && GetParentContainer() != this && selection.WithinSelection(GetRange().GetStart(), GetParentContainer()))
1792 flags |= wxRICHTEXT_DRAW_SELECTED;
1793
1794 // Don't draw guidelines if at top level
1795 int theseFlags = flags;
1796 if (!GetParent())
1797 theseFlags &= ~wxRICHTEXT_DRAW_GUIDELINES;
1798 DrawBoxAttributes(dc, GetBuffer(), attr, thisRect, theseFlags);
1799
1800 DrawFloats(dc, context, range, selection, rect, descent, style);
1801 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1802 while (node)
1803 {
1804 wxRichTextObject* child = node->GetData();
1805
1806 if (child && !child->GetRange().IsOutside(range))
1807 {
1808 wxRect childRect(child->GetPosition(), child->GetCachedSize());
1809 wxRichTextRange childRange = range;
1810 if (child->IsTopLevel())
1811 {
1812 childRange = child->GetOwnRange();
1813 }
1814
1815 if (((style & wxRICHTEXT_DRAW_IGNORE_CACHE) == 0) && childRect.GetTop() > rect.GetBottom())
1816 {
1817 // Stop drawing
1818 break;
1819 }
1820 else if (((style & wxRICHTEXT_DRAW_IGNORE_CACHE) == 0) && childRect.GetBottom() < rect.GetTop())
1821 {
1822 // Skip
1823 }
1824 else
1825 child->Draw(dc, context, childRange, selection, rect, descent, style);
1826 }
1827
1828 node = node->GetNext();
1829 }
1830 return true;
1831 }
1832
1833 /// Lay the item out
1834 bool wxRichTextParagraphLayoutBox::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& parentRect, int style)
1835 {
1836 SetPosition(rect.GetPosition());
1837
1838 if (!IsShown())
1839 return true;
1840
1841 wxRect availableSpace;
1842 bool formatRect = (style & wxRICHTEXT_LAYOUT_SPECIFIED_RECT) == wxRICHTEXT_LAYOUT_SPECIFIED_RECT;
1843
1844 wxRichTextAttr attr(GetAttributes());
1845 context.ApplyVirtualAttributes(attr, this);
1846
1847 // If only laying out a specific area, the passed rect has a different meaning:
1848 // the visible part of the buffer. This is used in wxRichTextCtrl::OnSize,
1849 // so that during a size, only the visible part will be relaid out, or
1850 // it would take too long causing flicker. As an approximation, we assume that
1851 // everything up to the start of the visible area is laid out correctly.
1852 if (formatRect)
1853 {
1854 wxRect rect2(0, 0, rect.width, rect.height);
1855 availableSpace = GetAvailableContentArea(dc, context, rect2);
1856
1857 // Invalidate the part of the buffer from the first visible line
1858 // to the end. If other parts of the buffer are currently invalid,
1859 // then they too will be taken into account if they are above
1860 // the visible point.
1861 long startPos = 0;
1862 wxRichTextLine* line = GetLineAtYPosition(rect.y);
1863 if (line)
1864 startPos = line->GetAbsoluteRange().GetStart();
1865
1866 Invalidate(wxRichTextRange(startPos, GetOwnRange().GetEnd()));
1867 }
1868 else
1869 {
1870 availableSpace = GetAvailableContentArea(dc, context, rect);
1871 }
1872
1873 int leftMargin, rightMargin, topMargin, bottomMargin;
1874 wxRichTextObject::GetTotalMargin(dc, GetBuffer(), attr, leftMargin, rightMargin,
1875 topMargin, bottomMargin);
1876
1877 int maxWidth = 0;
1878 int maxHeight = 0;
1879
1880 // The maximum paragraph maximum width, so we can set the overall maximum width for this object
1881 int maxMaxWidth = 0;
1882
1883 // The maximum paragraph minimum width, so we can set the overall minimum width for this object
1884 int maxMinWidth = 0;
1885
1886 // If we have vertical alignment, we must recalculate everything.
1887 bool hasVerticalAlignment = (attr.GetTextBoxAttr().HasVerticalAlignment() &&
1888 (attr.GetTextBoxAttr().GetVerticalAlignment() > wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT_TOP));
1889
1890 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1891
1892 bool layoutAll = true;
1893
1894 // Get invalid range, rounding to paragraph start/end.
1895 wxRichTextRange invalidRange = GetInvalidRange(true);
1896
1897 if (invalidRange == wxRICHTEXT_NONE && !formatRect)
1898 return true;
1899
1900 if (invalidRange == wxRICHTEXT_ALL || hasVerticalAlignment)
1901 layoutAll = true;
1902 else // If we know what range is affected, start laying out from that point on.
1903 if (invalidRange.GetStart() >= GetOwnRange().GetStart())
1904 {
1905 wxRichTextParagraph* firstParagraph = GetParagraphAtPosition(invalidRange.GetStart());
1906 if (firstParagraph)
1907 {
1908 wxRichTextObjectList::compatibility_iterator firstNode = m_children.Find(firstParagraph);
1909 wxRichTextObjectList::compatibility_iterator previousNode;
1910 if ( firstNode )
1911 previousNode = firstNode->GetPrevious();
1912 if (firstNode)
1913 {
1914 if (previousNode)
1915 {
1916 wxRichTextParagraph* previousParagraph = wxDynamicCast(previousNode->GetData(), wxRichTextParagraph);
1917 availableSpace.y = previousParagraph->GetPosition().y + previousParagraph->GetCachedSize().y;
1918 }
1919
1920 // Now we're going to start iterating from the first affected paragraph.
1921 node = firstNode;
1922
1923 layoutAll = false;
1924 }
1925 }
1926 }
1927
1928 // Gather information about only those floating objects that will not be formatted,
1929 // after which floats will be gathered per-paragraph during layout.
1930 UpdateFloatingObjects(availableSpace, node ? node->GetData() : (wxRichTextObject*) NULL);
1931
1932 // A way to force speedy rest-of-buffer layout (the 'else' below)
1933 bool forceQuickLayout = false;
1934
1935 // First get the size of the paragraphs we won't be laying out
1936 wxRichTextObjectList::compatibility_iterator n = m_children.GetFirst();
1937 while (n && n != node)
1938 {
1939 wxRichTextParagraph* child = wxDynamicCast(n->GetData(), wxRichTextParagraph);
1940 if (child)
1941 {
1942 maxWidth = wxMax(maxWidth, child->GetCachedSize().x);
1943 maxMinWidth = wxMax(maxMinWidth, child->GetMinSize().x);
1944 maxMaxWidth = wxMax(maxMaxWidth, child->GetMaxSize().x);
1945 }
1946 n = n->GetNext();
1947 }
1948
1949 while (node)
1950 {
1951 // Assume this box only contains paragraphs
1952
1953 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
1954 // Unsure if this is needed
1955 // wxCHECK_MSG( child, false, wxT("Unknown object in layout") );
1956
1957 if (child && child->IsShown())
1958 {
1959 // TODO: what if the child hasn't been laid out (e.g. involved in Undo) but still has 'old' lines
1960 if ( !forceQuickLayout &&
1961 (layoutAll ||
1962 child->GetLines().IsEmpty() ||
1963 !child->GetRange().IsOutside(invalidRange)) )
1964 {
1965 // Lays out the object first with a given amount of space, and then if no width was specified in attr,
1966 // lays out the object again using the minimum size
1967 child->LayoutToBestSize(dc, context, GetBuffer(),
1968 attr, child->GetAttributes(), availableSpace, rect, style&~wxRICHTEXT_LAYOUT_SPECIFIED_RECT);
1969
1970 // Layout must set the cached size
1971 availableSpace.y += child->GetCachedSize().y;
1972 maxWidth = wxMax(maxWidth, child->GetCachedSize().x);
1973 maxMinWidth = wxMax(maxMinWidth, child->GetMinSize().x);
1974 maxMaxWidth = wxMax(maxMaxWidth, child->GetMaxSize().x);
1975
1976 // If we're just formatting the visible part of the buffer,
1977 // and we're now past the bottom of the window, and we don't have any
1978 // floating objects (since they may cause wrapping to change for the rest of the
1979 // the buffer), start quick layout.
1980 if (!hasVerticalAlignment && formatRect && child->GetPosition().y > rect.GetBottom() && GetFloatingObjectCount() == 0)
1981 forceQuickLayout = true;
1982 }
1983 else
1984 {
1985 // We're outside the immediately affected range, so now let's just
1986 // move everything up or down. This assumes that all the children have previously
1987 // been laid out and have wrapped line lists associated with them.
1988 // TODO: check all paragraphs before the affected range.
1989
1990 int inc = availableSpace.y - child->GetPosition().y;
1991
1992 while (node)
1993 {
1994 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
1995 if (child)
1996 {
1997 if (child->GetLines().GetCount() == 0)
1998 {
1999 // Lays out the object first with a given amount of space, and then if no width was specified in attr,
2000 // lays out the object again using the minimum size
2001 child->LayoutToBestSize(dc, context, GetBuffer(),
2002 attr, child->GetAttributes(), availableSpace, rect, style&~wxRICHTEXT_LAYOUT_SPECIFIED_RECT);
2003
2004 //child->Layout(dc, availableChildRect, style);
2005 }
2006 else
2007 child->Move(wxPoint(child->GetPosition().x, child->GetPosition().y + inc));
2008
2009 availableSpace.y += child->GetCachedSize().y;
2010 maxWidth = wxMax(maxWidth, child->GetCachedSize().x);
2011 maxMinWidth = wxMax(maxMinWidth, child->GetMinSize().x);
2012 maxMaxWidth = wxMax(maxMaxWidth, child->GetMaxSize().x);
2013 }
2014
2015 node = node->GetNext();
2016 }
2017 break;
2018 }
2019 }
2020
2021 node = node->GetNext();
2022 }
2023
2024 node = m_children.GetLast();
2025 if (node && node->GetData()->IsShown())
2026 {
2027 wxRichTextObject* child = node->GetData();
2028 maxHeight = child->GetPosition().y - (GetPosition().y + topMargin) + child->GetCachedSize().y;
2029 }
2030 else
2031 maxHeight = 0; // topMargin + bottomMargin;
2032
2033 // Check the bottom edge of any floating object
2034 if (GetFloatCollector() && GetFloatCollector()->HasFloats())
2035 {
2036 int bottom = GetFloatCollector()->GetLastRectBottom();
2037 if (bottom > maxHeight)
2038 maxHeight = bottom;
2039 }
2040
2041 if (attr.GetTextBoxAttr().GetSize().GetWidth().IsValid())
2042 {
2043 wxRect r = AdjustAvailableSpace(dc, GetBuffer(), wxRichTextAttr() /* not used */, attr, parentRect, parentRect);
2044 int w = r.GetWidth();
2045
2046 // Convert external to content rect
2047 w = w - leftMargin - rightMargin;
2048 maxWidth = wxMax(maxWidth, w);
2049 maxMaxWidth = wxMax(maxMaxWidth, w);
2050 }
2051
2052 // TODO: (also in para layout) should set the
2053 // object's size to an absolute one if specified,
2054 // but if not specified, calculate it from content.
2055
2056 // We need to add back the margins etc.
2057 {
2058 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
2059 contentRect = wxRect(wxPoint(0, 0), wxSize(maxWidth, maxHeight));
2060 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
2061 SetCachedSize(marginRect.GetSize());
2062 }
2063
2064 // The maximum size is the greatest of all maximum widths for all paragraphs.
2065 {
2066 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
2067 contentRect = wxRect(wxPoint(0, 0), wxSize(maxMaxWidth, maxHeight)); // Actually max height is a lie, we can't know it
2068 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
2069 SetMaxSize(marginRect.GetSize());
2070 }
2071
2072 // The minimum size is the greatest of all minimum widths for all paragraphs.
2073 {
2074 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
2075 contentRect = wxRect(wxPoint(0, 0), wxSize(maxMinWidth, maxHeight)); // Actually max height is a lie, we can't know it
2076 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
2077 SetMinSize(marginRect.GetSize());
2078 }
2079
2080 if (attr.GetTextBoxAttr().HasVerticalAlignment() &&
2081 (attr.GetTextBoxAttr().GetVerticalAlignment() > wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT_TOP))
2082 {
2083 int yOffset = 0;
2084 int leftOverSpace = availableSpace.height - topMargin - bottomMargin - maxHeight;
2085 if (leftOverSpace > 0)
2086 {
2087 if (attr.GetTextBoxAttr().GetVerticalAlignment() == wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT_CENTRE)
2088 {
2089 yOffset = (leftOverSpace/2);
2090 }
2091 else if (attr.GetTextBoxAttr().GetVerticalAlignment() == wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT_BOTTOM)
2092 {
2093 yOffset = leftOverSpace;
2094 }
2095 }
2096
2097 // Move all the children to vertically align the content
2098 // This doesn't take into account floating objects, unfortunately.
2099 if (yOffset != 0)
2100 {
2101 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2102 while (node)
2103 {
2104 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2105 if (child)
2106 child->Move(wxPoint(child->GetPosition().x, child->GetPosition().y + yOffset));
2107
2108 node = node->GetNext();
2109 }
2110 }
2111 }
2112
2113 m_invalidRange = wxRICHTEXT_NONE;
2114
2115 return true;
2116 }
2117
2118 /// Get/set the size for the given range.
2119 bool wxRichTextParagraphLayoutBox::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, wxPoint position, wxArrayInt* WXUNUSED(partialExtents)) const
2120 {
2121 wxSize sz;
2122
2123 wxRichTextObjectList::compatibility_iterator startPara = wxRichTextObjectList::compatibility_iterator();
2124 wxRichTextObjectList::compatibility_iterator endPara = wxRichTextObjectList::compatibility_iterator();
2125
2126 // First find the first paragraph whose starting position is within the range.
2127 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2128 while (node)
2129 {
2130 // child is a paragraph
2131 wxRichTextObject* child = node->GetData();
2132 const wxRichTextRange& r = child->GetRange();
2133
2134 if (r.GetStart() <= range.GetStart() && r.GetEnd() >= range.GetStart())
2135 {
2136 startPara = node;
2137 break;
2138 }
2139
2140 node = node->GetNext();
2141 }
2142
2143 // Next find the last paragraph containing part of the range
2144 node = m_children.GetFirst();
2145 while (node)
2146 {
2147 // child is a paragraph
2148 wxRichTextObject* child = node->GetData();
2149 const wxRichTextRange& r = child->GetRange();
2150
2151 if (r.GetStart() <= range.GetEnd() && r.GetEnd() >= range.GetEnd())
2152 {
2153 endPara = node;
2154 break;
2155 }
2156
2157 node = node->GetNext();
2158 }
2159
2160 if (!startPara || !endPara)
2161 return false;
2162
2163 // Now we can add up the sizes
2164 for (node = startPara; node ; node = node->GetNext())
2165 {
2166 // child is a paragraph
2167 wxRichTextObject* child = node->GetData();
2168 const wxRichTextRange& childRange = child->GetRange();
2169 wxRichTextRange rangeToFind = range;
2170 rangeToFind.LimitTo(childRange);
2171
2172 if (child->IsTopLevel())
2173 rangeToFind = child->GetOwnRange();
2174
2175 wxSize childSize;
2176
2177 int childDescent = 0;
2178 child->GetRangeSize(rangeToFind, childSize, childDescent, dc, context, flags, position);
2179
2180 descent = wxMax(childDescent, descent);
2181
2182 sz.x = wxMax(sz.x, childSize.x);
2183 sz.y += childSize.y;
2184
2185 if (node == endPara)
2186 break;
2187 }
2188
2189 size = sz;
2190
2191 return true;
2192 }
2193
2194 /// Get the paragraph at the given position
2195 wxRichTextParagraph* wxRichTextParagraphLayoutBox::GetParagraphAtPosition(long pos, bool caretPosition) const
2196 {
2197 if (caretPosition)
2198 pos ++;
2199
2200 // First find the first paragraph whose starting position is within the range.
2201 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2202 while (node)
2203 {
2204 // child is a paragraph
2205 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2206 // wxASSERT (child != NULL);
2207
2208 if (child)
2209 {
2210 // Return first child in buffer if position is -1
2211 // if (pos == -1)
2212 // return child;
2213
2214 if (child->GetRange().Contains(pos))
2215 return child;
2216 }
2217
2218 node = node->GetNext();
2219 }
2220 return NULL;
2221 }
2222
2223 /// Get the line at the given position
2224 wxRichTextLine* wxRichTextParagraphLayoutBox::GetLineAtPosition(long pos, bool caretPosition) const
2225 {
2226 if (caretPosition)
2227 pos ++;
2228
2229 // First find the first paragraph whose starting position is within the range.
2230 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2231 while (node)
2232 {
2233 wxRichTextObject* obj = (wxRichTextObject*) node->GetData();
2234 if (obj->GetRange().Contains(pos))
2235 {
2236 // child is a paragraph
2237 wxRichTextParagraph* child = wxDynamicCast(obj, wxRichTextParagraph);
2238 // wxASSERT (child != NULL);
2239
2240 if (child)
2241 {
2242 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
2243 while (node2)
2244 {
2245 wxRichTextLine* line = node2->GetData();
2246
2247 wxRichTextRange range = line->GetAbsoluteRange();
2248
2249 if (range.Contains(pos) ||
2250
2251 // If the position is end-of-paragraph, then return the last line of
2252 // of the paragraph.
2253 ((range.GetEnd() == child->GetRange().GetEnd()-1) && (pos == child->GetRange().GetEnd())))
2254 return line;
2255
2256 node2 = node2->GetNext();
2257 }
2258 }
2259 }
2260
2261 node = node->GetNext();
2262 }
2263
2264 int lineCount = GetLineCount();
2265 if (lineCount > 0)
2266 return GetLineForVisibleLineNumber(lineCount-1);
2267 else
2268 return NULL;
2269 }
2270
2271 /// Get the line at the given y pixel position, or the last line.
2272 wxRichTextLine* wxRichTextParagraphLayoutBox::GetLineAtYPosition(int y) const
2273 {
2274 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2275 while (node)
2276 {
2277 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2278 // wxASSERT (child != NULL);
2279
2280 if (child)
2281 {
2282 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
2283 while (node2)
2284 {
2285 wxRichTextLine* line = node2->GetData();
2286
2287 wxRect rect(line->GetRect());
2288
2289 if (y <= rect.GetBottom())
2290 return line;
2291
2292 node2 = node2->GetNext();
2293 }
2294 }
2295
2296 node = node->GetNext();
2297 }
2298
2299 // Return last line
2300 int lineCount = GetLineCount();
2301 if (lineCount > 0)
2302 return GetLineForVisibleLineNumber(lineCount-1);
2303 else
2304 return NULL;
2305 }
2306
2307 /// Get the number of visible lines
2308 int wxRichTextParagraphLayoutBox::GetLineCount() const
2309 {
2310 int count = 0;
2311
2312 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2313 while (node)
2314 {
2315 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2316 // wxASSERT (child != NULL);
2317
2318 if (child)
2319 count += child->GetLines().GetCount();
2320
2321 node = node->GetNext();
2322 }
2323 return count;
2324 }
2325
2326
2327 /// Get the paragraph for a given line
2328 wxRichTextParagraph* wxRichTextParagraphLayoutBox::GetParagraphForLine(wxRichTextLine* line) const
2329 {
2330 return GetParagraphAtPosition(line->GetAbsoluteRange().GetStart());
2331 }
2332
2333 /// Get the line size at the given position
2334 wxSize wxRichTextParagraphLayoutBox::GetLineSizeAtPosition(long pos, bool caretPosition) const
2335 {
2336 wxRichTextLine* line = GetLineAtPosition(pos, caretPosition);
2337 if (line)
2338 {
2339 return line->GetSize();
2340 }
2341 else
2342 return wxSize(0, 0);
2343 }
2344
2345
2346 /// Convenience function to add a paragraph of text
2347 wxRichTextRange wxRichTextParagraphLayoutBox::AddParagraph(const wxString& text, wxRichTextAttr* paraStyle)
2348 {
2349 // Don't use the base style, just the default style, and the base style will
2350 // be combined at display time.
2351 // Divide into paragraph and character styles.
2352
2353 wxRichTextAttr defaultCharStyle;
2354 wxRichTextAttr defaultParaStyle;
2355
2356 // If the default style is a named paragraph style, don't apply any character formatting
2357 // to the initial text string.
2358 if (GetDefaultStyle().HasParagraphStyleName() && GetStyleSheet())
2359 {
2360 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(GetDefaultStyle().GetParagraphStyleName());
2361 if (def)
2362 defaultParaStyle = def->GetStyleMergedWithBase(GetStyleSheet());
2363 }
2364 else
2365 wxRichTextSplitParaCharStyles(GetDefaultStyle(), defaultParaStyle, defaultCharStyle);
2366
2367 wxRichTextAttr* pStyle = paraStyle ? paraStyle : (wxRichTextAttr*) & defaultParaStyle;
2368 wxRichTextAttr* cStyle = & defaultCharStyle;
2369
2370 wxRichTextParagraph* para = new wxRichTextParagraph(text, this, pStyle, cStyle);
2371
2372 AppendChild(para);
2373
2374 UpdateRanges();
2375
2376 return para->GetRange();
2377 }
2378
2379 /// Adds multiple paragraphs, based on newlines.
2380 wxRichTextRange wxRichTextParagraphLayoutBox::AddParagraphs(const wxString& text, wxRichTextAttr* paraStyle)
2381 {
2382 // Don't use the base style, just the default style, and the base style will
2383 // be combined at display time.
2384 // Divide into paragraph and character styles.
2385
2386 wxRichTextAttr defaultCharStyle;
2387 wxRichTextAttr defaultParaStyle;
2388
2389 // If the default style is a named paragraph style, don't apply any character formatting
2390 // to the initial text string.
2391 if (GetDefaultStyle().HasParagraphStyleName() && GetStyleSheet())
2392 {
2393 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(GetDefaultStyle().GetParagraphStyleName());
2394 if (def)
2395 defaultParaStyle = def->GetStyleMergedWithBase(GetStyleSheet());
2396 }
2397 else
2398 wxRichTextSplitParaCharStyles(GetDefaultStyle(), defaultParaStyle, defaultCharStyle);
2399
2400 wxRichTextAttr* pStyle = paraStyle ? paraStyle : (wxRichTextAttr*) & defaultParaStyle;
2401 wxRichTextAttr* cStyle = & defaultCharStyle;
2402
2403 wxRichTextParagraph* firstPara = NULL;
2404 wxRichTextParagraph* lastPara = NULL;
2405
2406 wxRichTextRange range(-1, -1);
2407
2408 size_t i = 0;
2409 size_t len = text.length();
2410 wxString line;
2411 wxRichTextParagraph* para = new wxRichTextParagraph(wxEmptyString, this, pStyle, cStyle);
2412
2413 AppendChild(para);
2414
2415 firstPara = para;
2416 lastPara = para;
2417
2418 while (i < len)
2419 {
2420 wxChar ch = text[i];
2421 if (ch == wxT('\n') || ch == wxT('\r'))
2422 {
2423 if (i != (len-1))
2424 {
2425 wxRichTextPlainText* plainText = (wxRichTextPlainText*) para->GetChildren().GetFirst()->GetData();
2426 plainText->SetText(line);
2427
2428 para = new wxRichTextParagraph(wxEmptyString, this, pStyle, cStyle);
2429
2430 AppendChild(para);
2431
2432 lastPara = para;
2433 line = wxEmptyString;
2434 }
2435 }
2436 else
2437 line += ch;
2438
2439 i ++;
2440 }
2441
2442 if (!line.empty())
2443 {
2444 wxRichTextPlainText* plainText = (wxRichTextPlainText*) para->GetChildren().GetFirst()->GetData();
2445 plainText->SetText(line);
2446 }
2447
2448 UpdateRanges();
2449
2450 return wxRichTextRange(firstPara->GetRange().GetStart(), lastPara->GetRange().GetEnd());
2451 }
2452
2453 /// Convenience function to add an image
2454 wxRichTextRange wxRichTextParagraphLayoutBox::AddImage(const wxImage& image, wxRichTextAttr* paraStyle)
2455 {
2456 // Don't use the base style, just the default style, and the base style will
2457 // be combined at display time.
2458 // Divide into paragraph and character styles.
2459
2460 wxRichTextAttr defaultCharStyle;
2461 wxRichTextAttr defaultParaStyle;
2462
2463 // If the default style is a named paragraph style, don't apply any character formatting
2464 // to the initial text string.
2465 if (GetDefaultStyle().HasParagraphStyleName() && GetStyleSheet())
2466 {
2467 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(GetDefaultStyle().GetParagraphStyleName());
2468 if (def)
2469 defaultParaStyle = def->GetStyleMergedWithBase(GetStyleSheet());
2470 }
2471 else
2472 wxRichTextSplitParaCharStyles(GetDefaultStyle(), defaultParaStyle, defaultCharStyle);
2473
2474 wxRichTextAttr* pStyle = paraStyle ? paraStyle : (wxRichTextAttr*) & defaultParaStyle;
2475 wxRichTextAttr* cStyle = & defaultCharStyle;
2476
2477 wxRichTextParagraph* para = new wxRichTextParagraph(this, pStyle);
2478 AppendChild(para);
2479 para->AppendChild(new wxRichTextImage(image, this, cStyle));
2480
2481 UpdateRanges();
2482
2483 return para->GetRange();
2484 }
2485
2486
2487 /// Insert fragment into this box at the given position. If partialParagraph is true,
2488 /// it is assumed that the last (or only) paragraph is just a piece of data with no paragraph
2489 /// marker.
2490
2491 bool wxRichTextParagraphLayoutBox::InsertFragment(long position, wxRichTextParagraphLayoutBox& fragment)
2492 {
2493 // First, find the first paragraph whose starting position is within the range.
2494 wxRichTextParagraph* para = GetParagraphAtPosition(position);
2495 if (para)
2496 {
2497 wxRichTextAttr originalAttr = para->GetAttributes();
2498
2499 wxRichTextObjectList::compatibility_iterator node = m_children.Find(para);
2500
2501 // Now split at this position, returning the object to insert the new
2502 // ones in front of.
2503 wxRichTextObject* nextObject = para->SplitAt(position);
2504
2505 // Special case: partial paragraph, just one paragraph. Might be a small amount of
2506 // text, for example, so let's optimize.
2507
2508 if (fragment.GetPartialParagraph() && fragment.GetChildren().GetCount() == 1)
2509 {
2510 // Add the first para to this para...
2511 wxRichTextObjectList::compatibility_iterator firstParaNode = fragment.GetChildren().GetFirst();
2512 if (!firstParaNode)
2513 return false;
2514
2515 // Iterate through the fragment paragraph inserting the content into this paragraph.
2516 wxRichTextParagraph* firstPara = wxDynamicCast(firstParaNode->GetData(), wxRichTextParagraph);
2517 wxASSERT (firstPara != NULL);
2518
2519 wxRichTextObjectList::compatibility_iterator objectNode = firstPara->GetChildren().GetFirst();
2520 while (objectNode)
2521 {
2522 wxRichTextObject* newObj = objectNode->GetData()->Clone();
2523
2524 if (!nextObject)
2525 {
2526 // Append
2527 para->AppendChild(newObj);
2528 }
2529 else
2530 {
2531 // Insert before nextObject
2532 para->InsertChild(newObj, nextObject);
2533 }
2534
2535 objectNode = objectNode->GetNext();
2536 }
2537
2538 return true;
2539 }
2540 else
2541 {
2542 // Procedure for inserting a fragment consisting of a number of
2543 // paragraphs:
2544 //
2545 // 1. Remove and save the content that's after the insertion point, for adding
2546 // back once we've added the fragment.
2547 // 2. Add the content from the first fragment paragraph to the current
2548 // paragraph.
2549 // 3. Add remaining fragment paragraphs after the current paragraph.
2550 // 4. Add back the saved content from the first paragraph. If partialParagraph
2551 // is true, add it to the last paragraph added and not a new one.
2552
2553 // 1. Remove and save objects after split point.
2554 wxList savedObjects;
2555 if (nextObject)
2556 para->MoveToList(nextObject, savedObjects);
2557
2558 // 2. Add the content from the 1st fragment paragraph.
2559 wxRichTextObjectList::compatibility_iterator firstParaNode = fragment.GetChildren().GetFirst();
2560 if (!firstParaNode)
2561 return false;
2562
2563 wxRichTextParagraph* firstPara = wxDynamicCast(firstParaNode->GetData(), wxRichTextParagraph);
2564 wxASSERT(firstPara != NULL);
2565
2566 if (!(fragment.GetAttributes().GetFlags() & wxTEXT_ATTR_KEEP_FIRST_PARA_STYLE))
2567 para->SetAttributes(firstPara->GetAttributes());
2568
2569 // Save empty paragraph attributes for appending later
2570 // These are character attributes deliberately set for a new paragraph. Without this,
2571 // we couldn't pass default attributes when appending a new paragraph.
2572 wxRichTextAttr emptyParagraphAttributes;
2573
2574 wxRichTextObjectList::compatibility_iterator objectNode = firstPara->GetChildren().GetFirst();
2575
2576 if (objectNode && firstPara->GetChildren().GetCount() == 1 && objectNode->GetData()->IsEmpty())
2577 emptyParagraphAttributes = objectNode->GetData()->GetAttributes();
2578
2579 while (objectNode)
2580 {
2581 wxRichTextObject* newObj = objectNode->GetData()->Clone();
2582
2583 // Append
2584 para->AppendChild(newObj);
2585
2586 objectNode = objectNode->GetNext();
2587 }
2588
2589 // 3. Add remaining fragment paragraphs after the current paragraph.
2590 wxRichTextObjectList::compatibility_iterator nextParagraphNode = node->GetNext();
2591 wxRichTextObject* nextParagraph = NULL;
2592 if (nextParagraphNode)
2593 nextParagraph = nextParagraphNode->GetData();
2594
2595 wxRichTextObjectList::compatibility_iterator i = fragment.GetChildren().GetFirst()->GetNext();
2596 wxRichTextParagraph* finalPara = para;
2597
2598 bool needExtraPara = (!i || !fragment.GetPartialParagraph());
2599
2600 // If there was only one paragraph, we need to insert a new one.
2601 while (i)
2602 {
2603 wxRichTextParagraph* para = wxDynamicCast(i->GetData(), wxRichTextParagraph);
2604 wxASSERT( para != NULL );
2605
2606 finalPara = (wxRichTextParagraph*) para->Clone();
2607
2608 if (nextParagraph)
2609 InsertChild(finalPara, nextParagraph);
2610 else
2611 AppendChild(finalPara);
2612
2613 i = i->GetNext();
2614 }
2615
2616 // If there was only one paragraph, or we have full paragraphs in our fragment,
2617 // we need to insert a new one.
2618 if (needExtraPara)
2619 {
2620 finalPara = new wxRichTextParagraph;
2621
2622 if (nextParagraph)
2623 InsertChild(finalPara, nextParagraph);
2624 else
2625 AppendChild(finalPara);
2626 }
2627
2628 // 4. Add back the remaining content.
2629 if (finalPara)
2630 {
2631 if (nextObject)
2632 finalPara->MoveFromList(savedObjects);
2633
2634 // Ensure there's at least one object
2635 if (finalPara->GetChildCount() == 0)
2636 {
2637 wxRichTextPlainText* text = new wxRichTextPlainText(wxEmptyString);
2638 text->SetAttributes(emptyParagraphAttributes);
2639
2640 finalPara->AppendChild(text);
2641 }
2642 }
2643
2644 if ((fragment.GetAttributes().GetFlags() & wxTEXT_ATTR_KEEP_FIRST_PARA_STYLE) && firstPara)
2645 finalPara->SetAttributes(firstPara->GetAttributes());
2646 else if (finalPara && finalPara != para)
2647 finalPara->SetAttributes(originalAttr);
2648
2649 return true;
2650 }
2651 }
2652 else
2653 {
2654 // Append
2655 wxRichTextObjectList::compatibility_iterator i = fragment.GetChildren().GetFirst();
2656 while (i)
2657 {
2658 wxRichTextParagraph* para = wxDynamicCast(i->GetData(), wxRichTextParagraph);
2659 wxASSERT( para != NULL );
2660
2661 AppendChild(para->Clone());
2662
2663 i = i->GetNext();
2664 }
2665
2666 return true;
2667 }
2668 }
2669
2670 /// Make a copy of the fragment corresponding to the given range, putting it in 'fragment'.
2671 /// If there was an incomplete paragraph at the end, partialParagraph is set to true.
2672 bool wxRichTextParagraphLayoutBox::CopyFragment(const wxRichTextRange& range, wxRichTextParagraphLayoutBox& fragment)
2673 {
2674 wxRichTextObjectList::compatibility_iterator i = GetChildren().GetFirst();
2675 while (i)
2676 {
2677 wxRichTextParagraph* para = wxDynamicCast(i->GetData(), wxRichTextParagraph);
2678 wxASSERT( para != NULL );
2679
2680 if (!para->GetRange().IsOutside(range))
2681 {
2682 fragment.AppendChild(para->Clone());
2683 }
2684 i = i->GetNext();
2685 }
2686
2687 // Now top and tail the first and last paragraphs in our new fragment (which might be the same).
2688 if (!fragment.IsEmpty())
2689 {
2690 wxRichTextParagraph* firstPara = wxDynamicCast(fragment.GetChildren().GetFirst()->GetData(), wxRichTextParagraph);
2691 wxASSERT( firstPara != NULL );
2692
2693 wxRichTextParagraph* lastPara = wxDynamicCast(fragment.GetChildren().GetLast()->GetData(), wxRichTextParagraph);
2694 wxASSERT( lastPara != NULL );
2695
2696 if (!firstPara || !lastPara)
2697 return false;
2698
2699 bool isFragment = (range.GetEnd() < lastPara->GetRange().GetEnd());
2700
2701 long firstPos = firstPara->GetRange().GetStart();
2702
2703 // Adjust for renumbering from zero
2704 wxRichTextRange topTailRange(range.GetStart() - firstPos, range.GetEnd() - firstPos);
2705
2706 long end;
2707 fragment.CalculateRange(0, end);
2708
2709 // Chop off the start of the paragraph
2710 if (topTailRange.GetStart() > 0)
2711 {
2712 wxRichTextRange r(0, topTailRange.GetStart()-1);
2713 firstPara->DeleteRange(r);
2714
2715 // Make sure the numbering is correct
2716 fragment.CalculateRange(0, end);
2717
2718 // Now, we've deleted some positions, so adjust the range
2719 // accordingly.
2720 topTailRange.SetStart(range.GetLength());
2721 topTailRange.SetEnd(fragment.GetOwnRange().GetEnd());
2722 }
2723 else
2724 {
2725 topTailRange.SetStart(range.GetLength());
2726 topTailRange.SetEnd(fragment.GetOwnRange().GetEnd());
2727 }
2728
2729 if (topTailRange.GetStart() < lastPara->GetRange().GetEnd())
2730 {
2731 lastPara->DeleteRange(topTailRange);
2732
2733 // Make sure the numbering is correct
2734 long end;
2735 fragment.CalculateRange(0, end);
2736
2737 // We only have part of a paragraph at the end
2738 fragment.SetPartialParagraph(true);
2739 }
2740 else
2741 {
2742 // We have a partial paragraph (don't save last new paragraph marker)
2743 // or complete paragraph
2744 fragment.SetPartialParagraph(isFragment);
2745 }
2746 }
2747
2748 return true;
2749 }
2750
2751 /// Given a position, get the number of the visible line (potentially many to a paragraph),
2752 /// starting from zero at the start of the buffer.
2753 long wxRichTextParagraphLayoutBox::GetVisibleLineNumber(long pos, bool caretPosition, bool startOfLine) const
2754 {
2755 if (caretPosition)
2756 pos ++;
2757
2758 int lineCount = 0;
2759
2760 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2761 while (node)
2762 {
2763 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2764 // wxASSERT( child != NULL );
2765
2766 if (child)
2767 {
2768 if (child->GetRange().Contains(pos))
2769 {
2770 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
2771 while (node2)
2772 {
2773 wxRichTextLine* line = node2->GetData();
2774 wxRichTextRange lineRange = line->GetAbsoluteRange();
2775
2776 if (lineRange.Contains(pos) || pos == lineRange.GetStart())
2777 {
2778 // If the caret is displayed at the end of the previous wrapped line,
2779 // we want to return the line it's _displayed_ at (not the actual line
2780 // containing the position).
2781 if (lineRange.GetStart() == pos && !startOfLine && child->GetRange().GetStart() != pos)
2782 return lineCount - 1;
2783 else
2784 return lineCount;
2785 }
2786
2787 lineCount ++;
2788
2789 node2 = node2->GetNext();
2790 }
2791 // If we didn't find it in the lines, it must be
2792 // the last position of the paragraph. So return the last line.
2793 return lineCount-1;
2794 }
2795 else
2796 lineCount += child->GetLines().GetCount();
2797 }
2798
2799 node = node->GetNext();
2800 }
2801
2802 // Not found
2803 return -1;
2804 }
2805
2806 /// Given a line number, get the corresponding wxRichTextLine object.
2807 wxRichTextLine* wxRichTextParagraphLayoutBox::GetLineForVisibleLineNumber(long lineNumber) const
2808 {
2809 int lineCount = 0;
2810
2811 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2812 while (node)
2813 {
2814 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2815 // wxASSERT(child != NULL);
2816
2817 if (child)
2818 {
2819 if (lineNumber < (int) (child->GetLines().GetCount() + lineCount))
2820 {
2821 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
2822 while (node2)
2823 {
2824 wxRichTextLine* line = node2->GetData();
2825
2826 if (lineCount == lineNumber)
2827 return line;
2828
2829 lineCount ++;
2830
2831 node2 = node2->GetNext();
2832 }
2833 }
2834 else
2835 lineCount += child->GetLines().GetCount();
2836 }
2837
2838 node = node->GetNext();
2839 }
2840
2841 // Didn't find it
2842 return NULL;
2843 }
2844
2845 /// Delete range from layout.
2846 bool wxRichTextParagraphLayoutBox::DeleteRange(const wxRichTextRange& range)
2847 {
2848 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2849
2850 wxRichTextParagraph* firstPara = NULL;
2851 while (node)
2852 {
2853 wxRichTextParagraph* obj = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2854 // wxASSERT (obj != NULL);
2855
2856 wxRichTextObjectList::compatibility_iterator next = node->GetNext();
2857
2858 if (obj)
2859 {
2860 // Delete the range in each paragraph
2861
2862 if (!obj->GetRange().IsOutside(range))
2863 {
2864 // Deletes the content of this object within the given range
2865 obj->DeleteRange(range);
2866
2867 wxRichTextRange thisRange = obj->GetRange();
2868 wxRichTextAttr thisAttr = obj->GetAttributes();
2869
2870 // If the whole paragraph is within the range to delete,
2871 // delete the whole thing.
2872 if (range.GetStart() <= thisRange.GetStart() && range.GetEnd() >= thisRange.GetEnd())
2873 {
2874 // Delete the whole object
2875 RemoveChild(obj, true);
2876 obj = NULL;
2877 }
2878 else if (!firstPara)
2879 firstPara = obj;
2880
2881 // If the range includes the paragraph end, we need to join this
2882 // and the next paragraph.
2883 if (range.GetEnd() <= thisRange.GetEnd())
2884 {
2885 // We need to move the objects from the next paragraph
2886 // to this paragraph
2887
2888 wxRichTextParagraph* nextParagraph = NULL;
2889 if ((range.GetEnd() < thisRange.GetEnd()) && obj)
2890 nextParagraph = obj;
2891 else
2892 {
2893 // We're ending at the end of the paragraph, so merge the _next_ paragraph.
2894 if (next)
2895 nextParagraph = wxDynamicCast(next->GetData(), wxRichTextParagraph);
2896 }
2897
2898 bool applyFinalParagraphStyle = firstPara && nextParagraph && nextParagraph != firstPara;
2899
2900 wxRichTextAttr nextParaAttr;
2901 if (applyFinalParagraphStyle)
2902 {
2903 // Special case when deleting the end of a paragraph - use _this_ paragraph's style,
2904 // not the next one.
2905 if (range.GetStart() == range.GetEnd() && range.GetStart() == thisRange.GetEnd())
2906 nextParaAttr = thisAttr;
2907 else
2908 nextParaAttr = nextParagraph->GetAttributes();
2909 }
2910
2911 if (firstPara && nextParagraph && firstPara != nextParagraph)
2912 {
2913 // Move the objects to the previous para
2914 wxRichTextObjectList::compatibility_iterator node1 = nextParagraph->GetChildren().GetFirst();
2915
2916 while (node1)
2917 {
2918 wxRichTextObject* obj1 = node1->GetData();
2919
2920 firstPara->AppendChild(obj1);
2921
2922 wxRichTextObjectList::compatibility_iterator next1 = node1->GetNext();
2923 nextParagraph->GetChildren().Erase(node1);
2924
2925 node1 = next1;
2926 }
2927
2928 // Delete the paragraph
2929 RemoveChild(nextParagraph, true);
2930 }
2931
2932 // Avoid empty paragraphs
2933 if (firstPara && firstPara->GetChildren().GetCount() == 0)
2934 {
2935 wxRichTextPlainText* text = new wxRichTextPlainText(wxEmptyString);
2936 firstPara->AppendChild(text);
2937 }
2938
2939 if (applyFinalParagraphStyle)
2940 firstPara->SetAttributes(nextParaAttr);
2941
2942 return true;
2943 }
2944 }
2945 }
2946
2947 node = next;
2948 }
2949
2950 return true;
2951 }
2952
2953 /// Get any text in this object for the given range
2954 wxString wxRichTextParagraphLayoutBox::GetTextForRange(const wxRichTextRange& range) const
2955 {
2956 int lineCount = 0;
2957 wxString text;
2958 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2959 while (node)
2960 {
2961 wxRichTextObject* child = node->GetData();
2962 if (!child->GetRange().IsOutside(range))
2963 {
2964 wxRichTextRange childRange = range;
2965 childRange.LimitTo(child->GetRange());
2966
2967 wxString childText = child->GetTextForRange(childRange);
2968
2969 text += childText;
2970
2971 if ((childRange.GetEnd() == child->GetRange().GetEnd()) && node->GetNext())
2972 text += wxT("\n");
2973
2974 lineCount ++;
2975 }
2976 node = node->GetNext();
2977 }
2978
2979 return text;
2980 }
2981
2982 /// Get all the text
2983 wxString wxRichTextParagraphLayoutBox::GetText() const
2984 {
2985 return GetTextForRange(GetOwnRange());
2986 }
2987
2988 /// Get the paragraph by number
2989 wxRichTextParagraph* wxRichTextParagraphLayoutBox::GetParagraphAtLine(long paragraphNumber) const
2990 {
2991 if ((size_t) paragraphNumber >= GetChildCount())
2992 return NULL;
2993
2994 return (wxRichTextParagraph*) GetChild((size_t) paragraphNumber);
2995 }
2996
2997 /// Get the length of the paragraph
2998 int wxRichTextParagraphLayoutBox::GetParagraphLength(long paragraphNumber) const
2999 {
3000 wxRichTextParagraph* para = GetParagraphAtLine(paragraphNumber);
3001 if (para)
3002 return para->GetRange().GetLength() - 1; // don't include newline
3003 else
3004 return 0;
3005 }
3006
3007 /// Get the text of the paragraph
3008 wxString wxRichTextParagraphLayoutBox::GetParagraphText(long paragraphNumber) const
3009 {
3010 wxRichTextParagraph* para = GetParagraphAtLine(paragraphNumber);
3011 if (para)
3012 return para->GetTextForRange(para->GetRange());
3013 else
3014 return wxEmptyString;
3015 }
3016
3017 /// Convert zero-based line column and paragraph number to a position.
3018 long wxRichTextParagraphLayoutBox::XYToPosition(long x, long y) const
3019 {
3020 wxRichTextParagraph* para = GetParagraphAtLine(y);
3021 if (para)
3022 {
3023 return para->GetRange().GetStart() + x;
3024 }
3025 else
3026 return -1;
3027 }
3028
3029 /// Convert zero-based position to line column and paragraph number
3030 bool wxRichTextParagraphLayoutBox::PositionToXY(long pos, long* x, long* y) const
3031 {
3032 wxRichTextParagraph* para = GetParagraphAtPosition(pos);
3033 if (para)
3034 {
3035 int count = 0;
3036 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3037 while (node)
3038 {
3039 wxRichTextObject* child = node->GetData();
3040 if (child == para)
3041 break;
3042 count ++;
3043 node = node->GetNext();
3044 }
3045
3046 *y = count;
3047 *x = pos - para->GetRange().GetStart();
3048
3049 return true;
3050 }
3051 else
3052 return false;
3053 }
3054
3055 /// Get the leaf object in a paragraph at this position.
3056 /// Given a line number, get the corresponding wxRichTextLine object.
3057 wxRichTextObject* wxRichTextParagraphLayoutBox::GetLeafObjectAtPosition(long position) const
3058 {
3059 wxRichTextParagraph* para = GetParagraphAtPosition(position);
3060 if (para)
3061 {
3062 wxRichTextObjectList::compatibility_iterator node = para->GetChildren().GetFirst();
3063
3064 while (node)
3065 {
3066 wxRichTextObject* child = node->GetData();
3067 if (child->GetRange().Contains(position))
3068 return child;
3069
3070 node = node->GetNext();
3071 }
3072 if (position == para->GetRange().GetEnd() && para->GetChildCount() > 0)
3073 return para->GetChildren().GetLast()->GetData();
3074 }
3075 return NULL;
3076 }
3077
3078 /// Set character or paragraph text attributes: apply character styles only to immediate text nodes
3079 bool wxRichTextParagraphLayoutBox::SetStyle(const wxRichTextRange& range, const wxRichTextAttr& style, int flags)
3080 {
3081 bool characterStyle = false;
3082 bool paragraphStyle = false;
3083
3084 if (style.IsCharacterStyle())
3085 characterStyle = true;
3086 if (style.IsParagraphStyle())
3087 paragraphStyle = true;
3088
3089 wxRichTextBuffer* buffer = GetBuffer();
3090
3091 bool withUndo = ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0);
3092 bool applyMinimal = ((flags & wxRICHTEXT_SETSTYLE_OPTIMIZE) != 0);
3093 bool parasOnly = ((flags & wxRICHTEXT_SETSTYLE_PARAGRAPHS_ONLY) != 0);
3094 bool charactersOnly = ((flags & wxRICHTEXT_SETSTYLE_CHARACTERS_ONLY) != 0);
3095 bool resetExistingStyle = ((flags & wxRICHTEXT_SETSTYLE_RESET) != 0);
3096 bool removeStyle = ((flags & wxRICHTEXT_SETSTYLE_REMOVE) != 0);
3097
3098 // Apply paragraph style first, if any
3099 wxRichTextAttr wholeStyle(style);
3100
3101 if (!removeStyle && wholeStyle.HasParagraphStyleName() && buffer->GetStyleSheet())
3102 {
3103 wxRichTextParagraphStyleDefinition* def = buffer->GetStyleSheet()->FindParagraphStyle(wholeStyle.GetParagraphStyleName());
3104 if (def)
3105 wxRichTextApplyStyle(wholeStyle, def->GetStyleMergedWithBase(buffer->GetStyleSheet()));
3106 }
3107
3108 // Limit the attributes to be set to the content to only character attributes.
3109 wxRichTextAttr characterAttributes(wholeStyle);
3110 characterAttributes.SetFlags(characterAttributes.GetFlags() & (wxTEXT_ATTR_CHARACTER));
3111
3112 if (!removeStyle && characterAttributes.HasCharacterStyleName() && buffer->GetStyleSheet())
3113 {
3114 wxRichTextCharacterStyleDefinition* def = buffer->GetStyleSheet()->FindCharacterStyle(characterAttributes.GetCharacterStyleName());
3115 if (def)
3116 wxRichTextApplyStyle(characterAttributes, def->GetStyleMergedWithBase(buffer->GetStyleSheet()));
3117 }
3118
3119 // If we are associated with a control, make undoable; otherwise, apply immediately
3120 // to the data.
3121
3122 bool haveControl = (buffer->GetRichTextCtrl() != NULL);
3123
3124 wxRichTextAction* action = NULL;
3125
3126 if (haveControl && withUndo)
3127 {
3128 action = new wxRichTextAction(NULL, _("Change Style"), wxRICHTEXT_CHANGE_STYLE, buffer, this, buffer->GetRichTextCtrl());
3129 action->SetRange(range);
3130 action->SetPosition(buffer->GetRichTextCtrl()->GetCaretPosition());
3131 }
3132
3133 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3134 while (node)
3135 {
3136 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3137 // wxASSERT (para != NULL);
3138
3139 if (para && para->GetChildCount() > 0)
3140 {
3141 // Stop searching if we're beyond the range of interest
3142 if (para->GetRange().GetStart() > range.GetEnd())
3143 break;
3144
3145 if (!para->GetRange().IsOutside(range))
3146 {
3147 // We'll be using a copy of the paragraph to make style changes,
3148 // not updating the buffer directly.
3149 wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL);
3150
3151 if (haveControl && withUndo)
3152 {
3153 newPara = new wxRichTextParagraph(*para);
3154 action->GetNewParagraphs().AppendChild(newPara);
3155
3156 // Also store the old ones for Undo
3157 action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para));
3158 }
3159 else
3160 newPara = para;
3161
3162 // If we're specifying paragraphs only, then we really mean character formatting
3163 // to be included in the paragraph style
3164 if ((paragraphStyle || parasOnly) && !charactersOnly)
3165 {
3166 if (removeStyle)
3167 {
3168 // Removes the given style from the paragraph
3169 wxRichTextRemoveStyle(newPara->GetAttributes(), style);
3170 }
3171 else if (resetExistingStyle)
3172 newPara->GetAttributes() = wholeStyle;
3173 else
3174 {
3175 if (applyMinimal)
3176 {
3177 // Only apply attributes that will make a difference to the combined
3178 // style as seen on the display
3179 wxRichTextAttr combinedAttr(para->GetCombinedAttributes(true));
3180 wxRichTextApplyStyle(newPara->GetAttributes(), wholeStyle, & combinedAttr);
3181 }
3182 else
3183 wxRichTextApplyStyle(newPara->GetAttributes(), wholeStyle);
3184 }
3185 }
3186
3187 // When applying paragraph styles dynamically, don't change the text objects' attributes
3188 // since they will computed as needed. Only apply the character styling if it's _only_
3189 // character styling. This policy is subject to change and might be put under user control.
3190
3191 // Hm. we might well be applying a mix of paragraph and character styles, in which
3192 // case we _do_ want to apply character styles regardless of what para styles are set.
3193 // But if we're applying a paragraph style, which has some character attributes, but
3194 // we only want the paragraphs to hold this character style, then we _don't_ want to
3195 // apply the character style. So we need to be able to choose.
3196
3197 if (!parasOnly && (characterStyle|charactersOnly) && range.GetStart() != newPara->GetRange().GetEnd())
3198 {
3199 wxRichTextRange childRange(range);
3200 childRange.LimitTo(newPara->GetRange());
3201
3202 // Find the starting position and if necessary split it so
3203 // we can start applying a different style.
3204 // TODO: check that the style actually changes or is different
3205 // from style outside of range
3206 wxRichTextObject* firstObject wxDUMMY_INITIALIZE(NULL);
3207 wxRichTextObject* lastObject wxDUMMY_INITIALIZE(NULL);
3208
3209 if (childRange.GetStart() == newPara->GetRange().GetStart())
3210 firstObject = newPara->GetChildren().GetFirst()->GetData();
3211 else
3212 firstObject = newPara->SplitAt(range.GetStart());
3213
3214 // Increment by 1 because we're apply the style one _after_ the split point
3215 long splitPoint = childRange.GetEnd();
3216 if (splitPoint != newPara->GetRange().GetEnd())
3217 splitPoint ++;
3218
3219 // Find last object
3220 if (splitPoint == newPara->GetRange().GetEnd())
3221 lastObject = newPara->GetChildren().GetLast()->GetData();
3222 else
3223 // lastObject is set as a side-effect of splitting. It's
3224 // returned as the object before the new object.
3225 (void) newPara->SplitAt(splitPoint, & lastObject);
3226
3227 wxASSERT(firstObject != NULL);
3228 wxASSERT(lastObject != NULL);
3229
3230 if (!firstObject || !lastObject)
3231 continue;
3232
3233 wxRichTextObjectList::compatibility_iterator firstNode = newPara->GetChildren().Find(firstObject);
3234 wxRichTextObjectList::compatibility_iterator lastNode = newPara->GetChildren().Find(lastObject);
3235
3236 wxASSERT(firstNode);
3237 wxASSERT(lastNode);
3238
3239 wxRichTextObjectList::compatibility_iterator node2 = firstNode;
3240
3241 while (node2)
3242 {
3243 wxRichTextObject* child = node2->GetData();
3244
3245 if (removeStyle)
3246 {
3247 // Removes the given style from the paragraph
3248 wxRichTextRemoveStyle(child->GetAttributes(), style);
3249 }
3250 else if (resetExistingStyle)
3251 child->GetAttributes() = characterAttributes;
3252 else
3253 {
3254 if (applyMinimal)
3255 {
3256 // Only apply attributes that will make a difference to the combined
3257 // style as seen on the display
3258 wxRichTextAttr combinedAttr(newPara->GetCombinedAttributes(child->GetAttributes(), true));
3259 wxRichTextApplyStyle(child->GetAttributes(), characterAttributes, & combinedAttr);
3260 }
3261 else
3262 wxRichTextApplyStyle(child->GetAttributes(), characterAttributes);
3263 }
3264
3265 if (node2 == lastNode)
3266 break;
3267
3268 node2 = node2->GetNext();
3269 }
3270 }
3271 }
3272 }
3273
3274 node = node->GetNext();
3275 }
3276
3277 // Do action, or delay it until end of batch.
3278 if (haveControl && withUndo)
3279 buffer->SubmitAction(action);
3280
3281 return true;
3282 }
3283
3284 // Just change the attributes for this single object.
3285 void wxRichTextParagraphLayoutBox::SetStyle(wxRichTextObject* obj, const wxRichTextAttr& textAttr, int flags)
3286 {
3287 wxRichTextBuffer* buffer = GetBuffer();
3288 bool withUndo = flags & wxRICHTEXT_SETSTYLE_WITH_UNDO;
3289 bool resetExistingStyle = ((flags & wxRICHTEXT_SETSTYLE_RESET) != 0);
3290 bool haveControl = (buffer->GetRichTextCtrl() != NULL);
3291
3292 wxRichTextAction *action = NULL;
3293 wxRichTextAttr newAttr = obj->GetAttributes();
3294 if (resetExistingStyle)
3295 newAttr = textAttr;
3296 else
3297 newAttr.Apply(textAttr);
3298
3299 if (haveControl && withUndo)
3300 {
3301 action = new wxRichTextAction(NULL, _("Change Object Style"), wxRICHTEXT_CHANGE_ATTRIBUTES, buffer, obj->GetContainer(), buffer->GetRichTextCtrl());
3302 action->SetRange(obj->GetRange().FromInternal());
3303 action->SetPosition(buffer->GetRichTextCtrl()->GetCaretPosition());
3304 action->MakeObject(obj);
3305
3306 action->GetAttributes() = newAttr;
3307 }
3308 else
3309 obj->GetAttributes() = newAttr;
3310
3311 if (haveControl && withUndo)
3312 buffer->SubmitAction(action);
3313 }
3314
3315 /// Get the text attributes for this position.
3316 bool wxRichTextParagraphLayoutBox::GetStyle(long position, wxRichTextAttr& style)
3317 {
3318 return DoGetStyle(position, style, true);
3319 }
3320
3321 bool wxRichTextParagraphLayoutBox::GetUncombinedStyle(long position, wxRichTextAttr& style)
3322 {
3323 return DoGetStyle(position, style, false);
3324 }
3325
3326 /// Implementation helper for GetStyle. If combineStyles is true, combine base, paragraph and
3327 /// context attributes.
3328 bool wxRichTextParagraphLayoutBox::DoGetStyle(long position, wxRichTextAttr& style, bool combineStyles)
3329 {
3330 wxRichTextObject* obj wxDUMMY_INITIALIZE(NULL);
3331
3332 if (style.IsParagraphStyle())
3333 {
3334 obj = GetParagraphAtPosition(position);
3335 if (obj)
3336 {
3337 if (combineStyles)
3338 {
3339 // Start with the base style
3340 style = GetAttributes();
3341
3342 // Apply the paragraph style
3343 wxRichTextApplyStyle(style, obj->GetAttributes());
3344 }
3345 else
3346 style = obj->GetAttributes();
3347
3348 return true;
3349 }
3350 }
3351 else
3352 {
3353 obj = GetLeafObjectAtPosition(position);
3354 if (obj)
3355 {
3356 if (combineStyles)
3357 {
3358 wxRichTextParagraph* para = wxDynamicCast(obj->GetParent(), wxRichTextParagraph);
3359 style = para ? para->GetCombinedAttributes(obj->GetAttributes()) : obj->GetAttributes();
3360 }
3361 else
3362 style = obj->GetAttributes();
3363
3364 return true;
3365 }
3366 }
3367 return false;
3368 }
3369
3370 static bool wxHasStyle(long flags, long style)
3371 {
3372 return (flags & style) != 0;
3373 }
3374
3375 /// Combines 'style' with 'currentStyle' for the purpose of summarising the attributes of a range of
3376 /// content.
3377 bool wxRichTextParagraphLayoutBox::CollectStyle(wxRichTextAttr& currentStyle, const wxRichTextAttr& style, wxRichTextAttr& clashingAttr, wxRichTextAttr& absentAttr)
3378 {
3379 currentStyle.CollectCommonAttributes(style, clashingAttr, absentAttr);
3380
3381 return true;
3382 }
3383
3384 /// Get the combined style for a range - if any attribute is different within the range,
3385 /// that attribute is not present within the flags.
3386 /// *** Note that this is not recursive, and so assumes that content inside a paragraph is not itself
3387 /// nested.
3388 bool wxRichTextParagraphLayoutBox::GetStyleForRange(const wxRichTextRange& range, wxRichTextAttr& style)
3389 {
3390 style = wxRichTextAttr();
3391
3392 wxRichTextAttr clashingAttr;
3393 wxRichTextAttr absentAttrPara, absentAttrChar;
3394
3395 wxRichTextObjectList::compatibility_iterator node = GetChildren().GetFirst();
3396 while (node)
3397 {
3398 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3399 if (para && !(para->GetRange().GetStart() > range.GetEnd() || para->GetRange().GetEnd() < range.GetStart()))
3400 {
3401 if (para->GetChildren().GetCount() == 0)
3402 {
3403 wxRichTextAttr paraStyle = para->GetCombinedAttributes(true /* use box attributes */);
3404
3405 CollectStyle(style, paraStyle, clashingAttr, absentAttrPara);
3406 }
3407 else
3408 {
3409 wxRichTextRange paraRange(para->GetRange());
3410 paraRange.LimitTo(range);
3411
3412 // First collect paragraph attributes only
3413 wxRichTextAttr paraStyle = para->GetCombinedAttributes();
3414 paraStyle.SetFlags(paraStyle.GetFlags() & wxTEXT_ATTR_PARAGRAPH);
3415 CollectStyle(style, paraStyle, clashingAttr, absentAttrPara);
3416
3417 wxRichTextObjectList::compatibility_iterator childNode = para->GetChildren().GetFirst();
3418
3419 while (childNode)
3420 {
3421 wxRichTextObject* child = childNode->GetData();
3422 if (!(child->GetRange().GetStart() > range.GetEnd() || child->GetRange().GetEnd() < range.GetStart()))
3423 {
3424 wxRichTextAttr childStyle = para->GetCombinedAttributes(child->GetAttributes(), true /* include box attributes */);
3425
3426 // Now collect character attributes only
3427 childStyle.SetFlags(childStyle.GetFlags() & wxTEXT_ATTR_CHARACTER);
3428
3429 CollectStyle(style, childStyle, clashingAttr, absentAttrChar);
3430 }
3431
3432 childNode = childNode->GetNext();
3433 }
3434 }
3435 }
3436 node = node->GetNext();
3437 }
3438 return true;
3439 }
3440
3441 /// Set default style
3442 bool wxRichTextParagraphLayoutBox::SetDefaultStyle(const wxRichTextAttr& style)
3443 {
3444 m_defaultAttributes = style;
3445 return true;
3446 }
3447
3448 /// Test if this whole range has character attributes of the specified kind. If any
3449 /// of the attributes are different within the range, the test fails. You
3450 /// can use this to implement, for example, bold button updating. style must have
3451 /// flags indicating which attributes are of interest.
3452 bool wxRichTextParagraphLayoutBox::HasCharacterAttributes(const wxRichTextRange& range, const wxRichTextAttr& style) const
3453 {
3454 int foundCount = 0;
3455 int matchingCount = 0;
3456
3457 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3458 while (node)
3459 {
3460 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3461 // wxASSERT (para != NULL);
3462
3463 if (para)
3464 {
3465 // Stop searching if we're beyond the range of interest
3466 if (para->GetRange().GetStart() > range.GetEnd())
3467 return foundCount == matchingCount && foundCount != 0;
3468
3469 if (!para->GetRange().IsOutside(range))
3470 {
3471 wxRichTextObjectList::compatibility_iterator node2 = para->GetChildren().GetFirst();
3472
3473 while (node2)
3474 {
3475 wxRichTextObject* child = node2->GetData();
3476 // Allow for empty string if no buffer
3477 wxRichTextRange childRange = child->GetRange();
3478 if (childRange.GetLength() == 0 && GetRange().GetLength() == 1)
3479 childRange.SetEnd(childRange.GetEnd()+1);
3480
3481 if (!childRange.IsOutside(range) && child->IsKindOf(CLASSINFO(wxRichTextPlainText)))
3482 {
3483 foundCount ++;
3484 wxRichTextAttr textAttr = para->GetCombinedAttributes(child->GetAttributes());
3485
3486 if (wxTextAttrEqPartial(textAttr, style))
3487 matchingCount ++;
3488 }
3489
3490 node2 = node2->GetNext();
3491 }
3492 }
3493 }
3494
3495 node = node->GetNext();
3496 }
3497
3498 return foundCount == matchingCount && foundCount != 0;
3499 }
3500
3501 /// Test if this whole range has paragraph attributes of the specified kind. If any
3502 /// of the attributes are different within the range, the test fails. You
3503 /// can use this to implement, for example, centering button updating. style must have
3504 /// flags indicating which attributes are of interest.
3505 bool wxRichTextParagraphLayoutBox::HasParagraphAttributes(const wxRichTextRange& range, const wxRichTextAttr& style) const
3506 {
3507 int foundCount = 0;
3508 int matchingCount = 0;
3509
3510 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3511 while (node)
3512 {
3513 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3514 // wxASSERT (para != NULL);
3515
3516 if (para)
3517 {
3518 // Stop searching if we're beyond the range of interest
3519 if (para->GetRange().GetStart() > range.GetEnd())
3520 return foundCount == matchingCount && foundCount != 0;
3521
3522 if (!para->GetRange().IsOutside(range))
3523 {
3524 wxRichTextAttr textAttr = GetAttributes();
3525 // Apply the paragraph style
3526 wxRichTextApplyStyle(textAttr, para->GetAttributes());
3527
3528 foundCount ++;
3529 if (wxTextAttrEqPartial(textAttr, style))
3530 matchingCount ++;
3531 }
3532 }
3533
3534 node = node->GetNext();
3535 }
3536 return foundCount == matchingCount && foundCount != 0;
3537 }
3538
3539 void wxRichTextParagraphLayoutBox::PrepareContent(wxRichTextParagraphLayoutBox& container)
3540 {
3541 wxRichTextBuffer* buffer = GetBuffer();
3542 if (buffer && buffer->GetRichTextCtrl())
3543 buffer->GetRichTextCtrl()->PrepareContent(container);
3544 }
3545
3546 /// Set character or paragraph properties
3547 bool wxRichTextParagraphLayoutBox::SetProperties(const wxRichTextRange& range, const wxRichTextProperties& properties, int flags)
3548 {
3549 wxRichTextBuffer* buffer = GetBuffer();
3550
3551 bool withUndo = ((flags & wxRICHTEXT_SETPROPERTIES_WITH_UNDO) != 0);
3552 bool parasOnly = ((flags & wxRICHTEXT_SETPROPERTIES_PARAGRAPHS_ONLY) != 0);
3553 bool charactersOnly = ((flags & wxRICHTEXT_SETPROPERTIES_CHARACTERS_ONLY) != 0);
3554 bool resetExistingProperties = ((flags & wxRICHTEXT_SETPROPERTIES_RESET) != 0);
3555 bool removeProperties = ((flags & wxRICHTEXT_SETPROPERTIES_REMOVE) != 0);
3556
3557 // If we are associated with a control, make undoable; otherwise, apply immediately
3558 // to the data.
3559
3560 bool haveControl = (buffer->GetRichTextCtrl() != NULL);
3561
3562 wxRichTextAction* action = NULL;
3563
3564 if (haveControl && withUndo)
3565 {
3566 action = new wxRichTextAction(NULL, _("Change Properties"), wxRICHTEXT_CHANGE_PROPERTIES, buffer, this, buffer->GetRichTextCtrl());
3567 action->SetRange(range);
3568 action->SetPosition(buffer->GetRichTextCtrl()->GetCaretPosition());
3569 }
3570
3571 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3572 while (node)
3573 {
3574 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3575 // wxASSERT (para != NULL);
3576
3577 if (para && para->GetChildCount() > 0)
3578 {
3579 // Stop searching if we're beyond the range of interest
3580 if (para->GetRange().GetStart() > range.GetEnd())
3581 break;
3582
3583 if (!para->GetRange().IsOutside(range))
3584 {
3585 // We'll be using a copy of the paragraph to make style changes,
3586 // not updating the buffer directly.
3587 wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL);
3588
3589 if (haveControl && withUndo)
3590 {
3591 newPara = new wxRichTextParagraph(*para);
3592 action->GetNewParagraphs().AppendChild(newPara);
3593
3594 // Also store the old ones for Undo
3595 action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para));
3596 }
3597 else
3598 newPara = para;
3599
3600 if (parasOnly)
3601 {
3602 if (removeProperties)
3603 {
3604 // Removes the given style from the paragraph
3605 // TODO
3606 newPara->GetProperties().RemoveProperties(properties);
3607 }
3608 else if (resetExistingProperties)
3609 newPara->GetProperties() = properties;
3610 else
3611 newPara->GetProperties().MergeProperties(properties);
3612 }
3613
3614 // When applying paragraph styles dynamically, don't change the text objects' attributes
3615 // since they will computed as needed. Only apply the character styling if it's _only_
3616 // character styling. This policy is subject to change and might be put under user control.
3617
3618 // Hm. we might well be applying a mix of paragraph and character styles, in which
3619 // case we _do_ want to apply character styles regardless of what para styles are set.
3620 // But if we're applying a paragraph style, which has some character attributes, but
3621 // we only want the paragraphs to hold this character style, then we _don't_ want to
3622 // apply the character style. So we need to be able to choose.
3623
3624 if (!parasOnly && charactersOnly && range.GetStart() != newPara->GetRange().GetEnd())
3625 {
3626 wxRichTextRange childRange(range);
3627 childRange.LimitTo(newPara->GetRange());
3628
3629 // Find the starting position and if necessary split it so
3630 // we can start applying different properties.
3631 // TODO: check that the properties actually change or are different
3632 // from properties outside of range
3633 wxRichTextObject* firstObject wxDUMMY_INITIALIZE(NULL);
3634 wxRichTextObject* lastObject wxDUMMY_INITIALIZE(NULL);
3635
3636 if (childRange.GetStart() == newPara->GetRange().GetStart())
3637 firstObject = newPara->GetChildren().GetFirst()->GetData();
3638 else
3639 firstObject = newPara->SplitAt(range.GetStart());
3640
3641 // Increment by 1 because we're apply the style one _after_ the split point
3642 long splitPoint = childRange.GetEnd();
3643 if (splitPoint != newPara->GetRange().GetEnd())
3644 splitPoint ++;
3645
3646 // Find last object
3647 if (splitPoint == newPara->GetRange().GetEnd())
3648 lastObject = newPara->GetChildren().GetLast()->GetData();
3649 else
3650 // lastObject is set as a side-effect of splitting. It's
3651 // returned as the object before the new object.
3652 (void) newPara->SplitAt(splitPoint, & lastObject);
3653
3654 wxASSERT(firstObject != NULL);
3655 wxASSERT(lastObject != NULL);
3656
3657 if (!firstObject || !lastObject)
3658 continue;
3659
3660 wxRichTextObjectList::compatibility_iterator firstNode = newPara->GetChildren().Find(firstObject);
3661 wxRichTextObjectList::compatibility_iterator lastNode = newPara->GetChildren().Find(lastObject);
3662
3663 wxASSERT(firstNode);
3664 wxASSERT(lastNode);
3665
3666 wxRichTextObjectList::compatibility_iterator node2 = firstNode;
3667
3668 while (node2)
3669 {
3670 wxRichTextObject* child = node2->GetData();
3671
3672 if (removeProperties)
3673 {
3674 // Removes the given properties from the paragraph
3675 child->GetProperties().RemoveProperties(properties);
3676 }
3677 else if (resetExistingProperties)
3678 child->GetProperties() = properties;
3679 else
3680 {
3681 child->GetProperties().MergeProperties(properties);
3682 }
3683
3684 if (node2 == lastNode)
3685 break;
3686
3687 node2 = node2->GetNext();
3688 }
3689 }
3690 }
3691 }
3692
3693 node = node->GetNext();
3694 }
3695
3696 // Do action, or delay it until end of batch.
3697 if (haveControl && withUndo)
3698 buffer->SubmitAction(action);
3699
3700 return true;
3701 }
3702
3703 void wxRichTextParagraphLayoutBox::Reset()
3704 {
3705 Clear();
3706
3707 wxRichTextBuffer* buffer = GetBuffer();
3708 if (buffer && buffer->GetRichTextCtrl())
3709 {
3710 wxRichTextEvent event(wxEVT_COMMAND_RICHTEXT_BUFFER_RESET, buffer->GetRichTextCtrl()->GetId());
3711 event.SetEventObject(buffer->GetRichTextCtrl());
3712 event.SetContainer(this);
3713
3714 buffer->SendEvent(event, true);
3715 }
3716
3717 AddParagraph(wxEmptyString);
3718
3719 PrepareContent(*this);
3720
3721 InvalidateHierarchy(wxRICHTEXT_ALL);
3722 }
3723
3724 /// Invalidate the buffer. With no argument, invalidates whole buffer.
3725 void wxRichTextParagraphLayoutBox::Invalidate(const wxRichTextRange& invalidRange)
3726 {
3727 wxRichTextCompositeObject::Invalidate(invalidRange);
3728
3729 DoInvalidate(invalidRange);
3730 }
3731
3732 // Do the (in)validation for this object only
3733 void wxRichTextParagraphLayoutBox::DoInvalidate(const wxRichTextRange& invalidRange)
3734 {
3735 if (invalidRange == wxRICHTEXT_ALL)
3736 {
3737 m_invalidRange = wxRICHTEXT_ALL;
3738 }
3739 // Already invalidating everything
3740 else if (m_invalidRange == wxRICHTEXT_ALL)
3741 {
3742 }
3743 else
3744 {
3745 if ((invalidRange.GetStart() < m_invalidRange.GetStart()) || m_invalidRange.GetStart() == -1)
3746 m_invalidRange.SetStart(invalidRange.GetStart());
3747 if (invalidRange.GetEnd() > m_invalidRange.GetEnd())
3748 m_invalidRange.SetEnd(invalidRange.GetEnd());
3749 }
3750 }
3751
3752 // Do the (in)validation both up and down the hierarchy
3753 void wxRichTextParagraphLayoutBox::InvalidateHierarchy(const wxRichTextRange& invalidRange)
3754 {
3755 Invalidate(invalidRange);
3756
3757 if (invalidRange != wxRICHTEXT_NONE)
3758 {
3759 // Now go up the hierarchy
3760 wxRichTextObject* thisObj = this;
3761 wxRichTextObject* p = GetParent();
3762 while (p)
3763 {
3764 wxRichTextParagraphLayoutBox* l = wxDynamicCast(p, wxRichTextParagraphLayoutBox);
3765 if (l)
3766 l->DoInvalidate(thisObj->GetRange());
3767
3768 thisObj = p;
3769 p = p->GetParent();
3770 }
3771 }
3772 }
3773
3774 /// Get invalid range, rounding to entire paragraphs if argument is true.
3775 wxRichTextRange wxRichTextParagraphLayoutBox::GetInvalidRange(bool wholeParagraphs) const
3776 {
3777 if (m_invalidRange == wxRICHTEXT_ALL || m_invalidRange == wxRICHTEXT_NONE)
3778 return m_invalidRange;
3779
3780 wxRichTextRange range = m_invalidRange;
3781
3782 if (wholeParagraphs)
3783 {
3784 wxRichTextParagraph* para1 = GetParagraphAtPosition(range.GetStart());
3785 if (para1)
3786 range.SetStart(para1->GetRange().GetStart());
3787 // floating layout make all child should be relayout
3788 range.SetEnd(GetOwnRange().GetEnd());
3789 }
3790 return range;
3791 }
3792
3793 /// Apply the style sheet to the buffer, for example if the styles have changed.
3794 bool wxRichTextParagraphLayoutBox::ApplyStyleSheet(wxRichTextStyleSheet* styleSheet)
3795 {
3796 wxASSERT(styleSheet != NULL);
3797 if (!styleSheet)
3798 return false;
3799
3800 int foundCount = 0;
3801
3802 wxRichTextAttr attr(GetBasicStyle());
3803 if (GetBasicStyle().HasParagraphStyleName())
3804 {
3805 wxRichTextParagraphStyleDefinition* paraDef = styleSheet->FindParagraphStyle(GetBasicStyle().GetParagraphStyleName());
3806 if (paraDef)
3807 {
3808 attr.Apply(paraDef->GetStyleMergedWithBase(styleSheet));
3809 SetBasicStyle(attr);
3810 foundCount ++;
3811 }
3812 }
3813
3814 if (GetBasicStyle().HasCharacterStyleName())
3815 {
3816 wxRichTextCharacterStyleDefinition* charDef = styleSheet->FindCharacterStyle(GetBasicStyle().GetCharacterStyleName());
3817 if (charDef)
3818 {
3819 attr.Apply(charDef->GetStyleMergedWithBase(styleSheet));
3820 SetBasicStyle(attr);
3821 foundCount ++;
3822 }
3823 }
3824
3825 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3826 while (node)
3827 {
3828 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3829 // wxASSERT (para != NULL);
3830
3831 if (para)
3832 {
3833 // Combine paragraph and list styles. If there is a list style in the original attributes,
3834 // the current indentation overrides anything else and is used to find the item indentation.
3835 // Also, for applying paragraph styles, consider having 2 modes: (1) we merge with what we have,
3836 // thereby taking into account all user changes, (2) reset the style completely (except for indentation/list
3837 // exception as above).
3838 // Problem: when changing from one list style to another, there's a danger that the level info will get lost.
3839 // So when changing a list style interactively, could retrieve level based on current style, then
3840 // set appropriate indent and apply new style.
3841
3842 int outline = -1;
3843 int num = -1;
3844 if (para->GetAttributes().HasOutlineLevel())
3845 outline = para->GetAttributes().GetOutlineLevel();
3846 if (para->GetAttributes().HasBulletNumber())
3847 num = para->GetAttributes().GetBulletNumber();
3848
3849 if (!para->GetAttributes().GetParagraphStyleName().IsEmpty() && !para->GetAttributes().GetListStyleName().IsEmpty())
3850 {
3851 int currentIndent = para->GetAttributes().GetLeftIndent();
3852
3853 wxRichTextParagraphStyleDefinition* paraDef = styleSheet->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName());
3854 wxRichTextListStyleDefinition* listDef = styleSheet->FindListStyle(para->GetAttributes().GetListStyleName());
3855 if (paraDef && !listDef)
3856 {
3857 para->GetAttributes() = paraDef->GetStyleMergedWithBase(styleSheet);
3858 foundCount ++;
3859 }
3860 else if (listDef && !paraDef)
3861 {
3862 // Set overall style defined for the list style definition
3863 para->GetAttributes() = listDef->GetStyleMergedWithBase(styleSheet);
3864
3865 // Apply the style for this level
3866 wxRichTextApplyStyle(para->GetAttributes(), * listDef->GetLevelAttributes(listDef->FindLevelForIndent(currentIndent)));
3867 foundCount ++;
3868 }
3869 else if (listDef && paraDef)
3870 {
3871 // Combines overall list style, style for level, and paragraph style
3872 para->GetAttributes() = listDef->CombineWithParagraphStyle(currentIndent, paraDef->GetStyleMergedWithBase(styleSheet));
3873 foundCount ++;
3874 }
3875 }
3876 else if (para->GetAttributes().GetParagraphStyleName().IsEmpty() && !para->GetAttributes().GetListStyleName().IsEmpty())
3877 {
3878 int currentIndent = para->GetAttributes().GetLeftIndent();
3879
3880 wxRichTextListStyleDefinition* listDef = styleSheet->FindListStyle(para->GetAttributes().GetListStyleName());
3881
3882 // Overall list definition style
3883 para->GetAttributes() = listDef->GetStyleMergedWithBase(styleSheet);
3884
3885 // Style for this level
3886 wxRichTextApplyStyle(para->GetAttributes(), * listDef->GetLevelAttributes(listDef->FindLevelForIndent(currentIndent)));
3887
3888 foundCount ++;
3889 }
3890 else if (!para->GetAttributes().GetParagraphStyleName().IsEmpty() && para->GetAttributes().GetListStyleName().IsEmpty())
3891 {
3892 wxRichTextParagraphStyleDefinition* def = styleSheet->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName());
3893 if (def)
3894 {
3895 para->GetAttributes() = def->GetStyleMergedWithBase(styleSheet);
3896 foundCount ++;
3897 }
3898 }
3899
3900 if (outline != -1)
3901 para->GetAttributes().SetOutlineLevel(outline);
3902 if (num != -1)
3903 para->GetAttributes().SetBulletNumber(num);
3904 }
3905
3906 node = node->GetNext();
3907 }
3908 return foundCount != 0;
3909 }
3910
3911 /// Set list style
3912 bool wxRichTextParagraphLayoutBox::SetListStyle(const wxRichTextRange& range, wxRichTextListStyleDefinition* def, int flags, int startFrom, int specifiedLevel)
3913 {
3914 wxRichTextBuffer* buffer = GetBuffer();
3915 wxRichTextStyleSheet* styleSheet = buffer->GetStyleSheet();
3916
3917 bool withUndo = ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0);
3918 // bool applyMinimal = ((flags & wxRICHTEXT_SETSTYLE_OPTIMIZE) != 0);
3919 bool specifyLevel = ((flags & wxRICHTEXT_SETSTYLE_SPECIFY_LEVEL) != 0);
3920 bool renumber = ((flags & wxRICHTEXT_SETSTYLE_RENUMBER) != 0);
3921
3922 // Current number, if numbering
3923 int n = startFrom;
3924
3925 wxASSERT (!specifyLevel || (specifyLevel && (specifiedLevel >= 0)));
3926
3927 // If we are associated with a control, make undoable; otherwise, apply immediately
3928 // to the data.
3929
3930 bool haveControl = (buffer->GetRichTextCtrl() != NULL);
3931
3932 wxRichTextAction* action = NULL;
3933
3934 if (haveControl && withUndo)
3935 {
3936 action = new wxRichTextAction(NULL, _("Change List Style"), wxRICHTEXT_CHANGE_STYLE, buffer, this, buffer->GetRichTextCtrl());
3937 action->SetRange(range);
3938 action->SetPosition(buffer->GetRichTextCtrl()->GetCaretPosition());
3939 }
3940
3941 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3942 while (node)
3943 {
3944 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3945 // wxASSERT (para != NULL);
3946
3947 if (para && para->GetChildCount() > 0)
3948 {
3949 // Stop searching if we're beyond the range of interest
3950 if (para->GetRange().GetStart() > range.GetEnd())
3951 break;
3952
3953 if (!para->GetRange().IsOutside(range))
3954 {
3955 // We'll be using a copy of the paragraph to make style changes,
3956 // not updating the buffer directly.
3957 wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL);
3958
3959 if (haveControl && withUndo)
3960 {
3961 newPara = new wxRichTextParagraph(*para);
3962 action->GetNewParagraphs().AppendChild(newPara);
3963
3964 // Also store the old ones for Undo
3965 action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para));
3966 }
3967 else
3968 newPara = para;
3969
3970 if (def)
3971 {
3972 int thisIndent = newPara->GetAttributes().GetLeftIndent();
3973 int thisLevel = specifyLevel ? specifiedLevel : def->FindLevelForIndent(thisIndent);
3974
3975 // How is numbering going to work?
3976 // If we are renumbering, or numbering for the first time, we need to keep
3977 // track of the number for each level. But we might be simply applying a different
3978 // list style.
3979 // In Word, applying a style to several paragraphs, even if at different levels,
3980 // reverts the level back to the same one. So we could do the same here.
3981 // Renumbering will need to be done when we promote/demote a paragraph.
3982
3983 // Apply the overall list style, and item style for this level
3984 wxRichTextAttr listStyle(def->GetCombinedStyleForLevel(thisLevel, styleSheet));
3985 wxRichTextApplyStyle(newPara->GetAttributes(), listStyle);
3986
3987 // Now we need to do numbering
3988 if (renumber)
3989 {
3990 newPara->GetAttributes().SetBulletNumber(n);
3991 }
3992
3993 n ++;
3994 }
3995 else if (!newPara->GetAttributes().GetListStyleName().IsEmpty())
3996 {
3997 // if def is NULL, remove list style, applying any associated paragraph style
3998 // to restore the attributes
3999
4000 newPara->GetAttributes().SetListStyleName(wxEmptyString);
4001 newPara->GetAttributes().SetLeftIndent(0, 0);
4002 newPara->GetAttributes().SetBulletText(wxEmptyString);
4003
4004 // Eliminate the main list-related attributes
4005 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);
4006
4007 if (styleSheet && !newPara->GetAttributes().GetParagraphStyleName().IsEmpty())
4008 {
4009 wxRichTextParagraphStyleDefinition* def = styleSheet->FindParagraphStyle(newPara->GetAttributes().GetParagraphStyleName());
4010 if (def)
4011 {
4012 newPara->GetAttributes() = def->GetStyleMergedWithBase(styleSheet);
4013 }
4014 }
4015 }
4016 }
4017 }
4018
4019 node = node->GetNext();
4020 }
4021
4022 // Do action, or delay it until end of batch.
4023 if (haveControl && withUndo)
4024 buffer->SubmitAction(action);
4025
4026 return true;
4027 }
4028
4029 bool wxRichTextParagraphLayoutBox::SetListStyle(const wxRichTextRange& range, const wxString& defName, int flags, int startFrom, int specifiedLevel)
4030 {
4031 wxRichTextBuffer* buffer = GetBuffer();
4032 if (buffer && buffer->GetStyleSheet())
4033 {
4034 wxRichTextListStyleDefinition* def = buffer->GetStyleSheet()->FindListStyle(defName);
4035 if (def)
4036 return SetListStyle(range, def, flags, startFrom, specifiedLevel);
4037 }
4038 return false;
4039 }
4040
4041 /// Clear list for given range
4042 bool wxRichTextParagraphLayoutBox::ClearListStyle(const wxRichTextRange& range, int flags)
4043 {
4044 return SetListStyle(range, NULL, flags);
4045 }
4046
4047 /// Number/renumber any list elements in the given range
4048 bool wxRichTextParagraphLayoutBox::NumberList(const wxRichTextRange& range, wxRichTextListStyleDefinition* def, int flags, int startFrom, int specifiedLevel)
4049 {
4050 return DoNumberList(range, range, 0, def, flags, startFrom, specifiedLevel);
4051 }
4052
4053 /// Number/renumber any list elements in the given range. Also do promotion or demotion of items, if specified
4054 bool wxRichTextParagraphLayoutBox::DoNumberList(const wxRichTextRange& range, const wxRichTextRange& promotionRange, int promoteBy,
4055 wxRichTextListStyleDefinition* def, int flags, int startFrom, int specifiedLevel)
4056 {
4057 wxRichTextBuffer* buffer = GetBuffer();
4058 wxRichTextStyleSheet* styleSheet = buffer->GetStyleSheet();
4059
4060 bool withUndo = ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0);
4061 // bool applyMinimal = ((flags & wxRICHTEXT_SETSTYLE_OPTIMIZE) != 0);
4062 #if wxDEBUG_LEVEL
4063 bool specifyLevel = ((flags & wxRICHTEXT_SETSTYLE_SPECIFY_LEVEL) != 0);
4064 #endif
4065
4066 bool renumber = ((flags & wxRICHTEXT_SETSTYLE_RENUMBER) != 0);
4067
4068 // Max number of levels
4069 const int maxLevels = 10;
4070
4071 // The level we're looking at now
4072 int currentLevel = -1;
4073
4074 // The item number for each level
4075 int levels[maxLevels];
4076 int i;
4077
4078 // Reset all numbering
4079 for (i = 0; i < maxLevels; i++)
4080 {
4081 if (startFrom != -1)
4082 levels[i] = startFrom-1;
4083 else if (renumber) // start again
4084 levels[i] = 0;
4085 else
4086 levels[i] = -1; // start from the number we found, if any
4087 }
4088
4089 #if wxDEBUG_LEVEL
4090 wxASSERT(!specifyLevel || (specifyLevel && (specifiedLevel >= 0)));
4091 #endif
4092
4093 // If we are associated with a control, make undoable; otherwise, apply immediately
4094 // to the data.
4095
4096 bool haveControl = (buffer->GetRichTextCtrl() != NULL);
4097
4098 wxRichTextAction* action = NULL;
4099
4100 if (haveControl && withUndo)
4101 {
4102 action = new wxRichTextAction(NULL, _("Renumber List"), wxRICHTEXT_CHANGE_STYLE, buffer, this, buffer->GetRichTextCtrl());
4103 action->SetRange(range);
4104 action->SetPosition(buffer->GetRichTextCtrl()->GetCaretPosition());
4105 }
4106
4107 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
4108 while (node)
4109 {
4110 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
4111 // wxASSERT (para != NULL);
4112
4113 if (para && para->GetChildCount() > 0)
4114 {
4115 // Stop searching if we're beyond the range of interest
4116 if (para->GetRange().GetStart() > range.GetEnd())
4117 break;
4118
4119 if (!para->GetRange().IsOutside(range))
4120 {
4121 // We'll be using a copy of the paragraph to make style changes,
4122 // not updating the buffer directly.
4123 wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL);
4124
4125 if (haveControl && withUndo)
4126 {
4127 newPara = new wxRichTextParagraph(*para);
4128 action->GetNewParagraphs().AppendChild(newPara);
4129
4130 // Also store the old ones for Undo
4131 action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para));
4132 }
4133 else
4134 newPara = para;
4135
4136 wxRichTextListStyleDefinition* defToUse = def;
4137 if (!defToUse)
4138 {
4139 if (styleSheet && !newPara->GetAttributes().GetListStyleName().IsEmpty())
4140 defToUse = styleSheet->FindListStyle(newPara->GetAttributes().GetListStyleName());
4141 }
4142
4143 if (defToUse)
4144 {
4145 int thisIndent = newPara->GetAttributes().GetLeftIndent();
4146 int thisLevel = defToUse->FindLevelForIndent(thisIndent);
4147
4148 // If we've specified a level to apply to all, change the level.
4149 if (specifiedLevel != -1)
4150 thisLevel = specifiedLevel;
4151
4152 // Do promotion if specified
4153 if ((promoteBy != 0) && !para->GetRange().IsOutside(promotionRange))
4154 {
4155 thisLevel = thisLevel - promoteBy;
4156 if (thisLevel < 0)
4157 thisLevel = 0;
4158 if (thisLevel > 9)
4159 thisLevel = 9;
4160 }
4161
4162 // Apply the overall list style, and item style for this level
4163 wxRichTextAttr listStyle(defToUse->GetCombinedStyleForLevel(thisLevel, styleSheet));
4164 wxRichTextApplyStyle(newPara->GetAttributes(), listStyle);
4165
4166 // OK, we've (re)applied the style, now let's get the numbering right.
4167
4168 if (currentLevel == -1)
4169 currentLevel = thisLevel;
4170
4171 // Same level as before, do nothing except increment level's number afterwards
4172 if (currentLevel == thisLevel)
4173 {
4174 }
4175 // A deeper level: start renumbering all levels after current level
4176 else if (thisLevel > currentLevel)
4177 {
4178 for (i = currentLevel+1; i <= thisLevel; i++)
4179 {
4180 levels[i] = 0;
4181 }
4182 currentLevel = thisLevel;
4183 }
4184 else if (thisLevel < currentLevel)
4185 {
4186 currentLevel = thisLevel;
4187 }
4188
4189 // Use the current numbering if -1 and we have a bullet number already
4190 if (levels[currentLevel] == -1)
4191 {
4192 if (newPara->GetAttributes().HasBulletNumber())
4193 levels[currentLevel] = newPara->GetAttributes().GetBulletNumber();
4194 else
4195 levels[currentLevel] = 1;
4196 }
4197 else
4198 {
4199 levels[currentLevel] ++;
4200 }
4201
4202 newPara->GetAttributes().SetBulletNumber(levels[currentLevel]);
4203
4204 // Create the bullet text if an outline list
4205 if (listStyle.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE)
4206 {
4207 wxString text;
4208 for (i = 0; i <= currentLevel; i++)
4209 {
4210 if (!text.IsEmpty())
4211 text += wxT(".");
4212 text += wxString::Format(wxT("%d"), levels[i]);
4213 }
4214 newPara->GetAttributes().SetBulletText(text);
4215 }
4216 }
4217 }
4218 }
4219
4220 node = node->GetNext();
4221 }
4222
4223 // Do action, or delay it until end of batch.
4224 if (haveControl && withUndo)
4225 buffer->SubmitAction(action);
4226
4227 return true;
4228 }
4229
4230 bool wxRichTextParagraphLayoutBox::NumberList(const wxRichTextRange& range, const wxString& defName, int flags, int startFrom, int specifiedLevel)
4231 {
4232 wxRichTextBuffer* buffer = GetBuffer();
4233 if (buffer->GetStyleSheet())
4234 {
4235 wxRichTextListStyleDefinition* def = NULL;
4236 if (!defName.IsEmpty())
4237 def = buffer->GetStyleSheet()->FindListStyle(defName);
4238 return NumberList(range, def, flags, startFrom, specifiedLevel);
4239 }
4240 return false;
4241 }
4242
4243 /// Promote the list items within the given range. promoteBy can be a positive or negative number, e.g. 1 or -1
4244 bool wxRichTextParagraphLayoutBox::PromoteList(int promoteBy, const wxRichTextRange& range, wxRichTextListStyleDefinition* def, int flags, int specifiedLevel)
4245 {
4246 // TODO
4247 // One strategy is to first work out the range within which renumbering must occur. Then could pass these two ranges
4248 // to NumberList with a flag indicating promotion is required within one of the ranges.
4249 // Find first and last paragraphs in range. Then for first, calculate new indentation and look back until we find
4250 // a paragraph that either has no list style, or has one that is different or whose indentation is less.
4251 // We start renumbering from the para after that different para we found. We specify that the numbering of that
4252 // list position will start from 1.
4253 // Similarly, we look after the last para in the promote range for an indentation that is less (or no list style).
4254 // We can end the renumbering at this point.
4255
4256 // For now, only renumber within the promotion range.
4257
4258 return DoNumberList(range, range, promoteBy, def, flags, 1, specifiedLevel);
4259 }
4260
4261 bool wxRichTextParagraphLayoutBox::PromoteList(int promoteBy, const wxRichTextRange& range, const wxString& defName, int flags, int specifiedLevel)
4262 {
4263 wxRichTextBuffer* buffer = GetBuffer();
4264 if (buffer->GetStyleSheet())
4265 {
4266 wxRichTextListStyleDefinition* def = NULL;
4267 if (!defName.IsEmpty())
4268 def = buffer->GetStyleSheet()->FindListStyle(defName);
4269 return PromoteList(promoteBy, range, def, flags, specifiedLevel);
4270 }
4271 return false;
4272 }
4273
4274 /// Fills in the attributes for numbering a paragraph after previousParagraph. It also finds the
4275 /// position of the paragraph that it had to start looking from.
4276 bool wxRichTextParagraphLayoutBox::FindNextParagraphNumber(wxRichTextParagraph* previousParagraph, wxRichTextAttr& attr) const
4277 {
4278 if (!previousParagraph->GetAttributes().HasFlag(wxTEXT_ATTR_BULLET_STYLE) || previousParagraph->GetAttributes().GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_NONE)
4279 return false;
4280
4281 wxRichTextBuffer* buffer = GetBuffer();
4282 wxRichTextStyleSheet* styleSheet = buffer->GetStyleSheet();
4283 if (styleSheet && !previousParagraph->GetAttributes().GetListStyleName().IsEmpty())
4284 {
4285 wxRichTextListStyleDefinition* def = styleSheet->FindListStyle(previousParagraph->GetAttributes().GetListStyleName());
4286 if (def)
4287 {
4288 // int thisIndent = previousParagraph->GetAttributes().GetLeftIndent();
4289 // int thisLevel = def->FindLevelForIndent(thisIndent);
4290
4291 bool isOutline = (previousParagraph->GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE) != 0;
4292
4293 attr.SetFlags(previousParagraph->GetAttributes().GetFlags() & (wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_BULLET_NUMBER|wxTEXT_ATTR_BULLET_TEXT|wxTEXT_ATTR_BULLET_NAME));
4294 if (previousParagraph->GetAttributes().HasBulletName())
4295 attr.SetBulletName(previousParagraph->GetAttributes().GetBulletName());
4296 attr.SetBulletStyle(previousParagraph->GetAttributes().GetBulletStyle());
4297 attr.SetListStyleName(previousParagraph->GetAttributes().GetListStyleName());
4298
4299 int nextNumber = previousParagraph->GetAttributes().GetBulletNumber() + 1;
4300 attr.SetBulletNumber(nextNumber);
4301
4302 if (isOutline)
4303 {
4304 wxString text = previousParagraph->GetAttributes().GetBulletText();
4305 if (!text.IsEmpty())
4306 {
4307 int pos = text.Find(wxT('.'), true);
4308 if (pos != wxNOT_FOUND)
4309 {
4310 text = text.Mid(0, text.Length() - pos - 1);
4311 }
4312 else
4313 text = wxEmptyString;
4314 if (!text.IsEmpty())
4315 text += wxT(".");
4316 text += wxString::Format(wxT("%d"), nextNumber);
4317 attr.SetBulletText(text);
4318 }
4319 }
4320
4321 return true;
4322 }
4323 else
4324 return false;
4325 }
4326 else
4327 return false;
4328 }
4329
4330 /*!
4331 * wxRichTextParagraph
4332 * This object represents a single paragraph (or in a straight text editor, a line).
4333 */
4334
4335 IMPLEMENT_DYNAMIC_CLASS(wxRichTextParagraph, wxRichTextCompositeObject)
4336
4337 wxArrayInt wxRichTextParagraph::sm_defaultTabs;
4338
4339 wxRichTextParagraph::wxRichTextParagraph(wxRichTextObject* parent, wxRichTextAttr* style):
4340 wxRichTextCompositeObject(parent)
4341 {
4342 if (style)
4343 SetAttributes(*style);
4344 }
4345
4346 wxRichTextParagraph::wxRichTextParagraph(const wxString& text, wxRichTextObject* parent, wxRichTextAttr* paraStyle, wxRichTextAttr* charStyle):
4347 wxRichTextCompositeObject(parent)
4348 {
4349 if (paraStyle)
4350 SetAttributes(*paraStyle);
4351
4352 AppendChild(new wxRichTextPlainText(text, this, charStyle));
4353 }
4354
4355 wxRichTextParagraph::~wxRichTextParagraph()
4356 {
4357 ClearLines();
4358 }
4359
4360 /// Draw the item
4361 bool wxRichTextParagraph::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int WXUNUSED(descent), int style)
4362 {
4363 if (!IsShown())
4364 return true;
4365
4366 // Currently we don't merge these attributes with the parent, but we
4367 // should consider whether we should (e.g. if we set a border colour
4368 // for all paragraphs). But generally box attributes are likely to be
4369 // different for different objects.
4370 wxRect paraRect = GetRect();
4371 wxRichTextAttr attr = GetCombinedAttributes();
4372 context.ApplyVirtualAttributes(attr, this);
4373
4374 DrawBoxAttributes(dc, GetBuffer(), attr, paraRect);
4375
4376 // Draw the bullet, if any
4377 if (attr.GetBulletStyle() != wxTEXT_ATTR_BULLET_STYLE_NONE)
4378 {
4379 if (attr.GetLeftSubIndent() != 0)
4380 {
4381 int spaceBeforePara = ConvertTenthsMMToPixels(dc, attr.GetParagraphSpacingBefore());
4382 int leftIndent = ConvertTenthsMMToPixels(dc, attr.GetLeftIndent());
4383
4384 wxRichTextAttr bulletAttr(attr);
4385
4386 // Combine with the font of the first piece of content, if one is specified
4387 if (GetChildren().GetCount() > 0)
4388 {
4389 wxRichTextObject* firstObj = (wxRichTextObject*) GetChildren().GetFirst()->GetData();
4390 if (!firstObj->IsFloatable() && firstObj->GetAttributes().HasFont())
4391 {
4392 wxRichTextApplyStyle(bulletAttr, firstObj->GetAttributes());
4393 }
4394 }
4395
4396 // Get line height from first line, if any
4397 wxRichTextLine* line = m_cachedLines.GetFirst() ? (wxRichTextLine* ) m_cachedLines.GetFirst()->GetData() : NULL;
4398
4399 wxPoint linePos;
4400 int lineHeight wxDUMMY_INITIALIZE(0);
4401 if (line)
4402 {
4403 lineHeight = line->GetSize().y;
4404 linePos = line->GetPosition() + GetPosition();
4405 }
4406 else
4407 {
4408 wxFont font;
4409 if (bulletAttr.HasFont() && GetBuffer())
4410 font = GetBuffer()->GetFontTable().FindFont(bulletAttr);
4411 else
4412 font = (*wxNORMAL_FONT);
4413
4414 wxCheckSetFont(dc, font);
4415
4416 lineHeight = dc.GetCharHeight();
4417 linePos = GetPosition();
4418 linePos.y += spaceBeforePara;
4419 }
4420
4421 wxRect bulletRect(GetPosition().x + leftIndent, linePos.y, linePos.x - (GetPosition().x + leftIndent), lineHeight);
4422
4423 if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_BITMAP)
4424 {
4425 if (wxRichTextBuffer::GetRenderer())
4426 wxRichTextBuffer::GetRenderer()->DrawBitmapBullet(this, dc, bulletAttr, bulletRect);
4427 }
4428 else if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_STANDARD)
4429 {
4430 if (wxRichTextBuffer::GetRenderer())
4431 wxRichTextBuffer::GetRenderer()->DrawStandardBullet(this, dc, bulletAttr, bulletRect);
4432 }
4433 else
4434 {
4435 wxString bulletText = GetBulletText();
4436
4437 if (!bulletText.empty() && wxRichTextBuffer::GetRenderer())
4438 wxRichTextBuffer::GetRenderer()->DrawTextBullet(this, dc, bulletAttr, bulletRect, bulletText);
4439 }
4440 }
4441 }
4442
4443 // Draw the range for each line, one object at a time.
4444
4445 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
4446 while (node)
4447 {
4448 wxRichTextLine* line = node->GetData();
4449 wxRichTextRange lineRange = line->GetAbsoluteRange();
4450
4451 // Lines are specified relative to the paragraph
4452
4453 wxPoint linePosition = line->GetPosition() + GetPosition();
4454
4455 // Don't draw if off the screen
4456 if (((style & wxRICHTEXT_DRAW_IGNORE_CACHE) != 0) || ((linePosition.y + line->GetSize().y) >= rect.y && linePosition.y <= rect.y + rect.height))
4457 {
4458 wxPoint objectPosition = linePosition;
4459 int maxDescent = line->GetDescent();
4460
4461 // Loop through objects until we get to the one within range
4462 wxRichTextObjectList::compatibility_iterator node2 = m_children.GetFirst();
4463
4464 int i = 0;
4465 while (node2)
4466 {
4467 wxRichTextObject* child = node2->GetData();
4468
4469 if (!child->IsFloating() && child->GetRange().GetLength() > 0 && !child->GetRange().IsOutside(lineRange) && !lineRange.IsOutside(range))
4470 {
4471 // Draw this part of the line at the correct position
4472 wxRichTextRange objectRange(child->GetRange());
4473 objectRange.LimitTo(lineRange);
4474
4475 wxSize objectSize;
4476 if (child->IsTopLevel())
4477 {
4478 objectSize = child->GetCachedSize();
4479 objectRange = child->GetOwnRange();
4480 }
4481 else
4482 {
4483 #if wxRICHTEXT_USE_OPTIMIZED_LINE_DRAWING && wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
4484 if (i < (int) line->GetObjectSizes().GetCount())
4485 {
4486 objectSize.x = line->GetObjectSizes()[(size_t) i];
4487 }
4488 else
4489 #endif
4490 {
4491 int descent = 0;
4492 child->GetRangeSize(objectRange, objectSize, descent, dc, context, wxRICHTEXT_UNFORMATTED, objectPosition);
4493 }
4494 }
4495
4496 // Use the child object's width, but the whole line's height
4497 wxRect childRect(objectPosition, wxSize(objectSize.x, line->GetSize().y));
4498 child->Draw(dc, context, objectRange, selection, childRect, maxDescent, style);
4499
4500 objectPosition.x += objectSize.x;
4501 i ++;
4502 }
4503 else if (child->GetRange().GetStart() > lineRange.GetEnd())
4504 // Can break out of inner loop now since we've passed this line's range
4505 break;
4506
4507 node2 = node2->GetNext();
4508 }
4509 }
4510
4511 node = node->GetNext();
4512 }
4513
4514 return true;
4515 }
4516
4517 // Get the range width using partial extents calculated for the whole paragraph.
4518 static int wxRichTextGetRangeWidth(const wxRichTextParagraph& para, const wxRichTextRange& range, const wxArrayInt& partialExtents)
4519 {
4520 wxASSERT(partialExtents.GetCount() >= (size_t) range.GetLength());
4521
4522 if (partialExtents.GetCount() < (size_t) range.GetLength())
4523 return 0;
4524
4525 int leftMostPos = 0;
4526 if (range.GetStart() - para.GetRange().GetStart() > 0)
4527 leftMostPos = partialExtents[range.GetStart() - para.GetRange().GetStart() - 1];
4528
4529 int rightMostPos = partialExtents[range.GetEnd() - para.GetRange().GetStart()];
4530
4531 int w = rightMostPos - leftMostPos;
4532
4533 return w;
4534 }
4535
4536 /// Lay the item out
4537 bool wxRichTextParagraph::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& parentRect, int style)
4538 {
4539 // Deal with floating objects firstly before the normal layout
4540 wxRichTextBuffer* buffer = GetBuffer();
4541 wxASSERT(buffer);
4542 wxRichTextFloatCollector* collector = GetContainer()->GetFloatCollector();
4543 wxASSERT(collector);
4544 LayoutFloat(dc, context, rect, style, collector);
4545
4546 wxRichTextAttr attr = GetCombinedAttributes();
4547 context.ApplyVirtualAttributes(attr, this);
4548
4549 // ClearLines();
4550
4551 // Increase the size of the paragraph due to spacing
4552 int spaceBeforePara = ConvertTenthsMMToPixels(dc, attr.GetParagraphSpacingBefore());
4553 int spaceAfterPara = ConvertTenthsMMToPixels(dc, attr.GetParagraphSpacingAfter());
4554 int leftIndent = ConvertTenthsMMToPixels(dc, attr.GetLeftIndent());
4555 int leftSubIndent = ConvertTenthsMMToPixels(dc, attr.GetLeftSubIndent());
4556 int rightIndent = ConvertTenthsMMToPixels(dc, attr.GetRightIndent());
4557
4558 int lineSpacing = 0;
4559
4560 // Let's assume line spacing of 10 is normal, 15 is 1.5, 20 is 2, etc.
4561 if (attr.HasLineSpacing() && attr.GetLineSpacing() > 0 && attr.GetFont().IsOk())
4562 {
4563 wxCheckSetFont(dc, attr.GetFont());
4564 lineSpacing = (int) (double(dc.GetCharHeight()) * (double(attr.GetLineSpacing())/10.0 - 1.0));
4565 }
4566
4567 // Start position for each line relative to the paragraph
4568 int startPositionFirstLine = leftIndent;
4569 int startPositionSubsequentLines = leftIndent + leftSubIndent;
4570
4571 // If we have a bullet in this paragraph, the start position for the first line's text
4572 // is actually leftIndent + leftSubIndent.
4573 if (attr.GetBulletStyle() != wxTEXT_ATTR_BULLET_STYLE_NONE)
4574 startPositionFirstLine = startPositionSubsequentLines;
4575
4576 long lastEndPos = GetRange().GetStart()-1;
4577 long lastCompletedEndPos = lastEndPos;
4578
4579 int currentWidth = 0;
4580 SetPosition(rect.GetPosition());
4581
4582 wxPoint currentPosition(0, spaceBeforePara); // We will calculate lines relative to paragraph
4583 int lineHeight = 0;
4584 int maxWidth = 0;
4585 int maxHeight = currentPosition.y;
4586 int maxAscent = 0;
4587 int maxDescent = 0;
4588 int lineCount = 0;
4589 int lineAscent = 0;
4590 int lineDescent = 0;
4591
4592 wxRichTextObjectList::compatibility_iterator node;
4593
4594 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
4595 wxUnusedVar(style);
4596 wxArrayInt partialExtents;
4597
4598 wxSize paraSize;
4599 int paraDescent = 0;
4600
4601 // This calculates the partial text extents
4602 GetRangeSize(GetRange(), paraSize, paraDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_CACHE_SIZE, rect.GetPosition(), & partialExtents);
4603 #else
4604 node = m_children.GetFirst();
4605 while (node)
4606 {
4607 wxRichTextObject* child = node->GetData();
4608
4609 //child->SetCachedSize(wxDefaultSize);
4610 child->Layout(dc, context, rect, style);
4611
4612 node = node->GetNext();
4613 }
4614 #endif
4615
4616 // Split up lines
4617
4618 // We may need to go back to a previous child, in which case create the new line,
4619 // find the child corresponding to the start position of the string, and
4620 // continue.
4621
4622 wxRect availableRect;
4623
4624 node = m_children.GetFirst();
4625 while (node)
4626 {
4627 wxRichTextObject* child = node->GetData();
4628
4629 // If floating, ignore. We already laid out floats.
4630 // Also ignore if empty object, except if we haven't got any
4631 // size yet.
4632 if (child->IsFloating() || !child->IsShown() ||
4633 (child->GetRange().GetLength() == 0 && maxHeight > spaceBeforePara)
4634 )
4635 {
4636 node = node->GetNext();
4637 continue;
4638 }
4639
4640 // If this is e.g. a composite text box, it will need to be laid out itself.
4641 // But if just a text fragment or image, for example, this will
4642 // do nothing. NB: won't we need to set the position after layout?
4643 // since for example if position is dependent on vertical line size, we
4644 // can't tell the position until the size is determined. So possibly introduce
4645 // another layout phase.
4646
4647 // We may only be looking at part of a child, if we searched back for wrapping
4648 // and found a suitable point some way into the child. So get the size for the fragment
4649 // if necessary.
4650
4651 long nextBreakPos = GetFirstLineBreakPosition(lastEndPos+1);
4652 long lastPosToUse = child->GetRange().GetEnd();
4653 bool lineBreakInThisObject = (nextBreakPos > -1 && nextBreakPos <= child->GetRange().GetEnd());
4654
4655 if (lineBreakInThisObject)
4656 lastPosToUse = nextBreakPos;
4657
4658 wxSize childSize;
4659 int childDescent = 0;
4660
4661 int startOffset = (lineCount == 0 ? startPositionFirstLine : startPositionSubsequentLines);
4662 availableRect = wxRect(rect.x + startOffset, rect.y + currentPosition.y,
4663 rect.width - startOffset - rightIndent, rect.height);
4664
4665 if (child->IsTopLevel())
4666 {
4667 wxSize oldSize = child->GetCachedSize();
4668
4669 child->Invalidate(wxRICHTEXT_ALL);
4670 child->SetPosition(wxPoint(0, 0));
4671
4672 // Lays out the object first with a given amount of space, and then if no width was specified in attr,
4673 // lays out the object again using the minimum size
4674 // The position will be determined by its location in its line,
4675 // and not by the child's actual position.
4676 child->LayoutToBestSize(dc, context, buffer,
4677 attr, child->GetAttributes(), availableRect, parentRect, style);
4678
4679 if (oldSize != child->GetCachedSize())
4680 {
4681 partialExtents.Clear();
4682
4683 // Recalculate the partial text extents since the child object changed size
4684 GetRangeSize(GetRange(), paraSize, paraDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_CACHE_SIZE, wxPoint(0,0), & partialExtents);
4685 }
4686 }
4687
4688 // Problem: we need to layout composites here for which we need the available width,
4689 // but we can't get the available width without using the float collector which
4690 // needs to know the object height.
4691
4692 if ((nextBreakPos == -1) && (lastEndPos == child->GetRange().GetStart() - 1)) // i.e. we want to get the whole thing
4693 {
4694 childSize = child->GetCachedSize();
4695 childDescent = child->GetDescent();
4696 }
4697 else
4698 {
4699 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
4700 // Get height only, then the width using the partial extents
4701 GetRangeSize(wxRichTextRange(lastEndPos+1, lastPosToUse), childSize, childDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_HEIGHT_ONLY);
4702 childSize.x = wxRichTextGetRangeWidth(*this, wxRichTextRange(lastEndPos+1, lastPosToUse), partialExtents);
4703 #else
4704 GetRangeSize(wxRichTextRange(lastEndPos+1, lastPosToUse), childSize, childDescent, dc, context, wxRICHTEXT_UNFORMATTED, rect.GetPosition());
4705 #endif
4706 }
4707
4708 bool doLoop = true;
4709 int loopIterations = 0;
4710
4711 // If there are nested objects that need to lay themselves out, we have to do this in a
4712 // loop because the height of the object may well depend on the available width.
4713 // And because of floating object positioning, the available width depends on the
4714 // height of the object and whether it will clash with the floating objects.
4715 // So, we see whether the available width changes due to the presence of floating images.
4716 // If it does, then we'll use the new restricted width to find the object height again.
4717 // If this causes another restriction in the available width, we'll try again, until
4718 // either we lose patience or the available width settles down.
4719 do
4720 {
4721 loopIterations ++;
4722
4723 wxRect oldAvailableRect = availableRect;
4724
4725 // Available width depends on the floating objects and the line height.
4726 // Note: the floating objects may be placed vertically along the two sides of
4727 // buffer, so we may have different available line widths with different
4728 // [startY, endY]. So, we can't determine how wide the available
4729 // space is until we know the exact line height.
4730 if (childDescent == 0)
4731 {
4732 lineHeight = wxMax(lineHeight, childSize.y);
4733 lineDescent = maxDescent;
4734 lineAscent = maxAscent;
4735 }
4736 else
4737 {
4738 lineDescent = wxMax(childDescent, maxDescent);
4739 lineAscent = wxMax(childSize.y-childDescent, maxAscent);
4740 }
4741 lineHeight = wxMax(lineHeight, (lineDescent + lineAscent));
4742 wxRect floatAvailableRect = collector->GetAvailableRect(rect.y + currentPosition.y, rect.y + currentPosition.y + lineHeight);
4743
4744 // Adjust availableRect to the space that is available when taking floating objects into account.
4745
4746 if (floatAvailableRect.x + startOffset > availableRect.x)
4747 {
4748 int newX = floatAvailableRect.x + startOffset;
4749 int newW = availableRect.width - (newX - availableRect.x);
4750 availableRect.x = newX;
4751 availableRect.width = newW;
4752 }
4753
4754 if (floatAvailableRect.width < availableRect.width)
4755 availableRect.width = floatAvailableRect.width;
4756
4757 currentPosition.x = availableRect.x - rect.x;
4758
4759 if (child->IsTopLevel() && loopIterations <= 20)
4760 {
4761 if (availableRect != oldAvailableRect)
4762 {
4763 wxSize oldSize = child->GetCachedSize();
4764
4765 // Lays out the object first with a given amount of space, and then if no width was specified in attr,
4766 // lays out the object again using the minimum size
4767 child->Invalidate(wxRICHTEXT_ALL);
4768 child->LayoutToBestSize(dc, context, buffer,
4769 attr, child->GetAttributes(), availableRect, parentRect, style);
4770 childSize = child->GetCachedSize();
4771 childDescent = child->GetDescent();
4772
4773 if (oldSize != child->GetCachedSize())
4774 {
4775 partialExtents.Clear();
4776
4777 // Recalculate the partial text extents since the child object changed size
4778 GetRangeSize(GetRange(), paraSize, paraDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_CACHE_SIZE, wxPoint(0,0), & partialExtents);
4779 }
4780
4781 // Go around the loop finding the available rect for the given floating objects
4782 }
4783 else
4784 doLoop = false;
4785 }
4786 else
4787 doLoop = false;
4788 }
4789 while (doLoop);
4790
4791 if (child->IsTopLevel())
4792 {
4793 // We can move it to the correct position at this point
4794 child->Move(GetPosition() + wxPoint(currentWidth, currentPosition.y));
4795 }
4796
4797 // Cases:
4798 // 1) There was a line break BEFORE the natural break
4799 // 2) There was a line break AFTER the natural break
4800 // 3) It's the last line
4801 // 4) The child still fits (carry on) - 'else' clause
4802
4803 if ((lineBreakInThisObject && (childSize.x + currentWidth <= availableRect.width))
4804 ||
4805 (childSize.x + currentWidth > availableRect.width)
4806 ||
4807 ((childSize.x + currentWidth <= availableRect.width) && !node->GetNext())
4808
4809 )
4810 {
4811 long wrapPosition = 0;
4812 if ((childSize.x + currentWidth <= availableRect.width) && !node->GetNext() && !lineBreakInThisObject)
4813 wrapPosition = child->GetRange().GetEnd();
4814 else
4815
4816 // Find a place to wrap. This may walk back to previous children,
4817 // for example if a word spans several objects.
4818 // Note: one object must contains only one wxTextAtrr, so the line height will not
4819 // change inside one object. Thus, we can pass the remain line width to the
4820 // FindWrapPosition function.
4821 if (!FindWrapPosition(wxRichTextRange(lastCompletedEndPos+1, child->GetRange().GetEnd()), dc, context, availableRect.width, wrapPosition, & partialExtents))
4822 {
4823 // If the function failed, just cut it off at the end of this child.
4824 wrapPosition = child->GetRange().GetEnd();
4825 }
4826
4827 // FindWrapPosition can still return a value that will put us in an endless wrapping loop
4828 if (wrapPosition <= lastCompletedEndPos)
4829 wrapPosition = wxMax(lastCompletedEndPos+1,child->GetRange().GetEnd());
4830
4831 // Line end position shouldn't be the same as the end, or greater.
4832 if (wrapPosition >= GetRange().GetEnd())
4833 wrapPosition = GetRange().GetEnd()-1;
4834
4835 // wxLogDebug(wxT("Split at %ld"), wrapPosition);
4836
4837 // Let's find the actual size of the current line now
4838 wxSize actualSize;
4839 wxRichTextRange actualRange(lastCompletedEndPos+1, wrapPosition);
4840
4841 childDescent = 0;
4842
4843 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
4844 if (!child->IsEmpty())
4845 {
4846 // Get height only, then the width using the partial extents
4847 GetRangeSize(actualRange, actualSize, childDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_HEIGHT_ONLY);
4848 actualSize.x = wxRichTextGetRangeWidth(*this, actualRange, partialExtents);
4849 }
4850 else
4851 #endif
4852 GetRangeSize(actualRange, actualSize, childDescent, dc, context, wxRICHTEXT_UNFORMATTED);
4853
4854 currentWidth = actualSize.x;
4855
4856 // The descent for the whole line at this point, is the correct max descent
4857 maxDescent = childDescent;
4858 // Maximum ascent
4859 maxAscent = actualSize.y-childDescent;
4860
4861 // lineHeight is given by the height for the whole line, since it will
4862 // take into account ascend/descend.
4863 lineHeight = actualSize.y;
4864
4865 if (lineHeight == 0 && buffer)
4866 {
4867 wxFont font(buffer->GetFontTable().FindFont(attr));
4868 wxCheckSetFont(dc, font);
4869 lineHeight = dc.GetCharHeight();
4870 }
4871
4872 if (maxDescent == 0)
4873 {
4874 int w, h;
4875 dc.GetTextExtent(wxT("X"), & w, &h, & maxDescent);
4876 }
4877
4878 // Add a new line
4879 wxRichTextLine* line = AllocateLine(lineCount);
4880
4881 // Set relative range so we won't have to change line ranges when paragraphs are moved
4882 line->SetRange(wxRichTextRange(actualRange.GetStart() - GetRange().GetStart(), actualRange.GetEnd() - GetRange().GetStart()));
4883 line->SetPosition(currentPosition);
4884 line->SetSize(wxSize(currentWidth, lineHeight));
4885 line->SetDescent(maxDescent);
4886
4887 maxHeight = currentPosition.y + lineHeight;
4888
4889 // Now move down a line. TODO: add margins, spacing
4890 currentPosition.y += lineHeight;
4891 currentPosition.y += lineSpacing;
4892 maxDescent = 0;
4893 maxAscent = 0;
4894 maxWidth = wxMax(maxWidth, currentWidth+startOffset);
4895 currentWidth = 0;
4896
4897 lineCount ++;
4898
4899 // TODO: account for zero-length objects
4900 // wxASSERT(wrapPosition > lastCompletedEndPos);
4901
4902 lastEndPos = wrapPosition;
4903 lastCompletedEndPos = lastEndPos;
4904
4905 lineHeight = 0;
4906
4907 if (wrapPosition < GetRange().GetEnd()-1)
4908 {
4909 // May need to set the node back to a previous one, due to searching back in wrapping
4910 wxRichTextObject* childAfterWrapPosition = FindObjectAtPosition(wrapPosition+1);
4911 if (childAfterWrapPosition)
4912 node = m_children.Find(childAfterWrapPosition);
4913 else
4914 node = node->GetNext();
4915 }
4916 else
4917 node = node->GetNext();
4918
4919 // Apply paragraph styles such as alignment to the wrapped line
4920 ApplyParagraphStyle(line, attr, availableRect, dc);
4921 }
4922 else
4923 {
4924 // We still fit, so don't add a line, and keep going
4925 currentWidth += childSize.x;
4926
4927 if (childDescent == 0)
4928 {
4929 // An object with a zero descend value wants to take up the whole
4930 // height regardless of baseline
4931 lineHeight = wxMax(lineHeight, childSize.y);
4932 }
4933 else
4934 {
4935 maxDescent = wxMax(childDescent, maxDescent);
4936 maxAscent = wxMax(childSize.y-childDescent, maxAscent);
4937 }
4938
4939 lineHeight = wxMax(lineHeight, (maxDescent + maxAscent));
4940
4941 maxWidth = wxMax(maxWidth, currentWidth+startOffset);
4942 lastEndPos = child->GetRange().GetEnd();
4943
4944 node = node->GetNext();
4945 }
4946 }
4947
4948 //wxASSERT(!(lastCompletedEndPos != -1 && lastCompletedEndPos < GetRange().GetEnd()-1));
4949
4950 // Remove remaining unused line objects, if any
4951 ClearUnusedLines(lineCount);
4952
4953 // We need to add back the margins etc.
4954 {
4955 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
4956 contentRect = wxRect(wxPoint(0, 0), wxSize(maxWidth, currentPosition.y + spaceAfterPara));
4957 GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
4958 SetCachedSize(marginRect.GetSize());
4959 }
4960
4961 // The maximum size is the length of the paragraph stretched out into a line.
4962 // So if there were a single word, or an image, or a fixed-size text box, the object could be shrunk around
4963 // this size. TODO: take into account line breaks.
4964 {
4965 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
4966 contentRect = wxRect(wxPoint(0, 0), wxSize(paraSize.x + wxMax(leftIndent, leftIndent + leftSubIndent) + rightIndent, currentPosition.y + spaceAfterPara));
4967 GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
4968 SetMaxSize(marginRect.GetSize());
4969 }
4970
4971 // Find the greatest minimum size. Currently we only look at non-text objects,
4972 // which isn't ideal but it would be slow to find the maximum word width to
4973 // use as the minimum.
4974 {
4975 int minWidth = 0;
4976 node = m_children.GetFirst();
4977 while (node)
4978 {
4979 wxRichTextObject* child = node->GetData();
4980
4981 // If floating, ignore. We already laid out floats.
4982 // Also ignore if empty object, except if we haven't got any
4983 // size yet.
4984 if (!child->IsFloating() && child->GetRange().GetLength() != 0 && !child->IsKindOf(CLASSINFO(wxRichTextPlainText)))
4985 {
4986 if (child->GetCachedSize().x > minWidth)
4987 minWidth = child->GetMinSize().x;
4988 }
4989 node = node->GetNext();
4990 }
4991
4992 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
4993 contentRect = wxRect(wxPoint(0, 0), wxSize(minWidth, currentPosition.y + spaceAfterPara));
4994 GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
4995 SetMinSize(marginRect.GetSize());
4996 }
4997
4998 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
4999 #if wxRICHTEXT_USE_OPTIMIZED_LINE_DRAWING
5000 // Use the text extents to calculate the size of each fragment in each line
5001 wxRichTextLineList::compatibility_iterator lineNode = m_cachedLines.GetFirst();
5002 while (lineNode)
5003 {
5004 wxRichTextLine* line = lineNode->GetData();
5005 wxRichTextRange lineRange = line->GetAbsoluteRange();
5006
5007 // Loop through objects until we get to the one within range
5008 wxRichTextObjectList::compatibility_iterator node2 = m_children.GetFirst();
5009
5010 while (node2)
5011 {
5012 wxRichTextObject* child = node2->GetData();
5013
5014 if (child->GetRange().GetLength() > 0 && !child->GetRange().IsOutside(lineRange))
5015 {
5016 wxRichTextRange rangeToUse = lineRange;
5017 rangeToUse.LimitTo(child->GetRange());
5018
5019 // Find the size of the child from the text extents, and store in an array
5020 // for drawing later
5021 int left = 0;
5022 if (rangeToUse.GetStart() > GetRange().GetStart())
5023 left = partialExtents[(rangeToUse.GetStart()-1) - GetRange().GetStart()];
5024 int right = partialExtents[rangeToUse.GetEnd() - GetRange().GetStart()];
5025 int sz = right - left;
5026 line->GetObjectSizes().Add(sz);
5027 }
5028 else if (child->GetRange().GetStart() > lineRange.GetEnd())
5029 // Can break out of inner loop now since we've passed this line's range
5030 break;
5031
5032 node2 = node2->GetNext();
5033 }
5034
5035 lineNode = lineNode->GetNext();
5036 }
5037 #endif
5038 #endif
5039
5040 return true;
5041 }
5042
5043 /// Apply paragraph styles, such as centering, to wrapped lines
5044 /// TODO: take into account box attributes, possibly
5045 void wxRichTextParagraph::ApplyParagraphStyle(wxRichTextLine* line, const wxRichTextAttr& attr, const wxRect& rect, wxDC& dc)
5046 {
5047 if (!attr.HasAlignment())
5048 return;
5049
5050 wxPoint pos = line->GetPosition();
5051 wxSize size = line->GetSize();
5052
5053 // centering, right-justification
5054 if (attr.HasAlignment() && attr.GetAlignment() == wxTEXT_ALIGNMENT_CENTRE)
5055 {
5056 int rightIndent = ConvertTenthsMMToPixels(dc, attr.GetRightIndent());
5057 pos.x = (rect.GetWidth() - rightIndent - size.x)/2 + pos.x;
5058 line->SetPosition(pos);
5059 }
5060 else if (attr.HasAlignment() && attr.GetAlignment() == wxTEXT_ALIGNMENT_RIGHT)
5061 {
5062 int rightIndent = ConvertTenthsMMToPixels(dc, attr.GetRightIndent());
5063 pos.x = pos.x + rect.GetWidth() - size.x - rightIndent;
5064 line->SetPosition(pos);
5065 }
5066 }
5067
5068 /// Insert text at the given position
5069 bool wxRichTextParagraph::InsertText(long pos, const wxString& text)
5070 {
5071 wxRichTextObject* childToUse = NULL;
5072 wxRichTextObjectList::compatibility_iterator nodeToUse = wxRichTextObjectList::compatibility_iterator();
5073
5074 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
5075 while (node)
5076 {
5077 wxRichTextObject* child = node->GetData();
5078 if (child->GetRange().Contains(pos) && child->GetRange().GetLength() > 0)
5079 {
5080 childToUse = child;
5081 nodeToUse = node;
5082 break;
5083 }
5084
5085 node = node->GetNext();
5086 }
5087
5088 if (childToUse)
5089 {
5090 wxRichTextPlainText* textObject = wxDynamicCast(childToUse, wxRichTextPlainText);
5091 if (textObject)
5092 {
5093 int posInString = pos - textObject->GetRange().GetStart();
5094
5095 wxString newText = textObject->GetText().Mid(0, posInString) +
5096 text + textObject->GetText().Mid(posInString);
5097 textObject->SetText(newText);
5098
5099 int textLength = text.length();
5100
5101 textObject->SetRange(wxRichTextRange(textObject->GetRange().GetStart(),
5102 textObject->GetRange().GetEnd() + textLength));
5103
5104 // Increment the end range of subsequent fragments in this paragraph.
5105 // We'll set the paragraph range itself at a higher level.
5106
5107 wxRichTextObjectList::compatibility_iterator node = nodeToUse->GetNext();
5108 while (node)
5109 {
5110 wxRichTextObject* child = node->GetData();
5111 child->SetRange(wxRichTextRange(textObject->GetRange().GetStart() + textLength,
5112 textObject->GetRange().GetEnd() + textLength));
5113
5114 node = node->GetNext();
5115 }
5116
5117 return true;
5118 }
5119 else
5120 {
5121 // TODO: if not a text object, insert at closest position, e.g. in front of it
5122 }
5123 }
5124 else
5125 {
5126 // Add at end.
5127 // Don't pass parent initially to suppress auto-setting of parent range.
5128 // We'll do that at a higher level.
5129 wxRichTextPlainText* textObject = new wxRichTextPlainText(text, this);
5130
5131 AppendChild(textObject);
5132 return true;
5133 }
5134
5135 return false;
5136 }
5137
5138 void wxRichTextParagraph::Copy(const wxRichTextParagraph& obj)
5139 {
5140 wxRichTextCompositeObject::Copy(obj);
5141 }
5142
5143 /// Clear the cached lines
5144 void wxRichTextParagraph::ClearLines()
5145 {
5146 WX_CLEAR_LIST(wxRichTextLineList, m_cachedLines);
5147 }
5148
5149 /// Get/set the object size for the given range. Returns false if the range
5150 /// is invalid for this object.
5151 bool wxRichTextParagraph::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, wxPoint position, wxArrayInt* partialExtents) const
5152 {
5153 if (!range.IsWithin(GetRange()))
5154 return false;
5155
5156 if (flags & wxRICHTEXT_UNFORMATTED)
5157 {
5158 // Just use unformatted data, assume no line breaks
5159 wxSize sz;
5160
5161 wxArrayInt childExtents;
5162 wxArrayInt* p;
5163 if (partialExtents)
5164 p = & childExtents;
5165 else
5166 p = NULL;
5167
5168 int maxDescent = 0;
5169 int maxAscent = 0;
5170 int maxLineHeight = 0;
5171
5172 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
5173 while (node)
5174 {
5175 wxRichTextObject* child = node->GetData();
5176 if (!child->GetRange().IsOutside(range))
5177 {
5178 // Floating objects have a zero size within the paragraph.
5179 if (child->IsFloating())
5180 {
5181 if (partialExtents)
5182 {
5183 int lastSize;
5184 if (partialExtents->GetCount() > 0)
5185 lastSize = (*partialExtents)[partialExtents->GetCount()-1];
5186 else
5187 lastSize = 0;
5188
5189 partialExtents->Add(0 /* zero size */ + lastSize);
5190 }
5191 }
5192 else
5193 {
5194 wxSize childSize;
5195
5196 wxRichTextRange rangeToUse = range;
5197 rangeToUse.LimitTo(child->GetRange());
5198 int childDescent = 0;
5199
5200 // At present wxRICHTEXT_HEIGHT_ONLY is only fast if we've already cached the size,
5201 // but it's only going to be used after caching has taken place.
5202 if ((flags & wxRICHTEXT_HEIGHT_ONLY) && child->GetCachedSize().y != 0)
5203 {
5204 childDescent = child->GetDescent();
5205 childSize = child->GetCachedSize();
5206
5207 if (childDescent == 0)
5208 {
5209 maxLineHeight = wxMax(maxLineHeight, childSize.y);
5210 }
5211 else
5212 {
5213 maxDescent = wxMax(maxDescent, childDescent);
5214 maxAscent = wxMax(maxAscent, (childSize.y - childDescent));
5215 }
5216
5217 maxLineHeight = wxMax(maxLineHeight, (maxAscent + maxDescent));
5218
5219 sz.y = wxMax(sz.y, maxLineHeight);
5220 sz.x += childSize.x;
5221 descent = maxDescent;
5222 }
5223 else if (child->IsTopLevel())
5224 {
5225 childDescent = child->GetDescent();
5226 childSize = child->GetCachedSize();
5227
5228 if (childDescent == 0)
5229 {
5230 maxLineHeight = wxMax(maxLineHeight, childSize.y);
5231 }
5232 else
5233 {
5234 maxDescent = wxMax(maxDescent, childDescent);
5235 maxAscent = wxMax(maxAscent, (childSize.y - childDescent));
5236 }
5237
5238 maxLineHeight = wxMax(maxLineHeight, (maxAscent + maxDescent));
5239
5240 sz.y = wxMax(sz.y, maxLineHeight);
5241 sz.x += childSize.x;
5242 descent = maxDescent;
5243
5244 // FIXME: this won't change the original values.
5245 // Should we be calling GetRangeSize above instead of using cached values?
5246 #if 0
5247 if ((flags & wxRICHTEXT_CACHE_SIZE) && (rangeToUse == child->GetRange()))
5248 {
5249 child->SetCachedSize(childSize);
5250 child->SetDescent(childDescent);
5251 }
5252 #endif
5253
5254 if (partialExtents)
5255 {
5256 int lastSize;
5257 if (partialExtents->GetCount() > 0)
5258 lastSize = (*partialExtents)[partialExtents->GetCount()-1];
5259 else
5260 lastSize = 0;
5261
5262 partialExtents->Add(childSize.x + lastSize);
5263 }
5264 }
5265 else if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, context, flags, wxPoint(position.x + sz.x, position.y), p))
5266 {
5267 if (childDescent == 0)
5268 {
5269 maxLineHeight = wxMax(maxLineHeight, childSize.y);
5270 }
5271 else
5272 {
5273 maxDescent = wxMax(maxDescent, childDescent);
5274 maxAscent = wxMax(maxAscent, (childSize.y - childDescent));
5275 }
5276
5277 maxLineHeight = wxMax(maxLineHeight, (maxAscent + maxDescent));
5278
5279 sz.y = wxMax(sz.y, maxLineHeight);
5280 sz.x += childSize.x;
5281 descent = maxDescent;
5282
5283 if ((flags & wxRICHTEXT_CACHE_SIZE) && (rangeToUse == child->GetRange()))
5284 {
5285 child->SetCachedSize(childSize);
5286 child->SetDescent(childDescent);
5287 }
5288
5289 if (partialExtents)
5290 {
5291 int lastSize;
5292 if (partialExtents->GetCount() > 0)
5293 lastSize = (*partialExtents)[partialExtents->GetCount()-1];
5294 else
5295 lastSize = 0;
5296
5297 size_t i;
5298 for (i = 0; i < childExtents.GetCount(); i++)
5299 {
5300 partialExtents->Add(childExtents[i] + lastSize);
5301 }
5302 }
5303 }
5304 }
5305
5306 if (p)
5307 p->Clear();
5308 }
5309
5310 node = node->GetNext();
5311 }
5312 size = sz;
5313 }
5314 else
5315 {
5316 // Use formatted data, with line breaks
5317 wxSize sz;
5318
5319 // We're going to loop through each line, and then for each line,
5320 // call GetRangeSize for the fragment that comprises that line.
5321 // Only we have to do that multiple times within the line, because
5322 // the line may be broken into pieces. For now ignore line break commands
5323 // (so we can assume that getting the unformatted size for a fragment
5324 // within a line is the actual size)
5325
5326 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
5327 while (node)
5328 {
5329 wxRichTextLine* line = node->GetData();
5330 wxRichTextRange lineRange = line->GetAbsoluteRange();
5331 if (!lineRange.IsOutside(range))
5332 {
5333 int maxDescent = 0;
5334 int maxAscent = 0;
5335 int maxLineHeight = 0;
5336 int maxLineWidth = 0;
5337
5338 wxRichTextObjectList::compatibility_iterator node2 = m_children.GetFirst();
5339 while (node2)
5340 {
5341 wxRichTextObject* child = node2->GetData();
5342
5343 if (!child->IsFloating() && !child->GetRange().IsOutside(lineRange))
5344 {
5345 wxRichTextRange rangeToUse = lineRange;
5346 rangeToUse.LimitTo(child->GetRange());
5347 if (child->IsTopLevel())
5348 rangeToUse = child->GetOwnRange();
5349
5350 wxSize childSize;
5351 int childDescent = 0;
5352 if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, context, flags, wxPoint(position.x + sz.x, position.y)))
5353 {
5354 if (childDescent == 0)
5355 {
5356 // Assume that if descent is zero, this child can occupy the full line height
5357 // and does not need space for the line's maximum descent. So we influence
5358 // the overall max line height only.
5359 maxLineHeight = wxMax(maxLineHeight, childSize.y);
5360 }
5361 else
5362 {
5363 maxAscent = wxMax(maxAscent, (childSize.y - childDescent));
5364 maxDescent = wxMax(maxAscent, childDescent);
5365 }
5366 maxLineHeight = wxMax(maxLineHeight, (maxAscent + maxDescent));
5367 maxLineWidth += childSize.x;
5368 }
5369 }
5370
5371 node2 = node2->GetNext();
5372 }
5373
5374 descent = wxMax(descent, maxDescent);
5375
5376 // Increase size by a line (TODO: paragraph spacing)
5377 sz.y += maxLineHeight;
5378 sz.x = wxMax(sz.x, maxLineWidth);
5379 }
5380 node = node->GetNext();
5381 }
5382 size = sz;
5383 }
5384 return true;
5385 }
5386
5387 /// Finds the absolute position and row height for the given character position
5388 bool wxRichTextParagraph::FindPosition(wxDC& dc, wxRichTextDrawingContext& context, long index, wxPoint& pt, int* height, bool forceLineStart)
5389 {
5390 if (index == -1)
5391 {
5392 wxRichTextLine* line = ((wxRichTextParagraphLayoutBox*)GetParent())->GetLineAtPosition(0);
5393 if (line)
5394 *height = line->GetSize().y;
5395 else
5396 *height = dc.GetCharHeight();
5397
5398 // -1 means 'the start of the buffer'.
5399 pt = GetPosition();
5400 if (line)
5401 pt = pt + line->GetPosition();
5402
5403 return true;
5404 }
5405
5406 // The final position in a paragraph is taken to mean the position
5407 // at the start of the next paragraph.
5408 if (index == GetRange().GetEnd())
5409 {
5410 wxRichTextParagraphLayoutBox* parent = wxDynamicCast(GetParent(), wxRichTextParagraphLayoutBox);
5411 wxASSERT( parent != NULL );
5412
5413 // Find the height at the next paragraph, if any
5414 wxRichTextLine* line = parent->GetLineAtPosition(index + 1);
5415 if (line)
5416 {
5417 *height = line->GetSize().y;
5418 pt = line->GetAbsolutePosition();
5419 }
5420 else
5421 {
5422 *height = dc.GetCharHeight();
5423 int indent = ConvertTenthsMMToPixels(dc, m_attributes.GetLeftIndent());
5424 pt = wxPoint(indent, GetCachedSize().y);
5425 }
5426
5427 return true;
5428 }
5429
5430 if (index < GetRange().GetStart() || index > GetRange().GetEnd())
5431 return false;
5432
5433 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
5434 while (node)
5435 {
5436 wxRichTextLine* line = node->GetData();
5437 wxRichTextRange lineRange = line->GetAbsoluteRange();
5438 if (index >= lineRange.GetStart() && index <= lineRange.GetEnd())
5439 {
5440 // If this is the last point in the line, and we're forcing the
5441 // returned value to be the start of the next line, do the required
5442 // thing.
5443 if (index == lineRange.GetEnd() && forceLineStart)
5444 {
5445 if (node->GetNext())
5446 {
5447 wxRichTextLine* nextLine = node->GetNext()->GetData();
5448 *height = nextLine->GetSize().y;
5449 pt = nextLine->GetAbsolutePosition();
5450 return true;
5451 }
5452 }
5453
5454 pt.y = line->GetPosition().y + GetPosition().y;
5455
5456 wxRichTextRange r(lineRange.GetStart(), index);
5457 wxSize rangeSize;
5458 int descent = 0;
5459
5460 // We find the size of the line up to this point,
5461 // then we can add this size to the line start position and
5462 // paragraph start position to find the actual position.
5463
5464 if (GetRangeSize(r, rangeSize, descent, dc, context, wxRICHTEXT_UNFORMATTED, line->GetPosition()+ GetPosition()))
5465 {
5466 pt.x = line->GetPosition().x + GetPosition().x + rangeSize.x;
5467 *height = line->GetSize().y;
5468
5469 return true;
5470 }
5471
5472 }
5473
5474 node = node->GetNext();
5475 }
5476
5477 return false;
5478 }
5479
5480 /// Hit-testing: returns a flag indicating hit test details, plus
5481 /// information about position
5482 int wxRichTextParagraph::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int flags)
5483 {
5484 if (!IsShown())
5485 return wxRICHTEXT_HITTEST_NONE;
5486
5487 // If we're in the top-level container, then we can return
5488 // a suitable hit test code even if the point is outside the container area,
5489 // so that we can position the caret sensibly even if we don't
5490 // click on valid content. If we're not at the top-level, and the point
5491 // is not within this paragraph object, then we don't want to stop more
5492 // precise hit-testing from working prematurely, so return immediately.
5493 // NEW STRATEGY: use the parent boundary to test whether we're in the
5494 // right region, not the paragraph, since the paragraph may be positioned
5495 // some way in from where the user clicks.
5496 {
5497 long tmpPos;
5498 wxRichTextObject* tempObj, *tempContextObj;
5499 if (GetParent() && GetParent()->wxRichTextObject::HitTest(dc, context, pt, tmpPos, & tempObj, & tempContextObj, flags) == wxRICHTEXT_HITTEST_NONE)
5500 return wxRICHTEXT_HITTEST_NONE;
5501 }
5502
5503 wxRichTextObjectList::compatibility_iterator objNode = m_children.GetFirst();
5504 while (objNode)
5505 {
5506 wxRichTextObject* child = objNode->GetData();
5507 // Don't recurse if we have wxRICHTEXT_HITTEST_NO_NESTED_OBJECTS,
5508 // and also, if this seems composite but actually is marked as atomic,
5509 // don't recurse.
5510 if (child->IsTopLevel() && ((flags & wxRICHTEXT_HITTEST_NO_NESTED_OBJECTS) == 0) &&
5511 (! (((flags & wxRICHTEXT_HITTEST_HONOUR_ATOMIC) != 0) && child->IsAtomic())))
5512 {
5513 {
5514 int hitTest = child->HitTest(dc, context, pt, textPosition, obj, contextObj);
5515 if (hitTest != wxRICHTEXT_HITTEST_NONE)
5516 return hitTest;
5517 }
5518 }
5519
5520 objNode = objNode->GetNext();
5521 }
5522
5523 wxPoint paraPos = GetPosition();
5524
5525 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
5526 while (node)
5527 {
5528 wxRichTextLine* line = node->GetData();
5529 wxPoint linePos = paraPos + line->GetPosition();
5530 wxSize lineSize = line->GetSize();
5531 wxRichTextRange lineRange = line->GetAbsoluteRange();
5532
5533 if (pt.y <= linePos.y + lineSize.y)
5534 {
5535 if (pt.x < linePos.x)
5536 {
5537 textPosition = lineRange.GetStart();
5538 *obj = FindObjectAtPosition(textPosition);
5539 *contextObj = GetContainer();
5540 return wxRICHTEXT_HITTEST_BEFORE|wxRICHTEXT_HITTEST_OUTSIDE;
5541 }
5542 else if (pt.x >= (linePos.x + lineSize.x))
5543 {
5544 textPosition = lineRange.GetEnd();
5545 *obj = FindObjectAtPosition(textPosition);
5546 *contextObj = GetContainer();
5547 return wxRICHTEXT_HITTEST_AFTER|wxRICHTEXT_HITTEST_OUTSIDE;
5548 }
5549 else
5550 {
5551 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
5552 wxArrayInt partialExtents;
5553
5554 wxSize paraSize;
5555 int paraDescent;
5556
5557 // This calculates the partial text extents
5558 GetRangeSize(lineRange, paraSize, paraDescent, dc, context, wxRICHTEXT_UNFORMATTED, linePos, & partialExtents);
5559
5560 int lastX = linePos.x;
5561 size_t i;
5562 for (i = 0; i < partialExtents.GetCount(); i++)
5563 {
5564 int nextX = partialExtents[i] + linePos.x;
5565
5566 if (pt.x >= lastX && pt.x <= nextX)
5567 {
5568 textPosition = i + lineRange.GetStart(); // minus 1?
5569
5570 *obj = FindObjectAtPosition(textPosition);
5571 *contextObj = GetContainer();
5572
5573 // So now we know it's between i-1 and i.
5574 // Let's see if we can be more precise about
5575 // which side of the position it's on.
5576
5577 int midPoint = (nextX + lastX)/2;
5578 if (pt.x >= midPoint)
5579 return wxRICHTEXT_HITTEST_AFTER;
5580 else
5581 return wxRICHTEXT_HITTEST_BEFORE;
5582 }
5583
5584 lastX = nextX;
5585 }
5586 #else
5587 long i;
5588 int lastX = linePos.x;
5589 for (i = lineRange.GetStart(); i <= lineRange.GetEnd(); i++)
5590 {
5591 wxSize childSize;
5592 int descent = 0;
5593
5594 wxRichTextRange rangeToUse(lineRange.GetStart(), i);
5595
5596 GetRangeSize(rangeToUse, childSize, descent, dc, context, wxRICHTEXT_UNFORMATTED, linePos);
5597
5598 int nextX = childSize.x + linePos.x;
5599
5600 if (pt.x >= lastX && pt.x <= nextX)
5601 {
5602 textPosition = i;
5603
5604 *obj = FindObjectAtPosition(textPosition);
5605 *contextObj = GetContainer();
5606
5607 // So now we know it's between i-1 and i.
5608 // Let's see if we can be more precise about
5609 // which side of the position it's on.
5610
5611 int midPoint = (nextX + lastX)/2;
5612 if (pt.x >= midPoint)
5613 return wxRICHTEXT_HITTEST_AFTER;
5614 else
5615 return wxRICHTEXT_HITTEST_BEFORE;
5616 }
5617 else
5618 {
5619 lastX = nextX;
5620 }
5621 }
5622 #endif
5623 }
5624 }
5625
5626 node = node->GetNext();
5627 }
5628
5629 return wxRICHTEXT_HITTEST_NONE;
5630 }
5631
5632 /// Split an object at this position if necessary, and return
5633 /// the previous object, or NULL if inserting at beginning.
5634 wxRichTextObject* wxRichTextParagraph::SplitAt(long pos, wxRichTextObject** previousObject)
5635 {
5636 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
5637 while (node)
5638 {
5639 wxRichTextObject* child = node->GetData();
5640
5641 if (pos == child->GetRange().GetStart())
5642 {
5643 if (previousObject)
5644 {
5645 if (node->GetPrevious())
5646 *previousObject = node->GetPrevious()->GetData();
5647 else
5648 *previousObject = NULL;
5649 }
5650
5651 return child;
5652 }
5653
5654 if (child->GetRange().Contains(pos))
5655 {
5656 // This should create a new object, transferring part of
5657 // the content to the old object and the rest to the new object.
5658 wxRichTextObject* newObject = child->DoSplit(pos);
5659
5660 // If we couldn't split this object, just insert in front of it.
5661 if (!newObject)
5662 {
5663 // Maybe this is an empty string, try the next one
5664 // return child;
5665 }
5666 else
5667 {
5668 // Insert the new object after 'child'
5669 if (node->GetNext())
5670 m_children.Insert(node->GetNext(), newObject);
5671 else
5672 m_children.Append(newObject);
5673 newObject->SetParent(this);
5674
5675 if (previousObject)
5676 *previousObject = child;
5677
5678 return newObject;
5679 }
5680 }
5681
5682 node = node->GetNext();
5683 }
5684 if (previousObject)
5685 *previousObject = NULL;
5686 return NULL;
5687 }
5688
5689 /// Move content to a list from obj on
5690 void wxRichTextParagraph::MoveToList(wxRichTextObject* obj, wxList& list)
5691 {
5692 wxRichTextObjectList::compatibility_iterator node = m_children.Find(obj);
5693 while (node)
5694 {
5695 wxRichTextObject* child = node->GetData();
5696 list.Append(child);
5697
5698 wxRichTextObjectList::compatibility_iterator oldNode = node;
5699
5700 node = node->GetNext();
5701
5702 m_children.DeleteNode(oldNode);
5703 }
5704 }
5705
5706 /// Add content back from list
5707 void wxRichTextParagraph::MoveFromList(wxList& list)
5708 {
5709 for (wxList::compatibility_iterator node = list.GetFirst(); node; node = node->GetNext())
5710 {
5711 AppendChild((wxRichTextObject*) node->GetData());
5712 }
5713 }
5714
5715 /// Calculate range
5716 void wxRichTextParagraph::CalculateRange(long start, long& end)
5717 {
5718 wxRichTextCompositeObject::CalculateRange(start, end);
5719
5720 // Add one for end of paragraph
5721 end ++;
5722
5723 m_range.SetRange(start, end);
5724 }
5725
5726 /// Find the object at the given position
5727 wxRichTextObject* wxRichTextParagraph::FindObjectAtPosition(long position)
5728 {
5729 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
5730 while (node)
5731 {
5732 wxRichTextObject* obj = node->GetData();
5733 if (obj->GetRange().Contains(position) ||
5734 obj->GetRange().GetStart() == position ||
5735 obj->GetRange().GetEnd() == position)
5736 return obj;
5737
5738 node = node->GetNext();
5739 }
5740 return NULL;
5741 }
5742
5743 /// Get the plain text searching from the start or end of the range.
5744 /// The resulting string may be shorter than the range given.
5745 bool wxRichTextParagraph::GetContiguousPlainText(wxString& text, const wxRichTextRange& range, bool fromStart)
5746 {
5747 text = wxEmptyString;
5748
5749 if (fromStart)
5750 {
5751 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
5752 while (node)
5753 {
5754 wxRichTextObject* obj = node->GetData();
5755 if (!obj->GetRange().IsOutside(range))
5756 {
5757 wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText);
5758 if (textObj)
5759 {
5760 text += textObj->GetTextForRange(range);
5761 }
5762 else
5763 {
5764 text += wxT(" ");
5765 }
5766 }
5767
5768 node = node->GetNext();
5769 }
5770 }
5771 else
5772 {
5773 wxRichTextObjectList::compatibility_iterator node = m_children.GetLast();
5774 while (node)
5775 {
5776 wxRichTextObject* obj = node->GetData();
5777 if (!obj->GetRange().IsOutside(range))
5778 {
5779 wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText);
5780 if (textObj)
5781 {
5782 text = textObj->GetTextForRange(range) + text;
5783 }
5784 else
5785 {
5786 text = wxT(" ") + text;
5787 }
5788 }
5789
5790 node = node->GetPrevious();
5791 }
5792 }
5793
5794 return true;
5795 }
5796
5797 /// Find a suitable wrap position.
5798 bool wxRichTextParagraph::FindWrapPosition(const wxRichTextRange& range, wxDC& dc, wxRichTextDrawingContext& context, int availableSpace, long& wrapPosition, wxArrayInt* partialExtents)
5799 {
5800 if (range.GetLength() <= 0)
5801 return false;
5802
5803 // Find the first position where the line exceeds the available space.
5804 wxSize sz;
5805 long breakPosition = range.GetEnd();
5806
5807 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
5808 if (partialExtents && partialExtents->GetCount() >= (size_t) (GetRange().GetLength()-1)) // the final position in a paragraph is the newline
5809 {
5810 int widthBefore;
5811
5812 if (range.GetStart() > GetRange().GetStart())
5813 widthBefore = (*partialExtents)[range.GetStart() - GetRange().GetStart() - 1];
5814 else
5815 widthBefore = 0;
5816
5817 size_t i;
5818 for (i = (size_t) range.GetStart(); i <= (size_t) range.GetEnd(); i++)
5819 {
5820 int widthFromStartOfThisRange = (*partialExtents)[i - GetRange().GetStart()] - widthBefore;
5821
5822 if (widthFromStartOfThisRange > availableSpace)
5823 {
5824 breakPosition = i-1;
5825 break;
5826 }
5827 }
5828 }
5829 else
5830 #endif
5831 {
5832 // Binary chop for speed
5833 long minPos = range.GetStart();
5834 long maxPos = range.GetEnd();
5835 while (true)
5836 {
5837 if (minPos == maxPos)
5838 {
5839 int descent = 0;
5840 GetRangeSize(wxRichTextRange(range.GetStart(), minPos), sz, descent, dc, context, wxRICHTEXT_UNFORMATTED);
5841
5842 if (sz.x > availableSpace)
5843 breakPosition = minPos - 1;
5844 break;
5845 }
5846 else if ((maxPos - minPos) == 1)
5847 {
5848 int descent = 0;
5849 GetRangeSize(wxRichTextRange(range.GetStart(), minPos), sz, descent, dc, context, wxRICHTEXT_UNFORMATTED);
5850
5851 if (sz.x > availableSpace)
5852 breakPosition = minPos - 1;
5853 else
5854 {
5855 GetRangeSize(wxRichTextRange(range.GetStart(), maxPos), sz, descent, dc, context, wxRICHTEXT_UNFORMATTED);
5856 if (sz.x > availableSpace)
5857 breakPosition = maxPos-1;
5858 }
5859 break;
5860 }
5861 else
5862 {
5863 long nextPos = minPos + ((maxPos - minPos) / 2);
5864
5865 int descent = 0;
5866 GetRangeSize(wxRichTextRange(range.GetStart(), nextPos), sz, descent, dc, context, wxRICHTEXT_UNFORMATTED);
5867
5868 if (sz.x > availableSpace)
5869 {
5870 maxPos = nextPos;
5871 }
5872 else
5873 {
5874 minPos = nextPos;
5875 }
5876 }
5877 }
5878 }
5879
5880 // Now we know the last position on the line.
5881 // Let's try to find a word break.
5882
5883 wxString plainText;
5884 if (GetContiguousPlainText(plainText, wxRichTextRange(range.GetStart(), breakPosition), false))
5885 {
5886 int newLinePos = plainText.Find(wxRichTextLineBreakChar);
5887 if (newLinePos != wxNOT_FOUND)
5888 {
5889 breakPosition = wxMax(0, range.GetStart() + newLinePos);
5890 }
5891 else
5892 {
5893 int spacePos = plainText.Find(wxT(' '), true);
5894 int tabPos = plainText.Find(wxT('\t'), true);
5895 int pos = wxMax(spacePos, tabPos);
5896 if (pos != wxNOT_FOUND)
5897 {
5898 int positionsFromEndOfString = plainText.length() - pos - 1;
5899 breakPosition = breakPosition - positionsFromEndOfString;
5900 }
5901 }
5902 }
5903
5904 wrapPosition = breakPosition;
5905
5906 return true;
5907 }
5908
5909 /// Get the bullet text for this paragraph.
5910 wxString wxRichTextParagraph::GetBulletText()
5911 {
5912 if (GetAttributes().GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_NONE ||
5913 (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_BITMAP))
5914 return wxEmptyString;
5915
5916 int number = GetAttributes().GetBulletNumber();
5917
5918 wxString text;
5919 if ((GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ARABIC) || (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE))
5920 {
5921 text.Printf(wxT("%d"), number);
5922 }
5923 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_LETTERS_UPPER)
5924 {
5925 // TODO: Unicode, and also check if number > 26
5926 text.Printf(wxT("%c"), (wxChar) (number+64));
5927 }
5928 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_LETTERS_LOWER)
5929 {
5930 // TODO: Unicode, and also check if number > 26
5931 text.Printf(wxT("%c"), (wxChar) (number+96));
5932 }
5933 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ROMAN_UPPER)
5934 {
5935 text = wxRichTextDecimalToRoman(number);
5936 }
5937 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ROMAN_LOWER)
5938 {
5939 text = wxRichTextDecimalToRoman(number);
5940 text.MakeLower();
5941 }
5942 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_SYMBOL)
5943 {
5944 text = GetAttributes().GetBulletText();
5945 }
5946
5947 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE)
5948 {
5949 // The outline style relies on the text being computed statically,
5950 // since it depends on other levels points (e.g. 1.2.1.1). So normally the bullet text
5951 // should be stored in the attributes; if not, just use the number for this
5952 // level, as previously computed.
5953 if (!GetAttributes().GetBulletText().IsEmpty())
5954 text = GetAttributes().GetBulletText();
5955 }
5956
5957 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_PARENTHESES)
5958 {
5959 text = wxT("(") + text + wxT(")");
5960 }
5961 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_RIGHT_PARENTHESIS)
5962 {
5963 text = text + wxT(")");
5964 }
5965
5966 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_PERIOD)
5967 {
5968 text += wxT(".");
5969 }
5970
5971 return text;
5972 }
5973
5974 /// Allocate or reuse a line object
5975 wxRichTextLine* wxRichTextParagraph::AllocateLine(int pos)
5976 {
5977 if (pos < (int) m_cachedLines.GetCount())
5978 {
5979 wxRichTextLine* line = m_cachedLines.Item(pos)->GetData();
5980 line->Init(this);
5981 return line;
5982 }
5983 else
5984 {
5985 wxRichTextLine* line = new wxRichTextLine(this);
5986 m_cachedLines.Append(line);
5987 return line;
5988 }
5989 }
5990
5991 /// Clear remaining unused line objects, if any
5992 bool wxRichTextParagraph::ClearUnusedLines(int lineCount)
5993 {
5994 int cachedLineCount = m_cachedLines.GetCount();
5995 if ((int) cachedLineCount > lineCount)
5996 {
5997 for (int i = 0; i < (int) (cachedLineCount - lineCount); i ++)
5998 {
5999 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetLast();
6000 wxRichTextLine* line = node->GetData();
6001 m_cachedLines.Erase(node);
6002 delete line;
6003 }
6004 }
6005 return true;
6006 }
6007
6008 /// Get combined attributes of the base style, paragraph style and character style. We use this to dynamically
6009 /// retrieve the actual style.
6010 wxRichTextAttr wxRichTextParagraph::GetCombinedAttributes(const wxRichTextAttr& contentStyle, bool includingBoxAttr) const
6011 {
6012 wxRichTextAttr attr;
6013 wxRichTextParagraphLayoutBox* buf = wxDynamicCast(GetParent(), wxRichTextParagraphLayoutBox);
6014 if (buf)
6015 {
6016 attr = buf->GetBasicStyle();
6017 if (!includingBoxAttr)
6018 {
6019 attr.GetTextBoxAttr().Reset();
6020 // The background colour will be painted by the container, and we don't
6021 // want to unnecessarily overwrite the background when we're drawing text
6022 // because this may erase the guideline (which appears just under the text
6023 // if there's no padding).
6024 attr.SetFlags(attr.GetFlags() & ~wxTEXT_ATTR_BACKGROUND_COLOUR);
6025 }
6026 wxRichTextApplyStyle(attr, GetAttributes());
6027 }
6028 else
6029 attr = GetAttributes();
6030
6031 wxRichTextApplyStyle(attr, contentStyle);
6032 return attr;
6033 }
6034
6035 /// Get combined attributes of the base style and paragraph style.
6036 wxRichTextAttr wxRichTextParagraph::GetCombinedAttributes(bool includingBoxAttr) const
6037 {
6038 wxRichTextAttr attr;
6039 wxRichTextParagraphLayoutBox* buf = wxDynamicCast(GetParent(), wxRichTextParagraphLayoutBox);
6040 if (buf)
6041 {
6042 attr = buf->GetBasicStyle();
6043 if (!includingBoxAttr)
6044 attr.GetTextBoxAttr().Reset();
6045 wxRichTextApplyStyle(attr, GetAttributes());
6046 }
6047 else
6048 attr = GetAttributes();
6049
6050 return attr;
6051 }
6052
6053 // Create default tabstop array
6054 void wxRichTextParagraph::InitDefaultTabs()
6055 {
6056 // create a default tab list at 10 mm each.
6057 for (int i = 0; i < 20; ++i)
6058 {
6059 sm_defaultTabs.Add(i*100);
6060 }
6061 }
6062
6063 // Clear default tabstop array
6064 void wxRichTextParagraph::ClearDefaultTabs()
6065 {
6066 sm_defaultTabs.Clear();
6067 }
6068
6069 void wxRichTextParagraph::LayoutFloat(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, int style, wxRichTextFloatCollector* floatCollector)
6070 {
6071 wxRichTextObjectList::compatibility_iterator node = GetChildren().GetFirst();
6072 while (node)
6073 {
6074 wxRichTextObject* anchored = node->GetData();
6075 if (anchored && anchored->IsFloating() && !floatCollector->HasFloat(anchored))
6076 {
6077 wxSize size;
6078 int descent, x = 0;
6079 anchored->GetRangeSize(anchored->GetRange(), size, descent, dc, context, style);
6080
6081 int offsetY = 0;
6082 if (anchored->GetAttributes().GetTextBoxAttr().GetTop().IsValid())
6083 {
6084 offsetY = anchored->GetAttributes().GetTextBoxAttr().GetTop().GetValue();
6085 if (anchored->GetAttributes().GetTextBoxAttr().GetTop().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
6086 {
6087 offsetY = ConvertTenthsMMToPixels(dc, offsetY);
6088 }
6089 }
6090
6091 int pos = floatCollector->GetFitPosition(anchored->GetAttributes().GetTextBoxAttr().GetFloatMode(), rect.y + offsetY, size.y);
6092
6093 /* Update the offset */
6094 int newOffsetY = pos - rect.y;
6095 if (newOffsetY != offsetY)
6096 {
6097 if (anchored->GetAttributes().GetTextBoxAttr().GetTop().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
6098 newOffsetY = ConvertPixelsToTenthsMM(dc, newOffsetY);
6099 anchored->GetAttributes().GetTextBoxAttr().GetTop().SetValue(newOffsetY);
6100 }
6101
6102 if (anchored->GetAttributes().GetTextBoxAttr().GetFloatMode() == wxTEXT_BOX_ATTR_FLOAT_LEFT)
6103 x = rect.x;
6104 else if (anchored->GetAttributes().GetTextBoxAttr().GetFloatMode() == wxTEXT_BOX_ATTR_FLOAT_RIGHT)
6105 x = rect.x + rect.width - size.x;
6106
6107 anchored->SetPosition(wxPoint(x, pos));
6108 anchored->SetCachedSize(size);
6109 floatCollector->CollectFloat(this, anchored);
6110 }
6111
6112 node = node->GetNext();
6113 }
6114 }
6115
6116 // Get the first position from pos that has a line break character.
6117 long wxRichTextParagraph::GetFirstLineBreakPosition(long pos)
6118 {
6119 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
6120 while (node)
6121 {
6122 wxRichTextObject* obj = node->GetData();
6123 if (pos >= obj->GetRange().GetStart() && pos <= obj->GetRange().GetEnd())
6124 {
6125 wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText);
6126 if (textObj)
6127 {
6128 long breakPos = textObj->GetFirstLineBreakPosition(pos);
6129 if (breakPos > -1)
6130 return breakPos;
6131 }
6132 }
6133 node = node->GetNext();
6134 }
6135 return -1;
6136 }
6137
6138 /*!
6139 * wxRichTextLine
6140 * This object represents a line in a paragraph, and stores
6141 * offsets from the start of the paragraph representing the
6142 * start and end positions of the line.
6143 */
6144
6145 wxRichTextLine::wxRichTextLine(wxRichTextParagraph* parent)
6146 {
6147 Init(parent);
6148 }
6149
6150 /// Initialisation
6151 void wxRichTextLine::Init(wxRichTextParagraph* parent)
6152 {
6153 m_parent = parent;
6154 m_range.SetRange(-1, -1);
6155 m_pos = wxPoint(0, 0);
6156 m_size = wxSize(0, 0);
6157 m_descent = 0;
6158 #if wxRICHTEXT_USE_OPTIMIZED_LINE_DRAWING
6159 m_objectSizes.Clear();
6160 #endif
6161 }
6162
6163 /// Copy
6164 void wxRichTextLine::Copy(const wxRichTextLine& obj)
6165 {
6166 m_range = obj.m_range;
6167 #if wxRICHTEXT_USE_OPTIMIZED_LINE_DRAWING
6168 m_objectSizes = obj.m_objectSizes;
6169 #endif
6170 }
6171
6172 /// Get the absolute object position
6173 wxPoint wxRichTextLine::GetAbsolutePosition() const
6174 {
6175 return m_parent->GetPosition() + m_pos;
6176 }
6177
6178 /// Get the absolute range
6179 wxRichTextRange wxRichTextLine::GetAbsoluteRange() const
6180 {
6181 wxRichTextRange range(m_range.GetStart() + m_parent->GetRange().GetStart(), 0);
6182 range.SetEnd(range.GetStart() + m_range.GetLength()-1);
6183 return range;
6184 }
6185
6186 /*!
6187 * wxRichTextPlainText
6188 * This object represents a single piece of text.
6189 */
6190
6191 IMPLEMENT_DYNAMIC_CLASS(wxRichTextPlainText, wxRichTextObject)
6192
6193 wxRichTextPlainText::wxRichTextPlainText(const wxString& text, wxRichTextObject* parent, wxRichTextAttr* style):
6194 wxRichTextObject(parent)
6195 {
6196 if (style)
6197 SetAttributes(*style);
6198
6199 m_text = text;
6200 }
6201
6202 #define USE_KERNING_FIX 1
6203
6204 // If insufficient tabs are defined, this is the tab width used
6205 #define WIDTH_FOR_DEFAULT_TABS 50
6206
6207 /// Draw the item
6208 bool wxRichTextPlainText::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int WXUNUSED(style))
6209 {
6210 wxRichTextParagraph* para = wxDynamicCast(GetParent(), wxRichTextParagraph);
6211 wxASSERT (para != NULL);
6212
6213 wxRichTextAttr textAttr(para ? para->GetCombinedAttributes(GetAttributes(), false /* no box attributes */) : GetAttributes());
6214 context.ApplyVirtualAttributes(textAttr, this);
6215
6216 // Let's make the assumption for now that for content in a paragraph, including
6217 // text, we never have a discontinuous selection. So we only deal with a
6218 // single range.
6219 wxRichTextRange selectionRange;
6220 if (selection.IsValid())
6221 {
6222 wxRichTextRangeArray selectionRanges = selection.GetSelectionForObject(this);
6223 if (selectionRanges.GetCount() > 0)
6224 selectionRange = selectionRanges[0];
6225 else
6226 selectionRange = wxRICHTEXT_NO_SELECTION;
6227 }
6228 else
6229 selectionRange = wxRICHTEXT_NO_SELECTION;
6230
6231 int offset = GetRange().GetStart();
6232
6233 // Replace line break characters with spaces
6234 wxString str = m_text;
6235 wxString toRemove = wxRichTextLineBreakChar;
6236 str.Replace(toRemove, wxT(" "));
6237 if (textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_CAPITALS))
6238 str.MakeUpper();
6239
6240 long len = range.GetLength();
6241 wxString stringChunk = str.Mid(range.GetStart() - offset, (size_t) len);
6242
6243 // Test for the optimized situations where all is selected, or none
6244 // is selected.
6245
6246 wxFont textFont(GetBuffer()->GetFontTable().FindFont(textAttr));
6247 wxCheckSetFont(dc, textFont);
6248 int charHeight = dc.GetCharHeight();
6249
6250 int x, y;
6251 if ( textFont.IsOk() )
6252 {
6253 if ( textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUPERSCRIPT) )
6254 {
6255 double size = static_cast<double>(textFont.GetPointSize()) / wxSCRIPT_MUL_FACTOR;
6256 textFont.SetPointSize( static_cast<int>(size) );
6257 x = rect.x;
6258 y = rect.y;
6259 wxCheckSetFont(dc, textFont);
6260 }
6261 else if ( textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUBSCRIPT) )
6262 {
6263 double size = static_cast<double>(textFont.GetPointSize()) / wxSCRIPT_MUL_FACTOR;
6264 textFont.SetPointSize( static_cast<int>(size) );
6265 x = rect.x;
6266 int sub_height = static_cast<int>( static_cast<double>(charHeight) / wxSCRIPT_MUL_FACTOR);
6267 y = rect.y + (rect.height - sub_height + (descent - m_descent));
6268 wxCheckSetFont(dc, textFont);
6269 }
6270 else
6271 {
6272 x = rect.x;
6273 y = rect.y + (rect.height - charHeight - (descent - m_descent));
6274 }
6275 }
6276 else
6277 {
6278 x = rect.x;
6279 y = rect.y + (rect.height - charHeight - (descent - m_descent));
6280 }
6281
6282 // TODO: new selection code
6283
6284 // (a) All selected.
6285 if (selectionRange.GetStart() <= range.GetStart() && selectionRange.GetEnd() >= range.GetEnd())
6286 {
6287 DrawTabbedString(dc, textAttr, rect, stringChunk, x, y, true);
6288 }
6289 // (b) None selected.
6290 else if (selectionRange.GetEnd() < range.GetStart() || selectionRange.GetStart() > range.GetEnd())
6291 {
6292 // Draw all unselected
6293 DrawTabbedString(dc, textAttr, rect, stringChunk, x, y, false);
6294 }
6295 else
6296 {
6297 // (c) Part selected, part not
6298 // Let's draw unselected chunk, selected chunk, then unselected chunk.
6299
6300 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
6301
6302 // 1. Initial unselected chunk, if any, up until start of selection.
6303 if (selectionRange.GetStart() > range.GetStart() && selectionRange.GetStart() <= range.GetEnd())
6304 {
6305 int r1 = range.GetStart();
6306 int s1 = selectionRange.GetStart()-1;
6307 int fragmentLen = s1 - r1 + 1;
6308 if (fragmentLen < 0)
6309 {
6310 wxLogDebug(wxT("Mid(%d, %d"), (int)(r1 - offset), (int)fragmentLen);
6311 }
6312 wxString stringFragment = str.Mid(r1 - offset, fragmentLen);
6313
6314 DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, false);
6315
6316 #if USE_KERNING_FIX
6317 if (stringChunk.Find(wxT("\t")) == wxNOT_FOUND)
6318 {
6319 // Compensate for kerning difference
6320 wxString stringFragment2(str.Mid(r1 - offset, fragmentLen+1));
6321 wxString stringFragment3(str.Mid(r1 - offset + fragmentLen, 1));
6322
6323 wxCoord w1, h1, w2, h2, w3, h3;
6324 dc.GetTextExtent(stringFragment, & w1, & h1);
6325 dc.GetTextExtent(stringFragment2, & w2, & h2);
6326 dc.GetTextExtent(stringFragment3, & w3, & h3);
6327
6328 int kerningDiff = (w1 + w3) - w2;
6329 x = x - kerningDiff;
6330 }
6331 #endif
6332 }
6333
6334 // 2. Selected chunk, if any.
6335 if (selectionRange.GetEnd() >= range.GetStart())
6336 {
6337 int s1 = wxMax(selectionRange.GetStart(), range.GetStart());
6338 int s2 = wxMin(selectionRange.GetEnd(), range.GetEnd());
6339
6340 int fragmentLen = s2 - s1 + 1;
6341 if (fragmentLen < 0)
6342 {
6343 wxLogDebug(wxT("Mid(%d, %d"), (int)(s1 - offset), (int)fragmentLen);
6344 }
6345 wxString stringFragment = str.Mid(s1 - offset, fragmentLen);
6346
6347 DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, true);
6348
6349 #if USE_KERNING_FIX
6350 if (stringChunk.Find(wxT("\t")) == wxNOT_FOUND)
6351 {
6352 // Compensate for kerning difference
6353 wxString stringFragment2(str.Mid(s1 - offset, fragmentLen+1));
6354 wxString stringFragment3(str.Mid(s1 - offset + fragmentLen, 1));
6355
6356 wxCoord w1, h1, w2, h2, w3, h3;
6357 dc.GetTextExtent(stringFragment, & w1, & h1);
6358 dc.GetTextExtent(stringFragment2, & w2, & h2);
6359 dc.GetTextExtent(stringFragment3, & w3, & h3);
6360
6361 int kerningDiff = (w1 + w3) - w2;
6362 x = x - kerningDiff;
6363 }
6364 #endif
6365 }
6366
6367 // 3. Remaining unselected chunk, if any
6368 if (selectionRange.GetEnd() < range.GetEnd())
6369 {
6370 int s2 = wxMin(selectionRange.GetEnd()+1, range.GetEnd());
6371 int r2 = range.GetEnd();
6372
6373 int fragmentLen = r2 - s2 + 1;
6374 if (fragmentLen < 0)
6375 {
6376 wxLogDebug(wxT("Mid(%d, %d"), (int)(s2 - offset), (int)fragmentLen);
6377 }
6378 wxString stringFragment = str.Mid(s2 - offset, fragmentLen);
6379
6380 DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, false);
6381 }
6382 }
6383
6384 return true;
6385 }
6386
6387 bool wxRichTextPlainText::DrawTabbedString(wxDC& dc, const wxRichTextAttr& attr, const wxRect& rect,wxString& str, wxCoord& x, wxCoord& y, bool selected)
6388 {
6389 bool hasTabs = (str.Find(wxT('\t')) != wxNOT_FOUND);
6390
6391 wxArrayInt tabArray;
6392 int tabCount;
6393 if (hasTabs)
6394 {
6395 if (attr.GetTabs().IsEmpty())
6396 tabArray = wxRichTextParagraph::GetDefaultTabs();
6397 else
6398 tabArray = attr.GetTabs();
6399 tabCount = tabArray.GetCount();
6400
6401 for (int i = 0; i < tabCount; ++i)
6402 {
6403 int pos = tabArray[i];
6404 pos = ConvertTenthsMMToPixels(dc, pos);
6405 tabArray[i] = pos;
6406 }
6407 }
6408 else
6409 tabCount = 0;
6410
6411 int nextTabPos = -1;
6412 int tabPos = -1;
6413 wxCoord w, h;
6414
6415 if (selected)
6416 {
6417 wxColour highlightColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT));
6418 wxColour highlightTextColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT));
6419
6420 wxCheckSetBrush(dc, wxBrush(highlightColour));
6421 wxCheckSetPen(dc, wxPen(highlightColour));
6422 dc.SetTextForeground(highlightTextColour);
6423 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
6424 }
6425 else
6426 {
6427 dc.SetTextForeground(attr.GetTextColour());
6428
6429 if (attr.HasFlag(wxTEXT_ATTR_BACKGROUND_COLOUR) && attr.GetBackgroundColour().IsOk())
6430 {
6431 dc.SetBackgroundMode(wxBRUSHSTYLE_SOLID);
6432 dc.SetTextBackground(attr.GetBackgroundColour());
6433 }
6434 else
6435 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
6436 }
6437
6438 wxCoord x_orig = GetParent()->GetPosition().x;
6439 while (hasTabs)
6440 {
6441 // the string has a tab
6442 // break up the string at the Tab
6443 wxString stringChunk = str.BeforeFirst(wxT('\t'));
6444 str = str.AfterFirst(wxT('\t'));
6445 dc.GetTextExtent(stringChunk, & w, & h);
6446 tabPos = x + w;
6447 bool not_found = true;
6448 for (int i = 0; i < tabCount && not_found; ++i)
6449 {
6450 nextTabPos = tabArray.Item(i) + x_orig;
6451
6452 // Find the next tab position.
6453 // Even if we're at the end of the tab array, we must still draw the chunk.
6454
6455 if (nextTabPos > tabPos || (i == (tabCount - 1)))
6456 {
6457 if (nextTabPos <= tabPos)
6458 {
6459 int defaultTabWidth = ConvertTenthsMMToPixels(dc, WIDTH_FOR_DEFAULT_TABS);
6460 nextTabPos = tabPos + defaultTabWidth;
6461 }
6462
6463 not_found = false;
6464 if (selected)
6465 {
6466 w = nextTabPos - x;
6467 wxRect selRect(x, rect.y, w, rect.GetHeight());
6468 dc.DrawRectangle(selRect);
6469 }
6470 dc.DrawText(stringChunk, x, y);
6471
6472 if (attr.HasTextEffects() && (attr.GetTextEffects() & wxTEXT_ATTR_EFFECT_STRIKETHROUGH))
6473 {
6474 wxPen oldPen = dc.GetPen();
6475 wxCheckSetPen(dc, wxPen(attr.GetTextColour(), 1));
6476 dc.DrawLine(x, (int) (y+(h/2)+0.5), x+w, (int) (y+(h/2)+0.5));
6477 wxCheckSetPen(dc, oldPen);
6478 }
6479
6480 x = nextTabPos;
6481 }
6482 }
6483 hasTabs = (str.Find(wxT('\t')) != wxNOT_FOUND);
6484 }
6485
6486 if (!str.IsEmpty())
6487 {
6488 dc.GetTextExtent(str, & w, & h);
6489 if (selected)
6490 {
6491 wxRect selRect(x, rect.y, w, rect.GetHeight());
6492 dc.DrawRectangle(selRect);
6493 }
6494 dc.DrawText(str, x, y);
6495
6496 if (attr.HasTextEffects() && (attr.GetTextEffects() & wxTEXT_ATTR_EFFECT_STRIKETHROUGH))
6497 {
6498 wxPen oldPen = dc.GetPen();
6499 wxCheckSetPen(dc, wxPen(attr.GetTextColour(), 1));
6500 dc.DrawLine(x, (int) (y+(h/2)+0.5), x+w, (int) (y+(h/2)+0.5));
6501 wxCheckSetPen(dc, oldPen);
6502 }
6503
6504 x += w;
6505 }
6506
6507 return true;
6508 }
6509
6510 /// Lay the item out
6511 bool wxRichTextPlainText::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& WXUNUSED(rect), const wxRect& WXUNUSED(parentRect), int WXUNUSED(style))
6512 {
6513 // Only lay out if we haven't already cached the size
6514 if (m_size.x == -1)
6515 GetRangeSize(GetRange(), m_size, m_descent, dc, context, 0, wxPoint(0, 0));
6516 m_maxSize = m_size;
6517 // Eventually we want to have a reasonable estimate of minimum size.
6518 m_minSize = wxSize(0, 0);
6519 return true;
6520 }
6521
6522 /// Copy
6523 void wxRichTextPlainText::Copy(const wxRichTextPlainText& obj)
6524 {
6525 wxRichTextObject::Copy(obj);
6526
6527 m_text = obj.m_text;
6528 }
6529
6530 /// Get/set the object size for the given range. Returns false if the range
6531 /// is invalid for this object.
6532 bool wxRichTextPlainText::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int WXUNUSED(flags), wxPoint position, wxArrayInt* partialExtents) const
6533 {
6534 if (!range.IsWithin(GetRange()))
6535 return false;
6536
6537 wxRichTextParagraph* para = wxDynamicCast(GetParent(), wxRichTextParagraph);
6538 wxASSERT (para != NULL);
6539
6540 int relativeX = position.x - GetParent()->GetPosition().x;
6541
6542 wxRichTextAttr textAttr(para ? para->GetCombinedAttributes(GetAttributes()) : GetAttributes());
6543 context.ApplyVirtualAttributes(textAttr, (wxRichTextObject*) this);
6544
6545 // Always assume unformatted text, since at this level we have no knowledge
6546 // of line breaks - and we don't need it, since we'll calculate size within
6547 // formatted text by doing it in chunks according to the line ranges
6548
6549 bool bScript(false);
6550 wxFont font(GetBuffer()->GetFontTable().FindFont(textAttr));
6551 if (font.IsOk())
6552 {
6553 if ( textAttr.HasTextEffects() && ( (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUPERSCRIPT)
6554 || (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUBSCRIPT) ) )
6555 {
6556 wxFont textFont = font;
6557 double size = static_cast<double>(textFont.GetPointSize()) / wxSCRIPT_MUL_FACTOR;
6558 textFont.SetPointSize( static_cast<int>(size) );
6559 wxCheckSetFont(dc, textFont);
6560 bScript = true;
6561 }
6562 else
6563 {
6564 wxCheckSetFont(dc, font);
6565 }
6566 }
6567
6568 bool haveDescent = false;
6569 int startPos = range.GetStart() - GetRange().GetStart();
6570 long len = range.GetLength();
6571
6572 wxString str(m_text);
6573 wxString toReplace = wxRichTextLineBreakChar;
6574 str.Replace(toReplace, wxT(" "));
6575
6576 wxString stringChunk = str.Mid(startPos, (size_t) len);
6577
6578 if (textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_CAPITALS))
6579 stringChunk.MakeUpper();
6580
6581 wxCoord w, h;
6582 int width = 0;
6583 if (stringChunk.Find(wxT('\t')) != wxNOT_FOUND)
6584 {
6585 // the string has a tab
6586 wxArrayInt tabArray;
6587 if (textAttr.GetTabs().IsEmpty())
6588 tabArray = wxRichTextParagraph::GetDefaultTabs();
6589 else
6590 tabArray = textAttr.GetTabs();
6591
6592 int tabCount = tabArray.GetCount();
6593
6594 for (int i = 0; i < tabCount; ++i)
6595 {
6596 int pos = tabArray[i];
6597 pos = ((wxRichTextPlainText*) this)->ConvertTenthsMMToPixels(dc, pos);
6598 tabArray[i] = pos;
6599 }
6600
6601 int nextTabPos = -1;
6602
6603 while (stringChunk.Find(wxT('\t')) >= 0)
6604 {
6605 int absoluteWidth = 0;
6606
6607 // the string has a tab
6608 // break up the string at the Tab
6609 wxString stringFragment = stringChunk.BeforeFirst(wxT('\t'));
6610 stringChunk = stringChunk.AfterFirst(wxT('\t'));
6611
6612 if (partialExtents)
6613 {
6614 int oldWidth;
6615 if (partialExtents->GetCount() > 0)
6616 oldWidth = (*partialExtents)[partialExtents->GetCount()-1];
6617 else
6618 oldWidth = 0;
6619
6620 // Add these partial extents
6621 wxArrayInt p;
6622 dc.GetPartialTextExtents(stringFragment, p);
6623 size_t j;
6624 for (j = 0; j < p.GetCount(); j++)
6625 partialExtents->Add(oldWidth + p[j]);
6626
6627 if (partialExtents->GetCount() > 0)
6628 absoluteWidth = (*partialExtents)[(*partialExtents).GetCount()-1] + relativeX;
6629 else
6630 absoluteWidth = relativeX;
6631 }
6632 else
6633 {
6634 dc.GetTextExtent(stringFragment, & w, & h);
6635 width += w;
6636 absoluteWidth = width + relativeX;
6637 haveDescent = true;
6638 }
6639
6640 bool notFound = true;
6641 for (int i = 0; i < tabCount && notFound; ++i)
6642 {
6643 nextTabPos = tabArray.Item(i);
6644
6645 // Find the next tab position.
6646 // Even if we're at the end of the tab array, we must still process the chunk.
6647
6648 if (nextTabPos > absoluteWidth || (i == (tabCount - 1)))
6649 {
6650 if (nextTabPos <= absoluteWidth)
6651 {
6652 int defaultTabWidth = ((wxRichTextPlainText*) this)->ConvertTenthsMMToPixels(dc, WIDTH_FOR_DEFAULT_TABS);
6653 nextTabPos = absoluteWidth + defaultTabWidth;
6654 }
6655
6656 notFound = false;
6657 width = nextTabPos - relativeX;
6658
6659 if (partialExtents)
6660 partialExtents->Add(width);
6661 }
6662 }
6663 }
6664 }
6665
6666 if (!stringChunk.IsEmpty())
6667 {
6668 if (partialExtents)
6669 {
6670 int oldWidth;
6671 if (partialExtents->GetCount() > 0)
6672 oldWidth = (*partialExtents)[partialExtents->GetCount()-1];
6673 else
6674 oldWidth = 0;
6675
6676 // Add these partial extents
6677 wxArrayInt p;
6678 dc.GetPartialTextExtents(stringChunk, p);
6679 size_t j;
6680 for (j = 0; j < p.GetCount(); j++)
6681 partialExtents->Add(oldWidth + p[j]);
6682 }
6683 else
6684 {
6685 dc.GetTextExtent(stringChunk, & w, & h, & descent);
6686 width += w;
6687 haveDescent = true;
6688 }
6689 }
6690
6691 if (partialExtents)
6692 {
6693 int charHeight = dc.GetCharHeight();
6694 if ((*partialExtents).GetCount() > 0)
6695 w = (*partialExtents)[partialExtents->GetCount()-1];
6696 else
6697 w = 0;
6698 size = wxSize(w, charHeight);
6699 }
6700 else
6701 {
6702 size = wxSize(width, dc.GetCharHeight());
6703 }
6704
6705 if (!haveDescent)
6706 dc.GetTextExtent(wxT("X"), & w, & h, & descent);
6707
6708 if ( bScript )
6709 dc.SetFont(font);
6710
6711 return true;
6712 }
6713
6714 /// Do a split, returning an object containing the second part, and setting
6715 /// the first part in 'this'.
6716 wxRichTextObject* wxRichTextPlainText::DoSplit(long pos)
6717 {
6718 long index = pos - GetRange().GetStart();
6719
6720 if (index < 0 || index >= (int) m_text.length())
6721 return NULL;
6722
6723 wxString firstPart = m_text.Mid(0, index);
6724 wxString secondPart = m_text.Mid(index);
6725
6726 m_text = firstPart;
6727
6728 wxRichTextPlainText* newObject = new wxRichTextPlainText(secondPart);
6729 newObject->SetAttributes(GetAttributes());
6730 newObject->SetProperties(GetProperties());
6731
6732 newObject->SetRange(wxRichTextRange(pos, GetRange().GetEnd()));
6733 GetRange().SetEnd(pos-1);
6734
6735 return newObject;
6736 }
6737
6738 /// Calculate range
6739 void wxRichTextPlainText::CalculateRange(long start, long& end)
6740 {
6741 end = start + m_text.length() - 1;
6742 m_range.SetRange(start, end);
6743 }
6744
6745 /// Delete range
6746 bool wxRichTextPlainText::DeleteRange(const wxRichTextRange& range)
6747 {
6748 wxRichTextRange r = range;
6749
6750 r.LimitTo(GetRange());
6751
6752 if (r.GetStart() == GetRange().GetStart() && r.GetEnd() == GetRange().GetEnd())
6753 {
6754 m_text.Empty();
6755 return true;
6756 }
6757
6758 long startIndex = r.GetStart() - GetRange().GetStart();
6759 long len = r.GetLength();
6760
6761 m_text = m_text.Mid(0, startIndex) + m_text.Mid(startIndex+len);
6762 return true;
6763 }
6764
6765 /// Get text for the given range.
6766 wxString wxRichTextPlainText::GetTextForRange(const wxRichTextRange& range) const
6767 {
6768 wxRichTextRange r = range;
6769
6770 r.LimitTo(GetRange());
6771
6772 long startIndex = r.GetStart() - GetRange().GetStart();
6773 long len = r.GetLength();
6774
6775 return m_text.Mid(startIndex, len);
6776 }
6777
6778 /// Returns true if this object can merge itself with the given one.
6779 bool wxRichTextPlainText::CanMerge(wxRichTextObject* object) const
6780 {
6781 return object->GetClassInfo() == CLASSINFO(wxRichTextPlainText) &&
6782 (m_text.empty() || (wxTextAttrEq(GetAttributes(), object->GetAttributes()) && m_properties == object->GetProperties()));
6783 }
6784
6785 /// Returns true if this object merged itself with the given one.
6786 /// The calling code will then delete the given object.
6787 bool wxRichTextPlainText::Merge(wxRichTextObject* object)
6788 {
6789 wxRichTextPlainText* textObject = wxDynamicCast(object, wxRichTextPlainText);
6790 wxASSERT( textObject != NULL );
6791
6792 if (textObject)
6793 {
6794 m_text += textObject->GetText();
6795 wxRichTextApplyStyle(m_attributes, textObject->GetAttributes());
6796 return true;
6797 }
6798 else
6799 return false;
6800 }
6801
6802 /// Dump to output stream for debugging
6803 void wxRichTextPlainText::Dump(wxTextOutputStream& stream)
6804 {
6805 wxRichTextObject::Dump(stream);
6806 stream << m_text << wxT("\n");
6807 }
6808
6809 /// Get the first position from pos that has a line break character.
6810 long wxRichTextPlainText::GetFirstLineBreakPosition(long pos)
6811 {
6812 int i;
6813 int len = m_text.length();
6814 int startPos = pos - m_range.GetStart();
6815 for (i = startPos; i < len; i++)
6816 {
6817 wxChar ch = m_text[i];
6818 if (ch == wxRichTextLineBreakChar)
6819 {
6820 return i + m_range.GetStart();
6821 }
6822 }
6823 return -1;
6824 }
6825
6826 /*!
6827 * wxRichTextBuffer
6828 * This is a kind of box, used to represent the whole buffer
6829 */
6830
6831 IMPLEMENT_DYNAMIC_CLASS(wxRichTextBuffer, wxRichTextParagraphLayoutBox)
6832
6833 wxList wxRichTextBuffer::sm_handlers;
6834 wxList wxRichTextBuffer::sm_drawingHandlers;
6835 wxRichTextFieldTypeHashMap wxRichTextBuffer::sm_fieldTypes;
6836 wxRichTextRenderer* wxRichTextBuffer::sm_renderer = NULL;
6837 int wxRichTextBuffer::sm_bulletRightMargin = 20;
6838 float wxRichTextBuffer::sm_bulletProportion = (float) 0.3;
6839
6840 /// Initialisation
6841 void wxRichTextBuffer::Init()
6842 {
6843 m_commandProcessor = new wxCommandProcessor;
6844 m_styleSheet = NULL;
6845 m_modified = false;
6846 m_batchedCommandDepth = 0;
6847 m_batchedCommand = NULL;
6848 m_suppressUndo = 0;
6849 m_handlerFlags = 0;
6850 m_scale = 1.0;
6851 SetMargins(4);
6852 }
6853
6854 /// Initialisation
6855 wxRichTextBuffer::~wxRichTextBuffer()
6856 {
6857 delete m_commandProcessor;
6858 delete m_batchedCommand;
6859
6860 ClearStyleStack();
6861 ClearEventHandlers();
6862 }
6863
6864 void wxRichTextBuffer::ResetAndClearCommands()
6865 {
6866 Reset();
6867
6868 GetCommandProcessor()->ClearCommands();
6869
6870 Modify(false);
6871 Invalidate(wxRICHTEXT_ALL);
6872 }
6873
6874 void wxRichTextBuffer::Copy(const wxRichTextBuffer& obj)
6875 {
6876 wxRichTextParagraphLayoutBox::Copy(obj);
6877
6878 m_styleSheet = obj.m_styleSheet;
6879 m_modified = obj.m_modified;
6880 m_batchedCommandDepth = 0;
6881 if (m_batchedCommand)
6882 delete m_batchedCommand;
6883 m_batchedCommand = NULL;
6884 m_suppressUndo = obj.m_suppressUndo;
6885 m_invalidRange = obj.m_invalidRange;
6886 }
6887
6888 /// Push style sheet to top of stack
6889 bool wxRichTextBuffer::PushStyleSheet(wxRichTextStyleSheet* styleSheet)
6890 {
6891 if (m_styleSheet)
6892 styleSheet->InsertSheet(m_styleSheet);
6893
6894 SetStyleSheet(styleSheet);
6895
6896 return true;
6897 }
6898
6899 /// Pop style sheet from top of stack
6900 wxRichTextStyleSheet* wxRichTextBuffer::PopStyleSheet()
6901 {
6902 if (m_styleSheet)
6903 {
6904 wxRichTextStyleSheet* oldSheet = m_styleSheet;
6905 m_styleSheet = oldSheet->GetNextSheet();
6906 oldSheet->Unlink();
6907
6908 return oldSheet;
6909 }
6910 else
6911 return NULL;
6912 }
6913
6914 /// Submit command to insert paragraphs
6915 bool wxRichTextBuffer::InsertParagraphsWithUndo(long pos, const wxRichTextParagraphLayoutBox& paragraphs, wxRichTextCtrl* ctrl, int flags)
6916 {
6917 return ctrl->GetFocusObject()->InsertParagraphsWithUndo(this, pos, paragraphs, ctrl, flags);
6918 }
6919
6920 /// Submit command to insert paragraphs
6921 bool wxRichTextParagraphLayoutBox::InsertParagraphsWithUndo(wxRichTextBuffer* buffer, long pos, const wxRichTextParagraphLayoutBox& paragraphs, wxRichTextCtrl* ctrl, int WXUNUSED(flags))
6922 {
6923 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
6924
6925 action->GetNewParagraphs() = paragraphs;
6926
6927 action->SetPosition(pos);
6928
6929 wxRichTextRange range = wxRichTextRange(pos, pos + paragraphs.GetOwnRange().GetEnd() - 1);
6930 if (!paragraphs.GetPartialParagraph())
6931 range.SetEnd(range.GetEnd()+1);
6932
6933 // Set the range we'll need to delete in Undo
6934 action->SetRange(range);
6935
6936 buffer->SubmitAction(action);
6937
6938 return true;
6939 }
6940
6941 /// Submit command to insert the given text
6942 bool wxRichTextBuffer::InsertTextWithUndo(long pos, const wxString& text, wxRichTextCtrl* ctrl, int flags)
6943 {
6944 return ctrl->GetFocusObject()->InsertTextWithUndo(this, pos, text, ctrl, flags);
6945 }
6946
6947 /// Submit command to insert the given text
6948 bool wxRichTextParagraphLayoutBox::InsertTextWithUndo(wxRichTextBuffer* buffer, long pos, const wxString& text, wxRichTextCtrl* ctrl, int flags)
6949 {
6950 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
6951
6952 wxRichTextAttr* p = NULL;
6953 wxRichTextAttr paraAttr;
6954 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
6955 {
6956 // Get appropriate paragraph style
6957 paraAttr = GetStyleForNewParagraph(buffer, pos, false, false);
6958 if (!paraAttr.IsDefault())
6959 p = & paraAttr;
6960 }
6961
6962 action->GetNewParagraphs().AddParagraphs(text, p);
6963
6964 int length = action->GetNewParagraphs().GetOwnRange().GetLength();
6965
6966 if (!text.empty() && text.Last() != wxT('\n'))
6967 {
6968 // Don't count the newline when undoing
6969 length --;
6970 action->GetNewParagraphs().SetPartialParagraph(true);
6971 }
6972 else if (!text.empty() && text.Last() == wxT('\n'))
6973 length --;
6974
6975 action->SetPosition(pos);
6976
6977 // Set the range we'll need to delete in Undo
6978 action->SetRange(wxRichTextRange(pos, pos + length - 1));
6979
6980 buffer->SubmitAction(action);
6981
6982 return true;
6983 }
6984
6985 /// Submit command to insert the given text
6986 bool wxRichTextBuffer::InsertNewlineWithUndo(long pos, wxRichTextCtrl* ctrl, int flags)
6987 {
6988 return ctrl->GetFocusObject()->InsertNewlineWithUndo(this, pos, ctrl, flags);
6989 }
6990
6991 /// Submit command to insert the given text
6992 bool wxRichTextParagraphLayoutBox::InsertNewlineWithUndo(wxRichTextBuffer* buffer, long pos, wxRichTextCtrl* ctrl, int flags)
6993 {
6994 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
6995
6996 wxRichTextAttr* p = NULL;
6997 wxRichTextAttr paraAttr;
6998 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
6999 {
7000 paraAttr = GetStyleForNewParagraph(buffer, pos, false, true /* look for next paragraph style */);
7001 if (!paraAttr.IsDefault())
7002 p = & paraAttr;
7003 }
7004
7005 wxRichTextAttr attr(buffer->GetDefaultStyle());
7006
7007 wxRichTextParagraph* newPara = new wxRichTextParagraph(wxEmptyString, this, & attr);
7008 action->GetNewParagraphs().AppendChild(newPara);
7009 action->GetNewParagraphs().UpdateRanges();
7010 action->GetNewParagraphs().SetPartialParagraph(false);
7011 wxRichTextParagraph* para = GetParagraphAtPosition(pos, false);
7012 long pos1 = pos;
7013
7014 if (p)
7015 newPara->SetAttributes(*p);
7016
7017 if (flags & wxRICHTEXT_INSERT_INTERACTIVE)
7018 {
7019 if (para && para->GetRange().GetEnd() == pos)
7020 pos1 ++;
7021
7022 // Now see if we need to number the paragraph.
7023 if (newPara->GetAttributes().HasBulletNumber())
7024 {
7025 wxRichTextAttr numberingAttr;
7026 if (FindNextParagraphNumber(para, numberingAttr))
7027 wxRichTextApplyStyle(newPara->GetAttributes(), (const wxRichTextAttr&) numberingAttr);
7028 }
7029 }
7030
7031 action->SetPosition(pos);
7032
7033 // Use the default character style
7034 // Use the default character style
7035 if (!buffer->GetDefaultStyle().IsDefault() && newPara->GetChildren().GetFirst())
7036 {
7037 // Check whether the default style merely reflects the paragraph/basic style,
7038 // in which case don't apply it.
7039 wxRichTextAttr defaultStyle(buffer->GetDefaultStyle());
7040 wxRichTextAttr toApply;
7041 if (para)
7042 {
7043 wxRichTextAttr combinedAttr = para->GetCombinedAttributes(true /* include box attributes */);
7044 wxRichTextAttr newAttr;
7045 // This filters out attributes that are accounted for by the current
7046 // paragraph/basic style
7047 wxRichTextApplyStyle(toApply, defaultStyle, & combinedAttr);
7048 }
7049 else
7050 toApply = defaultStyle;
7051
7052 if (!toApply.IsDefault())
7053 newPara->GetChildren().GetFirst()->GetData()->SetAttributes(toApply);
7054 }
7055
7056 // Set the range we'll need to delete in Undo
7057 action->SetRange(wxRichTextRange(pos1, pos1));
7058
7059 buffer->SubmitAction(action);
7060
7061 return true;
7062 }
7063
7064 /// Submit command to insert the given image
7065 bool wxRichTextBuffer::InsertImageWithUndo(long pos, const wxRichTextImageBlock& imageBlock, wxRichTextCtrl* ctrl, int flags,
7066 const wxRichTextAttr& textAttr)
7067 {
7068 return ctrl->GetFocusObject()->InsertImageWithUndo(this, pos, imageBlock, ctrl, flags, textAttr);
7069 }
7070
7071 /// Submit command to insert the given image
7072 bool wxRichTextParagraphLayoutBox::InsertImageWithUndo(wxRichTextBuffer* buffer, long pos, const wxRichTextImageBlock& imageBlock,
7073 wxRichTextCtrl* ctrl, int flags,
7074 const wxRichTextAttr& textAttr)
7075 {
7076 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Image"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
7077
7078 wxRichTextAttr* p = NULL;
7079 wxRichTextAttr paraAttr;
7080 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
7081 {
7082 paraAttr = GetStyleForNewParagraph(buffer, pos);
7083 if (!paraAttr.IsDefault())
7084 p = & paraAttr;
7085 }
7086
7087 wxRichTextAttr attr(buffer->GetDefaultStyle());
7088
7089 wxRichTextParagraph* newPara = new wxRichTextParagraph(this, & attr);
7090 if (p)
7091 newPara->SetAttributes(*p);
7092
7093 wxRichTextImage* imageObject = new wxRichTextImage(imageBlock, newPara);
7094 newPara->AppendChild(imageObject);
7095 imageObject->SetAttributes(textAttr);
7096 action->GetNewParagraphs().AppendChild(newPara);
7097 action->GetNewParagraphs().UpdateRanges();
7098
7099 action->GetNewParagraphs().SetPartialParagraph(true);
7100
7101 action->SetPosition(pos);
7102
7103 // Set the range we'll need to delete in Undo
7104 action->SetRange(wxRichTextRange(pos, pos));
7105
7106 buffer->SubmitAction(action);
7107
7108 return true;
7109 }
7110
7111 // Insert an object with no change of it
7112 wxRichTextObject* wxRichTextBuffer::InsertObjectWithUndo(long pos, wxRichTextObject *object, wxRichTextCtrl* ctrl, int flags)
7113 {
7114 return ctrl->GetFocusObject()->InsertObjectWithUndo(this, pos, object, ctrl, flags);
7115 }
7116
7117 // Insert an object with no change of it
7118 wxRichTextObject* wxRichTextParagraphLayoutBox::InsertObjectWithUndo(wxRichTextBuffer* buffer, long pos, wxRichTextObject *object, wxRichTextCtrl* ctrl, int flags)
7119 {
7120 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Object"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
7121
7122 wxRichTextAttr* p = NULL;
7123 wxRichTextAttr paraAttr;
7124 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
7125 {
7126 paraAttr = GetStyleForNewParagraph(buffer, pos);
7127 if (!paraAttr.IsDefault())
7128 p = & paraAttr;
7129 }
7130
7131 wxRichTextAttr attr(buffer->GetDefaultStyle());
7132
7133 wxRichTextParagraph* newPara = new wxRichTextParagraph(this, & attr);
7134 if (p)
7135 newPara->SetAttributes(*p);
7136
7137 newPara->AppendChild(object);
7138 action->GetNewParagraphs().AppendChild(newPara);
7139 action->GetNewParagraphs().UpdateRanges();
7140
7141 action->GetNewParagraphs().SetPartialParagraph(true);
7142
7143 action->SetPosition(pos);
7144
7145 // Set the range we'll need to delete in Undo
7146 action->SetRange(wxRichTextRange(pos, pos));
7147
7148 buffer->SubmitAction(action);
7149
7150 wxRichTextObject* obj = GetLeafObjectAtPosition(pos);
7151 return obj;
7152 }
7153
7154 wxRichTextField* wxRichTextParagraphLayoutBox::InsertFieldWithUndo(wxRichTextBuffer* buffer, long pos, const wxString& fieldType,
7155 const wxRichTextProperties& properties,
7156 wxRichTextCtrl* ctrl, int flags,
7157 const wxRichTextAttr& textAttr)
7158 {
7159 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Field"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
7160
7161 wxRichTextAttr* p = NULL;
7162 wxRichTextAttr paraAttr;
7163 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
7164 {
7165 paraAttr = GetStyleForNewParagraph(buffer, pos);
7166 if (!paraAttr.IsDefault())
7167 p = & paraAttr;
7168 }
7169
7170 wxRichTextAttr attr(buffer->GetDefaultStyle());
7171
7172 wxRichTextParagraph* newPara = new wxRichTextParagraph(this, & attr);
7173 if (p)
7174 newPara->SetAttributes(*p);
7175
7176 wxRichTextField* fieldObject = new wxRichTextField();
7177 fieldObject->wxRichTextObject::SetProperties(properties);
7178 fieldObject->SetFieldType(fieldType);
7179 fieldObject->SetAttributes(textAttr);
7180 newPara->AppendChild(fieldObject);
7181 action->GetNewParagraphs().AppendChild(newPara);
7182 action->GetNewParagraphs().UpdateRanges();
7183 action->GetNewParagraphs().SetPartialParagraph(true);
7184 action->SetPosition(pos);
7185
7186 // Set the range we'll need to delete in Undo
7187 action->SetRange(wxRichTextRange(pos, pos));
7188
7189 buffer->SubmitAction(action);
7190
7191 wxRichTextField* obj = wxDynamicCast(GetLeafObjectAtPosition(pos), wxRichTextField);
7192 return obj;
7193 }
7194
7195 /// Get the style that is appropriate for a new paragraph at this position.
7196 /// If the previous paragraph has a paragraph style name, look up the next-paragraph
7197 /// style.
7198 wxRichTextAttr wxRichTextParagraphLayoutBox::GetStyleForNewParagraph(wxRichTextBuffer* buffer, long pos, bool caretPosition, bool lookUpNewParaStyle) const
7199 {
7200 wxRichTextParagraph* para = GetParagraphAtPosition(pos, caretPosition);
7201 if (para)
7202 {
7203 wxRichTextAttr attr;
7204 bool foundAttributes = false;
7205
7206 // Look for a matching paragraph style
7207 if (lookUpNewParaStyle && !para->GetAttributes().GetParagraphStyleName().IsEmpty() && buffer->GetStyleSheet())
7208 {
7209 wxRichTextParagraphStyleDefinition* paraDef = buffer->GetStyleSheet()->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName());
7210 if (paraDef)
7211 {
7212 // If we're not at the end of the paragraph, then we apply THIS style, and not the designated next style.
7213 if (para->GetRange().GetEnd() == pos && !paraDef->GetNextStyle().IsEmpty())
7214 {
7215 wxRichTextParagraphStyleDefinition* nextParaDef = buffer->GetStyleSheet()->FindParagraphStyle(paraDef->GetNextStyle());
7216 if (nextParaDef)
7217 {
7218 foundAttributes = true;
7219 attr = nextParaDef->GetStyleMergedWithBase(buffer->GetStyleSheet());
7220 }
7221 }
7222
7223 // If we didn't find the 'next style', use this style instead.
7224 if (!foundAttributes)
7225 {
7226 foundAttributes = true;
7227 attr = paraDef->GetStyleMergedWithBase(buffer->GetStyleSheet());
7228 }
7229 }
7230 }
7231
7232 // Also apply list style if present
7233 if (lookUpNewParaStyle && !para->GetAttributes().GetListStyleName().IsEmpty() && buffer->GetStyleSheet())
7234 {
7235 wxRichTextListStyleDefinition* listDef = buffer->GetStyleSheet()->FindListStyle(para->GetAttributes().GetListStyleName());
7236 if (listDef)
7237 {
7238 int thisIndent = para->GetAttributes().GetLeftIndent();
7239 int thisLevel = para->GetAttributes().HasOutlineLevel() ? para->GetAttributes().GetOutlineLevel() : listDef->FindLevelForIndent(thisIndent);
7240
7241 // Apply the overall list style, and item style for this level
7242 wxRichTextAttr listStyle(listDef->GetCombinedStyleForLevel(thisLevel, buffer->GetStyleSheet()));
7243 wxRichTextApplyStyle(attr, listStyle);
7244 attr.SetOutlineLevel(thisLevel);
7245 if (para->GetAttributes().HasBulletNumber())
7246 attr.SetBulletNumber(para->GetAttributes().GetBulletNumber());
7247 }
7248 }
7249
7250 if (!foundAttributes)
7251 {
7252 attr = para->GetAttributes();
7253 int flags = attr.GetFlags();
7254
7255 // Eliminate character styles
7256 flags &= ( (~ wxTEXT_ATTR_FONT) |
7257 (~ wxTEXT_ATTR_TEXT_COLOUR) |
7258 (~ wxTEXT_ATTR_BACKGROUND_COLOUR) );
7259 attr.SetFlags(flags);
7260 }
7261
7262 return attr;
7263 }
7264 else
7265 return wxRichTextAttr();
7266 }
7267
7268 /// Submit command to delete this range
7269 bool wxRichTextBuffer::DeleteRangeWithUndo(const wxRichTextRange& range, wxRichTextCtrl* ctrl)
7270 {
7271 return ctrl->GetFocusObject()->DeleteRangeWithUndo(range, ctrl, this);
7272 }
7273
7274 /// Submit command to delete this range
7275 bool wxRichTextParagraphLayoutBox::DeleteRangeWithUndo(const wxRichTextRange& range, wxRichTextCtrl* ctrl, wxRichTextBuffer* buffer)
7276 {
7277 wxRichTextAction* action = new wxRichTextAction(NULL, _("Delete"), wxRICHTEXT_DELETE, buffer, this, ctrl);
7278
7279 action->SetPosition(ctrl->GetCaretPosition());
7280
7281 // Set the range to delete
7282 action->SetRange(range);
7283
7284 // Copy the fragment that we'll need to restore in Undo
7285 CopyFragment(range, action->GetOldParagraphs());
7286
7287 // See if we're deleting a paragraph marker, in which case we need to
7288 // make a note not to copy the attributes from the 2nd paragraph to the 1st.
7289 if (range.GetStart() == range.GetEnd())
7290 {
7291 wxRichTextParagraph* para = GetParagraphAtPosition(range.GetStart());
7292 if (para && para->GetRange().GetEnd() == range.GetEnd())
7293 {
7294 wxRichTextParagraph* nextPara = GetParagraphAtPosition(range.GetStart()+1);
7295 if (nextPara && nextPara != para)
7296 {
7297 action->GetOldParagraphs().GetChildren().GetFirst()->GetData()->SetAttributes(nextPara->GetAttributes());
7298 action->GetOldParagraphs().GetAttributes().SetFlags(action->GetOldParagraphs().GetAttributes().GetFlags() | wxTEXT_ATTR_KEEP_FIRST_PARA_STYLE);
7299 }
7300 }
7301 }
7302
7303 buffer->SubmitAction(action);
7304
7305 return true;
7306 }
7307
7308 /// Collapse undo/redo commands
7309 bool wxRichTextBuffer::BeginBatchUndo(const wxString& cmdName)
7310 {
7311 if (m_batchedCommandDepth == 0)
7312 {
7313 wxASSERT(m_batchedCommand == NULL);
7314 if (m_batchedCommand)
7315 {
7316 GetCommandProcessor()->Store(m_batchedCommand);
7317 }
7318 m_batchedCommand = new wxRichTextCommand(cmdName);
7319 }
7320
7321 m_batchedCommandDepth ++;
7322
7323 return true;
7324 }
7325
7326 /// Collapse undo/redo commands
7327 bool wxRichTextBuffer::EndBatchUndo()
7328 {
7329 m_batchedCommandDepth --;
7330
7331 wxASSERT(m_batchedCommandDepth >= 0);
7332 wxASSERT(m_batchedCommand != NULL);
7333
7334 if (m_batchedCommandDepth == 0)
7335 {
7336 GetCommandProcessor()->Store(m_batchedCommand);
7337 m_batchedCommand = NULL;
7338 }
7339
7340 return true;
7341 }
7342
7343 /// Submit immediately, or delay according to whether collapsing is on
7344 bool wxRichTextBuffer::SubmitAction(wxRichTextAction* action)
7345 {
7346 if (action && !action->GetNewParagraphs().IsEmpty())
7347 PrepareContent(action->GetNewParagraphs());
7348
7349 if (BatchingUndo() && m_batchedCommand && !SuppressingUndo())
7350 {
7351 wxRichTextCommand* cmd = new wxRichTextCommand(action->GetName());
7352 cmd->AddAction(action);
7353 cmd->Do();
7354 cmd->GetActions().Clear();
7355 delete cmd;
7356
7357 m_batchedCommand->AddAction(action);
7358 }
7359 else
7360 {
7361 wxRichTextCommand* cmd = new wxRichTextCommand(action->GetName());
7362 cmd->AddAction(action);
7363
7364 // Only store it if we're not suppressing undo.
7365 return GetCommandProcessor()->Submit(cmd, !SuppressingUndo());
7366 }
7367
7368 return true;
7369 }
7370
7371 /// Begin suppressing undo/redo commands.
7372 bool wxRichTextBuffer::BeginSuppressUndo()
7373 {
7374 m_suppressUndo ++;
7375
7376 return true;
7377 }
7378
7379 /// End suppressing undo/redo commands.
7380 bool wxRichTextBuffer::EndSuppressUndo()
7381 {
7382 m_suppressUndo --;
7383
7384 return true;
7385 }
7386
7387 /// Begin using a style
7388 bool wxRichTextBuffer::BeginStyle(const wxRichTextAttr& style)
7389 {
7390 wxRichTextAttr newStyle(GetDefaultStyle());
7391
7392 // Save the old default style
7393 m_attributeStack.Append((wxObject*) new wxRichTextAttr(GetDefaultStyle()));
7394
7395 wxRichTextApplyStyle(newStyle, style);
7396 newStyle.SetFlags(style.GetFlags()|newStyle.GetFlags());
7397
7398 SetDefaultStyle(newStyle);
7399
7400 return true;
7401 }
7402
7403 /// End the style
7404 bool wxRichTextBuffer::EndStyle()
7405 {
7406 if (!m_attributeStack.GetFirst())
7407 {
7408 wxLogDebug(_("Too many EndStyle calls!"));
7409 return false;
7410 }
7411
7412 wxList::compatibility_iterator node = m_attributeStack.GetLast();
7413 wxRichTextAttr* attr = (wxRichTextAttr*)node->GetData();
7414 m_attributeStack.Erase(node);
7415
7416 SetDefaultStyle(*attr);
7417
7418 delete attr;
7419 return true;
7420 }
7421
7422 /// End all styles
7423 bool wxRichTextBuffer::EndAllStyles()
7424 {
7425 while (m_attributeStack.GetCount() != 0)
7426 EndStyle();
7427 return true;
7428 }
7429
7430 /// Clear the style stack
7431 void wxRichTextBuffer::ClearStyleStack()
7432 {
7433 for (wxList::compatibility_iterator node = m_attributeStack.GetFirst(); node; node = node->GetNext())
7434 delete (wxRichTextAttr*) node->GetData();
7435 m_attributeStack.Clear();
7436 }
7437
7438 /// Begin using bold
7439 bool wxRichTextBuffer::BeginBold()
7440 {
7441 wxRichTextAttr attr;
7442 attr.SetFontWeight(wxFONTWEIGHT_BOLD);
7443
7444 return BeginStyle(attr);
7445 }
7446
7447 /// Begin using italic
7448 bool wxRichTextBuffer::BeginItalic()
7449 {
7450 wxRichTextAttr attr;
7451 attr.SetFontStyle(wxFONTSTYLE_ITALIC);
7452
7453 return BeginStyle(attr);
7454 }
7455
7456 /// Begin using underline
7457 bool wxRichTextBuffer::BeginUnderline()
7458 {
7459 wxRichTextAttr attr;
7460 attr.SetFontUnderlined(true);
7461
7462 return BeginStyle(attr);
7463 }
7464
7465 /// Begin using point size
7466 bool wxRichTextBuffer::BeginFontSize(int pointSize)
7467 {
7468 wxRichTextAttr attr;
7469 attr.SetFontSize(pointSize);
7470
7471 return BeginStyle(attr);
7472 }
7473
7474 /// Begin using this font
7475 bool wxRichTextBuffer::BeginFont(const wxFont& font)
7476 {
7477 wxRichTextAttr attr;
7478 attr.SetFont(font);
7479
7480 return BeginStyle(attr);
7481 }
7482
7483 /// Begin using this colour
7484 bool wxRichTextBuffer::BeginTextColour(const wxColour& colour)
7485 {
7486 wxRichTextAttr attr;
7487 attr.SetFlags(wxTEXT_ATTR_TEXT_COLOUR);
7488 attr.SetTextColour(colour);
7489
7490 return BeginStyle(attr);
7491 }
7492
7493 /// Begin using alignment
7494 bool wxRichTextBuffer::BeginAlignment(wxTextAttrAlignment alignment)
7495 {
7496 wxRichTextAttr attr;
7497 attr.SetFlags(wxTEXT_ATTR_ALIGNMENT);
7498 attr.SetAlignment(alignment);
7499
7500 return BeginStyle(attr);
7501 }
7502
7503 /// Begin left indent
7504 bool wxRichTextBuffer::BeginLeftIndent(int leftIndent, int leftSubIndent)
7505 {
7506 wxRichTextAttr attr;
7507 attr.SetFlags(wxTEXT_ATTR_LEFT_INDENT);
7508 attr.SetLeftIndent(leftIndent, leftSubIndent);
7509
7510 return BeginStyle(attr);
7511 }
7512
7513 /// Begin right indent
7514 bool wxRichTextBuffer::BeginRightIndent(int rightIndent)
7515 {
7516 wxRichTextAttr attr;
7517 attr.SetFlags(wxTEXT_ATTR_RIGHT_INDENT);
7518 attr.SetRightIndent(rightIndent);
7519
7520 return BeginStyle(attr);
7521 }
7522
7523 /// Begin paragraph spacing
7524 bool wxRichTextBuffer::BeginParagraphSpacing(int before, int after)
7525 {
7526 long flags = 0;
7527 if (before != 0)
7528 flags |= wxTEXT_ATTR_PARA_SPACING_BEFORE;
7529 if (after != 0)
7530 flags |= wxTEXT_ATTR_PARA_SPACING_AFTER;
7531
7532 wxRichTextAttr attr;
7533 attr.SetFlags(flags);
7534 attr.SetParagraphSpacingBefore(before);
7535 attr.SetParagraphSpacingAfter(after);
7536
7537 return BeginStyle(attr);
7538 }
7539
7540 /// Begin line spacing
7541 bool wxRichTextBuffer::BeginLineSpacing(int lineSpacing)
7542 {
7543 wxRichTextAttr attr;
7544 attr.SetFlags(wxTEXT_ATTR_LINE_SPACING);
7545 attr.SetLineSpacing(lineSpacing);
7546
7547 return BeginStyle(attr);
7548 }
7549
7550 /// Begin numbered bullet
7551 bool wxRichTextBuffer::BeginNumberedBullet(int bulletNumber, int leftIndent, int leftSubIndent, int bulletStyle)
7552 {
7553 wxRichTextAttr attr;
7554 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT);
7555 attr.SetBulletStyle(bulletStyle);
7556 attr.SetBulletNumber(bulletNumber);
7557 attr.SetLeftIndent(leftIndent, leftSubIndent);
7558
7559 return BeginStyle(attr);
7560 }
7561
7562 /// Begin symbol bullet
7563 bool wxRichTextBuffer::BeginSymbolBullet(const wxString& symbol, int leftIndent, int leftSubIndent, int bulletStyle)
7564 {
7565 wxRichTextAttr attr;
7566 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT);
7567 attr.SetBulletStyle(bulletStyle);
7568 attr.SetLeftIndent(leftIndent, leftSubIndent);
7569 attr.SetBulletText(symbol);
7570
7571 return BeginStyle(attr);
7572 }
7573
7574 /// Begin standard bullet
7575 bool wxRichTextBuffer::BeginStandardBullet(const wxString& bulletName, int leftIndent, int leftSubIndent, int bulletStyle)
7576 {
7577 wxRichTextAttr attr;
7578 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT);
7579 attr.SetBulletStyle(bulletStyle);
7580 attr.SetLeftIndent(leftIndent, leftSubIndent);
7581 attr.SetBulletName(bulletName);
7582
7583 return BeginStyle(attr);
7584 }
7585
7586 /// Begin named character style
7587 bool wxRichTextBuffer::BeginCharacterStyle(const wxString& characterStyle)
7588 {
7589 if (GetStyleSheet())
7590 {
7591 wxRichTextCharacterStyleDefinition* def = GetStyleSheet()->FindCharacterStyle(characterStyle);
7592 if (def)
7593 {
7594 wxRichTextAttr attr = def->GetStyleMergedWithBase(GetStyleSheet());
7595 return BeginStyle(attr);
7596 }
7597 }
7598 return false;
7599 }
7600
7601 /// Begin named paragraph style
7602 bool wxRichTextBuffer::BeginParagraphStyle(const wxString& paragraphStyle)
7603 {
7604 if (GetStyleSheet())
7605 {
7606 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(paragraphStyle);
7607 if (def)
7608 {
7609 wxRichTextAttr attr = def->GetStyleMergedWithBase(GetStyleSheet());
7610 return BeginStyle(attr);
7611 }
7612 }
7613 return false;
7614 }
7615
7616 /// Begin named list style
7617 bool wxRichTextBuffer::BeginListStyle(const wxString& listStyle, int level, int number)
7618 {
7619 if (GetStyleSheet())
7620 {
7621 wxRichTextListStyleDefinition* def = GetStyleSheet()->FindListStyle(listStyle);
7622 if (def)
7623 {
7624 wxRichTextAttr attr(def->GetCombinedStyleForLevel(level));
7625
7626 attr.SetBulletNumber(number);
7627
7628 return BeginStyle(attr);
7629 }
7630 }
7631 return false;
7632 }
7633
7634 /// Begin URL
7635 bool wxRichTextBuffer::BeginURL(const wxString& url, const wxString& characterStyle)
7636 {
7637 wxRichTextAttr attr;
7638
7639 if (!characterStyle.IsEmpty() && GetStyleSheet())
7640 {
7641 wxRichTextCharacterStyleDefinition* def = GetStyleSheet()->FindCharacterStyle(characterStyle);
7642 if (def)
7643 {
7644 attr = def->GetStyleMergedWithBase(GetStyleSheet());
7645 }
7646 }
7647 attr.SetURL(url);
7648
7649 return BeginStyle(attr);
7650 }
7651
7652 /// Adds a handler to the end
7653 void wxRichTextBuffer::AddHandler(wxRichTextFileHandler *handler)
7654 {
7655 sm_handlers.Append(handler);
7656 }
7657
7658 /// Inserts a handler at the front
7659 void wxRichTextBuffer::InsertHandler(wxRichTextFileHandler *handler)
7660 {
7661 sm_handlers.Insert( handler );
7662 }
7663
7664 /// Removes a handler
7665 bool wxRichTextBuffer::RemoveHandler(const wxString& name)
7666 {
7667 wxRichTextFileHandler *handler = FindHandler(name);
7668 if (handler)
7669 {
7670 sm_handlers.DeleteObject(handler);
7671 delete handler;
7672 return true;
7673 }
7674 else
7675 return false;
7676 }
7677
7678 /// Finds a handler by filename or, if supplied, type
7679 wxRichTextFileHandler *wxRichTextBuffer::FindHandlerFilenameOrType(const wxString& filename,
7680 wxRichTextFileType imageType)
7681 {
7682 if (imageType != wxRICHTEXT_TYPE_ANY)
7683 return FindHandler(imageType);
7684 else if (!filename.IsEmpty())
7685 {
7686 wxString path, file, ext;
7687 wxFileName::SplitPath(filename, & path, & file, & ext);
7688 return FindHandler(ext, imageType);
7689 }
7690 else
7691 return NULL;
7692 }
7693
7694
7695 /// Finds a handler by name
7696 wxRichTextFileHandler* wxRichTextBuffer::FindHandler(const wxString& name)
7697 {
7698 wxList::compatibility_iterator node = sm_handlers.GetFirst();
7699 while (node)
7700 {
7701 wxRichTextFileHandler *handler = (wxRichTextFileHandler*)node->GetData();
7702 if (handler->GetName().Lower() == name.Lower()) return handler;
7703
7704 node = node->GetNext();
7705 }
7706 return NULL;
7707 }
7708
7709 /// Finds a handler by extension and type
7710 wxRichTextFileHandler* wxRichTextBuffer::FindHandler(const wxString& extension, wxRichTextFileType type)
7711 {
7712 wxList::compatibility_iterator node = sm_handlers.GetFirst();
7713 while (node)
7714 {
7715 wxRichTextFileHandler *handler = (wxRichTextFileHandler*)node->GetData();
7716 if ( handler->GetExtension().Lower() == extension.Lower() &&
7717 (type == wxRICHTEXT_TYPE_ANY || handler->GetType() == type) )
7718 return handler;
7719 node = node->GetNext();
7720 }
7721 return 0;
7722 }
7723
7724 /// Finds a handler by type
7725 wxRichTextFileHandler* wxRichTextBuffer::FindHandler(wxRichTextFileType type)
7726 {
7727 wxList::compatibility_iterator node = sm_handlers.GetFirst();
7728 while (node)
7729 {
7730 wxRichTextFileHandler *handler = (wxRichTextFileHandler *)node->GetData();
7731 if (handler->GetType() == type) return handler;
7732 node = node->GetNext();
7733 }
7734 return NULL;
7735 }
7736
7737 void wxRichTextBuffer::InitStandardHandlers()
7738 {
7739 if (!FindHandler(wxRICHTEXT_TYPE_TEXT))
7740 AddHandler(new wxRichTextPlainTextHandler);
7741 }
7742
7743 void wxRichTextBuffer::CleanUpHandlers()
7744 {
7745 wxList::compatibility_iterator node = sm_handlers.GetFirst();
7746 while (node)
7747 {
7748 wxRichTextFileHandler* handler = (wxRichTextFileHandler*)node->GetData();
7749 wxList::compatibility_iterator next = node->GetNext();
7750 delete handler;
7751 node = next;
7752 }
7753
7754 sm_handlers.Clear();
7755 }
7756
7757 wxString wxRichTextBuffer::GetExtWildcard(bool combine, bool save, wxArrayInt* types)
7758 {
7759 if (types)
7760 types->Clear();
7761
7762 wxString wildcard;
7763
7764 wxList::compatibility_iterator node = GetHandlers().GetFirst();
7765 int count = 0;
7766 while (node)
7767 {
7768 wxRichTextFileHandler* handler = (wxRichTextFileHandler*) node->GetData();
7769 if (handler->IsVisible() && ((save && handler->CanSave()) || (!save && handler->CanLoad())))
7770 {
7771 if (combine)
7772 {
7773 if (count > 0)
7774 wildcard += wxT(";");
7775 wildcard += wxT("*.") + handler->GetExtension();
7776 }
7777 else
7778 {
7779 if (count > 0)
7780 wildcard += wxT("|");
7781 wildcard += handler->GetName();
7782 wildcard += wxT(" ");
7783 wildcard += _("files");
7784 wildcard += wxT(" (*.");
7785 wildcard += handler->GetExtension();
7786 wildcard += wxT(")|*.");
7787 wildcard += handler->GetExtension();
7788 if (types)
7789 types->Add(handler->GetType());
7790 }
7791 count ++;
7792 }
7793
7794 node = node->GetNext();
7795 }
7796
7797 if (combine)
7798 wildcard = wxT("(") + wildcard + wxT(")|") + wildcard;
7799 return wildcard;
7800 }
7801
7802 /// Load a file
7803 bool wxRichTextBuffer::LoadFile(const wxString& filename, wxRichTextFileType type)
7804 {
7805 wxRichTextFileHandler* handler = FindHandlerFilenameOrType(filename, type);
7806 if (handler)
7807 {
7808 SetDefaultStyle(wxRichTextAttr());
7809 handler->SetFlags(GetHandlerFlags());
7810 bool success = handler->LoadFile(this, filename);
7811 Invalidate(wxRICHTEXT_ALL);
7812 return success;
7813 }
7814 else
7815 return false;
7816 }
7817
7818 /// Save a file
7819 bool wxRichTextBuffer::SaveFile(const wxString& filename, wxRichTextFileType type)
7820 {
7821 wxRichTextFileHandler* handler = FindHandlerFilenameOrType(filename, type);
7822 if (handler)
7823 {
7824 handler->SetFlags(GetHandlerFlags());
7825 return handler->SaveFile(this, filename);
7826 }
7827 else
7828 return false;
7829 }
7830
7831 /// Load from a stream
7832 bool wxRichTextBuffer::LoadFile(wxInputStream& stream, wxRichTextFileType type)
7833 {
7834 wxRichTextFileHandler* handler = FindHandler(type);
7835 if (handler)
7836 {
7837 SetDefaultStyle(wxRichTextAttr());
7838 handler->SetFlags(GetHandlerFlags());
7839 bool success = handler->LoadFile(this, stream);
7840 Invalidate(wxRICHTEXT_ALL);
7841 return success;
7842 }
7843 else
7844 return false;
7845 }
7846
7847 /// Save to a stream
7848 bool wxRichTextBuffer::SaveFile(wxOutputStream& stream, wxRichTextFileType type)
7849 {
7850 wxRichTextFileHandler* handler = FindHandler(type);
7851 if (handler)
7852 {
7853 handler->SetFlags(GetHandlerFlags());
7854 return handler->SaveFile(this, stream);
7855 }
7856 else
7857 return false;
7858 }
7859
7860 /// Copy the range to the clipboard
7861 bool wxRichTextBuffer::CopyToClipboard(const wxRichTextRange& range)
7862 {
7863 bool success = false;
7864 wxRichTextParagraphLayoutBox* container = this;
7865 if (GetRichTextCtrl())
7866 container = GetRichTextCtrl()->GetFocusObject();
7867
7868 #if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
7869
7870 if (!wxTheClipboard->IsOpened() && wxTheClipboard->Open())
7871 {
7872 wxTheClipboard->Clear();
7873
7874 // Add composite object
7875
7876 wxDataObjectComposite* compositeObject = new wxDataObjectComposite();
7877
7878 {
7879 wxString text = container->GetTextForRange(range);
7880
7881 #ifdef __WXMSW__
7882 text = wxTextFile::Translate(text, wxTextFileType_Dos);
7883 #endif
7884
7885 compositeObject->Add(new wxTextDataObject(text), false /* not preferred */);
7886 }
7887
7888 // Add rich text buffer data object. This needs the XML handler to be present.
7889
7890 if (FindHandler(wxRICHTEXT_TYPE_XML))
7891 {
7892 wxRichTextBuffer* richTextBuf = new wxRichTextBuffer;
7893 container->CopyFragment(range, *richTextBuf);
7894
7895 compositeObject->Add(new wxRichTextBufferDataObject(richTextBuf), true /* preferred */);
7896 }
7897
7898 if (wxTheClipboard->SetData(compositeObject))
7899 success = true;
7900
7901 wxTheClipboard->Close();
7902 }
7903
7904 #else
7905 wxUnusedVar(range);
7906 #endif
7907 return success;
7908 }
7909
7910 /// Paste the clipboard content to the buffer
7911 bool wxRichTextBuffer::PasteFromClipboard(long position)
7912 {
7913 bool success = false;
7914 wxRichTextParagraphLayoutBox* container = this;
7915 if (GetRichTextCtrl())
7916 container = GetRichTextCtrl()->GetFocusObject();
7917
7918 #if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
7919 if (CanPasteFromClipboard())
7920 {
7921 if (wxTheClipboard->Open())
7922 {
7923 if (wxTheClipboard->IsSupported(wxDataFormat(wxRichTextBufferDataObject::GetRichTextBufferFormatId())))
7924 {
7925 wxRichTextBufferDataObject data;
7926 wxTheClipboard->GetData(data);
7927 wxRichTextBuffer* richTextBuffer = data.GetRichTextBuffer();
7928 if (richTextBuffer)
7929 {
7930 container->InsertParagraphsWithUndo(this, position+1, *richTextBuffer, GetRichTextCtrl(), 0);
7931 if (GetRichTextCtrl())
7932 GetRichTextCtrl()->ShowPosition(position + richTextBuffer->GetOwnRange().GetEnd());
7933 delete richTextBuffer;
7934 }
7935 }
7936 else if (wxTheClipboard->IsSupported(wxDF_TEXT)
7937 #if wxUSE_UNICODE
7938 || wxTheClipboard->IsSupported(wxDF_UNICODETEXT)
7939 #endif
7940 )
7941 {
7942 wxTextDataObject data;
7943 wxTheClipboard->GetData(data);
7944 wxString text(data.GetText());
7945 #ifdef __WXMSW__
7946 wxString text2;
7947 text2.Alloc(text.Length()+1);
7948 size_t i;
7949 for (i = 0; i < text.Length(); i++)
7950 {
7951 wxChar ch = text[i];
7952 if (ch != wxT('\r'))
7953 text2 += ch;
7954 }
7955 #else
7956 wxString text2 = text;
7957 #endif
7958 container->InsertTextWithUndo(this, position+1, text2, GetRichTextCtrl(), wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE);
7959
7960 if (GetRichTextCtrl())
7961 GetRichTextCtrl()->ShowPosition(position + text2.Length());
7962
7963 success = true;
7964 }
7965 else if (wxTheClipboard->IsSupported(wxDF_BITMAP))
7966 {
7967 wxBitmapDataObject data;
7968 wxTheClipboard->GetData(data);
7969 wxBitmap bitmap(data.GetBitmap());
7970 wxImage image(bitmap.ConvertToImage());
7971
7972 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Image"), wxRICHTEXT_INSERT, this, container, GetRichTextCtrl(), false);
7973
7974 action->GetNewParagraphs().AddImage(image);
7975
7976 if (action->GetNewParagraphs().GetChildCount() == 1)
7977 action->GetNewParagraphs().SetPartialParagraph(true);
7978
7979 action->SetPosition(position+1);
7980
7981 // Set the range we'll need to delete in Undo
7982 action->SetRange(wxRichTextRange(position+1, position+1));
7983
7984 SubmitAction(action);
7985
7986 success = true;
7987 }
7988 wxTheClipboard->Close();
7989 }
7990 }
7991 #else
7992 wxUnusedVar(position);
7993 #endif
7994 return success;
7995 }
7996
7997 /// Can we paste from the clipboard?
7998 bool wxRichTextBuffer::CanPasteFromClipboard() const
7999 {
8000 bool canPaste = false;
8001 #if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
8002 if (!wxTheClipboard->IsOpened() && wxTheClipboard->Open())
8003 {
8004 if (wxTheClipboard->IsSupported(wxDF_TEXT)
8005 #if wxUSE_UNICODE
8006 || wxTheClipboard->IsSupported(wxDF_UNICODETEXT)
8007 #endif
8008 || wxTheClipboard->IsSupported(wxDataFormat(wxRichTextBufferDataObject::GetRichTextBufferFormatId())) ||
8009 wxTheClipboard->IsSupported(wxDF_BITMAP))
8010 {
8011 canPaste = true;
8012 }
8013 wxTheClipboard->Close();
8014 }
8015 #endif
8016 return canPaste;
8017 }
8018
8019 /// Dumps contents of buffer for debugging purposes
8020 void wxRichTextBuffer::Dump()
8021 {
8022 wxString text;
8023 {
8024 wxStringOutputStream stream(& text);
8025 wxTextOutputStream textStream(stream);
8026 Dump(textStream);
8027 }
8028
8029 wxLogDebug(text);
8030 }
8031
8032 /// Add an event handler
8033 bool wxRichTextBuffer::AddEventHandler(wxEvtHandler* handler)
8034 {
8035 m_eventHandlers.Append(handler);
8036 return true;
8037 }
8038
8039 /// Remove an event handler
8040 bool wxRichTextBuffer::RemoveEventHandler(wxEvtHandler* handler, bool deleteHandler)
8041 {
8042 wxList::compatibility_iterator node = m_eventHandlers.Find(handler);
8043 if (node)
8044 {
8045 m_eventHandlers.Erase(node);
8046 if (deleteHandler)
8047 delete handler;
8048
8049 return true;
8050 }
8051 else
8052 return false;
8053 }
8054
8055 /// Clear event handlers
8056 void wxRichTextBuffer::ClearEventHandlers()
8057 {
8058 m_eventHandlers.Clear();
8059 }
8060
8061 /// Send event to event handlers. If sendToAll is true, will send to all event handlers,
8062 /// otherwise will stop at the first successful one.
8063 bool wxRichTextBuffer::SendEvent(wxEvent& event, bool sendToAll)
8064 {
8065 bool success = false;
8066 for (wxList::compatibility_iterator node = m_eventHandlers.GetFirst(); node; node = node->GetNext())
8067 {
8068 wxEvtHandler* handler = (wxEvtHandler*) node->GetData();
8069 if (handler->ProcessEvent(event))
8070 {
8071 success = true;
8072 if (!sendToAll)
8073 return true;
8074 }
8075 }
8076 return success;
8077 }
8078
8079 /// Set style sheet and notify of the change
8080 bool wxRichTextBuffer::SetStyleSheetAndNotify(wxRichTextStyleSheet* sheet)
8081 {
8082 wxRichTextStyleSheet* oldSheet = GetStyleSheet();
8083
8084 wxWindowID winid = wxID_ANY;
8085 if (GetRichTextCtrl())
8086 winid = GetRichTextCtrl()->GetId();
8087
8088 wxRichTextEvent event(wxEVT_COMMAND_RICHTEXT_STYLESHEET_REPLACING, winid);
8089 event.SetEventObject(GetRichTextCtrl());
8090 event.SetContainer(GetRichTextCtrl()->GetFocusObject());
8091 event.SetOldStyleSheet(oldSheet);
8092 event.SetNewStyleSheet(sheet);
8093 event.Allow();
8094
8095 if (SendEvent(event) && !event.IsAllowed())
8096 {
8097 if (sheet != oldSheet)
8098 delete sheet;
8099
8100 return false;
8101 }
8102
8103 if (oldSheet && oldSheet != sheet)
8104 delete oldSheet;
8105
8106 SetStyleSheet(sheet);
8107
8108 event.SetEventType(wxEVT_COMMAND_RICHTEXT_STYLESHEET_REPLACED);
8109 event.SetOldStyleSheet(NULL);
8110 event.Allow();
8111
8112 return SendEvent(event);
8113 }
8114
8115 /// Set renderer, deleting old one
8116 void wxRichTextBuffer::SetRenderer(wxRichTextRenderer* renderer)
8117 {
8118 if (sm_renderer)
8119 delete sm_renderer;
8120 sm_renderer = renderer;
8121 }
8122
8123 /// Hit-testing: returns a flag indicating hit test details, plus
8124 /// information about position
8125 int wxRichTextBuffer::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int flags)
8126 {
8127 int ret = wxRichTextParagraphLayoutBox::HitTest(dc, context, pt, textPosition, obj, contextObj, flags);
8128 if (ret != wxRICHTEXT_HITTEST_NONE)
8129 {
8130 return ret;
8131 }
8132 else
8133 {
8134 textPosition = m_ownRange.GetEnd()-1;
8135 *obj = this;
8136 *contextObj = this;
8137 return wxRICHTEXT_HITTEST_AFTER|wxRICHTEXT_HITTEST_OUTSIDE;
8138 }
8139 }
8140
8141 bool wxRichTextStdRenderer::DrawStandardBullet(wxRichTextParagraph* paragraph, wxDC& dc, const wxRichTextAttr& bulletAttr, const wxRect& rect)
8142 {
8143 if (bulletAttr.GetTextColour().IsOk())
8144 {
8145 wxCheckSetPen(dc, wxPen(bulletAttr.GetTextColour()));
8146 wxCheckSetBrush(dc, wxBrush(bulletAttr.GetTextColour()));
8147 }
8148 else
8149 {
8150 wxCheckSetPen(dc, *wxBLACK_PEN);
8151 wxCheckSetBrush(dc, *wxBLACK_BRUSH);
8152 }
8153
8154 wxFont font;
8155 if (bulletAttr.HasFont())
8156 {
8157 font = paragraph->GetBuffer()->GetFontTable().FindFont(bulletAttr);
8158 }
8159 else
8160 font = (*wxNORMAL_FONT);
8161
8162 wxCheckSetFont(dc, font);
8163
8164 int charHeight = dc.GetCharHeight();
8165
8166 int bulletWidth = (int) (((float) charHeight) * wxRichTextBuffer::GetBulletProportion());
8167 int bulletHeight = bulletWidth;
8168
8169 int x = rect.x;
8170
8171 // Calculate the top position of the character (as opposed to the whole line height)
8172 int y = rect.y + (rect.height - charHeight);
8173
8174 // Calculate where the bullet should be positioned
8175 y = y + (charHeight+1)/2 - (bulletHeight+1)/2;
8176
8177 // The margin between a bullet and text.
8178 int margin = paragraph->ConvertTenthsMMToPixels(dc, wxRichTextBuffer::GetBulletRightMargin());
8179
8180 if (bulletAttr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_RIGHT)
8181 x = rect.x + rect.width - bulletWidth - margin;
8182 else if (bulletAttr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_CENTRE)
8183 x = x + (rect.width)/2 - bulletWidth/2;
8184
8185 if (bulletAttr.GetBulletName() == wxT("standard/square"))
8186 {
8187 dc.DrawRectangle(x, y, bulletWidth, bulletHeight);
8188 }
8189 else if (bulletAttr.GetBulletName() == wxT("standard/diamond"))
8190 {
8191 wxPoint pts[5];
8192 pts[0].x = x; pts[0].y = y + bulletHeight/2;
8193 pts[1].x = x + bulletWidth/2; pts[1].y = y;
8194 pts[2].x = x + bulletWidth; pts[2].y = y + bulletHeight/2;
8195 pts[3].x = x + bulletWidth/2; pts[3].y = y + bulletHeight;
8196
8197 dc.DrawPolygon(4, pts);
8198 }
8199 else if (bulletAttr.GetBulletName() == wxT("standard/triangle"))
8200 {
8201 wxPoint pts[3];
8202 pts[0].x = x; pts[0].y = y;
8203 pts[1].x = x + bulletWidth; pts[1].y = y + bulletHeight/2;
8204 pts[2].x = x; pts[2].y = y + bulletHeight;
8205
8206 dc.DrawPolygon(3, pts);
8207 }
8208 else if (bulletAttr.GetBulletName() == wxT("standard/circle-outline"))
8209 {
8210 wxCheckSetBrush(dc, *wxTRANSPARENT_BRUSH);
8211 dc.DrawEllipse(x, y, bulletWidth, bulletHeight);
8212 }
8213 else // "standard/circle", and catch-all
8214 {
8215 dc.DrawEllipse(x, y, bulletWidth, bulletHeight);
8216 }
8217
8218 return true;
8219 }
8220
8221 bool wxRichTextStdRenderer::DrawTextBullet(wxRichTextParagraph* paragraph, wxDC& dc, const wxRichTextAttr& attr, const wxRect& rect, const wxString& text)
8222 {
8223 if (!text.empty())
8224 {
8225 wxFont font;
8226 if ((attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_SYMBOL) && !attr.GetBulletFont().IsEmpty() && attr.HasFont())
8227 {
8228 wxRichTextAttr fontAttr;
8229 fontAttr.SetFontSize(attr.GetFontSize());
8230 fontAttr.SetFontStyle(attr.GetFontStyle());
8231 fontAttr.SetFontWeight(attr.GetFontWeight());
8232 fontAttr.SetFontUnderlined(attr.GetFontUnderlined());
8233 fontAttr.SetFontFaceName(attr.GetBulletFont());
8234 font = paragraph->GetBuffer()->GetFontTable().FindFont(fontAttr);
8235 }
8236 else if (attr.HasFont())
8237 font = paragraph->GetBuffer()->GetFontTable().FindFont(attr);
8238 else
8239 font = (*wxNORMAL_FONT);
8240
8241 wxCheckSetFont(dc, font);
8242
8243 if (attr.GetTextColour().IsOk())
8244 dc.SetTextForeground(attr.GetTextColour());
8245
8246 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
8247
8248 int charHeight = dc.GetCharHeight();
8249 wxCoord tw, th;
8250 dc.GetTextExtent(text, & tw, & th);
8251
8252 int x = rect.x;
8253
8254 // Calculate the top position of the character (as opposed to the whole line height)
8255 int y = rect.y + (rect.height - charHeight);
8256
8257 // The margin between a bullet and text.
8258 int margin = paragraph->ConvertTenthsMMToPixels(dc, wxRichTextBuffer::GetBulletRightMargin());
8259
8260 if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_RIGHT)
8261 x = (rect.x + rect.width) - tw - margin;
8262 else if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_CENTRE)
8263 x = x + (rect.width)/2 - tw/2;
8264
8265 dc.DrawText(text, x, y);
8266
8267 return true;
8268 }
8269 else
8270 return false;
8271 }
8272
8273 bool wxRichTextStdRenderer::DrawBitmapBullet(wxRichTextParagraph* WXUNUSED(paragraph), wxDC& WXUNUSED(dc), const wxRichTextAttr& WXUNUSED(attr), const wxRect& WXUNUSED(rect))
8274 {
8275 // Currently unimplemented. The intention is to store bitmaps by name in a media store associated
8276 // with the buffer. The store will allow retrieval from memory, disk or other means.
8277 return false;
8278 }
8279
8280 /// Enumerate the standard bullet names currently supported
8281 bool wxRichTextStdRenderer::EnumerateStandardBulletNames(wxArrayString& bulletNames)
8282 {
8283 bulletNames.Add(wxTRANSLATE("standard/circle"));
8284 bulletNames.Add(wxTRANSLATE("standard/circle-outline"));
8285 bulletNames.Add(wxTRANSLATE("standard/square"));
8286 bulletNames.Add(wxTRANSLATE("standard/diamond"));
8287 bulletNames.Add(wxTRANSLATE("standard/triangle"));
8288
8289 return true;
8290 }
8291
8292 /*!
8293 * wxRichTextBox
8294 */
8295
8296 IMPLEMENT_DYNAMIC_CLASS(wxRichTextBox, wxRichTextParagraphLayoutBox)
8297
8298 wxRichTextBox::wxRichTextBox(wxRichTextObject* parent):
8299 wxRichTextParagraphLayoutBox(parent)
8300 {
8301 }
8302
8303 /// Draw the item
8304 bool wxRichTextBox::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
8305 {
8306 if (!IsShown())
8307 return true;
8308
8309 // TODO: if the active object in the control, draw an indication.
8310 // We need to add the concept of active object, and not just focus object,
8311 // so we can apply commands (properties, delete, ...) to objects such as text boxes and images.
8312 // Ultimately we would like to be able to interactively resize an active object
8313 // using drag handles.
8314 return wxRichTextParagraphLayoutBox::Draw(dc, context, range, selection, rect, descent, style);
8315 }
8316
8317 /// Copy
8318 void wxRichTextBox::Copy(const wxRichTextBox& obj)
8319 {
8320 wxRichTextParagraphLayoutBox::Copy(obj);
8321 }
8322
8323 // Edit properties via a GUI
8324 bool wxRichTextBox::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer)
8325 {
8326 wxRichTextObjectPropertiesDialog boxDlg(this, wxGetTopLevelParent(parent), wxID_ANY, _("Box Properties"));
8327 boxDlg.SetAttributes(GetAttributes());
8328
8329 if (boxDlg.ShowModal() == wxID_OK)
8330 {
8331 // By passing wxRICHTEXT_SETSTYLE_RESET, indeterminate attributes set by the user will be set as
8332 // indeterminate in the object.
8333 boxDlg.ApplyStyle(buffer->GetRichTextCtrl(), wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_RESET);
8334 return true;
8335 }
8336 else
8337 return false;
8338 }
8339
8340 /*!
8341 * wxRichTextField
8342 */
8343
8344 IMPLEMENT_DYNAMIC_CLASS(wxRichTextField, wxRichTextParagraphLayoutBox)
8345
8346 wxRichTextField::wxRichTextField(const wxString& fieldType, wxRichTextObject* parent):
8347 wxRichTextParagraphLayoutBox(parent)
8348 {
8349 SetFieldType(fieldType);
8350 }
8351
8352 /// Draw the item
8353 bool wxRichTextField::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
8354 {
8355 if (!IsShown())
8356 return true;
8357
8358 wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType());
8359 if (fieldType && fieldType->Draw(this, dc, context, range, selection, rect, descent, style))
8360 return true;
8361
8362 // Fallback; but don't draw guidelines.
8363 style &= ~wxRICHTEXT_DRAW_GUIDELINES;
8364 return wxRichTextParagraphLayoutBox::Draw(dc, context, range, selection, rect, descent, style);
8365 }
8366
8367 bool wxRichTextField::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& parentRect, int style)
8368 {
8369 wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType());
8370 if (fieldType && fieldType->Layout(this, dc, context, rect, parentRect, style))
8371 return true;
8372
8373 // Fallback
8374 return wxRichTextParagraphLayoutBox::Layout(dc, context, rect, parentRect, style);
8375 }
8376
8377 bool wxRichTextField::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, wxPoint position, wxArrayInt* partialExtents) const
8378 {
8379 wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType());
8380 if (fieldType)
8381 return fieldType->GetRangeSize((wxRichTextField*) this, range, size, descent, dc, context, flags, position, partialExtents);
8382
8383 return wxRichTextParagraphLayoutBox::GetRangeSize(range, size, descent, dc, context, flags, position, partialExtents);
8384 }
8385
8386 /// Calculate range
8387 void wxRichTextField::CalculateRange(long start, long& end)
8388 {
8389 if (IsTopLevel())
8390 wxRichTextParagraphLayoutBox::CalculateRange(start, end);
8391 else
8392 wxRichTextObject::CalculateRange(start, end);
8393 }
8394
8395 /// Copy
8396 void wxRichTextField::Copy(const wxRichTextField& obj)
8397 {
8398 wxRichTextParagraphLayoutBox::Copy(obj);
8399
8400 UpdateField();
8401 }
8402
8403 // Edit properties via a GUI
8404 bool wxRichTextField::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer)
8405 {
8406 wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType());
8407 if (fieldType)
8408 return fieldType->EditProperties(this, parent, buffer);
8409
8410 return false;
8411 }
8412
8413 bool wxRichTextField::CanEditProperties() const
8414 {
8415 wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType());
8416 if (fieldType)
8417 return fieldType->CanEditProperties((wxRichTextField*) this);
8418
8419 return false;
8420 }
8421
8422 wxString wxRichTextField::GetPropertiesMenuLabel() const
8423 {
8424 wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType());
8425 if (fieldType)
8426 return fieldType->GetPropertiesMenuLabel((wxRichTextField*) this);
8427
8428 return wxEmptyString;
8429 }
8430
8431 bool wxRichTextField::UpdateField()
8432 {
8433 wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType());
8434 if (fieldType)
8435 return fieldType->UpdateField((wxRichTextField*) this);
8436
8437 return false;
8438 }
8439
8440 bool wxRichTextField::IsTopLevel() const
8441 {
8442 wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType());
8443 if (fieldType)
8444 return fieldType->IsTopLevel((wxRichTextField*) this);
8445
8446 return true;
8447 }
8448
8449 IMPLEMENT_CLASS(wxRichTextFieldType, wxObject)
8450
8451 IMPLEMENT_CLASS(wxRichTextFieldTypeStandard, wxRichTextFieldType)
8452
8453 wxRichTextFieldTypeStandard::wxRichTextFieldTypeStandard(const wxString& name, const wxString& label, int displayStyle)
8454 {
8455 Init();
8456
8457 SetName(name);
8458 SetLabel(label);
8459 SetDisplayStyle(displayStyle);
8460 }
8461
8462 wxRichTextFieldTypeStandard::wxRichTextFieldTypeStandard(const wxString& name, const wxBitmap& bitmap, int displayStyle)
8463 {
8464 Init();
8465
8466 SetName(name);
8467 SetBitmap(bitmap);
8468 SetDisplayStyle(displayStyle);
8469 }
8470
8471 void wxRichTextFieldTypeStandard::Init()
8472 {
8473 m_displayStyle = wxRICHTEXT_FIELD_STYLE_RECTANGLE;
8474 m_font = wxFont(6, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
8475 m_textColour = *wxWHITE;
8476 m_borderColour = *wxBLACK;
8477 m_backgroundColour = *wxBLACK;
8478 m_verticalPadding = 1;
8479 m_horizontalPadding = 3;
8480 m_horizontalMargin = 2;
8481 m_verticalMargin = 0;
8482 }
8483
8484 void wxRichTextFieldTypeStandard::Copy(const wxRichTextFieldTypeStandard& field)
8485 {
8486 wxRichTextFieldType::Copy(field);
8487
8488 m_label = field.m_label;
8489 m_displayStyle = field.m_displayStyle;
8490 m_font = field.m_font;
8491 m_textColour = field.m_textColour;
8492 m_borderColour = field.m_borderColour;
8493 m_backgroundColour = field.m_backgroundColour;
8494 m_verticalPadding = field.m_verticalPadding;
8495 m_horizontalPadding = field.m_horizontalPadding;
8496 m_horizontalMargin = field.m_horizontalMargin;
8497 m_bitmap = field.m_bitmap;
8498 }
8499
8500 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))
8501 {
8502 if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_COMPOSITE)
8503 return false; // USe default composite drawing
8504 else // if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_RECTANGLE || m_displayStyle == wxRICHTEXT_FIELD_STYLE_NOBORDER)
8505 {
8506 int borderSize = 1;
8507
8508 wxPen borderPen(m_borderColour, 1, wxSOLID);
8509 wxBrush backgroundBrush(m_backgroundColour);
8510 wxColour textColour(m_textColour);
8511
8512 if (selection.WithinSelection(obj->GetRange().GetStart(), obj))
8513 {
8514 wxColour highlightColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT));
8515 wxColour highlightTextColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT));
8516
8517 borderPen = wxPen(highlightTextColour, 1, wxSOLID);
8518 backgroundBrush = wxBrush(highlightColour);
8519
8520 wxCheckSetBrush(dc, backgroundBrush);
8521 wxCheckSetPen(dc, wxPen(highlightColour, 1, wxSOLID));
8522 dc.DrawRectangle(rect);
8523 }
8524
8525 if (m_displayStyle != wxRICHTEXT_FIELD_STYLE_NO_BORDER)
8526 borderSize = 0;
8527
8528 // objectRect is the area where the content is drawn, after margins around it have been taken into account
8529 wxRect objectRect = wxRect(wxPoint(rect.x + m_horizontalMargin, rect.y + wxMax(0, rect.height - descent - obj->GetCachedSize().y)),
8530 wxSize(obj->GetCachedSize().x - 2*m_horizontalMargin - borderSize, obj->GetCachedSize().y));
8531
8532 // clientArea is where the text is actually written
8533 wxRect clientArea = objectRect;
8534
8535 if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_RECTANGLE)
8536 {
8537 dc.SetPen(borderPen);
8538 dc.SetBrush(backgroundBrush);
8539 dc.DrawRoundedRectangle(objectRect, 4.0);
8540 }
8541 else if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_START_TAG)
8542 {
8543 int arrowLength = objectRect.height/2;
8544 clientArea.width -= (arrowLength - m_horizontalPadding);
8545
8546 wxPoint pts[5];
8547 pts[0].x = objectRect.x; pts[0].y = objectRect.y;
8548 pts[1].x = objectRect.x + objectRect.width - arrowLength; pts[1].y = objectRect.y;
8549 pts[2].x = objectRect.x + objectRect.width; pts[2].y = objectRect.y + (objectRect.height/2);
8550 pts[3].x = objectRect.x + objectRect.width - arrowLength; pts[3].y = objectRect.y + objectRect.height;
8551 pts[4].x = objectRect.x; pts[4].y = objectRect.y + objectRect.height;
8552 dc.SetPen(borderPen);
8553 dc.SetBrush(backgroundBrush);
8554 dc.DrawPolygon(5, pts);
8555 }
8556 else if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_END_TAG)
8557 {
8558 int arrowLength = objectRect.height/2;
8559 clientArea.width -= (arrowLength - m_horizontalPadding);
8560 clientArea.x += (arrowLength - m_horizontalPadding);
8561
8562 wxPoint pts[5];
8563 pts[0].x = objectRect.x + objectRect.width; pts[0].y = objectRect.y;
8564 pts[1].x = objectRect.x + arrowLength; pts[1].y = objectRect.y;
8565 pts[2].x = objectRect.x; pts[2].y = objectRect.y + (objectRect.height/2);
8566 pts[3].x = objectRect.x + arrowLength; pts[3].y = objectRect.y + objectRect.height;
8567 pts[4].x = objectRect.x + objectRect.width; pts[4].y = objectRect.y + objectRect.height;
8568 dc.SetPen(borderPen);
8569 dc.SetBrush(backgroundBrush);
8570 dc.DrawPolygon(5, pts);
8571 }
8572
8573 if (m_bitmap.IsOk())
8574 {
8575 int x = clientArea.x + (clientArea.width - m_bitmap.GetWidth())/2;
8576 int y = clientArea.y + m_verticalPadding;
8577 dc.DrawBitmap(m_bitmap, x, y, true);
8578
8579 if (selection.WithinSelection(obj->GetRange().GetStart(), obj))
8580 {
8581 wxCheckSetBrush(dc, *wxBLACK_BRUSH);
8582 wxCheckSetPen(dc, *wxBLACK_PEN);
8583 dc.SetLogicalFunction(wxINVERT);
8584 dc.DrawRectangle(wxRect(x, y, m_bitmap.GetWidth(), m_bitmap.GetHeight()));
8585 dc.SetLogicalFunction(wxCOPY);
8586 }
8587 }
8588 else
8589 {
8590 wxString label(m_label);
8591 if (label.IsEmpty())
8592 label = wxT("??");
8593 int w, h, maxDescent;
8594 dc.SetFont(m_font);
8595 dc.GetTextExtent(m_label, & w, &h, & maxDescent);
8596 dc.SetTextForeground(textColour);
8597
8598 int x = clientArea.x + (clientArea.width - w)/2;
8599 int y = clientArea.y + (clientArea.height - (h - maxDescent))/2;
8600 dc.DrawText(m_label, x, y);
8601 }
8602 }
8603
8604 return true;
8605 }
8606
8607 bool wxRichTextFieldTypeStandard::Layout(wxRichTextField* obj, wxDC& dc, wxRichTextDrawingContext& context, const wxRect& WXUNUSED(rect), const wxRect& WXUNUSED(parentRect), int style)
8608 {
8609 if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_COMPOSITE)
8610 return false; // USe default composite layout
8611
8612 wxSize size = GetSize(obj, dc, context, style);
8613 obj->SetCachedSize(size);
8614 obj->SetMinSize(size);
8615 obj->SetMaxSize(size);
8616 return true;
8617 }
8618
8619 bool wxRichTextFieldTypeStandard::GetRangeSize(wxRichTextField* obj, const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, wxPoint position, wxArrayInt* partialExtents) const
8620 {
8621 if (IsTopLevel(obj))
8622 return obj->wxRichTextParagraphLayoutBox::GetRangeSize(range, size, descent, dc, context, flags, position);
8623 else
8624 {
8625 wxSize sz = GetSize(obj, dc, context, 0);
8626 if (partialExtents)
8627 {
8628 int lastSize;
8629 if (partialExtents->GetCount() > 0)
8630 lastSize = (*partialExtents)[partialExtents->GetCount()-1];
8631 else
8632 lastSize = 0;
8633 partialExtents->Add(lastSize + sz.x);
8634 }
8635 size = sz;
8636 return true;
8637 }
8638 }
8639
8640 wxSize wxRichTextFieldTypeStandard::GetSize(wxRichTextField* WXUNUSED(obj), wxDC& dc, wxRichTextDrawingContext& WXUNUSED(context), int WXUNUSED(style)) const
8641 {
8642 int borderSize = 1;
8643 int w = 0, h = 0, maxDescent = 0;
8644
8645 wxSize sz;
8646 if (m_bitmap.IsOk())
8647 {
8648 w = m_bitmap.GetWidth();
8649 h = m_bitmap.GetHeight();
8650
8651 sz = wxSize(w + m_horizontalMargin*2, h + m_verticalMargin*2);
8652 }
8653 else
8654 {
8655 wxString label(m_label);
8656 if (label.IsEmpty())
8657 label = wxT("??");
8658 dc.SetFont(m_font);
8659 dc.GetTextExtent(label, & w, &h, & maxDescent);
8660
8661 sz = wxSize(w + m_horizontalPadding*2 + m_horizontalMargin*2, h + m_verticalPadding *2 + m_verticalMargin*2);
8662 }
8663
8664 if (m_displayStyle != wxRICHTEXT_FIELD_STYLE_NO_BORDER)
8665 {
8666 sz.x += borderSize*2;
8667 sz.y += borderSize*2;
8668 }
8669
8670 if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_START_TAG || m_displayStyle == wxRICHTEXT_FIELD_STYLE_END_TAG)
8671 {
8672 // Add space for the arrow
8673 sz.x += (sz.y/2 - m_horizontalPadding);
8674 }
8675
8676 return sz;
8677 }
8678
8679 IMPLEMENT_DYNAMIC_CLASS(wxRichTextCell, wxRichTextBox)
8680
8681 wxRichTextCell::wxRichTextCell(wxRichTextObject* parent):
8682 wxRichTextBox(parent)
8683 {
8684 }
8685
8686 /// Draw the item
8687 bool wxRichTextCell::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
8688 {
8689 return wxRichTextBox::Draw(dc, context, range, selection, rect, descent, style);
8690 }
8691
8692 /// Copy
8693 void wxRichTextCell::Copy(const wxRichTextCell& obj)
8694 {
8695 wxRichTextBox::Copy(obj);
8696 }
8697
8698 // Edit properties via a GUI
8699 bool wxRichTextCell::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer)
8700 {
8701 // We need to gather common attributes for all selected cells.
8702
8703 wxRichTextTable* table = wxDynamicCast(GetParent(), wxRichTextTable);
8704 bool multipleCells = false;
8705 wxRichTextAttr attr;
8706
8707 if (table && buffer && buffer->GetRichTextCtrl() && buffer->GetRichTextCtrl()->GetSelection().IsValid() &&
8708 buffer->GetRichTextCtrl()->GetSelection().GetContainer() == GetParent())
8709 {
8710 wxRichTextAttr clashingAttr, absentAttr;
8711 const wxRichTextSelection& sel = buffer->GetRichTextCtrl()->GetSelection();
8712 size_t i;
8713 int selectedCellCount = 0;
8714 for (i = 0; i < sel.GetCount(); i++)
8715 {
8716 const wxRichTextRange& range = sel[i];
8717 wxRichTextCell* cell = table->GetCell(range.GetStart());
8718 if (cell)
8719 {
8720 wxRichTextAttr cellStyle = cell->GetAttributes();
8721
8722 CollectStyle(attr, cellStyle, clashingAttr, absentAttr);
8723
8724 selectedCellCount ++;
8725 }
8726 }
8727 multipleCells = selectedCellCount > 1;
8728 }
8729 else
8730 {
8731 attr = GetAttributes();
8732 }
8733
8734 wxString caption;
8735 if (multipleCells)
8736 caption = _("Multiple Cell Properties");
8737 else
8738 caption = _("Cell Properties");
8739
8740 wxRichTextObjectPropertiesDialog cellDlg(this, wxGetTopLevelParent(parent), wxID_ANY, caption);
8741 cellDlg.SetAttributes(attr);
8742
8743 wxRichTextSizePage* sizePage = wxDynamicCast(cellDlg.FindPage(CLASSINFO(wxRichTextSizePage)), wxRichTextSizePage);
8744 if (sizePage)
8745 {
8746 // We don't want position and floating controls for a cell.
8747 sizePage->ShowPositionControls(false);
8748 sizePage->ShowFloatingControls(false);
8749 }
8750
8751 if (cellDlg.ShowModal() == wxID_OK)
8752 {
8753 if (multipleCells)
8754 {
8755 const wxRichTextSelection& sel = buffer->GetRichTextCtrl()->GetSelection();
8756 // Apply the style; we interpret indeterminate attributes as 'don't touch this attribute'
8757 // since it may represent clashing attributes across multiple objects.
8758 table->SetCellStyle(sel, attr);
8759 }
8760 else
8761 // For a single object, indeterminate attributes set by the user should be reflected in the
8762 // actual object style, so pass the wxRICHTEXT_SETSTYLE_RESET flag to assign
8763 // the style directly instead of applying (which ignores indeterminate attributes,
8764 // leaving them as they were).
8765 cellDlg.ApplyStyle(buffer->GetRichTextCtrl(), wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_RESET);
8766 return true;
8767 }
8768 else
8769 return false;
8770 }
8771
8772 WX_DEFINE_OBJARRAY(wxRichTextObjectPtrArrayArray)
8773
8774 IMPLEMENT_DYNAMIC_CLASS(wxRichTextTable, wxRichTextBox)
8775
8776 wxRichTextTable::wxRichTextTable(wxRichTextObject* parent): wxRichTextBox(parent)
8777 {
8778 m_rowCount = 0;
8779 m_colCount = 0;
8780 }
8781
8782 // Draws the object.
8783 bool wxRichTextTable::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
8784 {
8785 return wxRichTextBox::Draw(dc, context, range, selection, rect, descent, style);
8786 }
8787
8788 WX_DECLARE_OBJARRAY(wxRect, wxRichTextRectArray);
8789 WX_DEFINE_OBJARRAY(wxRichTextRectArray);
8790
8791 // Lays the object out. rect is the space available for layout. Often it will
8792 // be the specified overall space for this object, if trying to constrain
8793 // layout to a particular size, or it could be the total space available in the
8794 // parent. rect is the overall size, so we must subtract margins and padding.
8795 // to get the actual available space.
8796 bool wxRichTextTable::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& WXUNUSED(parentRect), int style)
8797 {
8798 SetPosition(rect.GetPosition());
8799
8800 // TODO: the meaty bit. Calculate sizes of all cells and rows. Try to use
8801 // minimum size if within alloted size, then divide up remaining size
8802 // between rows/cols.
8803
8804 double scale = 1.0;
8805 wxRichTextBuffer* buffer = GetBuffer();
8806 if (buffer) scale = buffer->GetScale();
8807
8808 wxRect availableSpace = GetAvailableContentArea(dc, context, rect);
8809 wxTextAttrDimensionConverter converter(dc, scale, availableSpace.GetSize());
8810
8811 wxRichTextAttr attr(GetAttributes());
8812 context.ApplyVirtualAttributes(attr, this);
8813
8814 // If we have no fixed table size, and assuming we're not pushed for
8815 // space, then we don't have to try to stretch the table to fit the contents.
8816 bool stretchToFitTableWidth = false;
8817
8818 int tableWidth = rect.width;
8819 if (attr.GetTextBoxAttr().GetWidth().IsValid())
8820 {
8821 tableWidth = converter.GetPixels(attr.GetTextBoxAttr().GetWidth());
8822
8823 // Fixed table width, so we do want to stretch columns out if necessary.
8824 stretchToFitTableWidth = true;
8825
8826 // Shouldn't be able to exceed the size passed to this function
8827 tableWidth = wxMin(rect.width, tableWidth);
8828 }
8829
8830 // Get internal padding
8831 int paddingLeft = 0, paddingTop = 0;
8832 if (attr.GetTextBoxAttr().GetPadding().GetLeft().IsValid())
8833 paddingLeft = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetLeft());
8834 if (attr.GetTextBoxAttr().GetPadding().GetTop().IsValid())
8835 paddingTop = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetTop());
8836
8837 // Assume that left and top padding are also used for inter-cell padding.
8838 int paddingX = paddingLeft;
8839 int paddingY = paddingTop;
8840
8841 int totalLeftMargin = 0, totalRightMargin = 0, totalTopMargin = 0, totalBottomMargin = 0;
8842 GetTotalMargin(dc, buffer, attr, totalLeftMargin, totalRightMargin, totalTopMargin, totalBottomMargin);
8843
8844 // Internal table width - the area for content
8845 int internalTableWidth = tableWidth - totalLeftMargin - totalRightMargin;
8846
8847 int rowCount = m_cells.GetCount();
8848 if (m_colCount == 0 || rowCount == 0)
8849 {
8850 wxRect overallRect(rect.x, rect.y, totalLeftMargin + totalRightMargin, totalTopMargin + totalBottomMargin);
8851 SetCachedSize(overallRect.GetSize());
8852
8853 // Zero content size
8854 SetMinSize(overallRect.GetSize());
8855 SetMaxSize(GetMinSize());
8856 return true;
8857 }
8858
8859 // The final calculated widths
8860 wxArrayInt colWidths;
8861 colWidths.Add(0, m_colCount);
8862
8863 wxArrayInt absoluteColWidths;
8864 absoluteColWidths.Add(0, m_colCount);
8865
8866 wxArrayInt percentageColWidths;
8867 percentageColWidths.Add(0, m_colCount);
8868 // wxArrayInt percentageColWidthsSpanning(m_colCount);
8869 // These are only relevant when the first column contains spanning information.
8870 // wxArrayInt columnSpans(m_colCount); // Each contains 1 for non-spanning cell, > 1 for spanning cell.
8871 wxArrayInt maxColWidths;
8872 maxColWidths.Add(0, m_colCount);
8873 wxArrayInt minColWidths;
8874 minColWidths.Add(0, m_colCount);
8875
8876 wxSize tableSize(tableWidth, 0);
8877
8878 int i, j, k;
8879
8880 for (i = 0; i < m_colCount; i++)
8881 {
8882 absoluteColWidths[i] = 0;
8883 // absoluteColWidthsSpanning[i] = 0;
8884 percentageColWidths[i] = -1;
8885 // percentageColWidthsSpanning[i] = -1;
8886 colWidths[i] = 0;
8887 maxColWidths[i] = 0;
8888 minColWidths[i] = 0;
8889 // columnSpans[i] = 1;
8890 }
8891
8892 // (0) Determine which cells are visible according to spans
8893 // 1 2 3 4 5
8894 // __________________
8895 // | | | | | 1
8896 // |------| |----|
8897 // |------| | | 2
8898 // |------| | | 3
8899 // |------------------|
8900 // |__________________| 4
8901
8902 // To calculate cell visibility:
8903 // First find all spanning cells. Build an array of span records with start x, y and end x, y.
8904 // Then for each cell, test whether we're within one of those cells, and unless we're at the start of
8905 // that cell, hide the cell.
8906
8907 // We can also use this array to match the size of spanning cells to the grid. Or just do
8908 // this when we iterate through all cells.
8909
8910 // 0.1: add spanning cells to an array
8911 wxRichTextRectArray rectArray;
8912 for (j = 0; j < m_rowCount; j++)
8913 {
8914 for (i = 0; i < m_colCount; i++)
8915 {
8916 wxRichTextBox* cell = GetCell(j, i);
8917 int colSpan = 1, rowSpan = 1;
8918 if (cell->GetProperties().HasProperty(wxT("colspan")))
8919 colSpan = cell->GetProperties().GetPropertyLong(wxT("colspan"));
8920 if (cell->GetProperties().HasProperty(wxT("rowspan")))
8921 rowSpan = cell->GetProperties().GetPropertyLong(wxT("rowspan"));
8922 if (colSpan > 1 || rowSpan > 1)
8923 {
8924 rectArray.Add(wxRect(i, j, colSpan, rowSpan));
8925 }
8926 }
8927 }
8928 // 0.2: find which cells are subsumed by a spanning cell
8929 for (j = 0; j < m_rowCount; j++)
8930 {
8931 for (i = 0; i < m_colCount; i++)
8932 {
8933 wxRichTextBox* cell = GetCell(j, i);
8934 if (rectArray.GetCount() == 0)
8935 {
8936 cell->Show(true);
8937 }
8938 else
8939 {
8940 int colSpan = 1, rowSpan = 1;
8941 if (cell->GetProperties().HasProperty(wxT("colspan")))
8942 colSpan = cell->GetProperties().GetPropertyLong(wxT("colspan"));
8943 if (cell->GetProperties().HasProperty(wxT("rowspan")))
8944 rowSpan = cell->GetProperties().GetPropertyLong(wxT("rowspan"));
8945 if (colSpan > 1 || rowSpan > 1)
8946 {
8947 // Assume all spanning cells are shown
8948 cell->Show(true);
8949 }
8950 else
8951 {
8952 bool shown = true;
8953 for (k = 0; k < (int) rectArray.GetCount(); k++)
8954 {
8955 if (rectArray[k].Contains(wxPoint(i, j)))
8956 {
8957 shown = false;
8958 break;
8959 }
8960 }
8961 cell->Show(shown);
8962 }
8963 }
8964 }
8965 }
8966
8967 // TODO: find the first spanned cell in each row that spans the most columns and doesn't
8968 // overlap with a spanned cell starting at a previous column position.
8969 // This means we need to keep an array of rects so we can check. However
8970 // it does also mean that some spans simply may not be taken into account
8971 // where there are different spans happening on different rows. In these cases,
8972 // they will simply be as wide as their constituent columns.
8973
8974 // (1) Do an initial layout for all cells to get minimum and maximum size, and get
8975 // the absolute or percentage width of each column.
8976
8977 for (j = 0; j < m_rowCount; j++)
8978 {
8979 // First get the overall margins so we can calculate percentage widths based on
8980 // the available content space for all cells on the row
8981
8982 int overallRowContentMargin = 0;
8983 int visibleCellCount = 0;
8984
8985 for (i = 0; i < m_colCount; i++)
8986 {
8987 wxRichTextBox* cell = GetCell(j, i);
8988 if (cell->IsShown())
8989 {
8990 int cellTotalLeftMargin = 0, cellTotalRightMargin = 0, cellTotalTopMargin = 0, cellTotalBottomMargin = 0;
8991 GetTotalMargin(dc, buffer, cell->GetAttributes(), cellTotalLeftMargin, cellTotalRightMargin, cellTotalTopMargin, cellTotalBottomMargin);
8992
8993 overallRowContentMargin += (cellTotalLeftMargin + cellTotalRightMargin);
8994 visibleCellCount ++;
8995 }
8996 }
8997
8998 // Add in inter-cell padding
8999 overallRowContentMargin += ((visibleCellCount-1) * paddingX);
9000
9001 int rowContentWidth = internalTableWidth - overallRowContentMargin;
9002 wxSize rowTableSize(rowContentWidth, 0);
9003 wxTextAttrDimensionConverter converter(dc, scale, rowTableSize);
9004
9005 for (i = 0; i < m_colCount; i++)
9006 {
9007 wxRichTextBox* cell = GetCell(j, i);
9008 if (cell->IsShown())
9009 {
9010 int colSpan = 1;
9011 if (cell->GetProperties().HasProperty(wxT("colspan")))
9012 colSpan = cell->GetProperties().GetPropertyLong(wxT("colspan"));
9013
9014 // Lay out cell to find min/max widths
9015 cell->Invalidate(wxRICHTEXT_ALL);
9016 cell->Layout(dc, context, availableSpace, availableSpace, style);
9017
9018 if (colSpan == 1)
9019 {
9020 int absoluteCellWidth = -1;
9021 int percentageCellWidth = -1;
9022
9023 // I think we need to calculate percentages from the internal table size,
9024 // minus the padding between cells which we'll need to calculate from the
9025 // (number of VISIBLE cells - 1)*paddingX. Then percentages that add up to 100%
9026 // will add up to 100%. In CSS, the width specifies the cell's content rect width,
9027 // so if we want to conform to that we'll need to add in the overall cell margins.
9028 // However, this will make it difficult to specify percentages that add up to
9029 // 100% and still fit within the table width.
9030 // Let's say two cells have 50% width. They have 10 pixels of overall margin each.
9031 // The table content rect is 500 pixels and the inter-cell padding is 20 pixels.
9032 // If we're using internal content size for the width, we would calculate the
9033 // the overall cell width for n cells as:
9034 // (500 - 20*(n-1) - overallCellMargin1 - overallCellMargin2 - ...) * percentage / 100
9035 // + thisOverallCellMargin
9036 // = 500 - 20 - 10 - 10) * 0.5 + 10 = 240 pixels overall cell width.
9037 // Adding this back, we get 240 + 240 + 20 = 500 pixels.
9038
9039 if (cell->GetAttributes().GetTextBoxAttr().GetWidth().IsValid())
9040 {
9041 int w = converter.GetPixels(cell->GetAttributes().GetTextBoxAttr().GetWidth());
9042 if (cell->GetAttributes().GetTextBoxAttr().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE)
9043 {
9044 percentageCellWidth = w;
9045 }
9046 else
9047 {
9048 absoluteCellWidth = w;
9049 }
9050 // Override absolute width with minimum width if necessary
9051 if (cell->GetMinSize().x > 0 && absoluteCellWidth !=1 && cell->GetMinSize().x > absoluteCellWidth)
9052 absoluteCellWidth = cell->GetMinSize().x;
9053 }
9054
9055 if (absoluteCellWidth != -1)
9056 {
9057 if (absoluteCellWidth > absoluteColWidths[i])
9058 absoluteColWidths[i] = absoluteCellWidth;
9059 }
9060
9061 if (percentageCellWidth != -1)
9062 {
9063 if (percentageCellWidth > percentageColWidths[i])
9064 percentageColWidths[i] = percentageCellWidth;
9065 }
9066
9067 if (colSpan == 1 && cell->GetMinSize().x && cell->GetMinSize().x > minColWidths[i])
9068 minColWidths[i] = cell->GetMinSize().x;
9069 if (colSpan == 1 && cell->GetMaxSize().x && cell->GetMaxSize().x > maxColWidths[i])
9070 maxColWidths[i] = cell->GetMaxSize().x;
9071 }
9072 }
9073 }
9074 }
9075
9076 // (2) Allocate initial column widths from minimum widths, absolute values and proportions
9077 // TODO: simply merge this into (1).
9078 for (i = 0; i < m_colCount; i++)
9079 {
9080 if (absoluteColWidths[i] > 0)
9081 {
9082 colWidths[i] = absoluteColWidths[i];
9083 }
9084 else if (percentageColWidths[i] > 0)
9085 {
9086 colWidths[i] = percentageColWidths[i];
9087
9088 // This is rubbish - we calculated the absolute widths from percentages, so
9089 // we can't do it again here.
9090 //colWidths[i] = (int) (double(percentageColWidths[i]) * double(tableWidth) / 100.0 + 0.5);
9091 }
9092 }
9093
9094 // (3) Process absolute or proportional widths of spanning columns,
9095 // now that we know what our fixed column widths are going to be.
9096 // Spanned cells will try to adjust columns so the span will fit.
9097 // Even existing fixed column widths can be expanded if necessary.
9098 // Actually, currently fixed columns widths aren't adjusted; instead,
9099 // the algorithm favours earlier rows and adjusts unspecified column widths
9100 // the first time only. After that, we can't know whether the column has been
9101 // specified explicitly or not. (We could make a note if necessary.)
9102 for (j = 0; j < m_rowCount; j++)
9103 {
9104 // First get the overall margins so we can calculate percentage widths based on
9105 // the available content space for all cells on the row
9106
9107 int overallRowContentMargin = 0;
9108 int visibleCellCount = 0;
9109
9110 for (i = 0; i < m_colCount; i++)
9111 {
9112 wxRichTextBox* cell = GetCell(j, i);
9113 if (cell->IsShown())
9114 {
9115 int cellTotalLeftMargin = 0, cellTotalRightMargin = 0, cellTotalTopMargin = 0, cellTotalBottomMargin = 0;
9116 GetTotalMargin(dc, buffer, cell->GetAttributes(), cellTotalLeftMargin, cellTotalRightMargin, cellTotalTopMargin, cellTotalBottomMargin);
9117
9118 overallRowContentMargin += (cellTotalLeftMargin + cellTotalRightMargin);
9119 visibleCellCount ++;
9120 }
9121 }
9122
9123 // Add in inter-cell padding
9124 overallRowContentMargin += ((visibleCellCount-1) * paddingX);
9125
9126 int rowContentWidth = internalTableWidth - overallRowContentMargin;
9127 wxSize rowTableSize(rowContentWidth, 0);
9128 wxTextAttrDimensionConverter converter(dc, scale, rowTableSize);
9129
9130 for (i = 0; i < m_colCount; i++)
9131 {
9132 wxRichTextBox* cell = GetCell(j, i);
9133 if (cell->IsShown())
9134 {
9135 int colSpan = 1;
9136 if (cell->GetProperties().HasProperty(wxT("colspan")))
9137 colSpan = cell->GetProperties().GetPropertyLong(wxT("colspan"));
9138
9139 if (colSpan > 1)
9140 {
9141 int spans = wxMin(colSpan, m_colCount - i);
9142 int cellWidth = 0;
9143 if (spans > 0)
9144 {
9145 if (cell->GetAttributes().GetTextBoxAttr().GetWidth().IsValid())
9146 {
9147 cellWidth = converter.GetPixels(cell->GetAttributes().GetTextBoxAttr().GetWidth());
9148 // Override absolute width with minimum width if necessary
9149 if (cell->GetMinSize().x > 0 && cellWidth !=1 && cell->GetMinSize().x > cellWidth)
9150 cellWidth = cell->GetMinSize().x;
9151 }
9152 else
9153 {
9154 // Do we want to do this? It's the only chance we get to
9155 // use the cell's min/max sizes, so we need to work out
9156 // how we're going to balance the unspecified spanning cell
9157 // width with the possibility more-constrained constituent cell widths.
9158 // Say there's a tiny bitmap giving it a max width of 10 pixels. We
9159 // don't want to constraint all the spanned columns to fit into this cell.
9160 // OK, let's say that if any of the constituent columns don't fit,
9161 // then we simply stop constraining the columns; instead, we'll just fit the spanning
9162 // cells to the columns later.
9163 cellWidth = cell->GetMinSize().x;
9164 if (cell->GetMaxSize().x > cellWidth)
9165 cellWidth = cell->GetMaxSize().x;
9166 }
9167
9168 // Subtract the padding between cells
9169 int spanningWidth = cellWidth;
9170 spanningWidth -= paddingX * (spans-1);
9171
9172 if (spanningWidth > 0)
9173 {
9174 // Now share the spanning width between columns within that span
9175 // TODO: take into account min widths of columns within the span
9176 int spanningWidthLeft = spanningWidth;
9177 int stretchColCount = 0;
9178 for (k = i; k < (i+spans); k++)
9179 {
9180 if (colWidths[k] > 0) // absolute or proportional width has been specified
9181 spanningWidthLeft -= colWidths[k];
9182 else
9183 stretchColCount ++;
9184 }
9185 // Now divide what's left between the remaining columns
9186 int colShare = 0;
9187 if (stretchColCount > 0)
9188 colShare = spanningWidthLeft / stretchColCount;
9189 int colShareRemainder = spanningWidthLeft - (colShare * stretchColCount);
9190
9191 // If fixed-width columns are currently too big, then we'll later
9192 // stretch the spanned cell to fit.
9193
9194 if (spanningWidthLeft > 0)
9195 {
9196 for (k = i; k < (i+spans); k++)
9197 {
9198 if (colWidths[k] <= 0) // absolute or proportional width has not been specified
9199 {
9200 int newWidth = colShare;
9201 if (k == (i+spans-1))
9202 newWidth += colShareRemainder; // ensure all pixels are filled
9203 colWidths[k] = newWidth;
9204 }
9205 }
9206 }
9207 }
9208 }
9209 }
9210 }
9211 }
9212 }
9213
9214 // (4) Next, share any remaining space out between columns that have not yet been calculated.
9215 // TODO: take into account min widths of columns within the span
9216 int tableWidthMinusPadding = internalTableWidth - (m_colCount-1)*paddingX;
9217 int widthLeft = tableWidthMinusPadding;
9218 int stretchColCount = 0;
9219 for (i = 0; i < m_colCount; i++)
9220 {
9221 // TODO: we need to take into account min widths.
9222 // Subtract min width from width left, then
9223 // add the colShare to the min width
9224 if (colWidths[i] > 0) // absolute or proportional width has been specified
9225 widthLeft -= colWidths[i];
9226 else
9227 {
9228 if (minColWidths[i] > 0)
9229 widthLeft -= minColWidths[i];
9230
9231 stretchColCount ++;
9232 }
9233 }
9234
9235 // Now divide what's left between the remaining columns
9236 int colShare = 0;
9237 if (stretchColCount > 0)
9238 colShare = widthLeft / stretchColCount;
9239 int colShareRemainder = widthLeft - (colShare * stretchColCount);
9240
9241 // Check we don't have enough space, in which case shrink all columns, overriding
9242 // any absolute/proportional widths
9243 // TODO: actually we would like to divide up the shrinkage according to size.
9244 // How do we calculate the proportions that will achieve this?
9245 // Could first choose an arbitrary value for stretching cells, and then calculate
9246 // factors to multiply each width by.
9247 // TODO: want to record this fact and pass to an iteration that tries e.g. min widths
9248 if (widthLeft < 0 || (stretchToFitTableWidth && (stretchColCount == 0)))
9249 {
9250 colShare = tableWidthMinusPadding / m_colCount;
9251 colShareRemainder = tableWidthMinusPadding - (colShare * m_colCount);
9252 for (i = 0; i < m_colCount; i++)
9253 {
9254 colWidths[i] = 0;
9255 minColWidths[i] = 0;
9256 }
9257 }
9258
9259 // We have to adjust the columns if either we need to shrink the
9260 // table to fit the parent/table width, or we explicitly set the
9261 // table width and need to stretch out the table.
9262 if (widthLeft < 0 || stretchToFitTableWidth)
9263 {
9264 for (i = 0; i < m_colCount; i++)
9265 {
9266 if (colWidths[i] <= 0) // absolute or proportional width has not been specified
9267 {
9268 if (minColWidths[i] > 0)
9269 colWidths[i] = minColWidths[i] + colShare;
9270 else
9271 colWidths[i] = colShare;
9272 if (i == (m_colCount-1))
9273 colWidths[i] += colShareRemainder; // ensure all pixels are filled
9274 }
9275 }
9276 }
9277
9278 // TODO: if spanned cells have no specified or max width, make them the
9279 // as big as the columns they span. Do this for all spanned cells in all
9280 // rows, of course. Size any spanned cells left over at the end - even if they
9281 // have width > 0, make sure they're limited to the appropriate column edge.
9282
9283
9284 /*
9285 Sort out confusion between content width
9286 and overall width later. For now, assume we specify overall width.
9287
9288 So, now we've laid out the table to fit into the given space
9289 and have used specified widths and minimum widths.
9290
9291 Now we need to consider how we will try to take maximum width into account.
9292
9293 */
9294
9295 // (??) TODO: take max width into account
9296
9297 // (6) Lay out all cells again with the current values
9298
9299 int maxRight = 0;
9300 int y = availableSpace.y;
9301 for (j = 0; j < m_rowCount; j++)
9302 {
9303 int x = availableSpace.x; // TODO: take into account centering etc.
9304 int maxCellHeight = 0;
9305 int maxSpecifiedCellHeight = 0;
9306
9307 wxArrayInt actualWidths;
9308 actualWidths.Add(0, m_colCount);
9309
9310 wxTextAttrDimensionConverter converter(dc, scale);
9311 for (i = 0; i < m_colCount; i++)
9312 {
9313 wxRichTextCell* cell = GetCell(j, i);
9314 if (cell->IsShown())
9315 {
9316 // Get max specified cell height
9317 // Don't handle percentages for height
9318 if (cell->GetAttributes().GetTextBoxAttr().GetHeight().IsValid() && cell->GetAttributes().GetTextBoxAttr().GetHeight().GetUnits() != wxTEXT_ATTR_UNITS_PERCENTAGE)
9319 {
9320 int h = converter.GetPixels(cell->GetAttributes().GetTextBoxAttr().GetHeight());
9321 if (h > maxSpecifiedCellHeight)
9322 maxSpecifiedCellHeight = h;
9323 }
9324
9325 if (colWidths[i] > 0) // absolute or proportional width has been specified
9326 {
9327 int colSpan = 1;
9328 if (cell->GetProperties().HasProperty(wxT("colspan")))
9329 colSpan = cell->GetProperties().GetPropertyLong(wxT("colspan"));
9330
9331 wxRect availableCellSpace;
9332
9333 // TODO: take into acount spans
9334 if (colSpan > 1)
9335 {
9336 // Calculate the size of this spanning cell from its constituent columns
9337 int xx = x;
9338 int spans = wxMin(colSpan, m_colCount - i);
9339 for (k = i; k < spans; k++)
9340 {
9341 if (k != i)
9342 xx += paddingX;
9343 xx += colWidths[k];
9344 }
9345 availableCellSpace = wxRect(x, y, xx, -1);
9346 }
9347 else
9348 availableCellSpace = wxRect(x, y, colWidths[i], -1);
9349
9350 // Store actual width so we can force cell to be the appropriate width on the final loop
9351 actualWidths[i] = availableCellSpace.GetWidth();
9352
9353 // Lay out cell
9354 cell->Invalidate(wxRICHTEXT_ALL);
9355 cell->Layout(dc, context, availableCellSpace, availableSpace, style);
9356
9357 // TODO: use GetCachedSize().x to compute 'natural' size
9358
9359 x += (availableCellSpace.GetWidth() + paddingX);
9360 if (cell->GetCachedSize().y > maxCellHeight)
9361 maxCellHeight = cell->GetCachedSize().y;
9362 }
9363 }
9364 }
9365
9366 maxCellHeight = wxMax(maxCellHeight, maxSpecifiedCellHeight);
9367
9368 for (i = 0; i < m_colCount; i++)
9369 {
9370 wxRichTextCell* cell = GetCell(j, i);
9371 if (cell->IsShown())
9372 {
9373 wxRect availableCellSpace = wxRect(cell->GetPosition(), wxSize(actualWidths[i], maxCellHeight));
9374 // Lay out cell with new height
9375 cell->Invalidate(wxRICHTEXT_ALL);
9376 cell->Layout(dc, context, availableCellSpace, availableSpace, style);
9377
9378 // Make sure the cell size really is the appropriate size,
9379 // not the calculated box size
9380 cell->SetCachedSize(wxSize(actualWidths[i], maxCellHeight));
9381
9382 maxRight = wxMax(maxRight, cell->GetPosition().x + cell->GetCachedSize().x);
9383 }
9384 }
9385
9386 y += maxCellHeight;
9387 if (j < (m_rowCount-1))
9388 y += paddingY;
9389 }
9390
9391 // We need to add back the margins etc.
9392 {
9393 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
9394 contentRect = wxRect(wxPoint(0, 0), wxSize(maxRight - availableSpace.x, y - availableSpace.y));
9395 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
9396 SetCachedSize(marginRect.GetSize());
9397 }
9398
9399 // TODO: calculate max size
9400 {
9401 SetMaxSize(GetCachedSize());
9402 }
9403
9404 // TODO: calculate min size
9405 {
9406 SetMinSize(GetCachedSize());
9407 }
9408
9409 // TODO: currently we use either a fixed table width or the parent's size.
9410 // We also want to be able to calculate the table width from its content,
9411 // whether using fixed column widths or cell content min/max width.
9412 // Probably need a boolean flag to say whether we need to stretch cells
9413 // to fit the table width, or to simply use min/max cell widths. The
9414 // trouble with this is that if cell widths are not specified, they
9415 // will be tiny; we could use arbitrary defaults but this seems unsatisfactory.
9416 // Anyway, ignoring that problem, we probably need to factor layout into a function
9417 // that can can calculate the maximum unconstrained layout in case table size is
9418 // not specified. Then LayoutToBestSize() can choose to use either parent size to
9419 // constrain Layout(), or the previously-calculated max size to constraint layout.
9420
9421 return true;
9422 }
9423
9424 // Finds the absolute position and row height for the given character position
9425 bool wxRichTextTable::FindPosition(wxDC& dc, wxRichTextDrawingContext& context, long index, wxPoint& pt, int* height, bool forceLineStart)
9426 {
9427 wxRichTextCell* child = GetCell(index+1);
9428 if (child)
9429 {
9430 // Find the position at the start of the child cell, since the table doesn't
9431 // have any caret position of its own.
9432 return child->FindPosition(dc, context, -1, pt, height, forceLineStart);
9433 }
9434 else
9435 return false;
9436 }
9437
9438 // Get the cell at the given character position (in the range of the table).
9439 wxRichTextCell* wxRichTextTable::GetCell(long pos) const
9440 {
9441 int row = 0, col = 0;
9442 if (GetCellRowColumnPosition(pos, row, col))
9443 {
9444 return GetCell(row, col);
9445 }
9446 else
9447 return NULL;
9448 }
9449
9450 // Get the row/column for a given character position
9451 bool wxRichTextTable::GetCellRowColumnPosition(long pos, int& row, int& col) const
9452 {
9453 if (m_colCount == 0 || m_rowCount == 0)
9454 return false;
9455
9456 row = (int) (pos / m_colCount);
9457 col = pos - (row * m_colCount);
9458
9459 wxASSERT(row < m_rowCount && col < m_colCount);
9460
9461 if (row < m_rowCount && col < m_colCount)
9462 return true;
9463 else
9464 return false;
9465 }
9466
9467 // Calculate range, taking row/cell ordering into account instead of relying
9468 // on list ordering.
9469 void wxRichTextTable::CalculateRange(long start, long& end)
9470 {
9471 long current = start;
9472 long lastEnd = current;
9473
9474 if (IsTopLevel())
9475 {
9476 current = 0;
9477 lastEnd = 0;
9478 }
9479
9480 int i, j;
9481 for (i = 0; i < m_rowCount; i++)
9482 {
9483 for (j = 0; j < m_colCount; j++)
9484 {
9485 wxRichTextCell* child = GetCell(i, j);
9486 if (child)
9487 {
9488 long childEnd = 0;
9489
9490 child->CalculateRange(current, childEnd);
9491
9492 lastEnd = childEnd;
9493 current = childEnd + 1;
9494 }
9495 }
9496 }
9497
9498 // A top-level object always has a range of size 1,
9499 // because its children don't count at this level.
9500 end = start;
9501 m_range.SetRange(start, start);
9502
9503 // An object with no children has zero length
9504 if (m_children.GetCount() == 0)
9505 lastEnd --;
9506 m_ownRange.SetRange(0, lastEnd);
9507 }
9508
9509 // Gets the range size.
9510 bool wxRichTextTable::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, wxPoint position, wxArrayInt* partialExtents) const
9511 {
9512 return wxRichTextBox::GetRangeSize(range, size, descent, dc, context, flags, position, partialExtents);
9513 }
9514
9515 // Deletes content in the given range.
9516 bool wxRichTextTable::DeleteRange(const wxRichTextRange& WXUNUSED(range))
9517 {
9518 // TODO: implement deletion of cells
9519 return true;
9520 }
9521
9522 // Gets any text in this object for the given range.
9523 wxString wxRichTextTable::GetTextForRange(const wxRichTextRange& range) const
9524 {
9525 return wxRichTextBox::GetTextForRange(range);
9526 }
9527
9528 // Copies this object.
9529 void wxRichTextTable::Copy(const wxRichTextTable& obj)
9530 {
9531 wxRichTextBox::Copy(obj);
9532
9533 ClearTable();
9534
9535 m_rowCount = obj.m_rowCount;
9536 m_colCount = obj.m_colCount;
9537
9538 m_cells.Add(wxRichTextObjectPtrArray(), m_rowCount);
9539
9540 int i, j;
9541 for (i = 0; i < m_rowCount; i++)
9542 {
9543 wxRichTextObjectPtrArray& colArray = m_cells[i];
9544 for (j = 0; j < m_colCount; j++)
9545 {
9546 wxRichTextCell* cell = wxDynamicCast(obj.GetCell(i, j)->Clone(), wxRichTextCell);
9547 AppendChild(cell);
9548
9549 colArray.Add(cell);
9550 }
9551 }
9552 }
9553
9554 void wxRichTextTable::ClearTable()
9555 {
9556 m_cells.Clear();
9557 DeleteChildren();
9558 }
9559
9560 bool wxRichTextTable::CreateTable(int rows, int cols)
9561 {
9562 ClearTable();
9563
9564 m_rowCount = rows;
9565 m_colCount = cols;
9566
9567 m_cells.Add(wxRichTextObjectPtrArray(), rows);
9568
9569 int i, j;
9570 for (i = 0; i < rows; i++)
9571 {
9572 wxRichTextObjectPtrArray& colArray = m_cells[i];
9573 for (j = 0; j < cols; j++)
9574 {
9575 wxRichTextCell* cell = new wxRichTextCell;
9576 AppendChild(cell);
9577 cell->AddParagraph(wxEmptyString);
9578
9579 colArray.Add(cell);
9580 }
9581 }
9582
9583 return true;
9584 }
9585
9586 wxRichTextCell* wxRichTextTable::GetCell(int row, int col) const
9587 {
9588 wxASSERT(row < m_rowCount);
9589 wxASSERT(col < m_colCount);
9590
9591 if (row < m_rowCount && col < m_colCount)
9592 {
9593 wxRichTextObjectPtrArray& colArray = m_cells[row];
9594 wxRichTextObject* obj = colArray[col];
9595 return wxDynamicCast(obj, wxRichTextCell);
9596 }
9597 else
9598 return NULL;
9599 }
9600
9601 // Returns a selection object specifying the selections between start and end character positions.
9602 // For example, a table would deduce what cells (of range length 1) are selected when dragging across the table.
9603 wxRichTextSelection wxRichTextTable::GetSelection(long start, long end) const
9604 {
9605 wxRichTextSelection selection;
9606 selection.SetContainer((wxRichTextTable*) this);
9607
9608 if (start > end)
9609 {
9610 long tmp = end;
9611 end = start;
9612 start = tmp;
9613 }
9614
9615 wxASSERT( start >= 0 && end < (m_colCount * m_rowCount));
9616
9617 if (end >= (m_colCount * m_rowCount))
9618 return selection;
9619
9620 // We need to find the rectangle of cells that is described by the rectangle
9621 // with start, end as the diagonal. Make sure we don't add cells that are
9622 // not currenty visible because they are overlapped by spanning cells.
9623 /*
9624 --------------------------
9625 | 0 | 1 | 2 | 3 | 4 |
9626 --------------------------
9627 | 5 | 6 | 7 | 8 | 9 |
9628 --------------------------
9629 | 10 | 11 | 12 | 13 | 14 |
9630 --------------------------
9631 | 15 | 16 | 17 | 18 | 19 |
9632 --------------------------
9633
9634 Let's say we select 6 -> 18.
9635
9636 Left and right edge cols of rectangle are 1 and 3 inclusive. Find least/greatest to find
9637 which is left and which is right.
9638
9639 Top and bottom edge rows are 1 and 3 inclusive. Again, find least/greatest to find top and bottom.
9640
9641 Now go through rows from 1 to 3 and only add cells that are (a) within above column range
9642 and (b) shown.
9643
9644
9645 */
9646
9647 int leftCol = start - m_colCount * int(start/m_colCount);
9648 int rightCol = end - m_colCount * int(end/m_colCount);
9649
9650 int topRow = int(start/m_colCount);
9651 int bottomRow = int(end/m_colCount);
9652
9653 if (leftCol > rightCol)
9654 {
9655 int tmp = rightCol;
9656 rightCol = leftCol;
9657 leftCol = tmp;
9658 }
9659
9660 if (topRow > bottomRow)
9661 {
9662 int tmp = bottomRow;
9663 bottomRow = topRow;
9664 topRow = tmp;
9665 }
9666
9667 int i, j;
9668 for (i = topRow; i <= bottomRow; i++)
9669 {
9670 for (j = leftCol; j <= rightCol; j++)
9671 {
9672 wxRichTextCell* cell = GetCell(i, j);
9673 if (cell && cell->IsShown())
9674 selection.Add(cell->GetRange());
9675 }
9676 }
9677
9678 return selection;
9679 }
9680
9681 // Sets the attributes for the cells specified by the selection.
9682 bool wxRichTextTable::SetCellStyle(const wxRichTextSelection& selection, const wxRichTextAttr& style, int flags)
9683 {
9684 if (selection.GetContainer() != this)
9685 return false;
9686
9687 wxRichTextBuffer* buffer = GetBuffer();
9688 bool haveControl = (buffer && buffer->GetRichTextCtrl() != NULL);
9689 bool withUndo = haveControl && ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0);
9690
9691 if (withUndo)
9692 buffer->BeginBatchUndo(_("Set Cell Style"));
9693
9694 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
9695 while (node)
9696 {
9697 wxRichTextCell* cell = wxDynamicCast(node->GetData(), wxRichTextCell);
9698 if (cell && selection.WithinSelection(cell->GetRange().GetStart()))
9699 SetStyle(cell, style, flags);
9700 node = node->GetNext();
9701 }
9702
9703 // Do action, or delay it until end of batch.
9704 if (withUndo)
9705 buffer->EndBatchUndo();
9706
9707 return true;
9708 }
9709
9710 bool wxRichTextTable::DeleteRows(int startRow, int noRows)
9711 {
9712 wxASSERT((startRow + noRows) < m_rowCount);
9713 if ((startRow + noRows) >= m_rowCount)
9714 return false;
9715
9716 int i, j;
9717 for (i = startRow; i < (startRow+noRows); i++)
9718 {
9719 wxRichTextObjectPtrArray& colArray = m_cells[startRow];
9720 for (j = 0; j < (int) colArray.GetCount(); j++)
9721 {
9722 wxRichTextObject* cell = colArray[j];
9723 RemoveChild(cell, true);
9724 }
9725
9726 // Keep deleting at the same position, since we move all
9727 // the others up
9728 m_cells.RemoveAt(startRow);
9729 }
9730
9731 m_rowCount = m_rowCount - noRows;
9732
9733 return true;
9734 }
9735
9736 bool wxRichTextTable::DeleteColumns(int startCol, int noCols)
9737 {
9738 wxASSERT((startCol + noCols) < m_colCount);
9739 if ((startCol + noCols) >= m_colCount)
9740 return false;
9741
9742 bool deleteRows = (noCols == m_colCount);
9743
9744 int i, j;
9745 for (i = 0; i < m_rowCount; i++)
9746 {
9747 wxRichTextObjectPtrArray& colArray = m_cells[deleteRows ? 0 : i];
9748 for (j = startCol; j < (startCol+noCols); j++)
9749 {
9750 wxRichTextObject* cell = colArray[j];
9751 RemoveChild(cell, true);
9752 }
9753
9754 if (deleteRows)
9755 m_cells.RemoveAt(0);
9756 }
9757
9758 if (deleteRows)
9759 m_rowCount = 0;
9760 m_colCount = m_colCount - noCols;
9761
9762 return true;
9763 }
9764
9765 bool wxRichTextTable::AddRows(int startRow, int noRows, const wxRichTextAttr& attr)
9766 {
9767 wxASSERT(startRow <= m_rowCount);
9768 if (startRow > m_rowCount)
9769 return false;
9770
9771 int i, j;
9772 for (i = 0; i < noRows; i++)
9773 {
9774 int idx;
9775 if (startRow == m_rowCount)
9776 {
9777 m_cells.Add(wxRichTextObjectPtrArray());
9778 idx = m_cells.GetCount() - 1;
9779 }
9780 else
9781 {
9782 m_cells.Insert(wxRichTextObjectPtrArray(), startRow+i);
9783 idx = startRow+i;
9784 }
9785
9786 wxRichTextObjectPtrArray& colArray = m_cells[idx];
9787 for (j = 0; j < m_colCount; j++)
9788 {
9789 wxRichTextCell* cell = new wxRichTextCell;
9790 cell->GetAttributes() = attr;
9791
9792 AppendChild(cell);
9793 colArray.Add(cell);
9794 }
9795 }
9796
9797 m_rowCount = m_rowCount + noRows;
9798 return true;
9799 }
9800
9801 bool wxRichTextTable::AddColumns(int startCol, int noCols, const wxRichTextAttr& attr)
9802 {
9803 wxASSERT(startCol <= m_colCount);
9804 if (startCol > m_colCount)
9805 return false;
9806
9807 int i, j;
9808 for (i = 0; i < m_rowCount; i++)
9809 {
9810 wxRichTextObjectPtrArray& colArray = m_cells[i];
9811 for (j = 0; j < noCols; j++)
9812 {
9813 wxRichTextCell* cell = new wxRichTextCell;
9814 cell->GetAttributes() = attr;
9815
9816 AppendChild(cell);
9817
9818 if (startCol == m_colCount)
9819 colArray.Add(cell);
9820 else
9821 colArray.Insert(cell, startCol+j);
9822 }
9823 }
9824
9825 m_colCount = m_colCount + noCols;
9826
9827 return true;
9828 }
9829
9830 // Edit properties via a GUI
9831 bool wxRichTextTable::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer)
9832 {
9833 wxRichTextObjectPropertiesDialog boxDlg(this, wxGetTopLevelParent(parent), wxID_ANY, _("Table Properties"));
9834 boxDlg.SetAttributes(GetAttributes());
9835
9836 if (boxDlg.ShowModal() == wxID_OK)
9837 {
9838 boxDlg.ApplyStyle(buffer->GetRichTextCtrl(), wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_RESET);
9839 return true;
9840 }
9841 else
9842 return false;
9843 }
9844
9845 /*
9846 * Module to initialise and clean up handlers
9847 */
9848
9849 class wxRichTextModule: public wxModule
9850 {
9851 DECLARE_DYNAMIC_CLASS(wxRichTextModule)
9852 public:
9853 wxRichTextModule() {}
9854 bool OnInit()
9855 {
9856 wxRichTextBuffer::SetRenderer(new wxRichTextStdRenderer);
9857 wxRichTextBuffer::InitStandardHandlers();
9858 wxRichTextParagraph::InitDefaultTabs();
9859
9860 wxRichTextXMLHandler::RegisterNodeName(wxT("text"), wxT("wxRichTextPlainText"));
9861 wxRichTextXMLHandler::RegisterNodeName(wxT("symbol"), wxT("wxRichTextPlainText"));
9862 wxRichTextXMLHandler::RegisterNodeName(wxT("image"), wxT("wxRichTextImage"));
9863 wxRichTextXMLHandler::RegisterNodeName(wxT("paragraph"), wxT("wxRichTextParagraph"));
9864 wxRichTextXMLHandler::RegisterNodeName(wxT("paragraphlayout"), wxT("wxRichTextParagraphLayoutBox"));
9865 wxRichTextXMLHandler::RegisterNodeName(wxT("textbox"), wxT("wxRichTextBox"));
9866 wxRichTextXMLHandler::RegisterNodeName(wxT("cell"), wxT("wxRichTextCell"));
9867 wxRichTextXMLHandler::RegisterNodeName(wxT("table"), wxT("wxRichTextTable"));
9868 wxRichTextXMLHandler::RegisterNodeName(wxT("field"), wxT("wxRichTextField"));
9869
9870 return true;
9871 }
9872 void OnExit()
9873 {
9874 wxRichTextBuffer::CleanUpHandlers();
9875 wxRichTextBuffer::CleanUpDrawingHandlers();
9876 wxRichTextBuffer::CleanUpFieldTypes();
9877 wxRichTextXMLHandler::ClearNodeToClassMap();
9878 wxRichTextDecimalToRoman(-1);
9879 wxRichTextParagraph::ClearDefaultTabs();
9880 wxRichTextCtrl::ClearAvailableFontNames();
9881 wxRichTextBuffer::SetRenderer(NULL);
9882 }
9883 };
9884
9885 IMPLEMENT_DYNAMIC_CLASS(wxRichTextModule, wxModule)
9886
9887
9888 // If the richtext lib is dynamically loaded after the app has already started
9889 // (such as from wxPython) then the built-in module system will not init this
9890 // module. Provide this function to do it manually.
9891 void wxRichTextModuleInit()
9892 {
9893 wxModule* module = new wxRichTextModule;
9894 module->Init();
9895 wxModule::RegisterModule(module);
9896 }
9897
9898
9899 /*!
9900 * Commands for undo/redo
9901 *
9902 */
9903
9904 wxRichTextCommand::wxRichTextCommand(const wxString& name, wxRichTextCommandId id, wxRichTextBuffer* buffer,
9905 wxRichTextParagraphLayoutBox* container, wxRichTextCtrl* ctrl, bool ignoreFirstTime): wxCommand(true, name)
9906 {
9907 /* wxRichTextAction* action = */ new wxRichTextAction(this, name, id, buffer, container, ctrl, ignoreFirstTime);
9908 }
9909
9910 wxRichTextCommand::wxRichTextCommand(const wxString& name): wxCommand(true, name)
9911 {
9912 }
9913
9914 wxRichTextCommand::~wxRichTextCommand()
9915 {
9916 ClearActions();
9917 }
9918
9919 void wxRichTextCommand::AddAction(wxRichTextAction* action)
9920 {
9921 if (!m_actions.Member(action))
9922 m_actions.Append(action);
9923 }
9924
9925 bool wxRichTextCommand::Do()
9926 {
9927 for (wxList::compatibility_iterator node = m_actions.GetFirst(); node; node = node->GetNext())
9928 {
9929 wxRichTextAction* action = (wxRichTextAction*) node->GetData();
9930 action->Do();
9931 }
9932
9933 return true;
9934 }
9935
9936 bool wxRichTextCommand::Undo()
9937 {
9938 for (wxList::compatibility_iterator node = m_actions.GetLast(); node; node = node->GetPrevious())
9939 {
9940 wxRichTextAction* action = (wxRichTextAction*) node->GetData();
9941 action->Undo();
9942 }
9943
9944 return true;
9945 }
9946
9947 void wxRichTextCommand::ClearActions()
9948 {
9949 WX_CLEAR_LIST(wxList, m_actions);
9950 }
9951
9952 /*!
9953 * Individual action
9954 *
9955 */
9956
9957 wxRichTextAction::wxRichTextAction(wxRichTextCommand* cmd, const wxString& name, wxRichTextCommandId id,
9958 wxRichTextBuffer* buffer, wxRichTextParagraphLayoutBox* container,
9959 wxRichTextCtrl* ctrl, bool ignoreFirstTime)
9960 {
9961 m_buffer = buffer;
9962 m_object = NULL;
9963 m_containerAddress.Create(buffer, container);
9964 m_ignoreThis = ignoreFirstTime;
9965 m_cmdId = id;
9966 m_position = -1;
9967 m_ctrl = ctrl;
9968 m_name = name;
9969 m_newParagraphs.SetDefaultStyle(buffer->GetDefaultStyle());
9970 m_newParagraphs.SetBasicStyle(buffer->GetBasicStyle());
9971 if (cmd)
9972 cmd->AddAction(this);
9973 }
9974
9975 wxRichTextAction::~wxRichTextAction()
9976 {
9977 if (m_object)
9978 delete m_object;
9979 }
9980
9981 // Returns the container that this action refers to, using the container address and top-level buffer.
9982 wxRichTextParagraphLayoutBox* wxRichTextAction::GetContainer() const
9983 {
9984 wxRichTextParagraphLayoutBox* container = wxDynamicCast(GetContainerAddress().GetObject(m_buffer), wxRichTextParagraphLayoutBox);
9985 return container;
9986 }
9987
9988
9989 void wxRichTextAction::CalculateRefreshOptimizations(wxArrayInt& optimizationLineCharPositions, wxArrayInt& optimizationLineYPositions)
9990 {
9991 // Store a list of line start character and y positions so we can figure out which area
9992 // we need to refresh
9993
9994 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
9995 wxRichTextParagraphLayoutBox* container = GetContainer();
9996 wxASSERT(container != NULL);
9997 if (!container)
9998 return;
9999
10000 // NOTE: we're assuming that the buffer is laid out correctly at this point.
10001 // If we had several actions, which only invalidate and leave layout until the
10002 // paint handler is called, then this might not be true. So we may need to switch
10003 // optimisation on only when we're simply adding text and not simultaneously
10004 // deleting a selection, for example. Or, we make sure the buffer is laid out correctly
10005 // first, but of course this means we'll be doing it twice.
10006 if (!m_buffer->IsDirty() && m_ctrl) // can only do optimisation if the buffer is already laid out correctly
10007 {
10008 wxSize clientSize = m_ctrl->GetClientSize();
10009 wxPoint firstVisiblePt = m_ctrl->GetFirstVisiblePoint();
10010 int lastY = firstVisiblePt.y + clientSize.y;
10011
10012 wxRichTextParagraph* para = container->GetParagraphAtPosition(GetRange().GetStart());
10013 wxRichTextObjectList::compatibility_iterator node = container->GetChildren().Find(para);
10014 while (node)
10015 {
10016 wxRichTextParagraph* child = (wxRichTextParagraph*) node->GetData();
10017 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
10018 while (node2)
10019 {
10020 wxRichTextLine* line = node2->GetData();
10021 wxPoint pt = line->GetAbsolutePosition();
10022 wxRichTextRange range = line->GetAbsoluteRange();
10023
10024 if (pt.y > lastY)
10025 {
10026 node2 = wxRichTextLineList::compatibility_iterator();
10027 node = wxRichTextObjectList::compatibility_iterator();
10028 }
10029 else if (range.GetStart() > GetPosition() && pt.y >= firstVisiblePt.y)
10030 {
10031 optimizationLineCharPositions.Add(range.GetStart());
10032 optimizationLineYPositions.Add(pt.y);
10033 }
10034
10035 if (node2)
10036 node2 = node2->GetNext();
10037 }
10038
10039 if (node)
10040 node = node->GetNext();
10041 }
10042 }
10043 #endif
10044 }
10045
10046 bool wxRichTextAction::Do()
10047 {
10048 m_buffer->Modify(true);
10049
10050 wxRichTextParagraphLayoutBox* container = GetContainer();
10051 wxASSERT(container != NULL);
10052 if (!container)
10053 return false;
10054
10055 switch (m_cmdId)
10056 {
10057 case wxRICHTEXT_INSERT:
10058 {
10059 // Store a list of line start character and y positions so we can figure out which area
10060 // we need to refresh
10061 wxArrayInt optimizationLineCharPositions;
10062 wxArrayInt optimizationLineYPositions;
10063
10064 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
10065 CalculateRefreshOptimizations(optimizationLineCharPositions, optimizationLineYPositions);
10066 #endif
10067
10068 container->InsertFragment(GetRange().GetStart(), m_newParagraphs);
10069 container->UpdateRanges();
10070
10071 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
10072 // Layout() would stop prematurely at the top level.
10073 container->InvalidateHierarchy(wxRichTextRange(wxMax(0, GetRange().GetStart()-1), GetRange().GetEnd()));
10074
10075 long newCaretPosition = GetPosition() + m_newParagraphs.GetOwnRange().GetLength();
10076
10077 // Character position to caret position
10078 newCaretPosition --;
10079
10080 // Don't take into account the last newline
10081 if (m_newParagraphs.GetPartialParagraph())
10082 newCaretPosition --;
10083 else
10084 if (m_newParagraphs.GetChildren().GetCount() > 1)
10085 {
10086 wxRichTextObject* p = (wxRichTextObject*) m_newParagraphs.GetChildren().GetLast()->GetData();
10087 if (p->GetRange().GetLength() == 1)
10088 newCaretPosition --;
10089 }
10090
10091 newCaretPosition = wxMin(newCaretPosition, (container->GetOwnRange().GetEnd()-1));
10092
10093 UpdateAppearance(newCaretPosition, true /* send update event */, & optimizationLineCharPositions, & optimizationLineYPositions, true /* do */);
10094
10095 wxRichTextEvent cmdEvent(
10096 wxEVT_COMMAND_RICHTEXT_CONTENT_INSERTED,
10097 m_ctrl ? m_ctrl->GetId() : -1);
10098 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
10099 cmdEvent.SetRange(GetRange());
10100 cmdEvent.SetPosition(GetRange().GetStart());
10101 cmdEvent.SetContainer(container);
10102
10103 m_buffer->SendEvent(cmdEvent);
10104
10105 break;
10106 }
10107 case wxRICHTEXT_DELETE:
10108 {
10109 wxArrayInt optimizationLineCharPositions;
10110 wxArrayInt optimizationLineYPositions;
10111
10112 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
10113 CalculateRefreshOptimizations(optimizationLineCharPositions, optimizationLineYPositions);
10114 #endif
10115
10116 container->DeleteRange(GetRange());
10117 container->UpdateRanges();
10118 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
10119 // Layout() would stop prematurely at the top level.
10120 container->InvalidateHierarchy(wxRichTextRange(GetRange().GetStart(), GetRange().GetStart()));
10121
10122 long caretPos = GetRange().GetStart()-1;
10123 if (caretPos >= container->GetOwnRange().GetEnd())
10124 caretPos --;
10125
10126 UpdateAppearance(caretPos, true /* send update event */, & optimizationLineCharPositions, & optimizationLineYPositions, true /* do */);
10127
10128 wxRichTextEvent cmdEvent(
10129 wxEVT_COMMAND_RICHTEXT_CONTENT_DELETED,
10130 m_ctrl ? m_ctrl->GetId() : -1);
10131 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
10132 cmdEvent.SetRange(GetRange());
10133 cmdEvent.SetPosition(GetRange().GetStart());
10134 cmdEvent.SetContainer(container);
10135
10136 m_buffer->SendEvent(cmdEvent);
10137
10138 break;
10139 }
10140 case wxRICHTEXT_CHANGE_STYLE:
10141 case wxRICHTEXT_CHANGE_PROPERTIES:
10142 {
10143 ApplyParagraphs(GetNewParagraphs());
10144
10145 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
10146 // Layout() would stop prematurely at the top level.
10147 container->InvalidateHierarchy(GetRange());
10148
10149 UpdateAppearance(GetPosition());
10150
10151 wxRichTextEvent cmdEvent(
10152 m_cmdId == wxRICHTEXT_CHANGE_STYLE ? wxEVT_COMMAND_RICHTEXT_STYLE_CHANGED : wxEVT_COMMAND_RICHTEXT_PROPERTIES_CHANGED,
10153 m_ctrl ? m_ctrl->GetId() : -1);
10154 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
10155 cmdEvent.SetRange(GetRange());
10156 cmdEvent.SetPosition(GetRange().GetStart());
10157 cmdEvent.SetContainer(container);
10158
10159 m_buffer->SendEvent(cmdEvent);
10160
10161 break;
10162 }
10163 case wxRICHTEXT_CHANGE_ATTRIBUTES:
10164 {
10165 wxRichTextObject* obj = m_objectAddress.GetObject(m_buffer); // container->GetChildAtPosition(GetRange().GetStart());
10166 if (obj)
10167 {
10168 wxRichTextAttr oldAttr = obj->GetAttributes();
10169 obj->GetAttributes() = m_attributes;
10170 m_attributes = oldAttr;
10171 }
10172
10173 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
10174 // Layout() would stop prematurely at the top level.
10175 container->InvalidateHierarchy(GetRange());
10176
10177 UpdateAppearance(GetPosition());
10178
10179 wxRichTextEvent cmdEvent(
10180 wxEVT_COMMAND_RICHTEXT_STYLE_CHANGED,
10181 m_ctrl ? m_ctrl->GetId() : -1);
10182 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
10183 cmdEvent.SetRange(GetRange());
10184 cmdEvent.SetPosition(GetRange().GetStart());
10185 cmdEvent.SetContainer(container);
10186
10187 m_buffer->SendEvent(cmdEvent);
10188
10189 break;
10190 }
10191 case wxRICHTEXT_CHANGE_OBJECT:
10192 {
10193 wxRichTextObject* obj = m_objectAddress.GetObject(m_buffer);
10194 // wxRichTextObject* obj = container->GetChildAtPosition(GetRange().GetStart());
10195 if (obj && m_object)
10196 {
10197 wxRichTextObjectList::compatibility_iterator node = container->GetChildren().Find(obj);
10198 if (node)
10199 {
10200 wxRichTextObject* obj = node->GetData();
10201 node->SetData(m_object);
10202 m_object = obj;
10203 }
10204 }
10205
10206 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
10207 // Layout() would stop prematurely at the top level.
10208 container->InvalidateHierarchy(GetRange());
10209
10210 UpdateAppearance(GetPosition());
10211
10212 // TODO: send new kind of modification event
10213
10214 break;
10215 }
10216 default:
10217 break;
10218 }
10219
10220 return true;
10221 }
10222
10223 bool wxRichTextAction::Undo()
10224 {
10225 m_buffer->Modify(true);
10226
10227 wxRichTextParagraphLayoutBox* container = GetContainer();
10228 wxASSERT(container != NULL);
10229 if (!container)
10230 return false;
10231
10232 switch (m_cmdId)
10233 {
10234 case wxRICHTEXT_INSERT:
10235 {
10236 wxArrayInt optimizationLineCharPositions;
10237 wxArrayInt optimizationLineYPositions;
10238
10239 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
10240 CalculateRefreshOptimizations(optimizationLineCharPositions, optimizationLineYPositions);
10241 #endif
10242
10243 container->DeleteRange(GetRange());
10244 container->UpdateRanges();
10245
10246 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
10247 // Layout() would stop prematurely at the top level.
10248 container->InvalidateHierarchy(wxRichTextRange(GetRange().GetStart(), GetRange().GetStart()));
10249
10250 long newCaretPosition = GetPosition() - 1;
10251
10252 UpdateAppearance(newCaretPosition, true, /* send update event */ & optimizationLineCharPositions, & optimizationLineYPositions, false /* undo */);
10253
10254 wxRichTextEvent cmdEvent(
10255 wxEVT_COMMAND_RICHTEXT_CONTENT_DELETED,
10256 m_ctrl ? m_ctrl->GetId() : -1);
10257 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
10258 cmdEvent.SetRange(GetRange());
10259 cmdEvent.SetPosition(GetRange().GetStart());
10260 cmdEvent.SetContainer(container);
10261
10262 m_buffer->SendEvent(cmdEvent);
10263
10264 break;
10265 }
10266 case wxRICHTEXT_DELETE:
10267 {
10268 wxArrayInt optimizationLineCharPositions;
10269 wxArrayInt optimizationLineYPositions;
10270
10271 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
10272 CalculateRefreshOptimizations(optimizationLineCharPositions, optimizationLineYPositions);
10273 #endif
10274
10275 container->InsertFragment(GetRange().GetStart(), m_oldParagraphs);
10276 container->UpdateRanges();
10277
10278 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
10279 // Layout() would stop prematurely at the top level.
10280 container->InvalidateHierarchy(GetRange());
10281
10282 UpdateAppearance(GetPosition(), true, /* send update event */ & optimizationLineCharPositions, & optimizationLineYPositions, false /* undo */);
10283
10284 wxRichTextEvent cmdEvent(
10285 wxEVT_COMMAND_RICHTEXT_CONTENT_INSERTED,
10286 m_ctrl ? m_ctrl->GetId() : -1);
10287 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
10288 cmdEvent.SetRange(GetRange());
10289 cmdEvent.SetPosition(GetRange().GetStart());
10290 cmdEvent.SetContainer(container);
10291
10292 m_buffer->SendEvent(cmdEvent);
10293
10294 break;
10295 }
10296 case wxRICHTEXT_CHANGE_STYLE:
10297 case wxRICHTEXT_CHANGE_PROPERTIES:
10298 {
10299 ApplyParagraphs(GetOldParagraphs());
10300 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
10301 // Layout() would stop prematurely at the top level.
10302 container->InvalidateHierarchy(GetRange());
10303
10304 UpdateAppearance(GetPosition());
10305
10306 wxRichTextEvent cmdEvent(
10307 m_cmdId == wxRICHTEXT_CHANGE_STYLE ? wxEVT_COMMAND_RICHTEXT_STYLE_CHANGED : wxEVT_COMMAND_RICHTEXT_PROPERTIES_CHANGED,
10308 m_ctrl ? m_ctrl->GetId() : -1);
10309 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
10310 cmdEvent.SetRange(GetRange());
10311 cmdEvent.SetPosition(GetRange().GetStart());
10312 cmdEvent.SetContainer(container);
10313
10314 m_buffer->SendEvent(cmdEvent);
10315
10316 break;
10317 }
10318 case wxRICHTEXT_CHANGE_ATTRIBUTES:
10319 case wxRICHTEXT_CHANGE_OBJECT:
10320 {
10321 return Do();
10322 }
10323 default:
10324 break;
10325 }
10326
10327 return true;
10328 }
10329
10330 /// Update the control appearance
10331 void wxRichTextAction::UpdateAppearance(long caretPosition, bool sendUpdateEvent, wxArrayInt* optimizationLineCharPositions, wxArrayInt* optimizationLineYPositions, bool isDoCmd)
10332 {
10333 wxRichTextParagraphLayoutBox* container = GetContainer();
10334 wxASSERT(container != NULL);
10335 if (!container)
10336 return;
10337
10338 if (m_ctrl)
10339 {
10340 m_ctrl->SetFocusObject(container);
10341 m_ctrl->SetCaretPosition(caretPosition);
10342
10343 if (!m_ctrl->IsFrozen())
10344 {
10345 wxRect containerRect = container->GetRect();
10346
10347 m_ctrl->LayoutContent();
10348
10349 // Refresh everything if there were floating objects or the container changed size
10350 // (we can't yet optimize in these cases, since more complex interaction with other content occurs)
10351 if (container->GetFloatingObjectCount() > 0 || (container->GetParent() && containerRect != container->GetRect()))
10352 {
10353 m_ctrl->Refresh(false);
10354 }
10355 else
10356
10357 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
10358 // Find refresh rectangle if we are in a position to optimise refresh
10359 if ((m_cmdId == wxRICHTEXT_INSERT || m_cmdId == wxRICHTEXT_DELETE) && optimizationLineCharPositions)
10360 {
10361 size_t i;
10362
10363 wxSize clientSize = m_ctrl->GetClientSize();
10364 wxPoint firstVisiblePt = m_ctrl->GetFirstVisiblePoint();
10365
10366 // Start/end positions
10367 int firstY = 0;
10368 int lastY = firstVisiblePt.y + clientSize.y;
10369
10370 bool foundEnd = false;
10371
10372 // position offset - how many characters were inserted
10373 int positionOffset = GetRange().GetLength();
10374
10375 // Determine whether this is Do or Undo, and adjust positionOffset accordingly
10376 if ((m_cmdId == wxRICHTEXT_DELETE && isDoCmd) || (m_cmdId == wxRICHTEXT_INSERT && !isDoCmd))
10377 positionOffset = - positionOffset;
10378
10379 // find the first line which is being drawn at the same position as it was
10380 // before. Since we're talking about a simple insertion, we can assume
10381 // that the rest of the window does not need to be redrawn.
10382
10383 wxRichTextParagraph* para = container->GetParagraphAtPosition(GetPosition());
10384 // Since we support floating layout, we should redraw the whole para instead of just
10385 // the first line touching the invalid range.
10386 if (para)
10387 {
10388 firstY = para->GetPosition().y;
10389 }
10390
10391 wxRichTextObjectList::compatibility_iterator node = container->GetChildren().Find(para);
10392 while (node)
10393 {
10394 wxRichTextParagraph* child = (wxRichTextParagraph*) node->GetData();
10395 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
10396 while (node2)
10397 {
10398 wxRichTextLine* line = node2->GetData();
10399 wxPoint pt = line->GetAbsolutePosition();
10400 wxRichTextRange range = line->GetAbsoluteRange();
10401
10402 // we want to find the first line that is in the same position
10403 // as before. This will mean we're at the end of the changed text.
10404
10405 if (pt.y > lastY) // going past the end of the window, no more info
10406 {
10407 node2 = wxRichTextLineList::compatibility_iterator();
10408 node = wxRichTextObjectList::compatibility_iterator();
10409 }
10410 // Detect last line in the buffer
10411 else if (!node2->GetNext() && para->GetRange().Contains(container->GetOwnRange().GetEnd()))
10412 {
10413 // If deleting text, make sure we refresh below as well as above
10414 if (positionOffset >= 0)
10415 {
10416 foundEnd = true;
10417 lastY = pt.y + line->GetSize().y;
10418 }
10419
10420 node2 = wxRichTextLineList::compatibility_iterator();
10421 node = wxRichTextObjectList::compatibility_iterator();
10422
10423 break;
10424 }
10425 else
10426 {
10427 // search for this line being at the same position as before
10428 for (i = 0; i < optimizationLineCharPositions->GetCount(); i++)
10429 {
10430 if (((*optimizationLineCharPositions)[i] + positionOffset == range.GetStart()) &&
10431 ((*optimizationLineYPositions)[i] == pt.y))
10432 {
10433 // Stop, we're now the same as we were
10434 foundEnd = true;
10435
10436 lastY = pt.y;
10437
10438 node2 = wxRichTextLineList::compatibility_iterator();
10439 node = wxRichTextObjectList::compatibility_iterator();
10440
10441 break;
10442 }
10443 }
10444 }
10445
10446 if (node2)
10447 node2 = node2->GetNext();
10448 }
10449
10450 if (node)
10451 node = node->GetNext();
10452 }
10453
10454 firstY = wxMax(firstVisiblePt.y, firstY);
10455 if (!foundEnd)
10456 lastY = firstVisiblePt.y + clientSize.y;
10457
10458 // Convert to device coordinates
10459 wxRect rect(m_ctrl->GetPhysicalPoint(wxPoint(firstVisiblePt.x, firstY)), wxSize(clientSize.x, lastY - firstY));
10460 m_ctrl->RefreshRect(rect);
10461 }
10462 else
10463 #endif
10464 m_ctrl->Refresh(false);
10465
10466 m_ctrl->PositionCaret();
10467
10468 // This causes styles to persist when doing programmatic
10469 // content creation except when Freeze/Thaw is used, so
10470 // disable this and check for the consequences.
10471 // m_ctrl->SetDefaultStyleToCursorStyle();
10472
10473 if (sendUpdateEvent)
10474 wxTextCtrl::SendTextUpdatedEvent(m_ctrl);
10475 }
10476 }
10477 }
10478
10479 /// Replace the buffer paragraphs with the new ones.
10480 void wxRichTextAction::ApplyParagraphs(const wxRichTextParagraphLayoutBox& fragment)
10481 {
10482 wxRichTextParagraphLayoutBox* container = GetContainer();
10483 wxASSERT(container != NULL);
10484 if (!container)
10485 return;
10486
10487 wxRichTextObjectList::compatibility_iterator node = fragment.GetChildren().GetFirst();
10488 while (node)
10489 {
10490 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
10491 wxASSERT (para != NULL);
10492
10493 // We'll replace the existing paragraph by finding the paragraph at this position,
10494 // delete its node data, and setting a copy as the new node data.
10495 // TODO: make more efficient by simply swapping old and new paragraph objects.
10496
10497 wxRichTextParagraph* existingPara = container->GetParagraphAtPosition(para->GetRange().GetStart());
10498 if (existingPara)
10499 {
10500 wxRichTextObjectList::compatibility_iterator bufferParaNode = container->GetChildren().Find(existingPara);
10501 if (bufferParaNode)
10502 {
10503 wxRichTextParagraph* newPara = new wxRichTextParagraph(*para);
10504 newPara->SetParent(container);
10505
10506 bufferParaNode->SetData(newPara);
10507
10508 delete existingPara;
10509 }
10510 }
10511
10512 node = node->GetNext();
10513 }
10514 }
10515
10516
10517 /*!
10518 * wxRichTextRange
10519 * This stores beginning and end positions for a range of data.
10520 */
10521
10522 WX_DEFINE_OBJARRAY(wxRichTextRangeArray);
10523
10524 /// Limit this range to be within 'range'
10525 bool wxRichTextRange::LimitTo(const wxRichTextRange& range)
10526 {
10527 if (m_start < range.m_start)
10528 m_start = range.m_start;
10529
10530 if (m_end > range.m_end)
10531 m_end = range.m_end;
10532
10533 return true;
10534 }
10535
10536 /*!
10537 * wxRichTextImage implementation
10538 * This object represents an image.
10539 */
10540
10541 IMPLEMENT_DYNAMIC_CLASS(wxRichTextImage, wxRichTextObject)
10542
10543 wxRichTextImage::wxRichTextImage(const wxImage& image, wxRichTextObject* parent, wxRichTextAttr* charStyle):
10544 wxRichTextObject(parent)
10545 {
10546 Init();
10547 m_imageBlock.MakeImageBlockDefaultQuality(image, wxBITMAP_TYPE_PNG);
10548 if (charStyle)
10549 SetAttributes(*charStyle);
10550 }
10551
10552 wxRichTextImage::wxRichTextImage(const wxRichTextImageBlock& imageBlock, wxRichTextObject* parent, wxRichTextAttr* charStyle):
10553 wxRichTextObject(parent)
10554 {
10555 Init();
10556 m_imageBlock = imageBlock;
10557 if (charStyle)
10558 SetAttributes(*charStyle);
10559 }
10560
10561 void wxRichTextImage::Init()
10562 {
10563 m_originalImageSize = wxSize(-1, -1);
10564 }
10565
10566 /// Create a cached image at the required size
10567 bool wxRichTextImage::LoadImageCache(wxDC& dc, bool resetCache)
10568 {
10569 if (!m_imageBlock.IsOk())
10570 return false;
10571
10572 // If we have an original image size, use that to compute the cached bitmap size
10573 // instead of loading the image each time. This way we can avoid loading
10574 // the image so long as the new cached bitmap size hasn't changed.
10575
10576 wxImage image;
10577 if (resetCache || m_originalImageSize == wxSize(-1, -1))
10578 {
10579 m_imageCache = wxNullBitmap;
10580
10581 m_imageBlock.Load(image);
10582 if (!image.IsOk())
10583 return false;
10584
10585 m_originalImageSize = wxSize(image.GetWidth(), image.GetHeight());
10586 }
10587
10588 int width = m_originalImageSize.GetWidth();
10589 int height = m_originalImageSize.GetHeight();
10590
10591 int parentWidth = 0;
10592 int parentHeight = 0;
10593
10594 int maxWidth = -1;
10595 int maxHeight = -1;
10596
10597 wxRichTextBuffer* buffer = GetBuffer();
10598 if (buffer)
10599 {
10600 wxSize sz;
10601 if (buffer->GetRichTextCtrl())
10602 {
10603 // Subtract borders
10604 sz = buffer->GetRichTextCtrl()->GetClientSize();
10605
10606 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
10607 marginRect = wxRect(0, 0, sz.x, sz.y);
10608 buffer->GetBoxRects(dc, buffer, buffer->GetAttributes(), marginRect, borderRect, contentRect, paddingRect, outlineRect);
10609
10610 sz = contentRect.GetSize();
10611
10612 // Start with a maximum width of the control size, even if not specified by the content,
10613 // to minimize the amount of picture overlapping the right-hand side
10614 maxWidth = sz.x;
10615 }
10616 else
10617 sz = buffer->GetCachedSize();
10618 parentWidth = sz.GetWidth();
10619 parentHeight = sz.GetHeight();
10620 }
10621
10622 if (GetAttributes().GetTextBoxAttr().GetWidth().IsValid() && GetAttributes().GetTextBoxAttr().GetWidth().GetValue() > 0)
10623 {
10624 if (parentWidth > 0 && GetAttributes().GetTextBoxAttr().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE)
10625 width = (int) ((GetAttributes().GetTextBoxAttr().GetWidth().GetValue() * parentWidth)/100.0);
10626 else if (GetAttributes().GetTextBoxAttr().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
10627 width = ConvertTenthsMMToPixels(dc, GetAttributes().GetTextBoxAttr().GetWidth().GetValue());
10628 else if (GetAttributes().GetTextBoxAttr().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_PIXELS)
10629 width = GetAttributes().GetTextBoxAttr().GetWidth().GetValue();
10630 }
10631
10632 // Limit to max width
10633
10634 if (GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().IsValid() && GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetValue() > 0)
10635 {
10636 int mw = -1;
10637
10638 if (parentWidth > 0 && GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE)
10639 mw = (int) ((GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetValue() * parentWidth)/100.0);
10640 else if (GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
10641 mw = ConvertTenthsMMToPixels(dc, GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetValue());
10642 else if (GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_PIXELS)
10643 mw = GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetValue();
10644
10645 // If we already have a smaller max width due to the constraints of the control size,
10646 // don't use the larger max width.
10647 if (mw != -1 && ((maxWidth == -1) || (mw < maxWidth)))
10648 maxWidth = mw;
10649 }
10650
10651 if (maxWidth > 0 && width > maxWidth)
10652 width = maxWidth;
10653
10654 // Preserve the aspect ratio
10655 if (width != m_originalImageSize.GetWidth())
10656 height = (int) (float(m_originalImageSize.GetHeight()) * (float(width)/float(m_originalImageSize.GetWidth())));
10657
10658 if (GetAttributes().GetTextBoxAttr().GetHeight().IsValid() && GetAttributes().GetTextBoxAttr().GetHeight().GetValue() > 0)
10659 {
10660 if (parentHeight > 0 && GetAttributes().GetTextBoxAttr().GetHeight().GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE)
10661 height = (int) ((GetAttributes().GetTextBoxAttr().GetHeight().GetValue() * parentHeight)/100.0);
10662 else if (GetAttributes().GetTextBoxAttr().GetHeight().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
10663 height = ConvertTenthsMMToPixels(dc, GetAttributes().GetTextBoxAttr().GetHeight().GetValue());
10664 else if (GetAttributes().GetTextBoxAttr().GetHeight().GetUnits() == wxTEXT_ATTR_UNITS_PIXELS)
10665 height = GetAttributes().GetTextBoxAttr().GetHeight().GetValue();
10666
10667 // Preserve the aspect ratio
10668 if (height != m_originalImageSize.GetHeight())
10669 width = (int) (float(m_originalImageSize.GetWidth()) * (float(height)/float(m_originalImageSize.GetHeight())));
10670 }
10671
10672 // Limit to max height
10673
10674 if (GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().IsValid() && GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetValue() > 0)
10675 {
10676 if (parentHeight > 0 && GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE)
10677 maxHeight = (int) ((GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetValue() * parentHeight)/100.0);
10678 else if (GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
10679 maxHeight = ConvertTenthsMMToPixels(dc, GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetValue());
10680 else if (GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetUnits() == wxTEXT_ATTR_UNITS_PIXELS)
10681 maxHeight = GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetValue();
10682 }
10683
10684 if (maxHeight > 0 && height > maxHeight)
10685 {
10686 height = maxHeight;
10687
10688 // Preserve the aspect ratio
10689 if (height != m_originalImageSize.GetHeight())
10690 width = (int) (float(m_originalImageSize.GetWidth()) * (float(height)/float(m_originalImageSize.GetHeight())));
10691 }
10692
10693 if (m_imageCache.IsOk() && m_imageCache.GetWidth() == width && m_imageCache.GetHeight() == height)
10694 {
10695 // Do nothing, we didn't need to change the image cache
10696 }
10697 else
10698 {
10699 if (!image.IsOk())
10700 {
10701 m_imageBlock.Load(image);
10702 if (!image.IsOk())
10703 return false;
10704 }
10705
10706 if (image.GetWidth() == width && image.GetHeight() == height)
10707 m_imageCache = wxBitmap(image);
10708 else
10709 {
10710 // If the original width and height is small, e.g. 400 or below,
10711 // scale up and then down to improve image quality. This can make
10712 // a big difference, with not much performance hit.
10713 int upscaleThreshold = 400;
10714 wxImage img;
10715 if (image.GetWidth() <= upscaleThreshold || image.GetHeight() <= upscaleThreshold)
10716 {
10717 img = image.Scale(image.GetWidth()*2, image.GetHeight()*2);
10718 img.Rescale(width, height, wxIMAGE_QUALITY_HIGH);
10719 }
10720 else
10721 img = image.Scale(width, height, wxIMAGE_QUALITY_HIGH);
10722 m_imageCache = wxBitmap(img);
10723 }
10724 }
10725
10726 return m_imageCache.IsOk();
10727 }
10728
10729 /// Draw the item
10730 bool wxRichTextImage::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& WXUNUSED(range), const wxRichTextSelection& selection, const wxRect& rect, int WXUNUSED(descent), int WXUNUSED(style))
10731 {
10732 if (!IsShown())
10733 return true;
10734
10735 // Don't need cached size AFAIK
10736 // wxSize size = GetCachedSize();
10737 if (!LoadImageCache(dc))
10738 return false;
10739
10740 wxRichTextAttr attr(GetAttributes());
10741 context.ApplyVirtualAttributes(attr, this);
10742
10743 DrawBoxAttributes(dc, GetBuffer(), attr, wxRect(rect.GetPosition(), GetCachedSize()));
10744
10745 wxSize imageSize(m_imageCache.GetWidth(), m_imageCache.GetHeight());
10746 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
10747 marginRect = rect; // outer rectangle, will calculate contentRect
10748 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
10749
10750 dc.DrawBitmap(m_imageCache, contentRect.x, contentRect.y, true);
10751
10752 if (selection.WithinSelection(GetRange().GetStart(), this))
10753 {
10754 wxCheckSetBrush(dc, *wxBLACK_BRUSH);
10755 wxCheckSetPen(dc, *wxBLACK_PEN);
10756 dc.SetLogicalFunction(wxINVERT);
10757 dc.DrawRectangle(contentRect);
10758 dc.SetLogicalFunction(wxCOPY);
10759 }
10760
10761 return true;
10762 }
10763
10764 /// Lay the item out
10765 bool wxRichTextImage::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& WXUNUSED(parentRect), int WXUNUSED(style))
10766 {
10767 if (!LoadImageCache(dc))
10768 return false;
10769
10770 wxSize imageSize(m_imageCache.GetWidth(), m_imageCache.GetHeight());
10771 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
10772 contentRect = wxRect(wxPoint(0,0), imageSize);
10773
10774 wxRichTextAttr attr(GetAttributes());
10775 context.ApplyVirtualAttributes(attr, this);
10776
10777 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
10778
10779 wxSize overallSize = marginRect.GetSize();
10780
10781 SetCachedSize(overallSize);
10782 SetMaxSize(overallSize);
10783 SetMinSize(overallSize);
10784 SetPosition(rect.GetPosition());
10785
10786 return true;
10787 }
10788
10789 /// Get/set the object size for the given range. Returns false if the range
10790 /// is invalid for this object.
10791 bool wxRichTextImage::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& WXUNUSED(descent), wxDC& dc, wxRichTextDrawingContext& context, int WXUNUSED(flags), wxPoint WXUNUSED(position), wxArrayInt* partialExtents) const
10792 {
10793 if (!range.IsWithin(GetRange()))
10794 return false;
10795
10796 if (!((wxRichTextImage*)this)->LoadImageCache(dc))
10797 {
10798 size.x = 0; size.y = 0;
10799 if (partialExtents)
10800 partialExtents->Add(0);
10801 return false;
10802 }
10803
10804 wxRichTextAttr attr(GetAttributes());
10805 context.ApplyVirtualAttributes(attr, (wxRichTextObject*) this);
10806
10807 wxSize imageSize(m_imageCache.GetWidth(), m_imageCache.GetHeight());
10808 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
10809 contentRect = wxRect(wxPoint(0,0), imageSize);
10810 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
10811
10812 wxSize overallSize = marginRect.GetSize();
10813
10814 if (partialExtents)
10815 partialExtents->Add(overallSize.x);
10816
10817 size = overallSize;
10818
10819 return true;
10820 }
10821
10822 // Get the 'natural' size for an object. For an image, it would be the
10823 // image size.
10824 wxTextAttrSize wxRichTextImage::GetNaturalSize() const
10825 {
10826 wxTextAttrSize size;
10827 if (GetImageCache().IsOk())
10828 {
10829 size.SetWidth(GetImageCache().GetWidth(), wxTEXT_ATTR_UNITS_PIXELS);
10830 size.SetHeight(GetImageCache().GetHeight(), wxTEXT_ATTR_UNITS_PIXELS);
10831 }
10832 return size;
10833 }
10834
10835
10836 /// Copy
10837 void wxRichTextImage::Copy(const wxRichTextImage& obj)
10838 {
10839 wxRichTextObject::Copy(obj);
10840
10841 m_imageBlock = obj.m_imageBlock;
10842 m_originalImageSize = obj.m_originalImageSize;
10843 }
10844
10845 /// Edit properties via a GUI
10846 bool wxRichTextImage::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer)
10847 {
10848 wxRichTextObjectPropertiesDialog imageDlg(this, wxGetTopLevelParent(parent), wxID_ANY, _("Picture Properties"));
10849 imageDlg.SetAttributes(GetAttributes());
10850
10851 if (imageDlg.ShowModal() == wxID_OK)
10852 {
10853 // By passing wxRICHTEXT_SETSTYLE_RESET, indeterminate attributes set by the user will be set as
10854 // indeterminate in the object.
10855 imageDlg.ApplyStyle(buffer->GetRichTextCtrl(), wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_RESET);
10856 return true;
10857 }
10858 else
10859 return false;
10860 }
10861
10862 /*!
10863 * Utilities
10864 *
10865 */
10866
10867 /// Compare two attribute objects
10868 bool wxTextAttrEq(const wxRichTextAttr& attr1, const wxRichTextAttr& attr2)
10869 {
10870 return (attr1 == attr2);
10871 }
10872
10873 // Partial equality test taking flags into account
10874 bool wxTextAttrEqPartial(const wxRichTextAttr& attr1, const wxRichTextAttr& attr2)
10875 {
10876 return attr1.EqPartial(attr2);
10877 }
10878
10879 /// Compare tabs
10880 bool wxRichTextTabsEq(const wxArrayInt& tabs1, const wxArrayInt& tabs2)
10881 {
10882 if (tabs1.GetCount() != tabs2.GetCount())
10883 return false;
10884
10885 size_t i;
10886 for (i = 0; i < tabs1.GetCount(); i++)
10887 {
10888 if (tabs1[i] != tabs2[i])
10889 return false;
10890 }
10891 return true;
10892 }
10893
10894 bool wxRichTextApplyStyle(wxRichTextAttr& destStyle, const wxRichTextAttr& style, wxRichTextAttr* compareWith)
10895 {
10896 return destStyle.Apply(style, compareWith);
10897 }
10898
10899 // Remove attributes
10900 bool wxRichTextRemoveStyle(wxRichTextAttr& destStyle, const wxRichTextAttr& style)
10901 {
10902 return destStyle.RemoveStyle(style);
10903 }
10904
10905 /// Combine two bitlists, specifying the bits of interest with separate flags.
10906 bool wxRichTextCombineBitlists(int& valueA, int valueB, int& flagsA, int flagsB)
10907 {
10908 return wxRichTextAttr::CombineBitlists(valueA, valueB, flagsA, flagsB);
10909 }
10910
10911 /// Compare two bitlists
10912 bool wxRichTextBitlistsEqPartial(int valueA, int valueB, int flags)
10913 {
10914 return wxRichTextAttr::BitlistsEqPartial(valueA, valueB, flags);
10915 }
10916
10917 /// Split into paragraph and character styles
10918 bool wxRichTextSplitParaCharStyles(const wxRichTextAttr& style, wxRichTextAttr& parStyle, wxRichTextAttr& charStyle)
10919 {
10920 return wxRichTextAttr::SplitParaCharStyles(style, parStyle, charStyle);
10921 }
10922
10923 /// Convert a decimal to Roman numerals
10924 wxString wxRichTextDecimalToRoman(long n)
10925 {
10926 static wxArrayInt decimalNumbers;
10927 static wxArrayString romanNumbers;
10928
10929 // Clean up arrays
10930 if (n == -1)
10931 {
10932 decimalNumbers.Clear();
10933 romanNumbers.Clear();
10934 return wxEmptyString;
10935 }
10936
10937 if (decimalNumbers.GetCount() == 0)
10938 {
10939 #define wxRichTextAddDecRom(n, r) decimalNumbers.Add(n); romanNumbers.Add(r);
10940
10941 wxRichTextAddDecRom(1000, wxT("M"));
10942 wxRichTextAddDecRom(900, wxT("CM"));
10943 wxRichTextAddDecRom(500, wxT("D"));
10944 wxRichTextAddDecRom(400, wxT("CD"));
10945 wxRichTextAddDecRom(100, wxT("C"));
10946 wxRichTextAddDecRom(90, wxT("XC"));
10947 wxRichTextAddDecRom(50, wxT("L"));
10948 wxRichTextAddDecRom(40, wxT("XL"));
10949 wxRichTextAddDecRom(10, wxT("X"));
10950 wxRichTextAddDecRom(9, wxT("IX"));
10951 wxRichTextAddDecRom(5, wxT("V"));
10952 wxRichTextAddDecRom(4, wxT("IV"));
10953 wxRichTextAddDecRom(1, wxT("I"));
10954 }
10955
10956 int i = 0;
10957 wxString roman;
10958
10959 while (n > 0 && i < 13)
10960 {
10961 if (n >= decimalNumbers[i])
10962 {
10963 n -= decimalNumbers[i];
10964 roman += romanNumbers[i];
10965 }
10966 else
10967 {
10968 i ++;
10969 }
10970 }
10971 if (roman.IsEmpty())
10972 roman = wxT("0");
10973 return roman;
10974 }
10975
10976 /*!
10977 * wxRichTextFileHandler
10978 * Base class for file handlers
10979 */
10980
10981 IMPLEMENT_CLASS(wxRichTextFileHandler, wxObject)
10982
10983 #if wxUSE_FFILE && wxUSE_STREAMS
10984 bool wxRichTextFileHandler::LoadFile(wxRichTextBuffer *buffer, const wxString& filename)
10985 {
10986 wxFFileInputStream stream(filename);
10987 if (stream.IsOk())
10988 return LoadFile(buffer, stream);
10989
10990 return false;
10991 }
10992
10993 bool wxRichTextFileHandler::SaveFile(wxRichTextBuffer *buffer, const wxString& filename)
10994 {
10995 wxFFileOutputStream stream(filename);
10996 if (stream.IsOk())
10997 return SaveFile(buffer, stream);
10998
10999 return false;
11000 }
11001 #endif // wxUSE_FFILE && wxUSE_STREAMS
11002
11003 /// Can we handle this filename (if using files)? By default, checks the extension.
11004 bool wxRichTextFileHandler::CanHandle(const wxString& filename) const
11005 {
11006 wxString path, file, ext;
11007 wxFileName::SplitPath(filename, & path, & file, & ext);
11008
11009 return (ext.Lower() == GetExtension());
11010 }
11011
11012 /*!
11013 * wxRichTextTextHandler
11014 * Plain text handler
11015 */
11016
11017 IMPLEMENT_CLASS(wxRichTextPlainTextHandler, wxRichTextFileHandler)
11018
11019 #if wxUSE_STREAMS
11020 bool wxRichTextPlainTextHandler::DoLoadFile(wxRichTextBuffer *buffer, wxInputStream& stream)
11021 {
11022 if (!stream.IsOk())
11023 return false;
11024
11025 wxString str;
11026 int lastCh = 0;
11027
11028 while (!stream.Eof())
11029 {
11030 int ch = stream.GetC();
11031
11032 if (!stream.Eof())
11033 {
11034 if (ch == 10 && lastCh != 13)
11035 str += wxT('\n');
11036
11037 if (ch > 0 && ch != 10)
11038 str += wxChar(ch);
11039
11040 lastCh = ch;
11041 }
11042 }
11043
11044 buffer->ResetAndClearCommands();
11045 buffer->Clear();
11046 buffer->AddParagraphs(str);
11047 buffer->UpdateRanges();
11048
11049 return true;
11050 }
11051
11052 bool wxRichTextPlainTextHandler::DoSaveFile(wxRichTextBuffer *buffer, wxOutputStream& stream)
11053 {
11054 if (!stream.IsOk())
11055 return false;
11056
11057 wxString text = buffer->GetText();
11058
11059 wxString newLine = wxRichTextLineBreakChar;
11060 text.Replace(newLine, wxT("\n"));
11061
11062 wxCharBuffer buf = text.ToAscii();
11063
11064 stream.Write((const char*) buf, text.length());
11065 return true;
11066 }
11067 #endif // wxUSE_STREAMS
11068
11069 /*
11070 * Stores information about an image, in binary in-memory form
11071 */
11072
11073 wxRichTextImageBlock::wxRichTextImageBlock()
11074 {
11075 Init();
11076 }
11077
11078 wxRichTextImageBlock::wxRichTextImageBlock(const wxRichTextImageBlock& block):wxObject()
11079 {
11080 Init();
11081 Copy(block);
11082 }
11083
11084 wxRichTextImageBlock::~wxRichTextImageBlock()
11085 {
11086 wxDELETEA(m_data);
11087 }
11088
11089 void wxRichTextImageBlock::Init()
11090 {
11091 m_data = NULL;
11092 m_dataSize = 0;
11093 m_imageType = wxBITMAP_TYPE_INVALID;
11094 }
11095
11096 void wxRichTextImageBlock::Clear()
11097 {
11098 wxDELETEA(m_data);
11099 m_dataSize = 0;
11100 m_imageType = wxBITMAP_TYPE_INVALID;
11101 }
11102
11103
11104 // Load the original image into a memory block.
11105 // If the image is not a JPEG, we must convert it into a JPEG
11106 // to conserve space.
11107 // If it's not a JPEG we can make use of 'image', already scaled, so we don't have to
11108 // load the image a 2nd time.
11109
11110 bool wxRichTextImageBlock::MakeImageBlock(const wxString& filename, wxBitmapType imageType,
11111 wxImage& image, bool convertToJPEG)
11112 {
11113 m_imageType = imageType;
11114
11115 wxString filenameToRead(filename);
11116 bool removeFile = false;
11117
11118 if (imageType == wxBITMAP_TYPE_INVALID)
11119 return false; // Could not determine image type
11120
11121 if ((imageType != wxBITMAP_TYPE_JPEG) && convertToJPEG)
11122 {
11123 wxString tempFile =
11124 wxFileName::CreateTempFileName(_("image"));
11125
11126 wxASSERT(!tempFile.IsEmpty());
11127
11128 image.SaveFile(tempFile, wxBITMAP_TYPE_JPEG);
11129 filenameToRead = tempFile;
11130 removeFile = true;
11131
11132 m_imageType = wxBITMAP_TYPE_JPEG;
11133 }
11134 wxFile file;
11135 if (!file.Open(filenameToRead))
11136 return false;
11137
11138 m_dataSize = (size_t) file.Length();
11139 file.Close();
11140
11141 if (m_data)
11142 delete[] m_data;
11143 m_data = ReadBlock(filenameToRead, m_dataSize);
11144
11145 if (removeFile)
11146 wxRemoveFile(filenameToRead);
11147
11148 return (m_data != NULL);
11149 }
11150
11151 // Make an image block from the wxImage in the given
11152 // format.
11153 bool wxRichTextImageBlock::MakeImageBlock(wxImage& image, wxBitmapType imageType, int quality)
11154 {
11155 image.SetOption(wxT("quality"), quality);
11156
11157 if (imageType == wxBITMAP_TYPE_INVALID)
11158 return false; // Could not determine image type
11159
11160 return DoMakeImageBlock(image, imageType);
11161 }
11162
11163 // Uses a const wxImage for efficiency, but can't set quality (only relevant for JPEG)
11164 bool wxRichTextImageBlock::MakeImageBlockDefaultQuality(const wxImage& image, wxBitmapType imageType)
11165 {
11166 if (imageType == wxBITMAP_TYPE_INVALID)
11167 return false; // Could not determine image type
11168
11169 return DoMakeImageBlock(image, imageType);
11170 }
11171
11172 // Makes the image block
11173 bool wxRichTextImageBlock::DoMakeImageBlock(const wxImage& image, wxBitmapType imageType)
11174 {
11175 wxMemoryOutputStream memStream;
11176 if (!image.SaveFile(memStream, imageType))
11177 {
11178 return false;
11179 }
11180
11181 unsigned char* block = new unsigned char[memStream.GetSize()];
11182 if (!block)
11183 return false;
11184
11185 if (m_data)
11186 delete[] m_data;
11187 m_data = block;
11188
11189 m_imageType = imageType;
11190 m_dataSize = memStream.GetSize();
11191
11192 memStream.CopyTo(m_data, m_dataSize);
11193
11194 return (m_data != NULL);
11195 }
11196
11197 // Write to a file
11198 bool wxRichTextImageBlock::Write(const wxString& filename)
11199 {
11200 return WriteBlock(filename, m_data, m_dataSize);
11201 }
11202
11203 void wxRichTextImageBlock::Copy(const wxRichTextImageBlock& block)
11204 {
11205 m_imageType = block.m_imageType;
11206 wxDELETEA(m_data);
11207 m_dataSize = block.m_dataSize;
11208 if (m_dataSize == 0)
11209 return;
11210
11211 m_data = new unsigned char[m_dataSize];
11212 unsigned int i;
11213 for (i = 0; i < m_dataSize; i++)
11214 m_data[i] = block.m_data[i];
11215 }
11216
11217 //// Operators
11218 void wxRichTextImageBlock::operator=(const wxRichTextImageBlock& block)
11219 {
11220 Copy(block);
11221 }
11222
11223 // Load a wxImage from the block
11224 bool wxRichTextImageBlock::Load(wxImage& image)
11225 {
11226 if (!m_data)
11227 return false;
11228
11229 // Read in the image.
11230 #if wxUSE_STREAMS
11231 wxMemoryInputStream mstream(m_data, m_dataSize);
11232 bool success = image.LoadFile(mstream, GetImageType());
11233 #else
11234 wxString tempFile = wxFileName::CreateTempFileName(_("image"));
11235 wxASSERT(!tempFile.IsEmpty());
11236
11237 if (!WriteBlock(tempFile, m_data, m_dataSize))
11238 {
11239 return false;
11240 }
11241 success = image.LoadFile(tempFile, GetImageType());
11242 wxRemoveFile(tempFile);
11243 #endif
11244
11245 return success;
11246 }
11247
11248 // Write data in hex to a stream
11249 bool wxRichTextImageBlock::WriteHex(wxOutputStream& stream)
11250 {
11251 if (m_dataSize == 0)
11252 return true;
11253
11254 int bufSize = 100000;
11255 if (int(2*m_dataSize) < bufSize)
11256 bufSize = 2*m_dataSize;
11257 char* buf = new char[bufSize+1];
11258
11259 int left = m_dataSize;
11260 int n, i, j;
11261 j = 0;
11262 while (left > 0)
11263 {
11264 if (left*2 > bufSize)
11265 {
11266 n = bufSize; left -= (bufSize/2);
11267 }
11268 else
11269 {
11270 n = left*2; left = 0;
11271 }
11272
11273 char* b = buf;
11274 for (i = 0; i < (n/2); i++)
11275 {
11276 wxDecToHex(m_data[j], b, b+1);
11277 b += 2; j ++;
11278 }
11279
11280 buf[n] = 0;
11281 stream.Write((const char*) buf, n);
11282 }
11283 delete[] buf;
11284 return true;
11285 }
11286
11287 // Read data in hex from a stream
11288 bool wxRichTextImageBlock::ReadHex(wxInputStream& stream, int length, wxBitmapType imageType)
11289 {
11290 int dataSize = length/2;
11291
11292 if (m_data)
11293 delete[] m_data;
11294
11295 // create a null terminated temporary string:
11296 char str[3];
11297 str[2] = '\0';
11298
11299 m_data = new unsigned char[dataSize];
11300 int i;
11301 for (i = 0; i < dataSize; i ++)
11302 {
11303 str[0] = (char)stream.GetC();
11304 str[1] = (char)stream.GetC();
11305
11306 m_data[i] = (unsigned char)wxHexToDec(str);
11307 }
11308
11309 m_dataSize = dataSize;
11310 m_imageType = imageType;
11311
11312 return true;
11313 }
11314
11315 // Allocate and read from stream as a block of memory
11316 unsigned char* wxRichTextImageBlock::ReadBlock(wxInputStream& stream, size_t size)
11317 {
11318 unsigned char* block = new unsigned char[size];
11319 if (!block)
11320 return NULL;
11321
11322 stream.Read(block, size);
11323
11324 return block;
11325 }
11326
11327 unsigned char* wxRichTextImageBlock::ReadBlock(const wxString& filename, size_t size)
11328 {
11329 wxFileInputStream stream(filename);
11330 if (!stream.IsOk())
11331 return NULL;
11332
11333 return ReadBlock(stream, size);
11334 }
11335
11336 // Write memory block to stream
11337 bool wxRichTextImageBlock::WriteBlock(wxOutputStream& stream, unsigned char* block, size_t size)
11338 {
11339 stream.Write((void*) block, size);
11340 return stream.IsOk();
11341
11342 }
11343
11344 // Write memory block to file
11345 bool wxRichTextImageBlock::WriteBlock(const wxString& filename, unsigned char* block, size_t size)
11346 {
11347 wxFileOutputStream outStream(filename);
11348 if (!outStream.IsOk())
11349 return false;
11350
11351 return WriteBlock(outStream, block, size);
11352 }
11353
11354 // Gets the extension for the block's type
11355 wxString wxRichTextImageBlock::GetExtension() const
11356 {
11357 wxImageHandler* handler = wxImage::FindHandler(GetImageType());
11358 if (handler)
11359 return handler->GetExtension();
11360 else
11361 return wxEmptyString;
11362 }
11363
11364 #if wxUSE_DATAOBJ
11365
11366 /*!
11367 * The data object for a wxRichTextBuffer
11368 */
11369
11370 const wxChar *wxRichTextBufferDataObject::ms_richTextBufferFormatId = wxT("wxShape");
11371
11372 wxRichTextBufferDataObject::wxRichTextBufferDataObject(wxRichTextBuffer* richTextBuffer)
11373 {
11374 m_richTextBuffer = richTextBuffer;
11375
11376 // this string should uniquely identify our format, but is otherwise
11377 // arbitrary
11378 m_formatRichTextBuffer.SetId(GetRichTextBufferFormatId());
11379
11380 SetFormat(m_formatRichTextBuffer);
11381 }
11382
11383 wxRichTextBufferDataObject::~wxRichTextBufferDataObject()
11384 {
11385 delete m_richTextBuffer;
11386 }
11387
11388 // after a call to this function, the richTextBuffer is owned by the caller and it
11389 // is responsible for deleting it!
11390 wxRichTextBuffer* wxRichTextBufferDataObject::GetRichTextBuffer()
11391 {
11392 wxRichTextBuffer* richTextBuffer = m_richTextBuffer;
11393 m_richTextBuffer = NULL;
11394
11395 return richTextBuffer;
11396 }
11397
11398 wxDataFormat wxRichTextBufferDataObject::GetPreferredFormat(Direction WXUNUSED(dir)) const
11399 {
11400 return m_formatRichTextBuffer;
11401 }
11402
11403 size_t wxRichTextBufferDataObject::GetDataSize() const
11404 {
11405 if (!m_richTextBuffer)
11406 return 0;
11407
11408 wxString bufXML;
11409
11410 {
11411 wxStringOutputStream stream(& bufXML);
11412 if (!m_richTextBuffer->SaveFile(stream, wxRICHTEXT_TYPE_XML))
11413 {
11414 wxLogError(wxT("Could not write the buffer to an XML stream.\nYou may have forgotten to add the XML file handler."));
11415 return 0;
11416 }
11417 }
11418
11419 #if wxUSE_UNICODE
11420 wxCharBuffer buffer = bufXML.mb_str(wxConvUTF8);
11421 return strlen(buffer) + 1;
11422 #else
11423 return bufXML.Length()+1;
11424 #endif
11425 }
11426
11427 bool wxRichTextBufferDataObject::GetDataHere(void *pBuf) const
11428 {
11429 if (!pBuf || !m_richTextBuffer)
11430 return false;
11431
11432 wxString bufXML;
11433
11434 {
11435 wxStringOutputStream stream(& bufXML);
11436 if (!m_richTextBuffer->SaveFile(stream, wxRICHTEXT_TYPE_XML))
11437 {
11438 wxLogError(wxT("Could not write the buffer to an XML stream.\nYou may have forgotten to add the XML file handler."));
11439 return 0;
11440 }
11441 }
11442
11443 #if wxUSE_UNICODE
11444 wxCharBuffer buffer = bufXML.mb_str(wxConvUTF8);
11445 size_t len = strlen(buffer);
11446 memcpy((char*) pBuf, (const char*) buffer, len);
11447 ((char*) pBuf)[len] = 0;
11448 #else
11449 size_t len = bufXML.Length();
11450 memcpy((char*) pBuf, (const char*) bufXML.c_str(), len);
11451 ((char*) pBuf)[len] = 0;
11452 #endif
11453
11454 return true;
11455 }
11456
11457 bool wxRichTextBufferDataObject::SetData(size_t WXUNUSED(len), const void *buf)
11458 {
11459 wxDELETE(m_richTextBuffer);
11460
11461 wxString bufXML((const char*) buf, wxConvUTF8);
11462
11463 m_richTextBuffer = new wxRichTextBuffer;
11464
11465 wxStringInputStream stream(bufXML);
11466 if (!m_richTextBuffer->LoadFile(stream, wxRICHTEXT_TYPE_XML))
11467 {
11468 wxLogError(wxT("Could not read the buffer from an XML stream.\nYou may have forgotten to add the XML file handler."));
11469
11470 wxDELETE(m_richTextBuffer);
11471
11472 return false;
11473 }
11474 return true;
11475 }
11476
11477 #endif
11478 // wxUSE_DATAOBJ
11479
11480
11481 /*
11482 * wxRichTextFontTable
11483 * Manages quick access to a pool of fonts for rendering rich text
11484 */
11485
11486 WX_DECLARE_STRING_HASH_MAP_WITH_DECL(wxFont, wxRichTextFontTableHashMap, class WXDLLIMPEXP_RICHTEXT);
11487
11488 class wxRichTextFontTableData: public wxObjectRefData
11489 {
11490 public:
11491 wxRichTextFontTableData() {}
11492
11493 wxFont FindFont(const wxRichTextAttr& fontSpec);
11494
11495 wxRichTextFontTableHashMap m_hashMap;
11496 };
11497
11498 wxFont wxRichTextFontTableData::FindFont(const wxRichTextAttr& fontSpec)
11499 {
11500 wxString facename(fontSpec.GetFontFaceName());
11501 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()));
11502 wxRichTextFontTableHashMap::iterator entry = m_hashMap.find(spec);
11503
11504 if ( entry == m_hashMap.end() )
11505 {
11506 wxFont font(fontSpec.GetFontSize(), wxDEFAULT, fontSpec.GetFontStyle(), fontSpec.GetFontWeight(), fontSpec.GetFontUnderlined(), facename.c_str());
11507 m_hashMap[spec] = font;
11508 return font;
11509 }
11510 else
11511 {
11512 return entry->second;
11513 }
11514 }
11515
11516 IMPLEMENT_DYNAMIC_CLASS(wxRichTextFontTable, wxObject)
11517
11518 wxRichTextFontTable::wxRichTextFontTable()
11519 {
11520 m_refData = new wxRichTextFontTableData;
11521 }
11522
11523 wxRichTextFontTable::wxRichTextFontTable(const wxRichTextFontTable& table)
11524 : wxObject()
11525 {
11526 (*this) = table;
11527 }
11528
11529 wxRichTextFontTable::~wxRichTextFontTable()
11530 {
11531 UnRef();
11532 }
11533
11534 bool wxRichTextFontTable::operator == (const wxRichTextFontTable& table) const
11535 {
11536 return (m_refData == table.m_refData);
11537 }
11538
11539 void wxRichTextFontTable::operator= (const wxRichTextFontTable& table)
11540 {
11541 Ref(table);
11542 }
11543
11544 wxFont wxRichTextFontTable::FindFont(const wxRichTextAttr& fontSpec)
11545 {
11546 wxRichTextFontTableData* data = (wxRichTextFontTableData*) m_refData;
11547 if (data)
11548 return data->FindFont(fontSpec);
11549 else
11550 return wxFont();
11551 }
11552
11553 void wxRichTextFontTable::Clear()
11554 {
11555 wxRichTextFontTableData* data = (wxRichTextFontTableData*) m_refData;
11556 if (data)
11557 data->m_hashMap.clear();
11558 }
11559
11560 // wxTextBoxAttr
11561
11562 void wxTextBoxAttr::Reset()
11563 {
11564 m_flags = 0;
11565 m_floatMode = wxTEXT_BOX_ATTR_FLOAT_NONE;
11566 m_clearMode = wxTEXT_BOX_ATTR_CLEAR_NONE;
11567 m_collapseMode = wxTEXT_BOX_ATTR_COLLAPSE_NONE;
11568 m_verticalAlignment = wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT_NONE;
11569 m_boxStyleName = wxEmptyString;
11570
11571 m_margins.Reset();
11572 m_padding.Reset();
11573 m_position.Reset();
11574
11575 m_size.Reset();
11576 m_minSize.Reset();
11577 m_maxSize.Reset();
11578
11579 m_border.Reset();
11580 m_outline.Reset();
11581 }
11582
11583 // Equality test
11584 bool wxTextBoxAttr::operator== (const wxTextBoxAttr& attr) const
11585 {
11586 return (
11587 m_flags == attr.m_flags &&
11588 m_floatMode == attr.m_floatMode &&
11589 m_clearMode == attr.m_clearMode &&
11590 m_collapseMode == attr.m_collapseMode &&
11591 m_verticalAlignment == attr.m_verticalAlignment &&
11592
11593 m_margins == attr.m_margins &&
11594 m_padding == attr.m_padding &&
11595 m_position == attr.m_position &&
11596
11597 m_size == attr.m_size &&
11598 m_minSize == attr.m_minSize &&
11599 m_maxSize == attr.m_maxSize &&
11600
11601 m_border == attr.m_border &&
11602 m_outline == attr.m_outline &&
11603
11604 m_boxStyleName == attr.m_boxStyleName
11605 );
11606 }
11607
11608 // Partial equality test
11609 bool wxTextBoxAttr::EqPartial(const wxTextBoxAttr& attr) const
11610 {
11611 if (attr.HasFloatMode() && HasFloatMode() && (GetFloatMode() != attr.GetFloatMode()))
11612 return false;
11613
11614 if (attr.HasClearMode() && HasClearMode() && (GetClearMode() != attr.GetClearMode()))
11615 return false;
11616
11617 if (attr.HasCollapseBorders() && HasCollapseBorders() && (attr.GetCollapseBorders() != GetCollapseBorders()))
11618 return false;
11619
11620 if (attr.HasVerticalAlignment() && HasVerticalAlignment() && (attr.GetVerticalAlignment() != GetVerticalAlignment()))
11621 return false;
11622
11623 if (attr.HasBoxStyleName() && HasBoxStyleName() && (attr.GetBoxStyleName() != GetBoxStyleName()))
11624 return false;
11625
11626 // Position
11627
11628 if (!m_position.EqPartial(attr.m_position))
11629 return false;
11630
11631 // Size
11632
11633 if (!m_size.EqPartial(attr.m_size))
11634 return false;
11635 if (!m_minSize.EqPartial(attr.m_minSize))
11636 return false;
11637 if (!m_maxSize.EqPartial(attr.m_maxSize))
11638 return false;
11639
11640 // Margins
11641
11642 if (!m_margins.EqPartial(attr.m_margins))
11643 return false;
11644
11645 // Padding
11646
11647 if (!m_padding.EqPartial(attr.m_padding))
11648 return false;
11649
11650 // Border
11651
11652 if (!GetBorder().EqPartial(attr.GetBorder()))
11653 return false;
11654
11655 // Outline
11656
11657 if (!GetOutline().EqPartial(attr.GetOutline()))
11658 return false;
11659
11660 return true;
11661 }
11662
11663 // Merges the given attributes. If compareWith
11664 // is non-NULL, then it will be used to mask out those attributes that are the same in style
11665 // and compareWith, for situations where we don't want to explicitly set inherited attributes.
11666 bool wxTextBoxAttr::Apply(const wxTextBoxAttr& attr, const wxTextBoxAttr* compareWith)
11667 {
11668 if (attr.HasFloatMode())
11669 {
11670 if (!(compareWith && compareWith->HasFloatMode() && compareWith->GetFloatMode() == attr.GetFloatMode()))
11671 SetFloatMode(attr.GetFloatMode());
11672 }
11673
11674 if (attr.HasClearMode())
11675 {
11676 if (!(compareWith && compareWith->HasClearMode() && compareWith->GetClearMode() == attr.GetClearMode()))
11677 SetClearMode(attr.GetClearMode());
11678 }
11679
11680 if (attr.HasCollapseBorders())
11681 {
11682 if (!(compareWith && compareWith->HasCollapseBorders() && compareWith->GetCollapseBorders() == attr.GetCollapseBorders()))
11683 SetCollapseBorders(attr.GetCollapseBorders());
11684 }
11685
11686 if (attr.HasVerticalAlignment())
11687 {
11688 if (!(compareWith && compareWith->HasVerticalAlignment() && compareWith->GetVerticalAlignment() == attr.GetVerticalAlignment()))
11689 SetVerticalAlignment(attr.GetVerticalAlignment());
11690 }
11691
11692 if (attr.HasBoxStyleName())
11693 {
11694 if (!(compareWith && compareWith->HasBoxStyleName() && compareWith->GetBoxStyleName() == attr.GetBoxStyleName()))
11695 SetBoxStyleName(attr.GetBoxStyleName());
11696 }
11697
11698 m_margins.Apply(attr.m_margins, compareWith ? (& attr.m_margins) : (const wxTextAttrDimensions*) NULL);
11699 m_padding.Apply(attr.m_padding, compareWith ? (& attr.m_padding) : (const wxTextAttrDimensions*) NULL);
11700 m_position.Apply(attr.m_position, compareWith ? (& attr.m_position) : (const wxTextAttrDimensions*) NULL);
11701
11702 m_size.Apply(attr.m_size, compareWith ? (& attr.m_size) : (const wxTextAttrSize*) NULL);
11703 m_minSize.Apply(attr.m_minSize, compareWith ? (& attr.m_minSize) : (const wxTextAttrSize*) NULL);
11704 m_maxSize.Apply(attr.m_maxSize, compareWith ? (& attr.m_maxSize) : (const wxTextAttrSize*) NULL);
11705
11706 m_border.Apply(attr.m_border, compareWith ? (& attr.m_border) : (const wxTextAttrBorders*) NULL);
11707 m_outline.Apply(attr.m_outline, compareWith ? (& attr.m_outline) : (const wxTextAttrBorders*) NULL);
11708
11709 return true;
11710 }
11711
11712 // Remove specified attributes from this object
11713 bool wxTextBoxAttr::RemoveStyle(const wxTextBoxAttr& attr)
11714 {
11715 if (attr.HasFloatMode())
11716 RemoveFlag(wxTEXT_BOX_ATTR_FLOAT);
11717
11718 if (attr.HasClearMode())
11719 RemoveFlag(wxTEXT_BOX_ATTR_CLEAR);
11720
11721 if (attr.HasCollapseBorders())
11722 RemoveFlag(wxTEXT_BOX_ATTR_COLLAPSE_BORDERS);
11723
11724 if (attr.HasVerticalAlignment())
11725 RemoveFlag(wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT);
11726
11727 if (attr.HasBoxStyleName())
11728 {
11729 SetBoxStyleName(wxEmptyString);
11730 RemoveFlag(wxTEXT_BOX_ATTR_BOX_STYLE_NAME);
11731 }
11732
11733 m_margins.RemoveStyle(attr.m_margins);
11734 m_padding.RemoveStyle(attr.m_padding);
11735 m_position.RemoveStyle(attr.m_position);
11736
11737 m_size.RemoveStyle(attr.m_size);
11738 m_minSize.RemoveStyle(attr.m_minSize);
11739 m_maxSize.RemoveStyle(attr.m_maxSize);
11740
11741 m_border.RemoveStyle(attr.m_border);
11742 m_outline.RemoveStyle(attr.m_outline);
11743
11744 return true;
11745 }
11746
11747 // Collects the attributes that are common to a range of content, building up a note of
11748 // which attributes are absent in some objects and which clash in some objects.
11749 void wxTextBoxAttr::CollectCommonAttributes(const wxTextBoxAttr& attr, wxTextBoxAttr& clashingAttr, wxTextBoxAttr& absentAttr)
11750 {
11751 if (attr.HasFloatMode())
11752 {
11753 if (!clashingAttr.HasFloatMode() && !absentAttr.HasFloatMode())
11754 {
11755 if (HasFloatMode())
11756 {
11757 if (GetFloatMode() != attr.GetFloatMode())
11758 {
11759 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_FLOAT);
11760 RemoveFlag(wxTEXT_BOX_ATTR_FLOAT);
11761 }
11762 }
11763 else
11764 SetFloatMode(attr.GetFloatMode());
11765 }
11766 }
11767 else
11768 absentAttr.AddFlag(wxTEXT_BOX_ATTR_FLOAT);
11769
11770 if (attr.HasClearMode())
11771 {
11772 if (!clashingAttr.HasClearMode() && !absentAttr.HasClearMode())
11773 {
11774 if (HasClearMode())
11775 {
11776 if (GetClearMode() != attr.GetClearMode())
11777 {
11778 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_CLEAR);
11779 RemoveFlag(wxTEXT_BOX_ATTR_CLEAR);
11780 }
11781 }
11782 else
11783 SetClearMode(attr.GetClearMode());
11784 }
11785 }
11786 else
11787 absentAttr.AddFlag(wxTEXT_BOX_ATTR_CLEAR);
11788
11789 if (attr.HasCollapseBorders())
11790 {
11791 if (!clashingAttr.HasCollapseBorders() && !absentAttr.HasCollapseBorders())
11792 {
11793 if (HasCollapseBorders())
11794 {
11795 if (GetCollapseBorders() != attr.GetCollapseBorders())
11796 {
11797 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_COLLAPSE_BORDERS);
11798 RemoveFlag(wxTEXT_BOX_ATTR_COLLAPSE_BORDERS);
11799 }
11800 }
11801 else
11802 SetCollapseBorders(attr.GetCollapseBorders());
11803 }
11804 }
11805 else
11806 absentAttr.AddFlag(wxTEXT_BOX_ATTR_COLLAPSE_BORDERS);
11807
11808 if (attr.HasVerticalAlignment())
11809 {
11810 if (!clashingAttr.HasVerticalAlignment() && !absentAttr.HasVerticalAlignment())
11811 {
11812 if (HasVerticalAlignment())
11813 {
11814 if (GetVerticalAlignment() != attr.GetVerticalAlignment())
11815 {
11816 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT);
11817 RemoveFlag(wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT);
11818 }
11819 }
11820 else
11821 SetVerticalAlignment(attr.GetVerticalAlignment());
11822 }
11823 }
11824 else
11825 absentAttr.AddFlag(wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT);
11826
11827 if (attr.HasBoxStyleName())
11828 {
11829 if (!clashingAttr.HasBoxStyleName() && !absentAttr.HasBoxStyleName())
11830 {
11831 if (HasBoxStyleName())
11832 {
11833 if (GetBoxStyleName() != attr.GetBoxStyleName())
11834 {
11835 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_BOX_STYLE_NAME);
11836 RemoveFlag(wxTEXT_BOX_ATTR_BOX_STYLE_NAME);
11837 }
11838 }
11839 else
11840 SetBoxStyleName(attr.GetBoxStyleName());
11841 }
11842 }
11843 else
11844 absentAttr.AddFlag(wxTEXT_BOX_ATTR_BOX_STYLE_NAME);
11845
11846 m_margins.CollectCommonAttributes(attr.m_margins, clashingAttr.m_margins, absentAttr.m_margins);
11847 m_padding.CollectCommonAttributes(attr.m_padding, clashingAttr.m_padding, absentAttr.m_padding);
11848 m_position.CollectCommonAttributes(attr.m_position, clashingAttr.m_position, absentAttr.m_position);
11849
11850 m_size.CollectCommonAttributes(attr.m_size, clashingAttr.m_size, absentAttr.m_size);
11851 m_minSize.CollectCommonAttributes(attr.m_minSize, clashingAttr.m_minSize, absentAttr.m_minSize);
11852 m_maxSize.CollectCommonAttributes(attr.m_maxSize, clashingAttr.m_maxSize, absentAttr.m_maxSize);
11853
11854 m_border.CollectCommonAttributes(attr.m_border, clashingAttr.m_border, absentAttr.m_border);
11855 m_outline.CollectCommonAttributes(attr.m_outline, clashingAttr.m_outline, absentAttr.m_outline);
11856 }
11857
11858 bool wxTextBoxAttr::IsDefault() const
11859 {
11860 return GetFlags() == 0 && !m_border.IsValid() && !m_outline.IsValid() &&
11861 !m_size.IsValid() && !m_minSize.IsValid() && !m_maxSize.IsValid() &&
11862 !m_position.IsValid() && !m_padding.IsValid() && !m_margins.IsValid();
11863 }
11864
11865 // wxRichTextAttr
11866
11867 void wxRichTextAttr::Copy(const wxRichTextAttr& attr)
11868 {
11869 wxTextAttr::Copy(attr);
11870
11871 m_textBoxAttr = attr.m_textBoxAttr;
11872 }
11873
11874 bool wxRichTextAttr::operator==(const wxRichTextAttr& attr) const
11875 {
11876 if (!(wxTextAttr::operator==(attr)))
11877 return false;
11878
11879 return (m_textBoxAttr == attr.m_textBoxAttr);
11880 }
11881
11882 // Partial equality test taking comparison object into account
11883 bool wxRichTextAttr::EqPartial(const wxRichTextAttr& attr) const
11884 {
11885 if (!(wxTextAttr::EqPartial(attr)))
11886 return false;
11887
11888 return m_textBoxAttr.EqPartial(attr.m_textBoxAttr);
11889 }
11890
11891 // Merges the given attributes. If compareWith
11892 // is non-NULL, then it will be used to mask out those attributes that are the same in style
11893 // and compareWith, for situations where we don't want to explicitly set inherited attributes.
11894 bool wxRichTextAttr::Apply(const wxRichTextAttr& style, const wxRichTextAttr* compareWith)
11895 {
11896 wxTextAttr::Apply(style, compareWith);
11897
11898 return m_textBoxAttr.Apply(style.m_textBoxAttr, compareWith ? (& compareWith->m_textBoxAttr) : (const wxTextBoxAttr*) NULL);
11899 }
11900
11901 // Remove specified attributes from this object
11902 bool wxRichTextAttr::RemoveStyle(const wxRichTextAttr& attr)
11903 {
11904 wxTextAttr::RemoveStyle(*this, attr);
11905
11906 return m_textBoxAttr.RemoveStyle(attr.m_textBoxAttr);
11907 }
11908
11909 // Collects the attributes that are common to a range of content, building up a note of
11910 // which attributes are absent in some objects and which clash in some objects.
11911 void wxRichTextAttr::CollectCommonAttributes(const wxRichTextAttr& attr, wxRichTextAttr& clashingAttr, wxRichTextAttr& absentAttr)
11912 {
11913 wxTextAttrCollectCommonAttributes(*this, attr, clashingAttr, absentAttr);
11914
11915 m_textBoxAttr.CollectCommonAttributes(attr.m_textBoxAttr, clashingAttr.m_textBoxAttr, absentAttr.m_textBoxAttr);
11916 }
11917
11918 // Partial equality test
11919 bool wxTextAttrBorder::EqPartial(const wxTextAttrBorder& border) const
11920 {
11921 if (border.HasStyle() && !HasStyle() && (border.GetStyle() != GetStyle()))
11922 return false;
11923
11924 if (border.HasColour() && !HasColour() && (border.GetColourLong() != GetColourLong()))
11925 return false;
11926
11927 if (border.HasWidth() && !HasWidth() && !(border.GetWidth() == GetWidth()))
11928 return false;
11929
11930 return true;
11931 }
11932
11933 // Apply border to 'this', but not if the same as compareWith
11934 bool wxTextAttrBorder::Apply(const wxTextAttrBorder& border, const wxTextAttrBorder* compareWith)
11935 {
11936 if (border.HasStyle())
11937 {
11938 if (!(compareWith && (border.GetStyle() == compareWith->GetStyle())))
11939 SetStyle(border.GetStyle());
11940 }
11941 if (border.HasColour())
11942 {
11943 if (!(compareWith && (border.GetColourLong() == compareWith->GetColourLong())))
11944 SetColour(border.GetColourLong());
11945 }
11946 if (border.HasWidth())
11947 {
11948 if (!(compareWith && (border.GetWidth() == compareWith->GetWidth())))
11949 SetWidth(border.GetWidth());
11950 }
11951
11952 return true;
11953 }
11954
11955 // Remove specified attributes from this object
11956 bool wxTextAttrBorder::RemoveStyle(const wxTextAttrBorder& attr)
11957 {
11958 if (attr.HasStyle() && HasStyle())
11959 SetFlags(GetFlags() & ~wxTEXT_BOX_ATTR_BORDER_STYLE);
11960 if (attr.HasColour() && HasColour())
11961 SetFlags(GetFlags() & ~wxTEXT_BOX_ATTR_BORDER_COLOUR);
11962 if (attr.HasWidth() && HasWidth())
11963 m_borderWidth.Reset();
11964
11965 return true;
11966 }
11967
11968 // Collects the attributes that are common to a range of content, building up a note of
11969 // which attributes are absent in some objects and which clash in some objects.
11970 void wxTextAttrBorder::CollectCommonAttributes(const wxTextAttrBorder& attr, wxTextAttrBorder& clashingAttr, wxTextAttrBorder& absentAttr)
11971 {
11972 if (attr.HasStyle())
11973 {
11974 if (!clashingAttr.HasStyle() && !absentAttr.HasStyle())
11975 {
11976 if (HasStyle())
11977 {
11978 if (GetStyle() != attr.GetStyle())
11979 {
11980 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_BORDER_STYLE);
11981 RemoveFlag(wxTEXT_BOX_ATTR_BORDER_STYLE);
11982 }
11983 }
11984 else
11985 SetStyle(attr.GetStyle());
11986 }
11987 }
11988 else
11989 absentAttr.AddFlag(wxTEXT_BOX_ATTR_BORDER_STYLE);
11990
11991 if (attr.HasColour())
11992 {
11993 if (!clashingAttr.HasColour() && !absentAttr.HasColour())
11994 {
11995 if (HasColour())
11996 {
11997 if (GetColour() != attr.GetColour())
11998 {
11999 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_BORDER_COLOUR);
12000 RemoveFlag(wxTEXT_BOX_ATTR_BORDER_COLOUR);
12001 }
12002 }
12003 else
12004 SetColour(attr.GetColourLong());
12005 }
12006 }
12007 else
12008 absentAttr.AddFlag(wxTEXT_BOX_ATTR_BORDER_COLOUR);
12009
12010 m_borderWidth.CollectCommonAttributes(attr.m_borderWidth, clashingAttr.m_borderWidth, absentAttr.m_borderWidth);
12011 }
12012
12013 // Partial equality test
12014 bool wxTextAttrBorders::EqPartial(const wxTextAttrBorders& borders) const
12015 {
12016 return m_left.EqPartial(borders.m_left) && m_right.EqPartial(borders.m_right) &&
12017 m_top.EqPartial(borders.m_top) && m_bottom.EqPartial(borders.m_bottom);
12018 }
12019
12020 // Apply border to 'this', but not if the same as compareWith
12021 bool wxTextAttrBorders::Apply(const wxTextAttrBorders& borders, const wxTextAttrBorders* compareWith)
12022 {
12023 m_left.Apply(borders.m_left, compareWith ? (& compareWith->m_left) : (const wxTextAttrBorder*) NULL);
12024 m_right.Apply(borders.m_right, compareWith ? (& compareWith->m_right) : (const wxTextAttrBorder*) NULL);
12025 m_top.Apply(borders.m_top, compareWith ? (& compareWith->m_top) : (const wxTextAttrBorder*) NULL);
12026 m_bottom.Apply(borders.m_bottom, compareWith ? (& compareWith->m_bottom) : (const wxTextAttrBorder*) NULL);
12027 return true;
12028 }
12029
12030 // Remove specified attributes from this object
12031 bool wxTextAttrBorders::RemoveStyle(const wxTextAttrBorders& attr)
12032 {
12033 m_left.RemoveStyle(attr.m_left);
12034 m_right.RemoveStyle(attr.m_right);
12035 m_top.RemoveStyle(attr.m_top);
12036 m_bottom.RemoveStyle(attr.m_bottom);
12037 return true;
12038 }
12039
12040 // Collects the attributes that are common to a range of content, building up a note of
12041 // which attributes are absent in some objects and which clash in some objects.
12042 void wxTextAttrBorders::CollectCommonAttributes(const wxTextAttrBorders& attr, wxTextAttrBorders& clashingAttr, wxTextAttrBorders& absentAttr)
12043 {
12044 m_left.CollectCommonAttributes(attr.m_left, clashingAttr.m_left, absentAttr.m_left);
12045 m_right.CollectCommonAttributes(attr.m_right, clashingAttr.m_right, absentAttr.m_right);
12046 m_top.CollectCommonAttributes(attr.m_top, clashingAttr.m_top, absentAttr.m_top);
12047 m_bottom.CollectCommonAttributes(attr.m_bottom, clashingAttr.m_bottom, absentAttr.m_bottom);
12048 }
12049
12050 // Set style of all borders
12051 void wxTextAttrBorders::SetStyle(int style)
12052 {
12053 m_left.SetStyle(style);
12054 m_right.SetStyle(style);
12055 m_top.SetStyle(style);
12056 m_bottom.SetStyle(style);
12057 }
12058
12059 // Set colour of all borders
12060 void wxTextAttrBorders::SetColour(unsigned long colour)
12061 {
12062 m_left.SetColour(colour);
12063 m_right.SetColour(colour);
12064 m_top.SetColour(colour);
12065 m_bottom.SetColour(colour);
12066 }
12067
12068 void wxTextAttrBorders::SetColour(const wxColour& colour)
12069 {
12070 m_left.SetColour(colour);
12071 m_right.SetColour(colour);
12072 m_top.SetColour(colour);
12073 m_bottom.SetColour(colour);
12074 }
12075
12076 // Set width of all borders
12077 void wxTextAttrBorders::SetWidth(const wxTextAttrDimension& width)
12078 {
12079 m_left.SetWidth(width);
12080 m_right.SetWidth(width);
12081 m_top.SetWidth(width);
12082 m_bottom.SetWidth(width);
12083 }
12084
12085 // Partial equality test
12086 bool wxTextAttrDimension::EqPartial(const wxTextAttrDimension& dim) const
12087 {
12088 if (dim.IsValid() && IsValid() && !((*this) == dim))
12089 return false;
12090 else
12091 return true;
12092 }
12093
12094 bool wxTextAttrDimension::Apply(const wxTextAttrDimension& dim, const wxTextAttrDimension* compareWith)
12095 {
12096 if (dim.IsValid())
12097 {
12098 if (!(compareWith && dim == (*compareWith)))
12099 (*this) = dim;
12100 }
12101
12102 return true;
12103 }
12104
12105 // Collects the attributes that are common to a range of content, building up a note of
12106 // which attributes are absent in some objects and which clash in some objects.
12107 void wxTextAttrDimension::CollectCommonAttributes(const wxTextAttrDimension& attr, wxTextAttrDimension& clashingAttr, wxTextAttrDimension& absentAttr)
12108 {
12109 if (attr.IsValid())
12110 {
12111 if (!clashingAttr.IsValid() && !absentAttr.IsValid())
12112 {
12113 if (IsValid())
12114 {
12115 if (!((*this) == attr))
12116 {
12117 clashingAttr.SetValid(true);
12118 SetValid(false);
12119 }
12120 }
12121 else
12122 (*this) = attr;
12123 }
12124 }
12125 else
12126 absentAttr.SetValid(true);
12127 }
12128
12129 wxTextAttrDimensionConverter::wxTextAttrDimensionConverter(wxDC& dc, double scale, const wxSize& parentSize)
12130 {
12131 m_ppi = dc.GetPPI().x; m_scale = scale; m_parentSize = parentSize;
12132 }
12133
12134 wxTextAttrDimensionConverter::wxTextAttrDimensionConverter(int ppi, double scale, const wxSize& parentSize)
12135 {
12136 m_ppi = ppi; m_scale = scale; m_parentSize = parentSize;
12137 }
12138
12139 int wxTextAttrDimensionConverter::ConvertTenthsMMToPixels(int units) const
12140 {
12141 return wxRichTextObject::ConvertTenthsMMToPixels(m_ppi, units, m_scale);
12142 }
12143
12144 int wxTextAttrDimensionConverter::ConvertPixelsToTenthsMM(int pixels) const
12145 {
12146 return wxRichTextObject::ConvertPixelsToTenthsMM(m_ppi, pixels, m_scale);
12147 }
12148
12149 int wxTextAttrDimensionConverter::GetPixels(const wxTextAttrDimension& dim, int direction) const
12150 {
12151 if (dim.GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
12152 return ConvertTenthsMMToPixels(dim.GetValue());
12153 else if (dim.GetUnits() == wxTEXT_ATTR_UNITS_PIXELS)
12154 return dim.GetValue();
12155 else if (dim.GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE)
12156 {
12157 wxASSERT(m_parentSize != wxDefaultSize);
12158 if (direction == wxHORIZONTAL)
12159 return (int) (double(m_parentSize.x) * double(dim.GetValue()) / 100.0);
12160 else
12161 return (int) (double(m_parentSize.y) * double(dim.GetValue()) / 100.0);
12162 }
12163 else
12164 {
12165 wxASSERT(false);
12166 return 0;
12167 }
12168 }
12169
12170 int wxTextAttrDimensionConverter::GetTenthsMM(const wxTextAttrDimension& dim) const
12171 {
12172 if (dim.GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
12173 return dim.GetValue();
12174 else if (dim.GetUnits() == wxTEXT_ATTR_UNITS_PIXELS)
12175 return ConvertPixelsToTenthsMM(dim.GetValue());
12176 else
12177 {
12178 wxASSERT(false);
12179 return 0;
12180 }
12181 }
12182
12183 // Partial equality test
12184 bool wxTextAttrDimensions::EqPartial(const wxTextAttrDimensions& dims) const
12185 {
12186 if (!m_left.EqPartial(dims.m_left))
12187 return false;
12188
12189 if (!m_right.EqPartial(dims.m_right))
12190 return false;
12191
12192 if (!m_top.EqPartial(dims.m_top))
12193 return false;
12194
12195 if (!m_bottom.EqPartial(dims.m_bottom))
12196 return false;
12197
12198 return true;
12199 }
12200
12201 // Apply border to 'this', but not if the same as compareWith
12202 bool wxTextAttrDimensions::Apply(const wxTextAttrDimensions& dims, const wxTextAttrDimensions* compareWith)
12203 {
12204 m_left.Apply(dims.m_left, compareWith ? (& compareWith->m_left) : (const wxTextAttrDimension*) NULL);
12205 m_right.Apply(dims.m_right, compareWith ? (& compareWith->m_right): (const wxTextAttrDimension*) NULL);
12206 m_top.Apply(dims.m_top, compareWith ? (& compareWith->m_top): (const wxTextAttrDimension*) NULL);
12207 m_bottom.Apply(dims.m_bottom, compareWith ? (& compareWith->m_bottom): (const wxTextAttrDimension*) NULL);
12208
12209 return true;
12210 }
12211
12212 // Remove specified attributes from this object
12213 bool wxTextAttrDimensions::RemoveStyle(const wxTextAttrDimensions& attr)
12214 {
12215 if (attr.m_left.IsValid())
12216 m_left.Reset();
12217 if (attr.m_right.IsValid())
12218 m_right.Reset();
12219 if (attr.m_top.IsValid())
12220 m_top.Reset();
12221 if (attr.m_bottom.IsValid())
12222 m_bottom.Reset();
12223
12224 return true;
12225 }
12226
12227 // Collects the attributes that are common to a range of content, building up a note of
12228 // which attributes are absent in some objects and which clash in some objects.
12229 void wxTextAttrDimensions::CollectCommonAttributes(const wxTextAttrDimensions& attr, wxTextAttrDimensions& clashingAttr, wxTextAttrDimensions& absentAttr)
12230 {
12231 m_left.CollectCommonAttributes(attr.m_left, clashingAttr.m_left, absentAttr.m_left);
12232 m_right.CollectCommonAttributes(attr.m_right, clashingAttr.m_right, absentAttr.m_right);
12233 m_top.CollectCommonAttributes(attr.m_top, clashingAttr.m_top, absentAttr.m_top);
12234 m_bottom.CollectCommonAttributes(attr.m_bottom, clashingAttr.m_bottom, absentAttr.m_bottom);
12235 }
12236
12237 // Partial equality test
12238 bool wxTextAttrSize::EqPartial(const wxTextAttrSize& size) const
12239 {
12240 if (!m_width.EqPartial(size.m_width))
12241 return false;
12242
12243 if (!m_height.EqPartial(size.m_height))
12244 return false;
12245
12246 return true;
12247 }
12248
12249 // Apply border to 'this', but not if the same as compareWith
12250 bool wxTextAttrSize::Apply(const wxTextAttrSize& size, const wxTextAttrSize* compareWith)
12251 {
12252 m_width.Apply(size.m_width, compareWith ? (& compareWith->m_width) : (const wxTextAttrDimension*) NULL);
12253 m_height.Apply(size.m_height, compareWith ? (& compareWith->m_height): (const wxTextAttrDimension*) NULL);
12254
12255 return true;
12256 }
12257
12258 // Remove specified attributes from this object
12259 bool wxTextAttrSize::RemoveStyle(const wxTextAttrSize& attr)
12260 {
12261 if (attr.m_width.IsValid())
12262 m_width.Reset();
12263 if (attr.m_height.IsValid())
12264 m_height.Reset();
12265
12266 return true;
12267 }
12268
12269 // Collects the attributes that are common to a range of content, building up a note of
12270 // which attributes are absent in some objects and which clash in some objects.
12271 void wxTextAttrSize::CollectCommonAttributes(const wxTextAttrSize& attr, wxTextAttrSize& clashingAttr, wxTextAttrSize& absentAttr)
12272 {
12273 m_width.CollectCommonAttributes(attr.m_width, clashingAttr.m_width, absentAttr.m_width);
12274 m_height.CollectCommonAttributes(attr.m_height, clashingAttr.m_height, absentAttr.m_height);
12275 }
12276
12277 // Collects the attributes that are common to a range of content, building up a note of
12278 // which attributes are absent in some objects and which clash in some objects.
12279 void wxTextAttrCollectCommonAttributes(wxTextAttr& currentStyle, const wxTextAttr& attr, wxTextAttr& clashingAttr, wxTextAttr& absentAttr)
12280 {
12281 absentAttr.SetFlags(absentAttr.GetFlags() | (~attr.GetFlags() & wxTEXT_ATTR_ALL));
12282 absentAttr.SetTextEffectFlags(absentAttr.GetTextEffectFlags() | (~attr.GetTextEffectFlags() & 0xFFFF));
12283
12284 long forbiddenFlags = clashingAttr.GetFlags()|absentAttr.GetFlags();
12285
12286 if (attr.HasFont())
12287 {
12288 if (attr.HasFontSize() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_SIZE))
12289 {
12290 if (currentStyle.HasFontSize())
12291 {
12292 if (currentStyle.GetFontSize() != attr.GetFontSize())
12293 {
12294 // Clash of attr - mark as such
12295 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_SIZE);
12296 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_SIZE);
12297 }
12298 }
12299 else
12300 currentStyle.SetFontSize(attr.GetFontSize());
12301 }
12302
12303 if (attr.HasFontItalic() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_ITALIC))
12304 {
12305 if (currentStyle.HasFontItalic())
12306 {
12307 if (currentStyle.GetFontStyle() != attr.GetFontStyle())
12308 {
12309 // Clash of attr - mark as such
12310 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_ITALIC);
12311 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_ITALIC);
12312 }
12313 }
12314 else
12315 currentStyle.SetFontStyle(attr.GetFontStyle());
12316 }
12317
12318 if (attr.HasFontFamily() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_FAMILY))
12319 {
12320 if (currentStyle.HasFontFamily())
12321 {
12322 if (currentStyle.GetFontFamily() != attr.GetFontFamily())
12323 {
12324 // Clash of attr - mark as such
12325 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_FAMILY);
12326 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_FAMILY);
12327 }
12328 }
12329 else
12330 currentStyle.SetFontFamily(attr.GetFontFamily());
12331 }
12332
12333 if (attr.HasFontWeight() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_WEIGHT))
12334 {
12335 if (currentStyle.HasFontWeight())
12336 {
12337 if (currentStyle.GetFontWeight() != attr.GetFontWeight())
12338 {
12339 // Clash of attr - mark as such
12340 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_WEIGHT);
12341 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_WEIGHT);
12342 }
12343 }
12344 else
12345 currentStyle.SetFontWeight(attr.GetFontWeight());
12346 }
12347
12348 if (attr.HasFontFaceName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_FACE))
12349 {
12350 if (currentStyle.HasFontFaceName())
12351 {
12352 wxString faceName1(currentStyle.GetFontFaceName());
12353 wxString faceName2(attr.GetFontFaceName());
12354
12355 if (faceName1 != faceName2)
12356 {
12357 // Clash of attr - mark as such
12358 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_FACE);
12359 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_FACE);
12360 }
12361 }
12362 else
12363 currentStyle.SetFontFaceName(attr.GetFontFaceName());
12364 }
12365
12366 if (attr.HasFontUnderlined() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_UNDERLINE))
12367 {
12368 if (currentStyle.HasFontUnderlined())
12369 {
12370 if (currentStyle.GetFontUnderlined() != attr.GetFontUnderlined())
12371 {
12372 // Clash of attr - mark as such
12373 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_UNDERLINE);
12374 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_UNDERLINE);
12375 }
12376 }
12377 else
12378 currentStyle.SetFontUnderlined(attr.GetFontUnderlined());
12379 }
12380 }
12381
12382 if (attr.HasTextColour() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_TEXT_COLOUR))
12383 {
12384 if (currentStyle.HasTextColour())
12385 {
12386 if (currentStyle.GetTextColour() != attr.GetTextColour())
12387 {
12388 // Clash of attr - mark as such
12389 clashingAttr.AddFlag(wxTEXT_ATTR_TEXT_COLOUR);
12390 currentStyle.RemoveFlag(wxTEXT_ATTR_TEXT_COLOUR);
12391 }
12392 }
12393 else
12394 currentStyle.SetTextColour(attr.GetTextColour());
12395 }
12396
12397 if (attr.HasBackgroundColour() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BACKGROUND_COLOUR))
12398 {
12399 if (currentStyle.HasBackgroundColour())
12400 {
12401 if (currentStyle.GetBackgroundColour() != attr.GetBackgroundColour())
12402 {
12403 // Clash of attr - mark as such
12404 clashingAttr.AddFlag(wxTEXT_ATTR_BACKGROUND_COLOUR);
12405 currentStyle.RemoveFlag(wxTEXT_ATTR_BACKGROUND_COLOUR);
12406 }
12407 }
12408 else
12409 currentStyle.SetBackgroundColour(attr.GetBackgroundColour());
12410 }
12411
12412 if (attr.HasAlignment() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_ALIGNMENT))
12413 {
12414 if (currentStyle.HasAlignment())
12415 {
12416 if (currentStyle.GetAlignment() != attr.GetAlignment())
12417 {
12418 // Clash of attr - mark as such
12419 clashingAttr.AddFlag(wxTEXT_ATTR_ALIGNMENT);
12420 currentStyle.RemoveFlag(wxTEXT_ATTR_ALIGNMENT);
12421 }
12422 }
12423 else
12424 currentStyle.SetAlignment(attr.GetAlignment());
12425 }
12426
12427 if (attr.HasTabs() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_TABS))
12428 {
12429 if (currentStyle.HasTabs())
12430 {
12431 if (!wxRichTextTabsEq(currentStyle.GetTabs(), attr.GetTabs()))
12432 {
12433 // Clash of attr - mark as such
12434 clashingAttr.AddFlag(wxTEXT_ATTR_TABS);
12435 currentStyle.RemoveFlag(wxTEXT_ATTR_TABS);
12436 }
12437 }
12438 else
12439 currentStyle.SetTabs(attr.GetTabs());
12440 }
12441
12442 if (attr.HasLeftIndent() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_LEFT_INDENT))
12443 {
12444 if (currentStyle.HasLeftIndent())
12445 {
12446 if (currentStyle.GetLeftIndent() != attr.GetLeftIndent() || currentStyle.GetLeftSubIndent() != attr.GetLeftSubIndent())
12447 {
12448 // Clash of attr - mark as such
12449 clashingAttr.AddFlag(wxTEXT_ATTR_LEFT_INDENT);
12450 currentStyle.RemoveFlag(wxTEXT_ATTR_LEFT_INDENT);
12451 }
12452 }
12453 else
12454 currentStyle.SetLeftIndent(attr.GetLeftIndent(), attr.GetLeftSubIndent());
12455 }
12456
12457 if (attr.HasRightIndent() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_RIGHT_INDENT))
12458 {
12459 if (currentStyle.HasRightIndent())
12460 {
12461 if (currentStyle.GetRightIndent() != attr.GetRightIndent())
12462 {
12463 // Clash of attr - mark as such
12464 clashingAttr.AddFlag(wxTEXT_ATTR_RIGHT_INDENT);
12465 currentStyle.RemoveFlag(wxTEXT_ATTR_RIGHT_INDENT);
12466 }
12467 }
12468 else
12469 currentStyle.SetRightIndent(attr.GetRightIndent());
12470 }
12471
12472 if (attr.HasParagraphSpacingAfter() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_PARA_SPACING_AFTER))
12473 {
12474 if (currentStyle.HasParagraphSpacingAfter())
12475 {
12476 if (currentStyle.GetParagraphSpacingAfter() != attr.GetParagraphSpacingAfter())
12477 {
12478 // Clash of attr - mark as such
12479 clashingAttr.AddFlag(wxTEXT_ATTR_PARA_SPACING_AFTER);
12480 currentStyle.RemoveFlag(wxTEXT_ATTR_PARA_SPACING_AFTER);
12481 }
12482 }
12483 else
12484 currentStyle.SetParagraphSpacingAfter(attr.GetParagraphSpacingAfter());
12485 }
12486
12487 if (attr.HasParagraphSpacingBefore() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_PARA_SPACING_BEFORE))
12488 {
12489 if (currentStyle.HasParagraphSpacingBefore())
12490 {
12491 if (currentStyle.GetParagraphSpacingBefore() != attr.GetParagraphSpacingBefore())
12492 {
12493 // Clash of attr - mark as such
12494 clashingAttr.AddFlag(wxTEXT_ATTR_PARA_SPACING_BEFORE);
12495 currentStyle.RemoveFlag(wxTEXT_ATTR_PARA_SPACING_BEFORE);
12496 }
12497 }
12498 else
12499 currentStyle.SetParagraphSpacingBefore(attr.GetParagraphSpacingBefore());
12500 }
12501
12502 if (attr.HasLineSpacing() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_LINE_SPACING))
12503 {
12504 if (currentStyle.HasLineSpacing())
12505 {
12506 if (currentStyle.GetLineSpacing() != attr.GetLineSpacing())
12507 {
12508 // Clash of attr - mark as such
12509 clashingAttr.AddFlag(wxTEXT_ATTR_LINE_SPACING);
12510 currentStyle.RemoveFlag(wxTEXT_ATTR_LINE_SPACING);
12511 }
12512 }
12513 else
12514 currentStyle.SetLineSpacing(attr.GetLineSpacing());
12515 }
12516
12517 if (attr.HasCharacterStyleName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_CHARACTER_STYLE_NAME))
12518 {
12519 if (currentStyle.HasCharacterStyleName())
12520 {
12521 if (currentStyle.GetCharacterStyleName() != attr.GetCharacterStyleName())
12522 {
12523 // Clash of attr - mark as such
12524 clashingAttr.AddFlag(wxTEXT_ATTR_CHARACTER_STYLE_NAME);
12525 currentStyle.RemoveFlag(wxTEXT_ATTR_CHARACTER_STYLE_NAME);
12526 }
12527 }
12528 else
12529 currentStyle.SetCharacterStyleName(attr.GetCharacterStyleName());
12530 }
12531
12532 if (attr.HasParagraphStyleName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_PARAGRAPH_STYLE_NAME))
12533 {
12534 if (currentStyle.HasParagraphStyleName())
12535 {
12536 if (currentStyle.GetParagraphStyleName() != attr.GetParagraphStyleName())
12537 {
12538 // Clash of attr - mark as such
12539 clashingAttr.AddFlag(wxTEXT_ATTR_PARAGRAPH_STYLE_NAME);
12540 currentStyle.RemoveFlag(wxTEXT_ATTR_PARAGRAPH_STYLE_NAME);
12541 }
12542 }
12543 else
12544 currentStyle.SetParagraphStyleName(attr.GetParagraphStyleName());
12545 }
12546
12547 if (attr.HasListStyleName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_LIST_STYLE_NAME))
12548 {
12549 if (currentStyle.HasListStyleName())
12550 {
12551 if (currentStyle.GetListStyleName() != attr.GetListStyleName())
12552 {
12553 // Clash of attr - mark as such
12554 clashingAttr.AddFlag(wxTEXT_ATTR_LIST_STYLE_NAME);
12555 currentStyle.RemoveFlag(wxTEXT_ATTR_LIST_STYLE_NAME);
12556 }
12557 }
12558 else
12559 currentStyle.SetListStyleName(attr.GetListStyleName());
12560 }
12561
12562 if (attr.HasBulletStyle() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BULLET_STYLE))
12563 {
12564 if (currentStyle.HasBulletStyle())
12565 {
12566 if (currentStyle.GetBulletStyle() != attr.GetBulletStyle())
12567 {
12568 // Clash of attr - mark as such
12569 clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_STYLE);
12570 currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_STYLE);
12571 }
12572 }
12573 else
12574 currentStyle.SetBulletStyle(attr.GetBulletStyle());
12575 }
12576
12577 if (attr.HasBulletNumber() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BULLET_NUMBER))
12578 {
12579 if (currentStyle.HasBulletNumber())
12580 {
12581 if (currentStyle.GetBulletNumber() != attr.GetBulletNumber())
12582 {
12583 // Clash of attr - mark as such
12584 clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_NUMBER);
12585 currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_NUMBER);
12586 }
12587 }
12588 else
12589 currentStyle.SetBulletNumber(attr.GetBulletNumber());
12590 }
12591
12592 if (attr.HasBulletText() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BULLET_TEXT))
12593 {
12594 if (currentStyle.HasBulletText())
12595 {
12596 if (currentStyle.GetBulletText() != attr.GetBulletText())
12597 {
12598 // Clash of attr - mark as such
12599 clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_TEXT);
12600 currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_TEXT);
12601 }
12602 }
12603 else
12604 {
12605 currentStyle.SetBulletText(attr.GetBulletText());
12606 currentStyle.SetBulletFont(attr.GetBulletFont());
12607 }
12608 }
12609
12610 if (attr.HasBulletName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BULLET_NAME))
12611 {
12612 if (currentStyle.HasBulletName())
12613 {
12614 if (currentStyle.GetBulletName() != attr.GetBulletName())
12615 {
12616 // Clash of attr - mark as such
12617 clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_NAME);
12618 currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_NAME);
12619 }
12620 }
12621 else
12622 {
12623 currentStyle.SetBulletName(attr.GetBulletName());
12624 }
12625 }
12626
12627 if (attr.HasURL() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_URL))
12628 {
12629 if (currentStyle.HasURL())
12630 {
12631 if (currentStyle.GetURL() != attr.GetURL())
12632 {
12633 // Clash of attr - mark as such
12634 clashingAttr.AddFlag(wxTEXT_ATTR_URL);
12635 currentStyle.RemoveFlag(wxTEXT_ATTR_URL);
12636 }
12637 }
12638 else
12639 {
12640 currentStyle.SetURL(attr.GetURL());
12641 }
12642 }
12643
12644 if (attr.HasTextEffects() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_EFFECTS))
12645 {
12646 if (currentStyle.HasTextEffects())
12647 {
12648 // We need to find the bits in the new attr that are different:
12649 // just look at those bits that are specified by the new attr.
12650
12651 // We need to remove the bits and flags that are not common between current attr
12652 // and new attr. In so doing we need to take account of the styles absent from one or more of the
12653 // previous styles.
12654
12655 int currentRelevantTextEffects = currentStyle.GetTextEffects() & attr.GetTextEffectFlags();
12656 int newRelevantTextEffects = attr.GetTextEffects() & attr.GetTextEffectFlags();
12657
12658 if (currentRelevantTextEffects != newRelevantTextEffects)
12659 {
12660 // Find the text effects that were different, using XOR
12661 int differentEffects = currentRelevantTextEffects ^ newRelevantTextEffects;
12662
12663 // Clash of attr - mark as such
12664 clashingAttr.SetTextEffectFlags(clashingAttr.GetTextEffectFlags() | differentEffects);
12665 currentStyle.SetTextEffectFlags(currentStyle.GetTextEffectFlags() & ~differentEffects);
12666 }
12667 }
12668 else
12669 {
12670 currentStyle.SetTextEffects(attr.GetTextEffects());
12671 currentStyle.SetTextEffectFlags(attr.GetTextEffectFlags());
12672 }
12673
12674 // Mask out the flags and values that cannot be common because they were absent in one or more objecrs
12675 // that we've looked at so far
12676 currentStyle.SetTextEffects(currentStyle.GetTextEffects() & ~absentAttr.GetTextEffectFlags());
12677 currentStyle.SetTextEffectFlags(currentStyle.GetTextEffectFlags() & ~absentAttr.GetTextEffectFlags());
12678
12679 if (currentStyle.GetTextEffectFlags() == 0)
12680 currentStyle.RemoveFlag(wxTEXT_ATTR_EFFECTS);
12681 }
12682
12683 if (attr.HasOutlineLevel() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_OUTLINE_LEVEL))
12684 {
12685 if (currentStyle.HasOutlineLevel())
12686 {
12687 if (currentStyle.GetOutlineLevel() != attr.GetOutlineLevel())
12688 {
12689 // Clash of attr - mark as such
12690 clashingAttr.AddFlag(wxTEXT_ATTR_OUTLINE_LEVEL);
12691 currentStyle.RemoveFlag(wxTEXT_ATTR_OUTLINE_LEVEL);
12692 }
12693 }
12694 else
12695 currentStyle.SetOutlineLevel(attr.GetOutlineLevel());
12696 }
12697 }
12698
12699 WX_DEFINE_OBJARRAY(wxRichTextVariantArray);
12700
12701 IMPLEMENT_DYNAMIC_CLASS(wxRichTextProperties, wxObject)
12702
12703 bool wxRichTextProperties::operator==(const wxRichTextProperties& props) const
12704 {
12705 if (m_properties.GetCount() != props.GetCount())
12706 return false;
12707
12708 size_t i;
12709 for (i = 0; i < m_properties.GetCount(); i++)
12710 {
12711 const wxVariant& var1 = m_properties[i];
12712 int idx = props.Find(var1.GetName());
12713 if (idx == -1)
12714 return false;
12715 const wxVariant& var2 = props.m_properties[idx];
12716 if (!(var1 == var2))
12717 return false;
12718 }
12719
12720 return true;
12721 }
12722
12723 wxArrayString wxRichTextProperties::GetPropertyNames() const
12724 {
12725 wxArrayString arr;
12726 size_t i;
12727 for (i = 0; i < m_properties.GetCount(); i++)
12728 {
12729 arr.Add(m_properties[i].GetName());
12730 }
12731 return arr;
12732 }
12733
12734 int wxRichTextProperties::Find(const wxString& name) const
12735 {
12736 size_t i;
12737 for (i = 0; i < m_properties.GetCount(); i++)
12738 {
12739 if (m_properties[i].GetName() == name)
12740 return (int) i;
12741 }
12742 return -1;
12743 }
12744
12745 bool wxRichTextProperties::Remove(const wxString& name)
12746 {
12747 int idx = Find(name);
12748 if (idx != -1)
12749 {
12750 m_properties.RemoveAt(idx);
12751 return true;
12752 }
12753 else
12754 return false;
12755 }
12756
12757 wxVariant* wxRichTextProperties::FindOrCreateProperty(const wxString& name)
12758 {
12759 int idx = Find(name);
12760 if (idx == wxNOT_FOUND)
12761 SetProperty(name, wxString());
12762 idx = Find(name);
12763 if (idx != wxNOT_FOUND)
12764 {
12765 return & (*this)[idx];
12766 }
12767 else
12768 return NULL;
12769 }
12770
12771 const wxVariant& wxRichTextProperties::GetProperty(const wxString& name) const
12772 {
12773 static const wxVariant nullVariant;
12774 int idx = Find(name);
12775 if (idx != -1)
12776 return m_properties[idx];
12777 else
12778 return nullVariant;
12779 }
12780
12781 wxString wxRichTextProperties::GetPropertyString(const wxString& name) const
12782 {
12783 return GetProperty(name).GetString();
12784 }
12785
12786 long wxRichTextProperties::GetPropertyLong(const wxString& name) const
12787 {
12788 return GetProperty(name).GetLong();
12789 }
12790
12791 bool wxRichTextProperties::GetPropertyBool(const wxString& name) const
12792 {
12793 return GetProperty(name).GetBool();
12794 }
12795
12796 double wxRichTextProperties::GetPropertyDouble(const wxString& name) const
12797 {
12798 return GetProperty(name).GetDouble();
12799 }
12800
12801 void wxRichTextProperties::SetProperty(const wxVariant& variant)
12802 {
12803 wxASSERT(!variant.GetName().IsEmpty());
12804
12805 int idx = Find(variant.GetName());
12806
12807 if (idx == -1)
12808 m_properties.Add(variant);
12809 else
12810 m_properties[idx] = variant;
12811 }
12812
12813 void wxRichTextProperties::SetProperty(const wxString& name, const wxVariant& variant)
12814 {
12815 int idx = Find(name);
12816 wxVariant var(variant);
12817 var.SetName(name);
12818
12819 if (idx == -1)
12820 m_properties.Add(var);
12821 else
12822 m_properties[idx] = var;
12823 }
12824
12825 void wxRichTextProperties::SetProperty(const wxString& name, const wxString& value)
12826 {
12827 SetProperty(name, wxVariant(value, name));
12828 }
12829
12830 void wxRichTextProperties::SetProperty(const wxString& name, long value)
12831 {
12832 SetProperty(name, wxVariant(value, name));
12833 }
12834
12835 void wxRichTextProperties::SetProperty(const wxString& name, double value)
12836 {
12837 SetProperty(name, wxVariant(value, name));
12838 }
12839
12840 void wxRichTextProperties::SetProperty(const wxString& name, bool value)
12841 {
12842 SetProperty(name, wxVariant(value, name));
12843 }
12844
12845 void wxRichTextProperties::RemoveProperties(const wxRichTextProperties& properties)
12846 {
12847 size_t i;
12848 for (i = 0; i < properties.GetCount(); i++)
12849 {
12850 wxString name = properties.GetProperties()[i].GetName();
12851 if (HasProperty(name))
12852 Remove(name);
12853 }
12854 }
12855
12856 void wxRichTextProperties::MergeProperties(const wxRichTextProperties& properties)
12857 {
12858 size_t i;
12859 for (i = 0; i < properties.GetCount(); i++)
12860 {
12861 SetProperty(properties.GetProperties()[i]);
12862 }
12863 }
12864
12865 wxRichTextObject* wxRichTextObjectAddress::GetObject(wxRichTextParagraphLayoutBox* topLevelContainer) const
12866 {
12867 if (m_address.GetCount() == 0)
12868 return topLevelContainer;
12869
12870 wxRichTextCompositeObject* p = topLevelContainer;
12871 size_t i = 0;
12872 while (p && i < m_address.GetCount())
12873 {
12874 int pos = m_address[i];
12875 wxASSERT(pos >= 0 && pos < (int) p->GetChildren().GetCount());
12876 if (pos < 0 || pos >= (int) p->GetChildren().GetCount())
12877 return NULL;
12878
12879 wxRichTextObject* p1 = p->GetChild(pos);
12880 if (i == (m_address.GetCount()-1))
12881 return p1;
12882
12883 p = wxDynamicCast(p1, wxRichTextCompositeObject);
12884 i ++;
12885 }
12886 return NULL;
12887 }
12888
12889 bool wxRichTextObjectAddress::Create(wxRichTextParagraphLayoutBox* topLevelContainer, wxRichTextObject* obj)
12890 {
12891 m_address.Clear();
12892
12893 if (topLevelContainer == obj)
12894 return true;
12895
12896 wxRichTextObject* o = obj;
12897 while (o)
12898 {
12899 wxRichTextCompositeObject* p = wxDynamicCast(o->GetParent(), wxRichTextCompositeObject);
12900 if (!p)
12901 return false;
12902
12903 int pos = p->GetChildren().IndexOf(o);
12904 if (pos == -1)
12905 return false;
12906
12907 m_address.Insert(pos, 0);
12908
12909 if (p == topLevelContainer)
12910 return true;
12911
12912 o = p;
12913 }
12914 return false;
12915 }
12916
12917 // Equality test
12918 bool wxRichTextSelection::operator==(const wxRichTextSelection& sel) const
12919 {
12920 if (m_container != sel.m_container)
12921 return false;
12922 if (m_ranges.GetCount() != sel.m_ranges.GetCount())
12923 return false;
12924 size_t i;
12925 for (i = 0; i < m_ranges.GetCount(); i++)
12926 if (!(m_ranges[i] == sel.m_ranges[i]))
12927 return false;
12928 return true;
12929 }
12930
12931 // Get the selections appropriate to the specified object, if any; returns wxRICHTEXT_NO_SELECTION if none
12932 // or none at the level of the object's container.
12933 wxRichTextRangeArray wxRichTextSelection::GetSelectionForObject(wxRichTextObject* obj) const
12934 {
12935 if (IsValid())
12936 {
12937 wxRichTextParagraphLayoutBox* container = obj->GetParentContainer();
12938
12939 if (container == m_container)
12940 return m_ranges;
12941
12942 container = obj->GetContainer();
12943 while (container)
12944 {
12945 if (container->GetParent())
12946 {
12947 // If we found that our object's container is within the range of
12948 // a selection higher up, then assume the whole original object
12949 // is also selected.
12950 wxRichTextParagraphLayoutBox* parentContainer = container->GetParentContainer();
12951 if (parentContainer == m_container)
12952 {
12953 if (WithinSelection(container->GetRange().GetStart(), m_ranges))
12954 {
12955 wxRichTextRangeArray ranges;
12956 ranges.Add(obj->GetRange());
12957 return ranges;
12958 }
12959 }
12960
12961 container = parentContainer;
12962 }
12963 else
12964 {
12965 container = NULL;
12966 break;
12967 }
12968 }
12969 }
12970 return wxRichTextRangeArray();
12971 }
12972
12973 // Is the given position within the selection?
12974 bool wxRichTextSelection::WithinSelection(long pos, wxRichTextObject* obj) const
12975 {
12976 if (!IsValid())
12977 return false;
12978 else
12979 {
12980 wxRichTextRangeArray selectionRanges = GetSelectionForObject(obj);
12981 return WithinSelection(pos, selectionRanges);
12982 }
12983 }
12984
12985 // Is the given position within the selection range?
12986 bool wxRichTextSelection::WithinSelection(long pos, const wxRichTextRangeArray& ranges)
12987 {
12988 size_t i;
12989 for (i = 0; i < ranges.GetCount(); i++)
12990 {
12991 const wxRichTextRange& range = ranges[i];
12992 if (pos >= range.GetStart() && pos <= range.GetEnd())
12993 return true;
12994 }
12995 return false;
12996 }
12997
12998 // Is the given range completely within the selection range?
12999 bool wxRichTextSelection::WithinSelection(const wxRichTextRange& range, const wxRichTextRangeArray& ranges)
13000 {
13001 size_t i;
13002 for (i = 0; i < ranges.GetCount(); i++)
13003 {
13004 const wxRichTextRange& eachRange = ranges[i];
13005 if (range.IsWithin(eachRange))
13006 return true;
13007 }
13008 return false;
13009 }
13010
13011 IMPLEMENT_CLASS(wxRichTextDrawingHandler, wxObject)
13012 IMPLEMENT_CLASS(wxRichTextDrawingContext, wxObject)
13013
13014 bool wxRichTextDrawingContext::HasVirtualAttributes(wxRichTextObject* obj) const
13015 {
13016 wxList::compatibility_iterator node = m_buffer->GetDrawingHandlers().GetFirst();
13017 while (node)
13018 {
13019 wxRichTextDrawingHandler *handler = (wxRichTextDrawingHandler*)node->GetData();
13020 if (handler->HasVirtualAttributes(obj))
13021 return true;
13022
13023 node = node->GetNext();
13024 }
13025 return false;
13026 }
13027
13028 wxRichTextAttr wxRichTextDrawingContext::GetVirtualAttributes(wxRichTextObject* obj) const
13029 {
13030 wxRichTextAttr attr;
13031 // We apply all handlers, so we can may combine several different attributes
13032 wxList::compatibility_iterator node = m_buffer->GetDrawingHandlers().GetFirst();
13033 while (node)
13034 {
13035 wxRichTextDrawingHandler *handler = (wxRichTextDrawingHandler*)node->GetData();
13036 if (handler->HasVirtualAttributes(obj))
13037 {
13038 bool success = handler->GetVirtualAttributes(attr, obj);
13039 wxASSERT(success);
13040 wxUnusedVar(success);
13041 }
13042
13043 node = node->GetNext();
13044 }
13045 return attr;
13046 }
13047
13048 bool wxRichTextDrawingContext::ApplyVirtualAttributes(wxRichTextAttr& attr, wxRichTextObject* obj) const
13049 {
13050 if (HasVirtualAttributes(obj))
13051 {
13052 wxRichTextAttr a(GetVirtualAttributes(obj));
13053 attr.Apply(a);
13054 return true;
13055 }
13056 else
13057 return false;
13058 }
13059
13060 /// Adds a handler to the end
13061 void wxRichTextBuffer::AddDrawingHandler(wxRichTextDrawingHandler *handler)
13062 {
13063 sm_drawingHandlers.Append(handler);
13064 }
13065
13066 /// Inserts a handler at the front
13067 void wxRichTextBuffer::InsertDrawingHandler(wxRichTextDrawingHandler *handler)
13068 {
13069 sm_drawingHandlers.Insert( handler );
13070 }
13071
13072 /// Removes a handler
13073 bool wxRichTextBuffer::RemoveDrawingHandler(const wxString& name)
13074 {
13075 wxRichTextDrawingHandler *handler = FindDrawingHandler(name);
13076 if (handler)
13077 {
13078 sm_drawingHandlers.DeleteObject(handler);
13079 delete handler;
13080 return true;
13081 }
13082 else
13083 return false;
13084 }
13085
13086 wxRichTextDrawingHandler* wxRichTextBuffer::FindDrawingHandler(const wxString& name)
13087 {
13088 wxList::compatibility_iterator node = sm_drawingHandlers.GetFirst();
13089 while (node)
13090 {
13091 wxRichTextDrawingHandler *handler = (wxRichTextDrawingHandler*)node->GetData();
13092 if (handler->GetName().Lower() == name.Lower()) return handler;
13093
13094 node = node->GetNext();
13095 }
13096 return NULL;
13097 }
13098
13099 void wxRichTextBuffer::CleanUpDrawingHandlers()
13100 {
13101 wxList::compatibility_iterator node = sm_drawingHandlers.GetFirst();
13102 while (node)
13103 {
13104 wxRichTextDrawingHandler* handler = (wxRichTextDrawingHandler*)node->GetData();
13105 wxList::compatibility_iterator next = node->GetNext();
13106 delete handler;
13107 node = next;
13108 }
13109
13110 sm_drawingHandlers.Clear();
13111 }
13112
13113 void wxRichTextBuffer::AddFieldType(wxRichTextFieldType *fieldType)
13114 {
13115 sm_fieldTypes[fieldType->GetName()] = fieldType;
13116 }
13117
13118 bool wxRichTextBuffer::RemoveFieldType(const wxString& name)
13119 {
13120 wxRichTextFieldTypeHashMap::iterator it = sm_fieldTypes.find(name);
13121 if (it == sm_fieldTypes.end())
13122 return false;
13123 else
13124 {
13125 wxRichTextFieldType* fieldType = it->second;
13126 sm_fieldTypes.erase(it);
13127 delete fieldType;
13128 return true;
13129 }
13130 }
13131
13132 wxRichTextFieldType *wxRichTextBuffer::FindFieldType(const wxString& name)
13133 {
13134 wxRichTextFieldTypeHashMap::iterator it = sm_fieldTypes.find(name);
13135 if (it == sm_fieldTypes.end())
13136 return NULL;
13137 else
13138 return it->second;
13139 }
13140
13141 void wxRichTextBuffer::CleanUpFieldTypes()
13142 {
13143 wxRichTextFieldTypeHashMap::iterator it;
13144 for( it = sm_fieldTypes.begin(); it != sm_fieldTypes.end(); ++it )
13145 {
13146 wxRichTextFieldType* fieldType = it->second;
13147 delete fieldType;
13148 }
13149
13150 sm_fieldTypes.clear();
13151 }
13152
13153 #endif
13154 // wxUSE_RICHTEXT