Added support for wxTEXT_ATTR_EFFECT_SMALL_CAPITALS.
[wxWidgets.git] / src / richtext / richtextbuffer.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/richtext/richtextbuffer.cpp
3 // Purpose: Buffer for wxRichTextCtrl
4 // Author: Julian Smart
5 // Modified by:
6 // Created: 2005-09-30
7 // RCS-ID: $Id$
8 // Copyright: (c) Julian Smart
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 // For compilers that support precompilation, includes "wx.h".
13 #include "wx/wxprec.h"
14
15 #ifdef __BORLANDC__
16 #pragma hdrstop
17 #endif
18
19 #if wxUSE_RICHTEXT
20
21 #include "wx/richtext/richtextbuffer.h"
22
23 #ifndef WX_PRECOMP
24 #include "wx/dc.h"
25 #include "wx/intl.h"
26 #include "wx/log.h"
27 #include "wx/dataobj.h"
28 #include "wx/module.h"
29 #endif
30
31 #include "wx/settings.h"
32 #include "wx/filename.h"
33 #include "wx/clipbrd.h"
34 #include "wx/wfstream.h"
35 #include "wx/mstream.h"
36 #include "wx/sstream.h"
37 #include "wx/textfile.h"
38 #include "wx/hashmap.h"
39 #include "wx/dynarray.h"
40
41 #include "wx/richtext/richtextctrl.h"
42 #include "wx/richtext/richtextstyles.h"
43 #include "wx/richtext/richtextimagedlg.h"
44 #include "wx/richtext/richtextsizepage.h"
45 #include "wx/richtext/richtextxml.h"
46
47 #include "wx/listimpl.cpp"
48 #include "wx/arrimpl.cpp"
49
50 WX_DEFINE_LIST(wxRichTextObjectList)
51 WX_DEFINE_LIST(wxRichTextLineList)
52
53 // Switch off if the platform doesn't like it for some reason
54 #define wxRICHTEXT_USE_OPTIMIZED_DRAWING 1
55
56 // Use GetPartialTextExtents for platforms that support it natively
57 #define wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS 1
58
59 const wxChar wxRichTextLineBreakChar = (wxChar) 29;
60
61 // Helper classes for floating layout
62 struct wxRichTextFloatRectMap
63 {
64 wxRichTextFloatRectMap(int sY, int eY, int w, wxRichTextObject* obj)
65 {
66 startY = sY;
67 endY = eY;
68 width = w;
69 anchor = obj;
70 }
71
72 int startY, endY;
73 int width;
74 wxRichTextObject* anchor;
75 };
76
77 WX_DEFINE_SORTED_ARRAY(wxRichTextFloatRectMap*, wxRichTextFloatRectMapArray);
78
79 int wxRichTextFloatRectMapCmp(wxRichTextFloatRectMap* r1, wxRichTextFloatRectMap* r2)
80 {
81 return r1->startY - r2->startY;
82 }
83
84 class wxRichTextFloatCollector
85 {
86 public:
87 wxRichTextFloatCollector(const wxRect& availableRect);
88 ~wxRichTextFloatCollector();
89
90 // Collect the floating objects info in the given paragraph
91 void CollectFloat(wxRichTextParagraph* para);
92 void CollectFloat(wxRichTextParagraph* para, wxRichTextObject* floating);
93
94 // Return the last paragraph we collected
95 wxRichTextParagraph* LastParagraph();
96
97 // Given the start y position and the height of the line,
98 // find out how wide the line can be
99 wxRect GetAvailableRect(int startY, int endY);
100
101 // Given a floating box, find its fit position
102 int GetFitPosition(int direction, int start, int height) const;
103 int GetFitPosition(const wxRichTextFloatRectMapArray& array, int start, int height) const;
104
105 // Find the last y position
106 int GetLastRectBottom();
107
108 // Draw the floats inside a rect
109 void Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style);
110
111 // HitTest the floats
112 int HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, int flags);
113
114 // Get floating object count
115 int GetFloatingObjectCount() const { return m_left.GetCount() + m_right.GetCount(); }
116
117 // Get floating objects
118 bool GetFloatingObjects(wxRichTextObjectList& objects) const;
119
120 // Delete a float
121 bool DeleteFloat(wxRichTextObject* obj);
122
123 // Do we have this float already?
124 bool HasFloat(wxRichTextObject* obj);
125
126 bool HasFloats() const { return m_left.GetCount() >0 || m_right.GetCount() > 0; }
127
128 static int SearchAdjacentRect(const wxRichTextFloatRectMapArray& array, int point);
129
130 static int GetWidthFromFloatRect(const wxRichTextFloatRectMapArray& array, int index, int startY, int endY);
131
132 static void FreeFloatRectMapArray(wxRichTextFloatRectMapArray& array);
133
134 static void DrawFloat(const wxRichTextFloatRectMapArray& array, wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style);
135
136 static int HitTestFloat(const wxRichTextFloatRectMapArray& array, wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, int flags);
137
138 private:
139 wxRichTextFloatRectMapArray m_left;
140 wxRichTextFloatRectMapArray m_right;
141 //int m_width;
142 wxRect m_availableRect;
143 wxRichTextParagraph* m_para;
144 };
145
146 // Delete a float
147 bool wxRichTextFloatCollector::DeleteFloat(wxRichTextObject* obj)
148 {
149 size_t i;
150 for (i = 0; i < m_left.GetCount(); i++)
151 {
152 if (m_left[i]->anchor == obj)
153 {
154 m_left.RemoveAt(i);
155 return true;
156 }
157 }
158 for (i = 0; i < m_right.GetCount(); i++)
159 {
160 if (m_right[i]->anchor == obj)
161 {
162 m_right.RemoveAt(i);
163 return true;
164 }
165 }
166 return false;
167 }
168
169 // Do we have this float already?
170 bool wxRichTextFloatCollector::HasFloat(wxRichTextObject* obj)
171 {
172 size_t i;
173 for (i = 0; i < m_left.GetCount(); i++)
174 {
175 if (m_left[i]->anchor == obj)
176 {
177 return true;
178 }
179 }
180 for (i = 0; i < m_right.GetCount(); i++)
181 {
182 if (m_right[i]->anchor == obj)
183 {
184 return true;
185 }
186 }
187 return false;
188 }
189
190 // Get floating objects
191 bool wxRichTextFloatCollector::GetFloatingObjects(wxRichTextObjectList& objects) const
192 {
193 size_t i;
194 for (i = 0; i < m_left.GetCount(); i++)
195 objects.Append(m_left[i]->anchor);
196 for (i = 0; i < m_right.GetCount(); i++)
197 objects.Append(m_right[i]->anchor);
198 return true;
199 }
200
201
202 /*
203 * Binary search helper function
204 * The argument point is the Y coordinate, and this fuction
205 * always return the floating rect that contain this coordinate
206 * or under this coordinate.
207 */
208 int wxRichTextFloatCollector::SearchAdjacentRect(const wxRichTextFloatRectMapArray& array, int point)
209 {
210 int end = array.GetCount() - 1;
211 int start = 0;
212 int ret = 0;
213
214 wxASSERT(end >= 0);
215
216 while (true)
217 {
218 if (start > end)
219 {
220 break;
221 }
222
223 int mid = (start + end) / 2;
224 if (array[mid]->startY <= point && array[mid]->endY >= point)
225 return mid;
226 else if (array[mid]->startY > point)
227 {
228 end = mid - 1;
229 ret = mid;
230 }
231 else if (array[mid]->endY < point)
232 {
233 start = mid + 1;
234 ret = start;
235 }
236 }
237
238 return ret;
239 }
240
241 int wxRichTextFloatCollector::GetWidthFromFloatRect(const wxRichTextFloatRectMapArray& array, int index, int startY, int endY)
242 {
243 int ret = 0;
244 int len = array.GetCount();
245
246 wxASSERT(index >= 0 && index < len);
247
248 if (array[index]->startY < startY && array[index]->endY > startY)
249 ret = ret < array[index]->width ? array[index]->width : ret;
250 while (index < len && array[index]->startY <= endY)
251 {
252 ret = ret < array[index]->width ? array[index]->width : ret;
253 index++;
254 }
255
256 return ret;
257 }
258
259 wxRichTextFloatCollector::wxRichTextFloatCollector(const wxRect& rect) : m_left(wxRichTextFloatRectMapCmp), m_right(wxRichTextFloatRectMapCmp)
260 {
261 m_availableRect = rect;
262 m_para = NULL;
263 }
264
265 void wxRichTextFloatCollector::FreeFloatRectMapArray(wxRichTextFloatRectMapArray& array)
266 {
267 int len = array.GetCount();
268 for (int i = 0; i < len; i++)
269 delete array[i];
270 }
271
272 wxRichTextFloatCollector::~wxRichTextFloatCollector()
273 {
274 FreeFloatRectMapArray(m_left);
275 FreeFloatRectMapArray(m_right);
276 }
277
278 int wxRichTextFloatCollector::GetFitPosition(const wxRichTextFloatRectMapArray& array, int start, int height) const
279 {
280 if (array.GetCount() == 0)
281 return start;
282
283 int i = SearchAdjacentRect(array, start);
284 int last = start;
285 while (i < (int) array.GetCount())
286 {
287 if (array[i]->startY - last >= height)
288 return last + 1;
289 last = array[i]->endY;
290 i++;
291 }
292
293 return last + 1;
294 }
295
296 int wxRichTextFloatCollector::GetFitPosition(int direction, int start, int height) const
297 {
298 if (direction == wxTEXT_BOX_ATTR_FLOAT_LEFT)
299 return GetFitPosition(m_left, start, height);
300 else if (direction == wxTEXT_BOX_ATTR_FLOAT_RIGHT)
301 return GetFitPosition(m_right, start, height);
302 else
303 {
304 wxASSERT("Never should be here");
305 return start;
306 }
307 }
308
309 // Adds a floating image to the float collector.
310 // The actual positioning is done by wxRichTextParagraph::LayoutFloat.
311 void wxRichTextFloatCollector::CollectFloat(wxRichTextParagraph* para, wxRichTextObject* floating)
312 {
313 int direction = floating->GetFloatDirection();
314
315 wxPoint pos = floating->GetPosition();
316 wxSize size = floating->GetCachedSize();
317 wxRichTextFloatRectMap *map = new wxRichTextFloatRectMap(pos.y, pos.y + size.y, size.x, floating);
318 switch (direction)
319 {
320 case wxTEXT_BOX_ATTR_FLOAT_NONE:
321 delete map;
322 break;
323 case wxTEXT_BOX_ATTR_FLOAT_LEFT:
324 // Just a not-enough simple assertion
325 wxASSERT (m_left.Index(map) == wxNOT_FOUND);
326 m_left.Add(map);
327 break;
328 case wxTEXT_BOX_ATTR_FLOAT_RIGHT:
329 wxASSERT (m_right.Index(map) == wxNOT_FOUND);
330 m_right.Add(map);
331 break;
332 default:
333 delete map;
334 wxASSERT("Unrecognised float attribute.");
335 }
336
337 m_para = para;
338 }
339
340 void wxRichTextFloatCollector::CollectFloat(wxRichTextParagraph* para)
341 {
342 wxRichTextObjectList::compatibility_iterator node = para->GetChildren().GetFirst();
343 while (node)
344 {
345 wxRichTextObject* floating = node->GetData();
346
347 if (floating->IsFloating())
348 {
349 CollectFloat(para, floating);
350 }
351
352 node = node->GetNext();
353 }
354
355 m_para = para;
356 }
357
358 wxRichTextParagraph* wxRichTextFloatCollector::LastParagraph()
359 {
360 return m_para;
361 }
362
363 wxRect wxRichTextFloatCollector::GetAvailableRect(int startY, int endY)
364 {
365 int widthLeft = 0, widthRight = 0;
366 if (m_left.GetCount() != 0)
367 {
368 int i = SearchAdjacentRect(m_left, startY);
369 if (i < (int) m_left.GetCount())
370 widthLeft = GetWidthFromFloatRect(m_left, i, startY, endY);
371 }
372 if (m_right.GetCount() != 0)
373 {
374 int j = SearchAdjacentRect(m_right, startY);
375 if (j < (int) m_right.GetCount())
376 widthRight = GetWidthFromFloatRect(m_right, j, startY, endY);
377 }
378
379 // TODO: actually we want to use the actual image positions to find the
380 // available remaining space, since the image might not be right up against
381 // the left or right edge of the container.
382 return wxRect(widthLeft + m_availableRect.x, 0, m_availableRect.width - widthLeft - widthRight, 0);
383 }
384
385 int wxRichTextFloatCollector::GetLastRectBottom()
386 {
387 int ret = 0;
388 int len = m_left.GetCount();
389 if (len) {
390 ret = ret > m_left[len-1]->endY ? ret : m_left[len-1]->endY;
391 }
392 len = m_right.GetCount();
393 if (len) {
394 ret = ret > m_right[len-1]->endY ? ret : m_right[len-1]->endY;
395 }
396
397 return ret;
398 }
399
400 void wxRichTextFloatCollector::DrawFloat(const wxRichTextFloatRectMapArray& array, wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& WXUNUSED(range), const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
401 {
402 int start = rect.y;
403 int end = rect.y + rect.height;
404 int i, j;
405 i = SearchAdjacentRect(array, start);
406 if (i < 0 || i >= (int) array.GetCount())
407 return;
408 j = SearchAdjacentRect(array, end);
409 if (j < 0 || j >= (int) array.GetCount())
410 j = array.GetCount() - 1;
411 while (i <= j)
412 {
413 wxRichTextObject* obj = array[i]->anchor;
414 wxRichTextRange r = obj->GetRange();
415 obj->Draw(dc, context, r, selection, wxRect(obj->GetPosition(), obj->GetCachedSize()), descent, style);
416 i++;
417 }
418 }
419
420 void wxRichTextFloatCollector::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
421 {
422 if (m_left.GetCount() > 0)
423 DrawFloat(m_left, dc, context, range, selection, rect, descent, style);
424 if (m_right.GetCount() > 0)
425 DrawFloat(m_right, dc, context, range, selection, rect, descent, style);
426 }
427
428 int wxRichTextFloatCollector::HitTestFloat(const wxRichTextFloatRectMapArray& array, wxDC& WXUNUSED(dc), wxRichTextDrawingContext& WXUNUSED(context), const wxPoint& pt, long& textPosition, wxRichTextObject** obj, int WXUNUSED(flags))
429 {
430 int i;
431 if (array.GetCount() == 0)
432 return wxRICHTEXT_HITTEST_NONE;
433 i = SearchAdjacentRect(array, pt.y);
434 if (i < 0 || i >= (int) array.GetCount())
435 return wxRICHTEXT_HITTEST_NONE;
436 if (!array[i]->anchor->IsShown())
437 return wxRICHTEXT_HITTEST_NONE;
438
439 wxPoint point = array[i]->anchor->GetPosition();
440 wxSize size = array[i]->anchor->GetCachedSize();
441 if (point.x <= pt.x && point.x + size.x >= pt.x
442 && point.y <= pt.y && point.y + size.y >= pt.y)
443 {
444 textPosition = array[i]->anchor->GetRange().GetStart();
445 * obj = array[i]->anchor;
446 if (pt.x > (pt.x + pt.x + size.x) / 2)
447 return wxRICHTEXT_HITTEST_BEFORE;
448 else
449 return wxRICHTEXT_HITTEST_AFTER;
450 }
451
452 return wxRICHTEXT_HITTEST_NONE;
453 }
454
455 int wxRichTextFloatCollector::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, int flags)
456 {
457 int ret = HitTestFloat(m_left, dc, context, pt, textPosition, obj, flags);
458 if (ret == wxRICHTEXT_HITTEST_NONE)
459 {
460 ret = HitTestFloat(m_right, dc, context, pt, textPosition, obj, flags);
461 }
462 return ret;
463 }
464
465 // Helpers for efficiency
466 inline void wxCheckSetFont(wxDC& dc, const wxFont& font)
467 {
468 dc.SetFont(font);
469 }
470
471 inline void wxCheckSetPen(wxDC& dc, const wxPen& pen)
472 {
473 const wxPen& pen1 = dc.GetPen();
474 if (pen1.IsOk() && pen.IsOk())
475 {
476 if (pen1.GetWidth() == pen.GetWidth() &&
477 pen1.GetStyle() == pen.GetStyle() &&
478 pen1.GetColour() == pen.GetColour())
479 return;
480 }
481 dc.SetPen(pen);
482 }
483
484 inline void wxCheckSetBrush(wxDC& dc, const wxBrush& brush)
485 {
486 const wxBrush& brush1 = dc.GetBrush();
487 if (brush1.IsOk() && brush.IsOk())
488 {
489 if (brush1.GetStyle() == brush.GetStyle() &&
490 brush1.GetColour() == brush.GetColour())
491 return;
492 }
493 dc.SetBrush(brush);
494 }
495
496 /*!
497 * wxRichTextObject
498 * This is the base for drawable objects.
499 */
500
501 IMPLEMENT_CLASS(wxRichTextObject, wxObject)
502
503 wxRichTextObject::wxRichTextObject(wxRichTextObject* parent)
504 {
505 m_refCount = 1;
506 m_parent = parent;
507 m_descent = 0;
508 m_show = true;
509 }
510
511 wxRichTextObject::~wxRichTextObject()
512 {
513 }
514
515 void wxRichTextObject::Dereference()
516 {
517 m_refCount --;
518 if (m_refCount <= 0)
519 delete this;
520 }
521
522 /// Copy
523 void wxRichTextObject::Copy(const wxRichTextObject& obj)
524 {
525 m_size = obj.m_size;
526 m_maxSize = obj.m_maxSize;
527 m_minSize = obj.m_minSize;
528 m_pos = obj.m_pos;
529 m_range = obj.m_range;
530 m_ownRange = obj.m_ownRange;
531 m_attributes = obj.m_attributes;
532 m_properties = obj.m_properties;
533 m_descent = obj.m_descent;
534 m_show = obj.m_show;
535 }
536
537 // Get/set the top-level container of this object.
538 wxRichTextParagraphLayoutBox* wxRichTextObject::GetContainer() const
539 {
540 const wxRichTextObject* p = this;
541 while (p)
542 {
543 if (p->IsTopLevel())
544 {
545 return wxDynamicCast(p, wxRichTextParagraphLayoutBox);
546 }
547 p = p->GetParent();
548 }
549 return NULL;
550 }
551
552 void wxRichTextObject::SetMargins(int margin)
553 {
554 SetMargins(margin, margin, margin, margin);
555 }
556
557 void wxRichTextObject::SetMargins(int leftMargin, int rightMargin, int topMargin, int bottomMargin)
558 {
559 GetAttributes().GetTextBoxAttr().GetMargins().GetLeft().SetValue(leftMargin, wxTEXT_ATTR_UNITS_PIXELS);
560 GetAttributes().GetTextBoxAttr().GetMargins().GetRight().SetValue(rightMargin, wxTEXT_ATTR_UNITS_PIXELS);
561 GetAttributes().GetTextBoxAttr().GetMargins().GetTop().SetValue(topMargin, wxTEXT_ATTR_UNITS_PIXELS);
562 GetAttributes().GetTextBoxAttr().GetMargins().GetBottom().SetValue(bottomMargin, wxTEXT_ATTR_UNITS_PIXELS);
563 }
564
565 int wxRichTextObject::GetLeftMargin() const
566 {
567 return GetAttributes().GetTextBoxAttr().GetMargins().GetLeft().GetValue();
568 }
569
570 int wxRichTextObject::GetRightMargin() const
571 {
572 return GetAttributes().GetTextBoxAttr().GetMargins().GetRight().GetValue();
573 }
574
575 int wxRichTextObject::GetTopMargin() const
576 {
577 return GetAttributes().GetTextBoxAttr().GetMargins().GetTop().GetValue();
578 }
579
580 int wxRichTextObject::GetBottomMargin() const
581 {
582 return GetAttributes().GetTextBoxAttr().GetMargins().GetBottom().GetValue();
583 }
584
585 // Calculate the available content space in the given rectangle, given the
586 // margins, border and padding specified in the object's attributes.
587 wxRect wxRichTextObject::GetAvailableContentArea(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& outerRect) const
588 {
589 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
590 marginRect = outerRect;
591 wxRichTextAttr attr(GetAttributes());
592 context.ApplyVirtualAttributes(attr, (wxRichTextObject*) this);
593 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
594 return contentRect;
595 }
596
597 // Invalidate the buffer. With no argument, invalidates whole buffer.
598 void wxRichTextObject::Invalidate(const wxRichTextRange& invalidRange)
599 {
600 if (invalidRange != wxRICHTEXT_NONE)
601 {
602 // If this is a floating object, size may not be recalculated
603 // after floats have been collected in an early stage of Layout.
604 // So avoid resetting the cache for floating objects during layout.
605 if (!IsFloating())
606 SetCachedSize(wxDefaultSize);
607 SetMaxSize(wxDefaultSize);
608 SetMinSize(wxDefaultSize);
609 }
610 }
611
612 // Convert units in tenths of a millimetre to device units
613 int wxRichTextObject::ConvertTenthsMMToPixels(wxDC& dc, int units) const
614 {
615 // Unscale
616 double scale = 1.0;
617 if (GetBuffer())
618 scale = GetBuffer()->GetScale() / GetBuffer()->GetDimensionScale();
619 int p = ConvertTenthsMMToPixels(dc.GetPPI().x, units, scale);
620
621 return p;
622 }
623
624 // Convert units in tenths of a millimetre to device units
625 int wxRichTextObject::ConvertTenthsMMToPixels(int ppi, int units, double scale)
626 {
627 // There are ppi pixels in 254.1 "1/10 mm"
628
629 double pixels = ((double) units * (double)ppi) / 254.1;
630 if (scale != 1.0)
631 pixels /= scale;
632
633 // If the result is very small, make it at least one pixel in size.
634 if (pixels == 0 && units > 0)
635 pixels = 1;
636
637 return (int) pixels;
638 }
639
640 // Convert units in pixels to tenths of a millimetre
641 int wxRichTextObject::ConvertPixelsToTenthsMM(wxDC& dc, int pixels) const
642 {
643 int p = pixels;
644 double scale = 1.0;
645 if (GetBuffer())
646 scale = GetBuffer()->GetScale();
647
648 return ConvertPixelsToTenthsMM(dc.GetPPI().x, p, scale);
649 }
650
651 int wxRichTextObject::ConvertPixelsToTenthsMM(int ppi, int pixels, double scale)
652 {
653 // There are ppi pixels in 254.1 "1/10 mm"
654
655 double p = double(pixels);
656
657 if (scale != 1.0)
658 p *= scale;
659
660 int units = int( p * 254.1 / (double) ppi );
661 return units;
662 }
663
664 // Draw the borders and background for the given rectangle and attributes.
665 // Width and height are taken to be the outer margin size, not the content.
666 bool wxRichTextObject::DrawBoxAttributes(wxDC& dc, wxRichTextBuffer* buffer, const wxRichTextAttr& attr, const wxRect& boxRect, int flags)
667 {
668 // Assume boxRect is the area around the content
669 wxRect marginRect = boxRect;
670 wxRect contentRect, borderRect, paddingRect, outlineRect;
671
672 GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
673
674 // Margin is transparent. Draw background from margin.
675 if (attr.HasBackgroundColour() || (flags & wxRICHTEXT_DRAW_SELECTED))
676 {
677 wxColour colour;
678 if (flags & wxRICHTEXT_DRAW_SELECTED)
679 {
680 // TODO: get selection colour from control?
681 colour = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT);
682 }
683 else
684 colour = attr.GetBackgroundColour();
685
686 wxPen pen(colour);
687 wxBrush brush(colour);
688
689 dc.SetPen(pen);
690 dc.SetBrush(brush);
691 dc.DrawRectangle(borderRect);
692 }
693
694 if (flags & wxRICHTEXT_DRAW_GUIDELINES)
695 {
696 wxRichTextAttr editBorderAttr = attr;
697 // TODO: make guideline colour configurable
698 editBorderAttr.GetTextBoxAttr().GetBorder().SetColour(*wxLIGHT_GREY);
699 editBorderAttr.GetTextBoxAttr().GetBorder().SetWidth(1, wxTEXT_ATTR_UNITS_PIXELS);
700 editBorderAttr.GetTextBoxAttr().GetBorder().SetStyle(wxTEXT_BOX_ATTR_BORDER_SOLID);
701
702 DrawBorder(dc, buffer, editBorderAttr.GetTextBoxAttr().GetBorder(), borderRect, flags);
703 }
704
705 if (attr.GetTextBoxAttr().GetBorder().IsValid())
706 DrawBorder(dc, buffer, attr.GetTextBoxAttr().GetBorder(), borderRect);
707
708 if (attr.GetTextBoxAttr().GetOutline().IsValid())
709 DrawBorder(dc, buffer, attr.GetTextBoxAttr().GetOutline(), outlineRect);
710
711 return true;
712 }
713
714 // Draw a border
715 bool wxRichTextObject::DrawBorder(wxDC& dc, wxRichTextBuffer* buffer, const wxTextAttrBorders& attr, const wxRect& rect, int WXUNUSED(flags))
716 {
717 int borderLeft = 0, borderRight = 0, borderTop = 0, borderBottom = 0;
718 wxTextAttrDimensionConverter converter(dc, buffer ? buffer->GetScale() : 1.0);
719
720 if (attr.GetLeft().IsValid() && attr.GetLeft().GetStyle() != wxTEXT_BOX_ATTR_BORDER_NONE)
721 {
722 borderLeft = converter.GetPixels(attr.GetLeft().GetWidth());
723 wxColour col(attr.GetLeft().GetColour());
724
725 // If pen width is > 1, resorts to a solid rectangle.
726 if (borderLeft == 1)
727 {
728 int penStyle = wxSOLID;
729 if (attr.GetLeft().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DOTTED)
730 penStyle = wxDOT;
731 else if (attr.GetLeft().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DASHED)
732 penStyle = wxLONG_DASH;
733 wxPen pen(col, 1, penStyle);
734 dc.SetPen(pen);
735 dc.DrawLine(rect.x, rect.y, rect.x, rect.y + rect.height);
736
737 }
738 else if (borderLeft > 1)
739 {
740 wxPen pen(col);
741 wxBrush brush(col);
742 dc.SetPen(pen);
743 dc.SetBrush(brush);
744 dc.DrawRectangle(rect.x, rect.y, borderLeft, rect.height);
745 }
746 }
747
748 if (attr.GetRight().IsValid() && attr.GetRight().GetStyle() != wxTEXT_BOX_ATTR_BORDER_NONE)
749 {
750 borderRight = converter.GetPixels(attr.GetRight().GetWidth());
751
752 wxColour col(attr.GetRight().GetColour());
753
754 // If pen width is > 1, resorts to a solid rectangle.
755 if (borderRight == 1)
756 {
757 int penStyle = wxSOLID;
758 if (attr.GetRight().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DOTTED)
759 penStyle = wxDOT;
760 else if (attr.GetRight().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DASHED)
761 penStyle = wxLONG_DASH;
762 wxPen pen(col, 1, penStyle);
763 dc.SetPen(pen);
764 dc.DrawLine(rect.x + rect.width, rect.y, rect.x + rect.width, rect.y + rect.height + 1);
765
766 }
767 else if (borderRight > 1)
768 {
769 wxPen pen(col);
770 wxBrush brush(col);
771 dc.SetPen(pen);
772 dc.SetBrush(brush);
773 dc.DrawRectangle(rect.x + rect.width - borderRight, rect.y, borderRight, rect.height);
774 }
775 }
776
777 if (attr.GetTop().IsValid() && attr.GetTop().GetStyle() != wxTEXT_BOX_ATTR_BORDER_NONE)
778 {
779 borderTop = converter.GetPixels(attr.GetTop().GetWidth());
780
781 wxColour col(attr.GetTop().GetColour());
782
783 // If pen width is > 1, resorts to a solid rectangle.
784 if (borderTop == 1)
785 {
786 int penStyle = wxSOLID;
787 if (attr.GetTop().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DOTTED)
788 penStyle = wxDOT;
789 else if (attr.GetTop().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DASHED)
790 penStyle = wxLONG_DASH;
791 wxPen pen(col, 1, penStyle);
792 dc.SetPen(pen);
793 dc.DrawLine(rect.x, rect.y, rect.x + rect.width, rect.y);
794
795 }
796 else if (borderTop > 1)
797 {
798 wxPen pen(col);
799 wxBrush brush(col);
800 dc.SetPen(pen);
801 dc.SetBrush(brush);
802 dc.DrawRectangle(rect.x, rect.y, rect.width, borderTop);
803 }
804 }
805
806 if (attr.GetBottom().IsValid() && attr.GetBottom().GetStyle() != wxTEXT_BOX_ATTR_BORDER_NONE)
807 {
808 borderBottom = converter.GetPixels(attr.GetBottom().GetWidth());
809 wxColour col(attr.GetTop().GetColour());
810
811 // If pen width is > 1, resorts to a solid rectangle.
812 if (borderBottom == 1)
813 {
814 int penStyle = wxSOLID;
815 if (attr.GetBottom().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DOTTED)
816 penStyle = wxDOT;
817 else if (attr.GetBottom().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DASHED)
818 penStyle = wxLONG_DASH;
819 wxPen pen(col, 1, penStyle);
820 dc.SetPen(pen);
821 dc.DrawLine(rect.x, rect.y + rect.height, rect.x + rect.width, rect.y + rect.height);
822
823 }
824 else if (borderBottom > 1)
825 {
826 wxPen pen(col);
827 wxBrush brush(col);
828 dc.SetPen(pen);
829 dc.SetBrush(brush);
830 dc.DrawRectangle(rect.x, rect.y + rect.height - borderBottom, rect.width, borderBottom);
831 }
832 }
833
834 return true;
835 }
836
837 // Get the various rectangles of the box model in pixels. You can either specify contentRect (inner)
838 // or marginRect (outer), and the other must be the default rectangle (no width or height).
839 // Note that the outline doesn't affect the position of the rectangle, it's drawn in whatever space
840 // is available.
841 //
842 // | Margin | Border | Padding | CONTENT | Padding | Border | Margin |
843
844 bool wxRichTextObject::GetBoxRects(wxDC& dc, wxRichTextBuffer* buffer, const wxRichTextAttr& attr, wxRect& marginRect, wxRect& borderRect, wxRect& contentRect, wxRect& paddingRect, wxRect& outlineRect)
845 {
846 int borderLeft = 0, borderRight = 0, borderTop = 0, borderBottom = 0;
847 int outlineLeft = 0, outlineRight = 0, outlineTop = 0, outlineBottom = 0;
848 int paddingLeft = 0, paddingRight = 0, paddingTop = 0, paddingBottom = 0;
849 int marginLeft = 0, marginRight = 0, marginTop = 0, marginBottom = 0;
850
851 wxTextAttrDimensionConverter converter(dc, buffer ? buffer->GetScale() : 1.0);
852
853 if (attr.GetTextBoxAttr().GetMargins().GetLeft().IsValid())
854 marginLeft = converter.GetPixels(attr.GetTextBoxAttr().GetMargins().GetLeft());
855 if (attr.GetTextBoxAttr().GetMargins().GetRight().IsValid())
856 marginRight = converter.GetPixels(attr.GetTextBoxAttr().GetMargins().GetRight());
857 if (attr.GetTextBoxAttr().GetMargins().GetTop().IsValid())
858 marginTop = converter.GetPixels(attr.GetTextBoxAttr().GetMargins().GetTop());
859 if (attr.GetTextBoxAttr().GetMargins().GetBottom().IsValid())
860 marginBottom = converter.GetPixels(attr.GetTextBoxAttr().GetMargins().GetBottom());
861
862 if (attr.GetTextBoxAttr().GetBorder().GetLeft().GetWidth().IsValid())
863 borderLeft = converter.GetPixels(attr.GetTextBoxAttr().GetBorder().GetLeft().GetWidth());
864 if (attr.GetTextBoxAttr().GetBorder().GetRight().GetWidth().IsValid())
865 borderRight = converter.GetPixels(attr.GetTextBoxAttr().GetBorder().GetRight().GetWidth());
866 if (attr.GetTextBoxAttr().GetBorder().GetTop().GetWidth().IsValid())
867 borderTop = converter.GetPixels(attr.GetTextBoxAttr().GetBorder().GetTop().GetWidth());
868 if (attr.GetTextBoxAttr().GetBorder().GetBottom().GetWidth().IsValid())
869 borderBottom = converter.GetPixels(attr.GetTextBoxAttr().GetBorder().GetBottom().GetWidth());
870
871 if (attr.GetTextBoxAttr().GetPadding().GetLeft().IsValid())
872 paddingLeft = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetLeft());
873 if (attr.GetTextBoxAttr().GetPadding().GetRight().IsValid())
874 paddingRight = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetRight());
875 if (attr.GetTextBoxAttr().GetPadding().GetTop().IsValid())
876 paddingTop = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetTop());
877 if (attr.GetTextBoxAttr().GetPadding().GetBottom().IsValid())
878 paddingBottom = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetBottom());
879
880 if (attr.GetTextBoxAttr().GetOutline().GetLeft().GetWidth().IsValid())
881 outlineLeft = converter.GetPixels(attr.GetTextBoxAttr().GetOutline().GetLeft().GetWidth());
882 if (attr.GetTextBoxAttr().GetOutline().GetRight().GetWidth().IsValid())
883 outlineRight = converter.GetPixels(attr.GetTextBoxAttr().GetOutline().GetRight().GetWidth());
884 if (attr.GetTextBoxAttr().GetOutline().GetTop().GetWidth().IsValid())
885 outlineTop = converter.GetPixels(attr.GetTextBoxAttr().GetOutline().GetTop().GetWidth());
886 if (attr.GetTextBoxAttr().GetOutline().GetBottom().GetWidth().IsValid())
887 outlineBottom = converter.GetPixels(attr.GetTextBoxAttr().GetOutline().GetBottom().GetWidth());
888
889 int leftTotal = marginLeft + borderLeft + paddingLeft;
890 int rightTotal = marginRight + borderRight + paddingRight;
891 int topTotal = marginTop + borderTop + paddingTop;
892 int bottomTotal = marginBottom + borderBottom + paddingBottom;
893
894 if (marginRect != wxRect())
895 {
896 contentRect.x = marginRect.x + leftTotal;
897 contentRect.y = marginRect.y + topTotal;
898 contentRect.width = marginRect.width - (leftTotal + rightTotal);
899 contentRect.height = marginRect.height - (topTotal + bottomTotal);
900 }
901 else
902 {
903 marginRect.x = contentRect.x - leftTotal;
904 marginRect.y = contentRect.y - topTotal;
905 marginRect.width = contentRect.width + (leftTotal + rightTotal);
906 marginRect.height = contentRect.height + (topTotal + bottomTotal);
907 }
908
909 borderRect.x = marginRect.x + marginLeft;
910 borderRect.y = marginRect.y + marginTop;
911 borderRect.width = marginRect.width - (marginLeft + marginRight);
912 borderRect.height = marginRect.height - (marginTop + marginBottom);
913
914 paddingRect.x = marginRect.x + marginLeft + borderLeft;
915 paddingRect.y = marginRect.y + marginTop + borderTop;
916 paddingRect.width = marginRect.width - (marginLeft + marginRight + borderLeft + borderRight);
917 paddingRect.height = marginRect.height - (marginTop + marginBottom + borderTop + borderBottom);
918
919 // The outline is outside the margin and doesn't influence the overall box position or content size.
920 outlineRect.x = marginRect.x - outlineLeft;
921 outlineRect.y = marginRect.y - outlineTop;
922 outlineRect.width = marginRect.width + (outlineLeft + outlineRight);
923 outlineRect.height = marginRect.height + (outlineTop + outlineBottom);
924
925 return true;
926 }
927
928 // Get the total margin for the object in pixels, taking into account margin, padding and border size
929 bool wxRichTextObject::GetTotalMargin(wxDC& dc, wxRichTextBuffer* buffer, const wxRichTextAttr& attr, int& leftMargin, int& rightMargin,
930 int& topMargin, int& bottomMargin)
931 {
932 // Assume boxRect is the area around the content
933 wxRect contentRect, marginRect, borderRect, paddingRect, outlineRect;
934 marginRect = wxRect(0, 0, 1000, 1000);
935
936 GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
937
938 leftMargin = contentRect.GetLeft() - marginRect.GetLeft();
939 rightMargin = marginRect.GetRight() - contentRect.GetRight();
940 topMargin = contentRect.GetTop() - marginRect.GetTop();
941 bottomMargin = marginRect.GetBottom() - contentRect.GetBottom();
942
943 return true;
944 }
945
946 // Returns the rectangle which the child has available to it given restrictions specified in the
947 // child attribute, e.g. 50% width of the parent, 400 pixels, x position 20% of the parent, etc.
948 // availableContainerSpace might be a parent that the cell has to compute its width relative to.
949 // E.g. a cell that's 50% of its parent.
950 wxRect wxRichTextObject::AdjustAvailableSpace(wxDC& dc, wxRichTextBuffer* buffer, const wxRichTextAttr& WXUNUSED(parentAttr), const wxRichTextAttr& childAttr, const wxRect& availableParentSpace, const wxRect& availableContainerSpace)
951 {
952 wxRect rect = availableParentSpace;
953 double scale = 1.0;
954 if (buffer)
955 scale = buffer->GetScale();
956
957 wxTextAttrDimensionConverter converter(dc, scale, availableContainerSpace.GetSize());
958
959 if (childAttr.GetTextBoxAttr().GetWidth().IsValid())
960 rect.width = converter.GetPixels(childAttr.GetTextBoxAttr().GetWidth());
961
962 if (childAttr.GetTextBoxAttr().GetHeight().IsValid())
963 rect.height = converter.GetPixels(childAttr.GetTextBoxAttr().GetHeight());
964
965 // Can specify either left or right for the position (we're assuming we can't
966 // set the left and right edges to effectively set the size. Would we want to do that?)
967 if (childAttr.GetTextBoxAttr().GetPosition().GetLeft().IsValid())
968 {
969 rect.x = rect.x + converter.GetPixels(childAttr.GetTextBoxAttr().GetPosition().GetLeft());
970 }
971 else if (childAttr.GetTextBoxAttr().GetPosition().GetRight().IsValid())
972 {
973 int x = converter.GetPixels(childAttr.GetTextBoxAttr().GetPosition().GetRight());
974 if (childAttr.GetTextBoxAttr().GetPosition().GetRight().GetPosition() == wxTEXT_BOX_ATTR_POSITION_RELATIVE)
975 rect.x = availableContainerSpace.x + availableContainerSpace.width - rect.width;
976 else
977 rect.x += x;
978 }
979
980 if (childAttr.GetTextBoxAttr().GetPosition().GetTop().IsValid())
981 {
982 rect.y = rect.y + converter.GetPixels(childAttr.GetTextBoxAttr().GetPosition().GetTop());
983 }
984 else if (childAttr.GetTextBoxAttr().GetPosition().GetBottom().IsValid())
985 {
986 int y = converter.GetPixels(childAttr.GetTextBoxAttr().GetPosition().GetBottom());
987 if (childAttr.GetTextBoxAttr().GetPosition().GetBottom().GetPosition() == wxTEXT_BOX_ATTR_POSITION_RELATIVE)
988 rect.y = availableContainerSpace.y + availableContainerSpace.height - rect.height;
989 else
990 rect.y += y;
991 }
992
993 if (rect.GetWidth() > availableParentSpace.GetWidth())
994 rect.SetWidth(availableParentSpace.GetWidth());
995
996 return rect;
997 }
998
999 // Dump to output stream for debugging
1000 void wxRichTextObject::Dump(wxTextOutputStream& stream)
1001 {
1002 stream << GetClassInfo()->GetClassName() << wxT("\n");
1003 stream << wxString::Format(wxT("Size: %d,%d. Position: %d,%d, Range: %ld,%ld"), m_size.x, m_size.y, m_pos.x, m_pos.y, m_range.GetStart(), m_range.GetEnd()) << wxT("\n");
1004 stream << wxString::Format(wxT("Text colour: %d,%d,%d."), (int) m_attributes.GetTextColour().Red(), (int) m_attributes.GetTextColour().Green(), (int) m_attributes.GetTextColour().Blue()) << wxT("\n");
1005 }
1006
1007 // Gets the containing buffer
1008 wxRichTextBuffer* wxRichTextObject::GetBuffer() const
1009 {
1010 const wxRichTextObject* obj = this;
1011 while (obj && !wxDynamicCast(obj, wxRichTextBuffer))
1012 obj = obj->GetParent();
1013 return wxDynamicCast(obj, wxRichTextBuffer);
1014 }
1015
1016 // Get the absolute object position, by traversing up the child/parent hierarchy
1017 wxPoint wxRichTextObject::GetAbsolutePosition() const
1018 {
1019 wxPoint pt = GetPosition();
1020
1021 wxRichTextObject* p = GetParent();
1022 while (p)
1023 {
1024 pt = pt + p->GetPosition();
1025 p = p->GetParent();
1026 }
1027
1028 return pt;
1029 }
1030
1031 // Hit-testing: returns a flag indicating hit test details, plus
1032 // information about position
1033 int wxRichTextObject::HitTest(wxDC& WXUNUSED(dc), wxRichTextDrawingContext& WXUNUSED(context), const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int WXUNUSED(flags))
1034 {
1035 if (!IsShown())
1036 return wxRICHTEXT_HITTEST_NONE;
1037
1038 wxRect rect = GetRect();
1039 if (pt.x >= rect.x && pt.x < rect.x + rect.width &&
1040 pt.y >= rect.y && pt.y < rect.y + rect.height)
1041 {
1042 *obj = this;
1043 *contextObj = GetParentContainer();
1044 textPosition = GetRange().GetStart();
1045 return wxRICHTEXT_HITTEST_ON;
1046 }
1047 else
1048 return wxRICHTEXT_HITTEST_NONE;
1049 }
1050
1051 // Lays out the object first with a given amount of space, and then if no width was specified in attr,
1052 // lays out the object again using the maximum ('best') size
1053 bool wxRichTextObject::LayoutToBestSize(wxDC& dc, wxRichTextDrawingContext& context, wxRichTextBuffer* buffer,
1054 const wxRichTextAttr& parentAttr, const wxRichTextAttr& attr,
1055 const wxRect& availableParentSpace, const wxRect& availableContainerSpace,
1056 int style)
1057 {
1058 wxRect availableChildRect = AdjustAvailableSpace(dc, buffer, parentAttr, attr, availableParentSpace, availableContainerSpace);
1059 wxRect originalAvailableRect = availableChildRect;
1060 Layout(dc, context, availableChildRect, availableContainerSpace, style);
1061
1062 wxSize maxSize = GetMaxSize();
1063
1064 // Don't ignore if maxSize.x is zero, since we need to redo the paragraph's lines
1065 // on this basis
1066 if (!attr.GetTextBoxAttr().GetWidth().IsValid() && maxSize.x < availableChildRect.width)
1067 {
1068 // Redo the layout with a fixed, minimum size this time.
1069 Invalidate(wxRICHTEXT_ALL);
1070 wxRichTextAttr newAttr(attr);
1071 newAttr.GetTextBoxAttr().GetWidth().SetValue(maxSize.x, wxTEXT_ATTR_UNITS_PIXELS);
1072 newAttr.GetTextBoxAttr().GetWidth().SetPosition(wxTEXT_BOX_ATTR_POSITION_ABSOLUTE);
1073
1074 availableChildRect = AdjustAvailableSpace(dc, buffer, parentAttr, newAttr, availableParentSpace, availableContainerSpace);
1075
1076 // If a paragraph, align the whole paragraph.
1077 // Problem with this: if we're limited by a floating object, a line may be centered
1078 // w.r.t. the smaller resulting box rather than the actual available width.
1079 if (attr.HasAlignment() && !GetContainer()->GetFloatCollector()->HasFloats()) // FIXME: aligning whole paragraph not compatible with floating objects
1080 {
1081 // centering, right-justification
1082 if (attr.GetAlignment() == wxTEXT_ALIGNMENT_CENTRE)
1083 {
1084 availableChildRect.x = (originalAvailableRect.GetWidth() - availableChildRect.GetWidth())/2 + availableChildRect.x;
1085 }
1086 else if (attr.GetAlignment() == wxTEXT_ALIGNMENT_RIGHT)
1087 {
1088 availableChildRect.x = availableChildRect.x + originalAvailableRect.GetWidth() - availableChildRect.GetWidth();
1089 }
1090 }
1091
1092 Layout(dc, context, availableChildRect, availableContainerSpace, style);
1093 }
1094
1095 /*
1096 __________________
1097 | ____________ |
1098 | | | |
1099
1100
1101 */
1102
1103 return true;
1104 }
1105
1106 // Move the object recursively, by adding the offset from old to new
1107 void wxRichTextObject::Move(const wxPoint& pt)
1108 {
1109 SetPosition(pt);
1110 }
1111
1112
1113 /*!
1114 * wxRichTextCompositeObject
1115 * This is the base for drawable objects.
1116 */
1117
1118 IMPLEMENT_CLASS(wxRichTextCompositeObject, wxRichTextObject)
1119
1120 wxRichTextCompositeObject::wxRichTextCompositeObject(wxRichTextObject* parent):
1121 wxRichTextObject(parent)
1122 {
1123 }
1124
1125 wxRichTextCompositeObject::~wxRichTextCompositeObject()
1126 {
1127 DeleteChildren();
1128 }
1129
1130 /// Get the nth child
1131 wxRichTextObject* wxRichTextCompositeObject::GetChild(size_t n) const
1132 {
1133 wxASSERT ( n < m_children.GetCount() );
1134
1135 return m_children.Item(n)->GetData();
1136 }
1137
1138 /// Append a child, returning the position
1139 size_t wxRichTextCompositeObject::AppendChild(wxRichTextObject* child)
1140 {
1141 m_children.Append(child);
1142 child->SetParent(this);
1143 return m_children.GetCount() - 1;
1144 }
1145
1146 /// Insert the child in front of the given object, or at the beginning
1147 bool wxRichTextCompositeObject::InsertChild(wxRichTextObject* child, wxRichTextObject* inFrontOf)
1148 {
1149 if (inFrontOf)
1150 {
1151 wxRichTextObjectList::compatibility_iterator node = m_children.Find(inFrontOf);
1152 m_children.Insert(node, child);
1153 }
1154 else
1155 m_children.Insert(child);
1156 child->SetParent(this);
1157
1158 return true;
1159 }
1160
1161 /// Delete the child
1162 bool wxRichTextCompositeObject::RemoveChild(wxRichTextObject* child, bool deleteChild)
1163 {
1164 wxRichTextObjectList::compatibility_iterator node = m_children.Find(child);
1165 if (node)
1166 {
1167 wxRichTextObject* obj = node->GetData();
1168 m_children.Erase(node);
1169 if (deleteChild)
1170 delete obj;
1171
1172 return true;
1173 }
1174 return false;
1175 }
1176
1177 /// Delete all children
1178 bool wxRichTextCompositeObject::DeleteChildren()
1179 {
1180 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1181 while (node)
1182 {
1183 wxRichTextObjectList::compatibility_iterator oldNode = node;
1184
1185 wxRichTextObject* child = node->GetData();
1186 child->Dereference(); // Only delete if reference count is zero
1187
1188 node = node->GetNext();
1189 m_children.Erase(oldNode);
1190 }
1191
1192 return true;
1193 }
1194
1195 /// Get the child count
1196 size_t wxRichTextCompositeObject::GetChildCount() const
1197 {
1198 return m_children.GetCount();
1199 }
1200
1201 /// Copy
1202 void wxRichTextCompositeObject::Copy(const wxRichTextCompositeObject& obj)
1203 {
1204 wxRichTextObject::Copy(obj);
1205
1206 DeleteChildren();
1207
1208 wxRichTextObjectList::compatibility_iterator node = obj.m_children.GetFirst();
1209 while (node)
1210 {
1211 wxRichTextObject* child = node->GetData();
1212 wxRichTextObject* newChild = child->Clone();
1213 newChild->SetParent(this);
1214 m_children.Append(newChild);
1215
1216 node = node->GetNext();
1217 }
1218 }
1219
1220 /// Hit-testing: returns a flag indicating hit test details, plus
1221 /// information about position
1222 int wxRichTextCompositeObject::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int flags)
1223 {
1224 if (!IsShown())
1225 return wxRICHTEXT_HITTEST_NONE;
1226
1227 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1228 while (node)
1229 {
1230 wxRichTextObject* child = node->GetData();
1231
1232 if (child->IsShown() && child->IsTopLevel() && (flags & wxRICHTEXT_HITTEST_NO_NESTED_OBJECTS))
1233 {
1234 // Just check if we hit the overall object
1235 int ret = child->wxRichTextObject::HitTest(dc, context, pt, textPosition, obj, contextObj, flags);
1236 if (ret != wxRICHTEXT_HITTEST_NONE)
1237 return ret;
1238 }
1239 else if (child->IsShown())
1240 {
1241 int ret = child->HitTest(dc, context, pt, textPosition, obj, contextObj, flags);
1242 if (ret != wxRICHTEXT_HITTEST_NONE)
1243 return ret;
1244 }
1245
1246 node = node->GetNext();
1247 }
1248
1249 return wxRICHTEXT_HITTEST_NONE;
1250 }
1251
1252 /// Finds the absolute position and row height for the given character position
1253 bool wxRichTextCompositeObject::FindPosition(wxDC& dc, wxRichTextDrawingContext& context, long index, wxPoint& pt, int* height, bool forceLineStart)
1254 {
1255 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1256 while (node)
1257 {
1258 wxRichTextObject* child = node->GetData();
1259
1260 // Don't recurse if the child is a top-level object,
1261 // such as a text box, because the character position will no longer
1262 // apply. By definition, a top-level object has its own range of
1263 // character positions.
1264 if (!child->IsTopLevel() && child->FindPosition(dc, context, index, pt, height, forceLineStart))
1265 return true;
1266
1267 node = node->GetNext();
1268 }
1269
1270 return false;
1271 }
1272
1273 /// Calculate range
1274 void wxRichTextCompositeObject::CalculateRange(long start, long& end)
1275 {
1276 long current = start;
1277 long lastEnd = current;
1278
1279 if (IsTopLevel())
1280 {
1281 current = 0;
1282 lastEnd = 0;
1283 }
1284
1285 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1286 while (node)
1287 {
1288 wxRichTextObject* child = node->GetData();
1289 long childEnd = 0;
1290
1291 child->CalculateRange(current, childEnd);
1292 lastEnd = childEnd;
1293
1294 current = childEnd + 1;
1295
1296 node = node->GetNext();
1297 }
1298
1299 if (IsTopLevel())
1300 {
1301 // A top-level object always has a range of size 1,
1302 // because its children don't count at this level.
1303 end = start;
1304 m_range.SetRange(start, start);
1305
1306 // An object with no children has zero length
1307 if (m_children.GetCount() == 0)
1308 lastEnd --;
1309 m_ownRange.SetRange(0, lastEnd);
1310 }
1311 else
1312 {
1313 end = lastEnd;
1314
1315 // An object with no children has zero length
1316 if (m_children.GetCount() == 0)
1317 end --;
1318
1319 m_range.SetRange(start, end);
1320 }
1321 }
1322
1323 /// Delete range from layout.
1324 bool wxRichTextCompositeObject::DeleteRange(const wxRichTextRange& range)
1325 {
1326 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1327
1328 while (node)
1329 {
1330 wxRichTextObject* obj = (wxRichTextObject*) node->GetData();
1331 wxRichTextObjectList::compatibility_iterator next = node->GetNext();
1332
1333 // Delete the range in each paragraph
1334
1335 // When a chunk has been deleted, internally the content does not
1336 // now match the ranges.
1337 // However, so long as deletion is not done on the same object twice this is OK.
1338 // If you may delete content from the same object twice, recalculate
1339 // the ranges inbetween DeleteRange calls by calling CalculateRanges, and
1340 // adjust the range you're deleting accordingly.
1341
1342 if (!obj->GetRange().IsOutside(range))
1343 {
1344 // No need to delete within a top-level object; just removing this object will do fine
1345 if (!obj->IsTopLevel())
1346 obj->DeleteRange(range);
1347
1348 // Delete an empty object, or paragraph within this range.
1349 if (obj->IsEmpty() ||
1350 (range.GetStart() <= obj->GetRange().GetStart() && range.GetEnd() >= obj->GetRange().GetEnd()))
1351 {
1352 // An empty paragraph has length 1, so won't be deleted unless the
1353 // whole range is deleted.
1354 RemoveChild(obj, true);
1355 }
1356 }
1357
1358 node = next;
1359 }
1360
1361 return true;
1362 }
1363
1364 /// Get any text in this object for the given range
1365 wxString wxRichTextCompositeObject::GetTextForRange(const wxRichTextRange& range) const
1366 {
1367 wxString text;
1368 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1369 while (node)
1370 {
1371 wxRichTextObject* child = node->GetData();
1372 wxRichTextRange childRange = range;
1373 if (!child->GetRange().IsOutside(range))
1374 {
1375 childRange.LimitTo(child->GetRange());
1376
1377 wxString childText = child->GetTextForRange(childRange);
1378
1379 text += childText;
1380 }
1381 node = node->GetNext();
1382 }
1383
1384 return text;
1385 }
1386
1387 /// Get the child object at the given character position
1388 wxRichTextObject* wxRichTextCompositeObject::GetChildAtPosition(long pos) const
1389 {
1390 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1391 while (node)
1392 {
1393 wxRichTextObject* child = node->GetData();
1394 if (child->GetRange().GetStart() == pos)
1395 return child;
1396 node = node->GetNext();
1397 }
1398 return NULL;
1399 }
1400
1401 /// Recursively merge all pieces that can be merged.
1402 bool wxRichTextCompositeObject::Defragment(const wxRichTextRange& range)
1403 {
1404 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1405 while (node)
1406 {
1407 wxRichTextObject* child = node->GetData();
1408 if (range == wxRICHTEXT_ALL || !child->GetRange().IsOutside(range))
1409 {
1410 wxRichTextCompositeObject* composite = wxDynamicCast(child, wxRichTextCompositeObject);
1411 if (composite)
1412 composite->Defragment();
1413
1414 if (node->GetNext())
1415 {
1416 wxRichTextObject* nextChild = node->GetNext()->GetData();
1417 if (child->CanMerge(nextChild) && child->Merge(nextChild))
1418 {
1419 nextChild->Dereference();
1420 m_children.Erase(node->GetNext());
1421
1422 // Don't set node -- we'll see if we can merge again with the next
1423 // child.
1424 }
1425 else
1426 node = node->GetNext();
1427 }
1428 else
1429 node = node->GetNext();
1430 }
1431 else
1432 node = node->GetNext();
1433 }
1434
1435 // Delete any remaining empty objects, but leave at least one empty object per composite object.
1436 if (GetChildCount() > 1)
1437 {
1438 node = m_children.GetFirst();
1439 while (node)
1440 {
1441 wxRichTextObjectList::compatibility_iterator next = node->GetNext();
1442 wxRichTextObject* child = node->GetData();
1443 if (range == wxRICHTEXT_ALL || !child->GetRange().IsOutside(range))
1444 {
1445 if (child->IsEmpty())
1446 {
1447 child->Dereference();
1448 m_children.Erase(node);
1449 }
1450 node = next;
1451 }
1452 else
1453 node = node->GetNext();
1454 }
1455 }
1456
1457 return true;
1458 }
1459
1460 /// Dump to output stream for debugging
1461 void wxRichTextCompositeObject::Dump(wxTextOutputStream& stream)
1462 {
1463 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1464 while (node)
1465 {
1466 wxRichTextObject* child = node->GetData();
1467 child->Dump(stream);
1468 node = node->GetNext();
1469 }
1470 }
1471
1472 /// Get/set the object size for the given range. Returns false if the range
1473 /// is invalid for this object.
1474 bool wxRichTextCompositeObject::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, wxPoint position, wxArrayInt* partialExtents) const
1475 {
1476 if (!range.IsWithin(GetRange()))
1477 return false;
1478
1479 wxSize sz;
1480
1481 wxArrayInt childExtents;
1482 wxArrayInt* p;
1483 if (partialExtents)
1484 p = & childExtents;
1485 else
1486 p = NULL;
1487
1488 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1489 while (node)
1490 {
1491 wxRichTextObject* child = node->GetData();
1492 if (!child->GetRange().IsOutside(range))
1493 {
1494 // Floating objects have a zero size within the paragraph.
1495 if (child->IsFloating())
1496 {
1497 if (partialExtents)
1498 {
1499 int lastSize;
1500 if (partialExtents->GetCount() > 0)
1501 lastSize = (*partialExtents)[partialExtents->GetCount()-1];
1502 else
1503 lastSize = 0;
1504
1505 partialExtents->Add(0 /* zero size */ + lastSize);
1506 }
1507 }
1508 else
1509 {
1510 wxSize childSize;
1511
1512 wxRichTextRange rangeToUse = range;
1513 rangeToUse.LimitTo(child->GetRange());
1514 if (child->IsTopLevel())
1515 rangeToUse = child->GetOwnRange();
1516
1517 int childDescent = 0;
1518
1519 // At present wxRICHTEXT_HEIGHT_ONLY is only fast if we're already cached the size,
1520 // but it's only going to be used after caching has taken place.
1521 if ((flags & wxRICHTEXT_HEIGHT_ONLY) && child->GetCachedSize().y != 0)
1522 {
1523 childDescent = child->GetDescent();
1524 childSize = child->GetCachedSize();
1525
1526 sz.y = wxMax(sz.y, childSize.y);
1527 sz.x += childSize.x;
1528 descent = wxMax(descent, childDescent);
1529 }
1530 else if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, context, flags, wxPoint(position.x + sz.x, position.y), p))
1531 {
1532 sz.y = wxMax(sz.y, childSize.y);
1533 sz.x += childSize.x;
1534 descent = wxMax(descent, childDescent);
1535
1536 if ((flags & wxRICHTEXT_CACHE_SIZE) && (rangeToUse == child->GetRange() || child->IsTopLevel()))
1537 {
1538 child->SetCachedSize(childSize);
1539 child->SetDescent(childDescent);
1540 }
1541
1542 if (partialExtents)
1543 {
1544 int lastSize;
1545 if (partialExtents->GetCount() > 0)
1546 lastSize = (*partialExtents)[partialExtents->GetCount()-1];
1547 else
1548 lastSize = 0;
1549
1550 size_t i;
1551 for (i = 0; i < childExtents.GetCount(); i++)
1552 {
1553 partialExtents->Add(childExtents[i] + lastSize);
1554 }
1555 }
1556 }
1557 }
1558
1559 if (p)
1560 p->Clear();
1561 }
1562
1563 node = node->GetNext();
1564 }
1565 size = sz;
1566 return true;
1567 }
1568
1569 // Invalidate the buffer. With no argument, invalidates whole buffer.
1570 void wxRichTextCompositeObject::Invalidate(const wxRichTextRange& invalidRange)
1571 {
1572 wxRichTextObject::Invalidate(invalidRange);
1573
1574 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1575 while (node)
1576 {
1577 wxRichTextObject* child = node->GetData();
1578 if (invalidRange != wxRICHTEXT_ALL && invalidRange != wxRICHTEXT_NONE && child->GetRange().IsOutside(invalidRange))
1579 {
1580 // Skip
1581 }
1582 else if (child->IsTopLevel())
1583 {
1584 if (invalidRange == wxRICHTEXT_NONE)
1585 child->Invalidate(wxRICHTEXT_NONE);
1586 else
1587 child->Invalidate(wxRICHTEXT_ALL); // All children must be invalidated if within parent range
1588 }
1589 else
1590 child->Invalidate(invalidRange);
1591 node = node->GetNext();
1592 }
1593 }
1594
1595 // Move the object recursively, by adding the offset from old to new
1596 void wxRichTextCompositeObject::Move(const wxPoint& pt)
1597 {
1598 wxPoint oldPos = GetPosition();
1599 SetPosition(pt);
1600 wxPoint offset = pt - oldPos;
1601
1602 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1603 while (node)
1604 {
1605 wxRichTextObject* child = node->GetData();
1606 wxPoint childPos = child->GetPosition() + offset;
1607 child->Move(childPos);
1608 node = node->GetNext();
1609 }
1610 }
1611
1612
1613 /*!
1614 * wxRichTextParagraphLayoutBox
1615 * This box knows how to lay out paragraphs.
1616 */
1617
1618 IMPLEMENT_DYNAMIC_CLASS(wxRichTextParagraphLayoutBox, wxRichTextCompositeObject)
1619
1620 wxRichTextParagraphLayoutBox::wxRichTextParagraphLayoutBox(wxRichTextObject* parent):
1621 wxRichTextCompositeObject(parent)
1622 {
1623 Init();
1624 }
1625
1626 wxRichTextParagraphLayoutBox::~wxRichTextParagraphLayoutBox()
1627 {
1628 if (m_floatCollector)
1629 {
1630 delete m_floatCollector;
1631 m_floatCollector = NULL;
1632 }
1633 }
1634
1635 /// Initialize the object.
1636 void wxRichTextParagraphLayoutBox::Init()
1637 {
1638 m_ctrl = NULL;
1639
1640 // For now, assume is the only box and has no initial size.
1641 m_range = wxRichTextRange(0, -1);
1642 m_ownRange = wxRichTextRange(0, -1);
1643
1644 m_invalidRange = wxRICHTEXT_ALL;
1645
1646 m_partialParagraph = false;
1647 m_floatCollector = NULL;
1648 }
1649
1650 void wxRichTextParagraphLayoutBox::Clear()
1651 {
1652 DeleteChildren();
1653
1654 if (m_floatCollector)
1655 delete m_floatCollector;
1656 m_floatCollector = NULL;
1657 m_partialParagraph = false;
1658 }
1659
1660 /// Copy
1661 void wxRichTextParagraphLayoutBox::Copy(const wxRichTextParagraphLayoutBox& obj)
1662 {
1663 Clear();
1664
1665 wxRichTextCompositeObject::Copy(obj);
1666
1667 m_partialParagraph = obj.m_partialParagraph;
1668 m_defaultAttributes = obj.m_defaultAttributes;
1669 }
1670
1671 // Gather information about floating objects; only gather floats for those paragraphs that
1672 // will not be formatted again due to optimization, after which floats will be gathered per-paragraph
1673 // during layout.
1674 bool wxRichTextParagraphLayoutBox::UpdateFloatingObjects(const wxRect& availableRect, wxRichTextObject* untilObj)
1675 {
1676 if (m_floatCollector != NULL)
1677 delete m_floatCollector;
1678 m_floatCollector = new wxRichTextFloatCollector(availableRect);
1679 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1680 // Only gather floats up to the point we'll start formatting paragraphs.
1681 while (untilObj && node && node->GetData() != untilObj)
1682 {
1683 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
1684 wxASSERT (child != NULL);
1685 if (child)
1686 m_floatCollector->CollectFloat(child);
1687 node = node->GetNext();
1688 }
1689
1690 return true;
1691 }
1692
1693 // Returns the style sheet associated with the overall buffer.
1694 wxRichTextStyleSheet* wxRichTextParagraphLayoutBox::GetStyleSheet() const
1695 {
1696 return GetBuffer() ? GetBuffer()->GetStyleSheet() : (wxRichTextStyleSheet*) NULL;
1697 }
1698
1699 // Get the number of floating objects at this level
1700 int wxRichTextParagraphLayoutBox::GetFloatingObjectCount() const
1701 {
1702 if (m_floatCollector)
1703 return m_floatCollector->GetFloatingObjectCount();
1704 else
1705 return 0;
1706 }
1707
1708 // Get a list of floating objects
1709 bool wxRichTextParagraphLayoutBox::GetFloatingObjects(wxRichTextObjectList& objects) const
1710 {
1711 if (m_floatCollector)
1712 {
1713 return m_floatCollector->GetFloatingObjects(objects);
1714 }
1715 else
1716 return false;
1717 }
1718
1719 // Calculate ranges
1720 void wxRichTextParagraphLayoutBox::UpdateRanges()
1721 {
1722 long start = 0;
1723 if (GetParent())
1724 start = GetRange().GetStart();
1725 long end;
1726 CalculateRange(start, end);
1727 }
1728
1729 // HitTest
1730 int wxRichTextParagraphLayoutBox::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int flags)
1731 {
1732 if (!IsShown())
1733 return wxRICHTEXT_HITTEST_NONE;
1734
1735 int ret = wxRICHTEXT_HITTEST_NONE;
1736 if (m_floatCollector && (flags & wxRICHTEXT_HITTEST_NO_FLOATING_OBJECTS) == 0)
1737 ret = m_floatCollector->HitTest(dc, context, pt, textPosition, obj, flags);
1738
1739 if (ret == wxRICHTEXT_HITTEST_NONE)
1740 return wxRichTextCompositeObject::HitTest(dc, context, pt, textPosition, obj, contextObj, flags);
1741 else
1742 {
1743 *contextObj = this;
1744 return ret;
1745 }
1746 }
1747
1748 /// Draw the floating objects
1749 void wxRichTextParagraphLayoutBox::DrawFloats(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
1750 {
1751 if (m_floatCollector)
1752 m_floatCollector->Draw(dc, context, range, selection, rect, descent, style);
1753 }
1754
1755 void wxRichTextParagraphLayoutBox::MoveAnchoredObjectToParagraph(wxRichTextParagraph* from, wxRichTextParagraph* to, wxRichTextObject* obj)
1756 {
1757 if (from == to)
1758 return;
1759
1760 from->RemoveChild(obj);
1761 to->AppendChild(obj);
1762 }
1763
1764 /// Draw the item
1765 bool wxRichTextParagraphLayoutBox::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
1766 {
1767 if (!IsShown())
1768 return true;
1769
1770 wxRect thisRect(GetPosition(), GetCachedSize());
1771
1772 wxRichTextAttr attr(GetAttributes());
1773 context.ApplyVirtualAttributes(attr, this);
1774
1775 int flags = style;
1776 if (selection.IsValid() && GetParentContainer() != this && selection.WithinSelection(GetRange().GetStart(), GetParentContainer()))
1777 flags |= wxRICHTEXT_DRAW_SELECTED;
1778
1779 // Don't draw guidelines if at top level
1780 int theseFlags = flags;
1781 if (!GetParent())
1782 theseFlags &= ~wxRICHTEXT_DRAW_GUIDELINES;
1783 DrawBoxAttributes(dc, GetBuffer(), attr, thisRect, theseFlags);
1784
1785 DrawFloats(dc, context, range, selection, rect, descent, style);
1786 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1787 while (node)
1788 {
1789 wxRichTextObject* child = node->GetData();
1790
1791 if (child && !child->GetRange().IsOutside(range))
1792 {
1793 wxRect childRect(child->GetPosition(), child->GetCachedSize());
1794 wxRichTextRange childRange = range;
1795 if (child->IsTopLevel())
1796 {
1797 childRange = child->GetOwnRange();
1798 }
1799
1800 if (((style & wxRICHTEXT_DRAW_IGNORE_CACHE) == 0) && childRect.GetTop() > rect.GetBottom())
1801 {
1802 // Stop drawing
1803 break;
1804 }
1805 else if (((style & wxRICHTEXT_DRAW_IGNORE_CACHE) == 0) && childRect.GetBottom() < rect.GetTop())
1806 {
1807 // Skip
1808 }
1809 else
1810 child->Draw(dc, context, childRange, selection, rect, descent, style);
1811 }
1812
1813 node = node->GetNext();
1814 }
1815 return true;
1816 }
1817
1818 /// Lay the item out
1819 bool wxRichTextParagraphLayoutBox::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& parentRect, int style)
1820 {
1821 SetPosition(rect.GetPosition());
1822
1823 if (!IsShown())
1824 return true;
1825
1826 wxRect availableSpace;
1827 bool formatRect = (style & wxRICHTEXT_LAYOUT_SPECIFIED_RECT) == wxRICHTEXT_LAYOUT_SPECIFIED_RECT;
1828
1829 wxRichTextAttr attr(GetAttributes());
1830 context.ApplyVirtualAttributes(attr, this);
1831
1832 // If only laying out a specific area, the passed rect has a different meaning:
1833 // the visible part of the buffer. This is used in wxRichTextCtrl::OnSize,
1834 // so that during a size, only the visible part will be relaid out, or
1835 // it would take too long causing flicker. As an approximation, we assume that
1836 // everything up to the start of the visible area is laid out correctly.
1837 if (formatRect)
1838 {
1839 wxRect rect2(0, 0, rect.width, rect.height);
1840 availableSpace = GetAvailableContentArea(dc, context, rect2);
1841
1842 // Invalidate the part of the buffer from the first visible line
1843 // to the end. If other parts of the buffer are currently invalid,
1844 // then they too will be taken into account if they are above
1845 // the visible point.
1846 long startPos = 0;
1847 wxRichTextLine* line = GetLineAtYPosition(rect.y);
1848 if (line)
1849 startPos = line->GetAbsoluteRange().GetStart();
1850
1851 Invalidate(wxRichTextRange(startPos, GetOwnRange().GetEnd()));
1852 }
1853 else
1854 {
1855 availableSpace = GetAvailableContentArea(dc, context, rect);
1856 }
1857
1858 // Fix the width if we're at the top level
1859 if (!GetParent())
1860 attr.GetTextBoxAttr().GetWidth().SetValue(rect.GetWidth(), wxTEXT_ATTR_UNITS_PIXELS);
1861
1862 int leftMargin, rightMargin, topMargin, bottomMargin;
1863 wxRichTextObject::GetTotalMargin(dc, GetBuffer(), attr, leftMargin, rightMargin,
1864 topMargin, bottomMargin);
1865
1866 int maxWidth = 0;
1867 int maxHeight = 0;
1868
1869 // The maximum paragraph maximum width, so we can set the overall maximum width for this object
1870 int maxMaxWidth = 0;
1871
1872 // The maximum paragraph minimum width, so we can set the overall minimum width for this object
1873 int maxMinWidth = 0;
1874
1875 // If we have vertical alignment, we must recalculate everything.
1876 bool hasVerticalAlignment = (attr.GetTextBoxAttr().HasVerticalAlignment() &&
1877 (attr.GetTextBoxAttr().GetVerticalAlignment() > wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT_TOP));
1878
1879 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1880
1881 bool layoutAll = true;
1882
1883 // Get invalid range, rounding to paragraph start/end.
1884 wxRichTextRange invalidRange = GetInvalidRange(true);
1885
1886 if (invalidRange == wxRICHTEXT_NONE && !formatRect)
1887 return true;
1888
1889 if (invalidRange == wxRICHTEXT_ALL || hasVerticalAlignment)
1890 layoutAll = true;
1891 else // If we know what range is affected, start laying out from that point on.
1892 if (invalidRange.GetStart() >= GetOwnRange().GetStart())
1893 {
1894 wxRichTextParagraph* firstParagraph = GetParagraphAtPosition(invalidRange.GetStart());
1895 if (firstParagraph)
1896 {
1897 wxRichTextObjectList::compatibility_iterator firstNode = m_children.Find(firstParagraph);
1898 wxRichTextObjectList::compatibility_iterator previousNode;
1899 if ( firstNode )
1900 previousNode = firstNode->GetPrevious();
1901 if (firstNode)
1902 {
1903 if (previousNode)
1904 {
1905 wxRichTextParagraph* previousParagraph = wxDynamicCast(previousNode->GetData(), wxRichTextParagraph);
1906 availableSpace.y = previousParagraph->GetPosition().y + previousParagraph->GetCachedSize().y;
1907 }
1908
1909 // Now we're going to start iterating from the first affected paragraph.
1910 node = firstNode;
1911
1912 layoutAll = false;
1913 }
1914 }
1915 }
1916
1917 // Gather information about only those floating objects that will not be formatted,
1918 // after which floats will be gathered per-paragraph during layout.
1919 UpdateFloatingObjects(availableSpace, node ? node->GetData() : (wxRichTextObject*) NULL);
1920
1921 // A way to force speedy rest-of-buffer layout (the 'else' below)
1922 bool forceQuickLayout = false;
1923
1924 // First get the size of the paragraphs we won't be laying out
1925 wxRichTextObjectList::compatibility_iterator n = m_children.GetFirst();
1926 while (n && n != node)
1927 {
1928 wxRichTextParagraph* child = wxDynamicCast(n->GetData(), wxRichTextParagraph);
1929 if (child)
1930 {
1931 maxWidth = wxMax(maxWidth, child->GetCachedSize().x);
1932 maxMinWidth = wxMax(maxMinWidth, child->GetMinSize().x);
1933 maxMaxWidth = wxMax(maxMaxWidth, child->GetMaxSize().x);
1934 }
1935 n = n->GetNext();
1936 }
1937
1938 while (node)
1939 {
1940 // Assume this box only contains paragraphs
1941
1942 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
1943 // Unsure if this is needed
1944 // wxCHECK_MSG( child, false, wxT("Unknown object in layout") );
1945
1946 if (child && child->IsShown())
1947 {
1948 // TODO: what if the child hasn't been laid out (e.g. involved in Undo) but still has 'old' lines
1949 if ( !forceQuickLayout &&
1950 (layoutAll ||
1951 child->GetLines().IsEmpty() ||
1952 !child->GetRange().IsOutside(invalidRange)) )
1953 {
1954 // Lays out the object first with a given amount of space, and then if no width was specified in attr,
1955 // lays out the object again using the minimum size
1956 child->LayoutToBestSize(dc, context, GetBuffer(),
1957 attr, child->GetAttributes(), availableSpace, rect, style&~wxRICHTEXT_LAYOUT_SPECIFIED_RECT);
1958
1959 // Layout must set the cached size
1960 availableSpace.y += child->GetCachedSize().y;
1961 maxWidth = wxMax(maxWidth, child->GetCachedSize().x);
1962 maxMinWidth = wxMax(maxMinWidth, child->GetMinSize().x);
1963 maxMaxWidth = wxMax(maxMaxWidth, child->GetMaxSize().x);
1964
1965 // If we're just formatting the visible part of the buffer,
1966 // and we're now past the bottom of the window, and we don't have any
1967 // floating objects (since they may cause wrapping to change for the rest of the
1968 // the buffer), start quick layout.
1969 if (!hasVerticalAlignment && formatRect && child->GetPosition().y > rect.GetBottom() && GetFloatingObjectCount() == 0)
1970 forceQuickLayout = true;
1971 }
1972 else
1973 {
1974 // We're outside the immediately affected range, so now let's just
1975 // move everything up or down. This assumes that all the children have previously
1976 // been laid out and have wrapped line lists associated with them.
1977 // TODO: check all paragraphs before the affected range.
1978
1979 int inc = availableSpace.y - child->GetPosition().y;
1980
1981 while (node)
1982 {
1983 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
1984 if (child)
1985 {
1986 if (child->GetLines().GetCount() == 0)
1987 {
1988 // Lays out the object first with a given amount of space, and then if no width was specified in attr,
1989 // lays out the object again using the minimum size
1990 child->LayoutToBestSize(dc, context, GetBuffer(),
1991 attr, child->GetAttributes(), availableSpace, rect, style&~wxRICHTEXT_LAYOUT_SPECIFIED_RECT);
1992
1993 //child->Layout(dc, availableChildRect, style);
1994 }
1995 else
1996 child->Move(wxPoint(child->GetPosition().x, child->GetPosition().y + inc));
1997
1998 availableSpace.y += child->GetCachedSize().y;
1999 maxWidth = wxMax(maxWidth, child->GetCachedSize().x);
2000 maxMinWidth = wxMax(maxMinWidth, child->GetMinSize().x);
2001 maxMaxWidth = wxMax(maxMaxWidth, child->GetMaxSize().x);
2002 }
2003
2004 node = node->GetNext();
2005 }
2006 break;
2007 }
2008 }
2009
2010 node = node->GetNext();
2011 }
2012
2013 node = m_children.GetLast();
2014 if (node && node->GetData()->IsShown())
2015 {
2016 wxRichTextObject* child = node->GetData();
2017 maxHeight = child->GetPosition().y - (GetPosition().y + topMargin) + child->GetCachedSize().y;
2018 }
2019 else
2020 maxHeight = 0; // topMargin + bottomMargin;
2021
2022 // Check the bottom edge of any floating object
2023 if (GetFloatCollector() && GetFloatCollector()->HasFloats())
2024 {
2025 int bottom = GetFloatCollector()->GetLastRectBottom();
2026 if (bottom > maxHeight)
2027 maxHeight = bottom;
2028 }
2029
2030 if (attr.GetTextBoxAttr().GetSize().GetWidth().IsValid())
2031 {
2032 wxRect r = AdjustAvailableSpace(dc, GetBuffer(), wxRichTextAttr() /* not used */, attr, parentRect, parentRect);
2033 int w = r.GetWidth();
2034
2035 // Convert external to content rect
2036 w = w - leftMargin - rightMargin;
2037 maxWidth = wxMax(maxWidth, w);
2038 maxMaxWidth = wxMax(maxMaxWidth, w);
2039 }
2040 else
2041 {
2042 // TODO: Make sure the layout box's position reflects
2043 // the position of the children, but without
2044 // breaking layout of a box within a paragraph.
2045 }
2046
2047 // TODO: (also in para layout) should set the
2048 // object's size to an absolute one if specified,
2049 // but if not specified, calculate it from content.
2050
2051 // We need to add back the margins etc.
2052 {
2053 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
2054 contentRect = wxRect(wxPoint(0, 0), wxSize(maxWidth, maxHeight));
2055 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
2056 SetCachedSize(marginRect.GetSize());
2057 }
2058
2059 // The maximum size is the greatest of all maximum widths for all paragraphs.
2060 {
2061 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
2062 contentRect = wxRect(wxPoint(0, 0), wxSize(maxMaxWidth, maxHeight)); // Actually max height is a lie, we can't know it
2063 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
2064 SetMaxSize(marginRect.GetSize());
2065 }
2066
2067 // The minimum size is the greatest of all minimum widths for all paragraphs.
2068 {
2069 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
2070 contentRect = wxRect(wxPoint(0, 0), wxSize(maxMinWidth, maxHeight)); // Actually max height is a lie, we can't know it
2071 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
2072 SetMinSize(marginRect.GetSize());
2073 }
2074
2075 if (attr.GetTextBoxAttr().HasVerticalAlignment() &&
2076 (attr.GetTextBoxAttr().GetVerticalAlignment() > wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT_TOP))
2077 {
2078 int yOffset = 0;
2079 int leftOverSpace = availableSpace.height - topMargin - bottomMargin - maxHeight;
2080 if (leftOverSpace > 0)
2081 {
2082 if (attr.GetTextBoxAttr().GetVerticalAlignment() == wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT_CENTRE)
2083 {
2084 yOffset = (leftOverSpace/2);
2085 }
2086 else if (attr.GetTextBoxAttr().GetVerticalAlignment() == wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT_BOTTOM)
2087 {
2088 yOffset = leftOverSpace;
2089 }
2090 }
2091
2092 // Move all the children to vertically align the content
2093 // This doesn't take into account floating objects, unfortunately.
2094 if (yOffset != 0)
2095 {
2096 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2097 while (node)
2098 {
2099 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2100 if (child)
2101 child->Move(wxPoint(child->GetPosition().x, child->GetPosition().y + yOffset));
2102
2103 node = node->GetNext();
2104 }
2105 }
2106 }
2107
2108 m_invalidRange = wxRICHTEXT_NONE;
2109
2110 return true;
2111 }
2112
2113 /// Get/set the size for the given range.
2114 bool wxRichTextParagraphLayoutBox::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, wxPoint position, wxArrayInt* WXUNUSED(partialExtents)) const
2115 {
2116 wxSize sz;
2117
2118 wxRichTextObjectList::compatibility_iterator startPara = wxRichTextObjectList::compatibility_iterator();
2119 wxRichTextObjectList::compatibility_iterator endPara = wxRichTextObjectList::compatibility_iterator();
2120
2121 // First find the first paragraph whose starting position is within the range.
2122 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2123 while (node)
2124 {
2125 // child is a paragraph
2126 wxRichTextObject* child = node->GetData();
2127 const wxRichTextRange& r = child->GetRange();
2128
2129 if (r.GetStart() <= range.GetStart() && r.GetEnd() >= range.GetStart())
2130 {
2131 startPara = node;
2132 break;
2133 }
2134
2135 node = node->GetNext();
2136 }
2137
2138 // Next find the last paragraph containing part of the range
2139 node = m_children.GetFirst();
2140 while (node)
2141 {
2142 // child is a paragraph
2143 wxRichTextObject* child = node->GetData();
2144 const wxRichTextRange& r = child->GetRange();
2145
2146 if (r.GetStart() <= range.GetEnd() && r.GetEnd() >= range.GetEnd())
2147 {
2148 endPara = node;
2149 break;
2150 }
2151
2152 node = node->GetNext();
2153 }
2154
2155 if (!startPara || !endPara)
2156 return false;
2157
2158 // Now we can add up the sizes
2159 for (node = startPara; node ; node = node->GetNext())
2160 {
2161 // child is a paragraph
2162 wxRichTextObject* child = node->GetData();
2163 const wxRichTextRange& childRange = child->GetRange();
2164 wxRichTextRange rangeToFind = range;
2165 rangeToFind.LimitTo(childRange);
2166
2167 if (child->IsTopLevel())
2168 rangeToFind = child->GetOwnRange();
2169
2170 wxSize childSize;
2171
2172 int childDescent = 0;
2173 child->GetRangeSize(rangeToFind, childSize, childDescent, dc, context, flags, position);
2174
2175 descent = wxMax(childDescent, descent);
2176
2177 sz.x = wxMax(sz.x, childSize.x);
2178 sz.y += childSize.y;
2179
2180 if (node == endPara)
2181 break;
2182 }
2183
2184 size = sz;
2185
2186 return true;
2187 }
2188
2189 /// Get the paragraph at the given position
2190 wxRichTextParagraph* wxRichTextParagraphLayoutBox::GetParagraphAtPosition(long pos, bool caretPosition) const
2191 {
2192 if (caretPosition)
2193 pos ++;
2194
2195 // First find the first paragraph whose starting position is within the range.
2196 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2197 while (node)
2198 {
2199 // child is a paragraph
2200 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2201 // wxASSERT (child != NULL);
2202
2203 if (child)
2204 {
2205 // Return first child in buffer if position is -1
2206 // if (pos == -1)
2207 // return child;
2208
2209 if (child->GetRange().Contains(pos))
2210 return child;
2211 }
2212
2213 node = node->GetNext();
2214 }
2215 return NULL;
2216 }
2217
2218 /// Get the line at the given position
2219 wxRichTextLine* wxRichTextParagraphLayoutBox::GetLineAtPosition(long pos, bool caretPosition) const
2220 {
2221 if (caretPosition)
2222 pos ++;
2223
2224 // First find the first paragraph whose starting position is within the range.
2225 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2226 while (node)
2227 {
2228 wxRichTextObject* obj = (wxRichTextObject*) node->GetData();
2229 if (obj->GetRange().Contains(pos))
2230 {
2231 // child is a paragraph
2232 wxRichTextParagraph* child = wxDynamicCast(obj, wxRichTextParagraph);
2233 // wxASSERT (child != NULL);
2234
2235 if (child)
2236 {
2237 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
2238 while (node2)
2239 {
2240 wxRichTextLine* line = node2->GetData();
2241
2242 wxRichTextRange range = line->GetAbsoluteRange();
2243
2244 if (range.Contains(pos) ||
2245
2246 // If the position is end-of-paragraph, then return the last line of
2247 // of the paragraph.
2248 ((range.GetEnd() == child->GetRange().GetEnd()-1) && (pos == child->GetRange().GetEnd())))
2249 return line;
2250
2251 node2 = node2->GetNext();
2252 }
2253 }
2254 }
2255
2256 node = node->GetNext();
2257 }
2258
2259 int lineCount = GetLineCount();
2260 if (lineCount > 0)
2261 return GetLineForVisibleLineNumber(lineCount-1);
2262 else
2263 return NULL;
2264 }
2265
2266 /// Get the line at the given y pixel position, or the last line.
2267 wxRichTextLine* wxRichTextParagraphLayoutBox::GetLineAtYPosition(int y) const
2268 {
2269 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2270 while (node)
2271 {
2272 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2273 // wxASSERT (child != NULL);
2274
2275 if (child)
2276 {
2277 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
2278 while (node2)
2279 {
2280 wxRichTextLine* line = node2->GetData();
2281
2282 wxRect rect(line->GetRect());
2283
2284 if (y <= rect.GetBottom())
2285 return line;
2286
2287 node2 = node2->GetNext();
2288 }
2289 }
2290
2291 node = node->GetNext();
2292 }
2293
2294 // Return last line
2295 int lineCount = GetLineCount();
2296 if (lineCount > 0)
2297 return GetLineForVisibleLineNumber(lineCount-1);
2298 else
2299 return NULL;
2300 }
2301
2302 /// Get the number of visible lines
2303 int wxRichTextParagraphLayoutBox::GetLineCount() const
2304 {
2305 int count = 0;
2306
2307 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2308 while (node)
2309 {
2310 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2311 // wxASSERT (child != NULL);
2312
2313 if (child)
2314 count += child->GetLines().GetCount();
2315
2316 node = node->GetNext();
2317 }
2318 return count;
2319 }
2320
2321
2322 /// Get the paragraph for a given line
2323 wxRichTextParagraph* wxRichTextParagraphLayoutBox::GetParagraphForLine(wxRichTextLine* line) const
2324 {
2325 return GetParagraphAtPosition(line->GetAbsoluteRange().GetStart());
2326 }
2327
2328 /// Get the line size at the given position
2329 wxSize wxRichTextParagraphLayoutBox::GetLineSizeAtPosition(long pos, bool caretPosition) const
2330 {
2331 wxRichTextLine* line = GetLineAtPosition(pos, caretPosition);
2332 if (line)
2333 {
2334 return line->GetSize();
2335 }
2336 else
2337 return wxSize(0, 0);
2338 }
2339
2340
2341 /// Convenience function to add a paragraph of text
2342 wxRichTextRange wxRichTextParagraphLayoutBox::AddParagraph(const wxString& text, wxRichTextAttr* paraStyle)
2343 {
2344 // Don't use the base style, just the default style, and the base style will
2345 // be combined at display time.
2346 // Divide into paragraph and character styles.
2347
2348 wxRichTextAttr defaultCharStyle;
2349 wxRichTextAttr defaultParaStyle;
2350
2351 // If the default style is a named paragraph style, don't apply any character formatting
2352 // to the initial text string.
2353 if (GetDefaultStyle().HasParagraphStyleName() && GetStyleSheet())
2354 {
2355 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(GetDefaultStyle().GetParagraphStyleName());
2356 if (def)
2357 defaultParaStyle = def->GetStyleMergedWithBase(GetStyleSheet());
2358 }
2359 else
2360 wxRichTextSplitParaCharStyles(GetDefaultStyle(), defaultParaStyle, defaultCharStyle);
2361
2362 wxRichTextAttr* pStyle = paraStyle ? paraStyle : (wxRichTextAttr*) & defaultParaStyle;
2363 wxRichTextAttr* cStyle = & defaultCharStyle;
2364
2365 wxRichTextParagraph* para = new wxRichTextParagraph(text, this, pStyle, cStyle);
2366 para->GetAttributes().GetTextBoxAttr().Reset();
2367
2368 AppendChild(para);
2369
2370 UpdateRanges();
2371
2372 return para->GetRange();
2373 }
2374
2375 /// Adds multiple paragraphs, based on newlines.
2376 wxRichTextRange wxRichTextParagraphLayoutBox::AddParagraphs(const wxString& text, wxRichTextAttr* paraStyle)
2377 {
2378 // Don't use the base style, just the default style, and the base style will
2379 // be combined at display time.
2380 // Divide into paragraph and character styles.
2381
2382 wxRichTextAttr defaultCharStyle;
2383 wxRichTextAttr defaultParaStyle;
2384
2385 // If the default style is a named paragraph style, don't apply any character formatting
2386 // to the initial text string.
2387 if (GetDefaultStyle().HasParagraphStyleName() && GetStyleSheet())
2388 {
2389 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(GetDefaultStyle().GetParagraphStyleName());
2390 if (def)
2391 defaultParaStyle = def->GetStyleMergedWithBase(GetStyleSheet());
2392 }
2393 else
2394 wxRichTextSplitParaCharStyles(GetDefaultStyle(), defaultParaStyle, defaultCharStyle);
2395
2396 wxRichTextAttr* pStyle = paraStyle ? paraStyle : (wxRichTextAttr*) & defaultParaStyle;
2397 wxRichTextAttr* cStyle = & defaultCharStyle;
2398
2399 wxRichTextParagraph* firstPara = NULL;
2400 wxRichTextParagraph* lastPara = NULL;
2401
2402 wxRichTextRange range(-1, -1);
2403
2404 size_t i = 0;
2405 size_t len = text.length();
2406 wxString line;
2407 wxRichTextParagraph* para = new wxRichTextParagraph(wxEmptyString, this, pStyle, cStyle);
2408 para->GetAttributes().GetTextBoxAttr().Reset();
2409
2410 AppendChild(para);
2411
2412 firstPara = para;
2413 lastPara = para;
2414
2415 while (i < len)
2416 {
2417 wxChar ch = text[i];
2418 if (ch == wxT('\n') || ch == wxT('\r'))
2419 {
2420 if (i != (len-1))
2421 {
2422 wxRichTextPlainText* plainText = (wxRichTextPlainText*) para->GetChildren().GetFirst()->GetData();
2423 plainText->SetText(line);
2424
2425 para = new wxRichTextParagraph(wxEmptyString, this, pStyle, cStyle);
2426 para->GetAttributes().GetTextBoxAttr().Reset();
2427
2428 AppendChild(para);
2429
2430 lastPara = para;
2431 line = wxEmptyString;
2432 }
2433 }
2434 else
2435 line += ch;
2436
2437 i ++;
2438 }
2439
2440 if (!line.empty())
2441 {
2442 wxRichTextPlainText* plainText = (wxRichTextPlainText*) para->GetChildren().GetFirst()->GetData();
2443 plainText->SetText(line);
2444 }
2445
2446 UpdateRanges();
2447
2448 return wxRichTextRange(firstPara->GetRange().GetStart(), lastPara->GetRange().GetEnd());
2449 }
2450
2451 /// Convenience function to add an image
2452 wxRichTextRange wxRichTextParagraphLayoutBox::AddImage(const wxImage& image, wxRichTextAttr* paraStyle)
2453 {
2454 // Don't use the base style, just the default style, and the base style will
2455 // be combined at display time.
2456 // Divide into paragraph and character styles.
2457
2458 wxRichTextAttr defaultCharStyle;
2459 wxRichTextAttr defaultParaStyle;
2460
2461 // If the default style is a named paragraph style, don't apply any character formatting
2462 // to the initial text string.
2463 if (GetDefaultStyle().HasParagraphStyleName() && GetStyleSheet())
2464 {
2465 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(GetDefaultStyle().GetParagraphStyleName());
2466 if (def)
2467 defaultParaStyle = def->GetStyleMergedWithBase(GetStyleSheet());
2468 }
2469 else
2470 wxRichTextSplitParaCharStyles(GetDefaultStyle(), defaultParaStyle, defaultCharStyle);
2471
2472 wxRichTextAttr* pStyle = paraStyle ? paraStyle : (wxRichTextAttr*) & defaultParaStyle;
2473 wxRichTextAttr* cStyle = & defaultCharStyle;
2474
2475 wxRichTextParagraph* para = new wxRichTextParagraph(this, pStyle);
2476 para->GetAttributes().GetTextBoxAttr().Reset();
2477 AppendChild(para);
2478 para->AppendChild(new wxRichTextImage(image, this, cStyle));
2479
2480 UpdateRanges();
2481
2482 return para->GetRange();
2483 }
2484
2485
2486 /// Insert fragment into this box at the given position. If partialParagraph is true,
2487 /// it is assumed that the last (or only) paragraph is just a piece of data with no paragraph
2488 /// marker.
2489
2490 bool wxRichTextParagraphLayoutBox::InsertFragment(long position, wxRichTextParagraphLayoutBox& fragment)
2491 {
2492 // First, find the first paragraph whose starting position is within the range.
2493 wxRichTextParagraph* para = GetParagraphAtPosition(position);
2494 if (para)
2495 {
2496 wxRichTextAttr originalAttr = para->GetAttributes();
2497
2498 wxRichTextObjectList::compatibility_iterator node = m_children.Find(para);
2499
2500 // Now split at this position, returning the object to insert the new
2501 // ones in front of.
2502 wxRichTextObject* nextObject = para->SplitAt(position);
2503
2504 // Special case: partial paragraph, just one paragraph. Might be a small amount of
2505 // text, for example, so let's optimize.
2506
2507 if (fragment.GetPartialParagraph() && fragment.GetChildren().GetCount() == 1)
2508 {
2509 // Add the first para to this para...
2510 wxRichTextObjectList::compatibility_iterator firstParaNode = fragment.GetChildren().GetFirst();
2511 if (!firstParaNode)
2512 return false;
2513
2514 // Iterate through the fragment paragraph inserting the content into this paragraph.
2515 wxRichTextParagraph* firstPara = wxDynamicCast(firstParaNode->GetData(), wxRichTextParagraph);
2516 wxASSERT (firstPara != NULL);
2517
2518 wxRichTextObjectList::compatibility_iterator objectNode = firstPara->GetChildren().GetFirst();
2519 while (objectNode)
2520 {
2521 wxRichTextObject* newObj = objectNode->GetData()->Clone();
2522
2523 if (!nextObject)
2524 {
2525 // Append
2526 para->AppendChild(newObj);
2527 }
2528 else
2529 {
2530 // Insert before nextObject
2531 para->InsertChild(newObj, nextObject);
2532 }
2533
2534 objectNode = objectNode->GetNext();
2535 }
2536
2537 return true;
2538 }
2539 else
2540 {
2541 // Procedure for inserting a fragment consisting of a number of
2542 // paragraphs:
2543 //
2544 // 1. Remove and save the content that's after the insertion point, for adding
2545 // back once we've added the fragment.
2546 // 2. Add the content from the first fragment paragraph to the current
2547 // paragraph.
2548 // 3. Add remaining fragment paragraphs after the current paragraph.
2549 // 4. Add back the saved content from the first paragraph. If partialParagraph
2550 // is true, add it to the last paragraph added and not a new one.
2551
2552 // 1. Remove and save objects after split point.
2553 wxList savedObjects;
2554 if (nextObject)
2555 para->MoveToList(nextObject, savedObjects);
2556
2557 // 2. Add the content from the 1st fragment paragraph.
2558 wxRichTextObjectList::compatibility_iterator firstParaNode = fragment.GetChildren().GetFirst();
2559 if (!firstParaNode)
2560 return false;
2561
2562 wxRichTextParagraph* firstPara = wxDynamicCast(firstParaNode->GetData(), wxRichTextParagraph);
2563 wxASSERT(firstPara != NULL);
2564
2565 if (!(fragment.GetAttributes().GetFlags() & wxTEXT_ATTR_KEEP_FIRST_PARA_STYLE))
2566 para->SetAttributes(firstPara->GetAttributes());
2567
2568 // Save empty paragraph attributes for appending later
2569 // These are character attributes deliberately set for a new paragraph. Without this,
2570 // we couldn't pass default attributes when appending a new paragraph.
2571 wxRichTextAttr emptyParagraphAttributes;
2572
2573 wxRichTextObjectList::compatibility_iterator objectNode = firstPara->GetChildren().GetFirst();
2574
2575 if (objectNode && firstPara->GetChildren().GetCount() == 1 && objectNode->GetData()->IsEmpty())
2576 emptyParagraphAttributes = objectNode->GetData()->GetAttributes();
2577
2578 while (objectNode)
2579 {
2580 wxRichTextObject* newObj = objectNode->GetData()->Clone();
2581
2582 // Append
2583 para->AppendChild(newObj);
2584
2585 objectNode = objectNode->GetNext();
2586 }
2587
2588 // 3. Add remaining fragment paragraphs after the current paragraph.
2589 wxRichTextObjectList::compatibility_iterator nextParagraphNode = node->GetNext();
2590 wxRichTextObject* nextParagraph = NULL;
2591 if (nextParagraphNode)
2592 nextParagraph = nextParagraphNode->GetData();
2593
2594 wxRichTextObjectList::compatibility_iterator i = fragment.GetChildren().GetFirst()->GetNext();
2595 wxRichTextParagraph* finalPara = para;
2596
2597 bool needExtraPara = (!i || !fragment.GetPartialParagraph());
2598
2599 // If there was only one paragraph, we need to insert a new one.
2600 while (i)
2601 {
2602 wxRichTextParagraph* para = wxDynamicCast(i->GetData(), wxRichTextParagraph);
2603 wxASSERT( para != NULL );
2604
2605 finalPara = (wxRichTextParagraph*) para->Clone();
2606
2607 if (nextParagraph)
2608 InsertChild(finalPara, nextParagraph);
2609 else
2610 AppendChild(finalPara);
2611
2612 i = i->GetNext();
2613 }
2614
2615 // If there was only one paragraph, or we have full paragraphs in our fragment,
2616 // we need to insert a new one.
2617 if (needExtraPara)
2618 {
2619 finalPara = new wxRichTextParagraph;
2620
2621 if (nextParagraph)
2622 InsertChild(finalPara, nextParagraph);
2623 else
2624 AppendChild(finalPara);
2625 }
2626
2627 // 4. Add back the remaining content.
2628 if (finalPara)
2629 {
2630 if (nextObject)
2631 finalPara->MoveFromList(savedObjects);
2632
2633 // Ensure there's at least one object
2634 if (finalPara->GetChildCount() == 0)
2635 {
2636 wxRichTextPlainText* text = new wxRichTextPlainText(wxEmptyString);
2637 text->SetAttributes(emptyParagraphAttributes);
2638
2639 finalPara->AppendChild(text);
2640 }
2641 }
2642
2643 if ((fragment.GetAttributes().GetFlags() & wxTEXT_ATTR_KEEP_FIRST_PARA_STYLE) && firstPara)
2644 finalPara->SetAttributes(firstPara->GetAttributes());
2645 else if (finalPara && finalPara != para)
2646 finalPara->SetAttributes(originalAttr);
2647
2648 return true;
2649 }
2650 }
2651 else
2652 {
2653 // Append
2654 wxRichTextObjectList::compatibility_iterator i = fragment.GetChildren().GetFirst();
2655 while (i)
2656 {
2657 wxRichTextParagraph* para = wxDynamicCast(i->GetData(), wxRichTextParagraph);
2658 wxASSERT( para != NULL );
2659
2660 AppendChild(para->Clone());
2661
2662 i = i->GetNext();
2663 }
2664
2665 return true;
2666 }
2667 }
2668
2669 /// Make a copy of the fragment corresponding to the given range, putting it in 'fragment'.
2670 /// If there was an incomplete paragraph at the end, partialParagraph is set to true.
2671 bool wxRichTextParagraphLayoutBox::CopyFragment(const wxRichTextRange& range, wxRichTextParagraphLayoutBox& fragment)
2672 {
2673 wxRichTextObjectList::compatibility_iterator i = GetChildren().GetFirst();
2674 while (i)
2675 {
2676 wxRichTextParagraph* para = wxDynamicCast(i->GetData(), wxRichTextParagraph);
2677 wxASSERT( para != NULL );
2678
2679 if (!para->GetRange().IsOutside(range))
2680 {
2681 fragment.AppendChild(para->Clone());
2682 }
2683 i = i->GetNext();
2684 }
2685
2686 // Now top and tail the first and last paragraphs in our new fragment (which might be the same).
2687 if (!fragment.IsEmpty())
2688 {
2689 wxRichTextParagraph* firstPara = wxDynamicCast(fragment.GetChildren().GetFirst()->GetData(), wxRichTextParagraph);
2690 wxASSERT( firstPara != NULL );
2691
2692 wxRichTextParagraph* lastPara = wxDynamicCast(fragment.GetChildren().GetLast()->GetData(), wxRichTextParagraph);
2693 wxASSERT( lastPara != NULL );
2694
2695 if (!firstPara || !lastPara)
2696 return false;
2697
2698 bool isFragment = (range.GetEnd() < lastPara->GetRange().GetEnd());
2699
2700 long firstPos = firstPara->GetRange().GetStart();
2701
2702 // Adjust for renumbering from zero
2703 wxRichTextRange topTailRange(range.GetStart() - firstPos, range.GetEnd() - firstPos);
2704
2705 long end;
2706 fragment.CalculateRange(0, end);
2707
2708 // Chop off the start of the paragraph
2709 if (topTailRange.GetStart() > 0)
2710 {
2711 wxRichTextRange r(0, topTailRange.GetStart()-1);
2712 firstPara->DeleteRange(r);
2713
2714 // Make sure the numbering is correct
2715 fragment.CalculateRange(0, end);
2716
2717 // Now, we've deleted some positions, so adjust the range
2718 // accordingly.
2719 topTailRange.SetStart(range.GetLength());
2720 topTailRange.SetEnd(fragment.GetOwnRange().GetEnd());
2721 }
2722 else
2723 {
2724 topTailRange.SetStart(range.GetLength());
2725 topTailRange.SetEnd(fragment.GetOwnRange().GetEnd());
2726 }
2727
2728 if (topTailRange.GetStart() < lastPara->GetRange().GetEnd())
2729 {
2730 lastPara->DeleteRange(topTailRange);
2731
2732 // Make sure the numbering is correct
2733 long end;
2734 fragment.CalculateRange(0, end);
2735
2736 // We only have part of a paragraph at the end
2737 fragment.SetPartialParagraph(true);
2738 }
2739 else
2740 {
2741 // We have a partial paragraph (don't save last new paragraph marker)
2742 // or complete paragraph
2743 fragment.SetPartialParagraph(isFragment);
2744 }
2745 }
2746
2747 return true;
2748 }
2749
2750 /// Given a position, get the number of the visible line (potentially many to a paragraph),
2751 /// starting from zero at the start of the buffer.
2752 long wxRichTextParagraphLayoutBox::GetVisibleLineNumber(long pos, bool caretPosition, bool startOfLine) const
2753 {
2754 if (caretPosition)
2755 pos ++;
2756
2757 int lineCount = 0;
2758
2759 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2760 while (node)
2761 {
2762 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2763 // wxASSERT( child != NULL );
2764
2765 if (child)
2766 {
2767 if (child->GetRange().Contains(pos))
2768 {
2769 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
2770 while (node2)
2771 {
2772 wxRichTextLine* line = node2->GetData();
2773 wxRichTextRange lineRange = line->GetAbsoluteRange();
2774
2775 if (lineRange.Contains(pos) || pos == lineRange.GetStart())
2776 {
2777 // If the caret is displayed at the end of the previous wrapped line,
2778 // we want to return the line it's _displayed_ at (not the actual line
2779 // containing the position).
2780 if (lineRange.GetStart() == pos && !startOfLine && child->GetRange().GetStart() != pos)
2781 return lineCount - 1;
2782 else
2783 return lineCount;
2784 }
2785
2786 lineCount ++;
2787
2788 node2 = node2->GetNext();
2789 }
2790 // If we didn't find it in the lines, it must be
2791 // the last position of the paragraph. So return the last line.
2792 return lineCount-1;
2793 }
2794 else
2795 lineCount += child->GetLines().GetCount();
2796 }
2797
2798 node = node->GetNext();
2799 }
2800
2801 // Not found
2802 return -1;
2803 }
2804
2805 /// Given a line number, get the corresponding wxRichTextLine object.
2806 wxRichTextLine* wxRichTextParagraphLayoutBox::GetLineForVisibleLineNumber(long lineNumber) const
2807 {
2808 int lineCount = 0;
2809
2810 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2811 while (node)
2812 {
2813 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2814 // wxASSERT(child != NULL);
2815
2816 if (child)
2817 {
2818 if (lineNumber < (int) (child->GetLines().GetCount() + lineCount))
2819 {
2820 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
2821 while (node2)
2822 {
2823 wxRichTextLine* line = node2->GetData();
2824
2825 if (lineCount == lineNumber)
2826 return line;
2827
2828 lineCount ++;
2829
2830 node2 = node2->GetNext();
2831 }
2832 }
2833 else
2834 lineCount += child->GetLines().GetCount();
2835 }
2836
2837 node = node->GetNext();
2838 }
2839
2840 // Didn't find it
2841 return NULL;
2842 }
2843
2844 /// Delete range from layout.
2845 bool wxRichTextParagraphLayoutBox::DeleteRange(const wxRichTextRange& range)
2846 {
2847 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2848
2849 wxRichTextParagraph* firstPara = NULL;
2850 while (node)
2851 {
2852 wxRichTextParagraph* obj = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2853 // wxASSERT (obj != NULL);
2854
2855 wxRichTextObjectList::compatibility_iterator next = node->GetNext();
2856
2857 if (obj)
2858 {
2859 // Delete the range in each paragraph
2860
2861 if (!obj->GetRange().IsOutside(range))
2862 {
2863 // Deletes the content of this object within the given range
2864 obj->DeleteRange(range);
2865
2866 wxRichTextRange thisRange = obj->GetRange();
2867 wxRichTextAttr thisAttr = obj->GetAttributes();
2868
2869 // If the whole paragraph is within the range to delete,
2870 // delete the whole thing.
2871 if (range.GetStart() <= thisRange.GetStart() && range.GetEnd() >= thisRange.GetEnd())
2872 {
2873 // Delete the whole object
2874 RemoveChild(obj, true);
2875 obj = NULL;
2876 }
2877 else if (!firstPara)
2878 firstPara = obj;
2879
2880 // If the range includes the paragraph end, we need to join this
2881 // and the next paragraph.
2882 if (range.GetEnd() <= thisRange.GetEnd())
2883 {
2884 // We need to move the objects from the next paragraph
2885 // to this paragraph
2886
2887 wxRichTextParagraph* nextParagraph = NULL;
2888 if ((range.GetEnd() < thisRange.GetEnd()) && obj)
2889 nextParagraph = obj;
2890 else
2891 {
2892 // We're ending at the end of the paragraph, so merge the _next_ paragraph.
2893 if (next)
2894 nextParagraph = wxDynamicCast(next->GetData(), wxRichTextParagraph);
2895 }
2896
2897 bool applyFinalParagraphStyle = firstPara && nextParagraph && nextParagraph != firstPara;
2898
2899 wxRichTextAttr nextParaAttr;
2900 if (applyFinalParagraphStyle)
2901 {
2902 // Special case when deleting the end of a paragraph - use _this_ paragraph's style,
2903 // not the next one.
2904 if (range.GetStart() == range.GetEnd() && range.GetStart() == thisRange.GetEnd())
2905 nextParaAttr = thisAttr;
2906 else
2907 nextParaAttr = nextParagraph->GetAttributes();
2908 }
2909
2910 if (firstPara && nextParagraph && firstPara != nextParagraph)
2911 {
2912 // Move the objects to the previous para
2913 wxRichTextObjectList::compatibility_iterator node1 = nextParagraph->GetChildren().GetFirst();
2914
2915 while (node1)
2916 {
2917 wxRichTextObject* obj1 = node1->GetData();
2918
2919 firstPara->AppendChild(obj1);
2920
2921 wxRichTextObjectList::compatibility_iterator next1 = node1->GetNext();
2922 nextParagraph->GetChildren().Erase(node1);
2923
2924 node1 = next1;
2925 }
2926
2927 // Delete the paragraph
2928 RemoveChild(nextParagraph, true);
2929 }
2930
2931 // Avoid empty paragraphs
2932 if (firstPara && firstPara->GetChildren().GetCount() == 0)
2933 {
2934 wxRichTextPlainText* text = new wxRichTextPlainText(wxEmptyString);
2935 firstPara->AppendChild(text);
2936 }
2937
2938 if (applyFinalParagraphStyle)
2939 firstPara->SetAttributes(nextParaAttr);
2940
2941 return true;
2942 }
2943 }
2944 }
2945
2946 node = next;
2947 }
2948
2949 return true;
2950 }
2951
2952 /// Get any text in this object for the given range
2953 wxString wxRichTextParagraphLayoutBox::GetTextForRange(const wxRichTextRange& range) const
2954 {
2955 int lineCount = 0;
2956 wxString text;
2957 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2958 while (node)
2959 {
2960 wxRichTextObject* child = node->GetData();
2961 if (!child->GetRange().IsOutside(range))
2962 {
2963 wxRichTextRange childRange = range;
2964 childRange.LimitTo(child->GetRange());
2965
2966 wxString childText = child->GetTextForRange(childRange);
2967
2968 text += childText;
2969
2970 if ((childRange.GetEnd() == child->GetRange().GetEnd()) && node->GetNext())
2971 text += wxT("\n");
2972
2973 lineCount ++;
2974 }
2975 node = node->GetNext();
2976 }
2977
2978 return text;
2979 }
2980
2981 /// Get all the text
2982 wxString wxRichTextParagraphLayoutBox::GetText() const
2983 {
2984 return GetTextForRange(GetOwnRange());
2985 }
2986
2987 /// Get the paragraph by number
2988 wxRichTextParagraph* wxRichTextParagraphLayoutBox::GetParagraphAtLine(long paragraphNumber) const
2989 {
2990 if ((size_t) paragraphNumber >= GetChildCount())
2991 return NULL;
2992
2993 return (wxRichTextParagraph*) GetChild((size_t) paragraphNumber);
2994 }
2995
2996 /// Get the length of the paragraph
2997 int wxRichTextParagraphLayoutBox::GetParagraphLength(long paragraphNumber) const
2998 {
2999 wxRichTextParagraph* para = GetParagraphAtLine(paragraphNumber);
3000 if (para)
3001 return para->GetRange().GetLength() - 1; // don't include newline
3002 else
3003 return 0;
3004 }
3005
3006 /// Get the text of the paragraph
3007 wxString wxRichTextParagraphLayoutBox::GetParagraphText(long paragraphNumber) const
3008 {
3009 wxRichTextParagraph* para = GetParagraphAtLine(paragraphNumber);
3010 if (para)
3011 return para->GetTextForRange(para->GetRange());
3012 else
3013 return wxEmptyString;
3014 }
3015
3016 /// Convert zero-based line column and paragraph number to a position.
3017 long wxRichTextParagraphLayoutBox::XYToPosition(long x, long y) const
3018 {
3019 wxRichTextParagraph* para = GetParagraphAtLine(y);
3020 if (para)
3021 {
3022 return para->GetRange().GetStart() + x;
3023 }
3024 else
3025 return -1;
3026 }
3027
3028 /// Convert zero-based position to line column and paragraph number
3029 bool wxRichTextParagraphLayoutBox::PositionToXY(long pos, long* x, long* y) const
3030 {
3031 wxRichTextParagraph* para = GetParagraphAtPosition(pos);
3032 if (para)
3033 {
3034 int count = 0;
3035 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3036 while (node)
3037 {
3038 wxRichTextObject* child = node->GetData();
3039 if (child == para)
3040 break;
3041 count ++;
3042 node = node->GetNext();
3043 }
3044
3045 *y = count;
3046 *x = pos - para->GetRange().GetStart();
3047
3048 return true;
3049 }
3050 else
3051 return false;
3052 }
3053
3054 /// Get the leaf object in a paragraph at this position.
3055 /// Given a line number, get the corresponding wxRichTextLine object.
3056 wxRichTextObject* wxRichTextParagraphLayoutBox::GetLeafObjectAtPosition(long position) const
3057 {
3058 wxRichTextParagraph* para = GetParagraphAtPosition(position);
3059 if (para)
3060 {
3061 wxRichTextObjectList::compatibility_iterator node = para->GetChildren().GetFirst();
3062
3063 while (node)
3064 {
3065 wxRichTextObject* child = node->GetData();
3066 if (child->GetRange().Contains(position))
3067 return child;
3068
3069 node = node->GetNext();
3070 }
3071 if (position == para->GetRange().GetEnd() && para->GetChildCount() > 0)
3072 return para->GetChildren().GetLast()->GetData();
3073 }
3074 return NULL;
3075 }
3076
3077 /// Set character or paragraph text attributes: apply character styles only to immediate text nodes
3078 bool wxRichTextParagraphLayoutBox::SetStyle(const wxRichTextRange& range, const wxRichTextAttr& style, int flags)
3079 {
3080 bool characterStyle = false;
3081 bool paragraphStyle = false;
3082
3083 if (style.IsCharacterStyle())
3084 characterStyle = true;
3085 if (style.IsParagraphStyle())
3086 paragraphStyle = true;
3087
3088 wxRichTextBuffer* buffer = GetBuffer();
3089
3090 bool withUndo = ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0);
3091 bool applyMinimal = ((flags & wxRICHTEXT_SETSTYLE_OPTIMIZE) != 0);
3092 bool parasOnly = ((flags & wxRICHTEXT_SETSTYLE_PARAGRAPHS_ONLY) != 0);
3093 bool charactersOnly = ((flags & wxRICHTEXT_SETSTYLE_CHARACTERS_ONLY) != 0);
3094 bool resetExistingStyle = ((flags & wxRICHTEXT_SETSTYLE_RESET) != 0);
3095 bool removeStyle = ((flags & wxRICHTEXT_SETSTYLE_REMOVE) != 0);
3096
3097 // Apply paragraph style first, if any
3098 wxRichTextAttr wholeStyle(style);
3099
3100 if (!removeStyle && wholeStyle.HasParagraphStyleName() && buffer->GetStyleSheet())
3101 {
3102 wxRichTextParagraphStyleDefinition* def = buffer->GetStyleSheet()->FindParagraphStyle(wholeStyle.GetParagraphStyleName());
3103 if (def)
3104 wxRichTextApplyStyle(wholeStyle, def->GetStyleMergedWithBase(buffer->GetStyleSheet()));
3105 }
3106
3107 // Limit the attributes to be set to the content to only character attributes.
3108 wxRichTextAttr characterAttributes(wholeStyle);
3109 characterAttributes.SetFlags(characterAttributes.GetFlags() & (wxTEXT_ATTR_CHARACTER));
3110
3111 if (!removeStyle && characterAttributes.HasCharacterStyleName() && buffer->GetStyleSheet())
3112 {
3113 wxRichTextCharacterStyleDefinition* def = buffer->GetStyleSheet()->FindCharacterStyle(characterAttributes.GetCharacterStyleName());
3114 if (def)
3115 wxRichTextApplyStyle(characterAttributes, def->GetStyleMergedWithBase(buffer->GetStyleSheet()));
3116 }
3117
3118 // If we are associated with a control, make undoable; otherwise, apply immediately
3119 // to the data.
3120
3121 bool haveControl = (buffer->GetRichTextCtrl() != NULL);
3122
3123 wxRichTextAction* action = NULL;
3124
3125 if (haveControl && withUndo)
3126 {
3127 action = new wxRichTextAction(NULL, _("Change Style"), wxRICHTEXT_CHANGE_STYLE, buffer, this, buffer->GetRichTextCtrl());
3128 action->SetRange(range);
3129 action->SetPosition(buffer->GetRichTextCtrl()->GetCaretPosition());
3130 }
3131
3132 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3133 while (node)
3134 {
3135 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3136 // wxASSERT (para != NULL);
3137
3138 if (para && para->GetChildCount() > 0)
3139 {
3140 // Stop searching if we're beyond the range of interest
3141 if (para->GetRange().GetStart() > range.GetEnd())
3142 break;
3143
3144 if (!para->GetRange().IsOutside(range))
3145 {
3146 // We'll be using a copy of the paragraph to make style changes,
3147 // not updating the buffer directly.
3148 wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL);
3149
3150 if (haveControl && withUndo)
3151 {
3152 newPara = new wxRichTextParagraph(*para);
3153 action->GetNewParagraphs().AppendChild(newPara);
3154
3155 // Also store the old ones for Undo
3156 action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para));
3157 }
3158 else
3159 newPara = para;
3160
3161 // If we're specifying paragraphs only, then we really mean character formatting
3162 // to be included in the paragraph style
3163 if ((paragraphStyle || parasOnly) && !charactersOnly)
3164 {
3165 if (removeStyle)
3166 {
3167 // Removes the given style from the paragraph
3168 wxRichTextRemoveStyle(newPara->GetAttributes(), style);
3169 }
3170 else if (resetExistingStyle)
3171 newPara->GetAttributes() = wholeStyle;
3172 else
3173 {
3174 if (applyMinimal)
3175 {
3176 // Only apply attributes that will make a difference to the combined
3177 // style as seen on the display
3178 wxRichTextAttr combinedAttr(para->GetCombinedAttributes(true));
3179 wxRichTextApplyStyle(newPara->GetAttributes(), wholeStyle, & combinedAttr);
3180 }
3181 else
3182 wxRichTextApplyStyle(newPara->GetAttributes(), wholeStyle);
3183 }
3184 }
3185
3186 // When applying paragraph styles dynamically, don't change the text objects' attributes
3187 // since they will computed as needed. Only apply the character styling if it's _only_
3188 // character styling. This policy is subject to change and might be put under user control.
3189
3190 // Hm. we might well be applying a mix of paragraph and character styles, in which
3191 // case we _do_ want to apply character styles regardless of what para styles are set.
3192 // But if we're applying a paragraph style, which has some character attributes, but
3193 // we only want the paragraphs to hold this character style, then we _don't_ want to
3194 // apply the character style. So we need to be able to choose.
3195
3196 if (!parasOnly && (characterStyle|charactersOnly) && range.GetStart() != newPara->GetRange().GetEnd())
3197 {
3198 wxRichTextRange childRange(range);
3199 childRange.LimitTo(newPara->GetRange());
3200
3201 // Find the starting position and if necessary split it so
3202 // we can start applying a different style.
3203 // TODO: check that the style actually changes or is different
3204 // from style outside of range
3205 wxRichTextObject* firstObject wxDUMMY_INITIALIZE(NULL);
3206 wxRichTextObject* lastObject wxDUMMY_INITIALIZE(NULL);
3207
3208 if (childRange.GetStart() == newPara->GetRange().GetStart())
3209 firstObject = newPara->GetChildren().GetFirst()->GetData();
3210 else
3211 firstObject = newPara->SplitAt(range.GetStart());
3212
3213 // Increment by 1 because we're apply the style one _after_ the split point
3214 long splitPoint = childRange.GetEnd();
3215 if (splitPoint != newPara->GetRange().GetEnd())
3216 splitPoint ++;
3217
3218 // Find last object
3219 if (splitPoint == newPara->GetRange().GetEnd())
3220 lastObject = newPara->GetChildren().GetLast()->GetData();
3221 else
3222 // lastObject is set as a side-effect of splitting. It's
3223 // returned as the object before the new object.
3224 (void) newPara->SplitAt(splitPoint, & lastObject);
3225
3226 wxASSERT(firstObject != NULL);
3227 wxASSERT(lastObject != NULL);
3228
3229 if (!firstObject || !lastObject)
3230 continue;
3231
3232 wxRichTextObjectList::compatibility_iterator firstNode = newPara->GetChildren().Find(firstObject);
3233 wxRichTextObjectList::compatibility_iterator lastNode = newPara->GetChildren().Find(lastObject);
3234
3235 wxASSERT(firstNode);
3236 wxASSERT(lastNode);
3237
3238 wxRichTextObjectList::compatibility_iterator node2 = firstNode;
3239
3240 while (node2)
3241 {
3242 wxRichTextObject* child = node2->GetData();
3243
3244 if (removeStyle)
3245 {
3246 // Removes the given style from the paragraph
3247 wxRichTextRemoveStyle(child->GetAttributes(), style);
3248 }
3249 else if (resetExistingStyle)
3250 child->GetAttributes() = characterAttributes;
3251 else
3252 {
3253 if (applyMinimal)
3254 {
3255 // Only apply attributes that will make a difference to the combined
3256 // style as seen on the display
3257 wxRichTextAttr combinedAttr(newPara->GetCombinedAttributes(child->GetAttributes(), true));
3258 wxRichTextApplyStyle(child->GetAttributes(), characterAttributes, & combinedAttr);
3259 }
3260 else
3261 wxRichTextApplyStyle(child->GetAttributes(), characterAttributes);
3262 }
3263
3264 if (node2 == lastNode)
3265 break;
3266
3267 node2 = node2->GetNext();
3268 }
3269 }
3270 }
3271 }
3272
3273 node = node->GetNext();
3274 }
3275
3276 // Do action, or delay it until end of batch.
3277 if (haveControl && withUndo)
3278 buffer->SubmitAction(action);
3279
3280 return true;
3281 }
3282
3283 // Just change the attributes for this single object.
3284 void wxRichTextParagraphLayoutBox::SetStyle(wxRichTextObject* obj, const wxRichTextAttr& textAttr, int flags)
3285 {
3286 wxRichTextBuffer* buffer = GetBuffer();
3287 bool withUndo = flags & wxRICHTEXT_SETSTYLE_WITH_UNDO;
3288 bool resetExistingStyle = ((flags & wxRICHTEXT_SETSTYLE_RESET) != 0);
3289 bool haveControl = (buffer->GetRichTextCtrl() != NULL);
3290
3291 wxRichTextAction *action = NULL;
3292 wxRichTextAttr newAttr = obj->GetAttributes();
3293 if (resetExistingStyle)
3294 newAttr = textAttr;
3295 else
3296 newAttr.Apply(textAttr);
3297
3298 if (haveControl && withUndo)
3299 {
3300 action = new wxRichTextAction(NULL, _("Change Object Style"), wxRICHTEXT_CHANGE_ATTRIBUTES, buffer, obj->GetContainer(), buffer->GetRichTextCtrl());
3301 action->SetRange(obj->GetRange().FromInternal());
3302 action->SetPosition(buffer->GetRichTextCtrl()->GetCaretPosition());
3303 action->MakeObject(obj);
3304
3305 action->GetAttributes() = newAttr;
3306 }
3307 else
3308 obj->GetAttributes() = newAttr;
3309
3310 if (haveControl && withUndo)
3311 buffer->SubmitAction(action);
3312 }
3313
3314 /// Get the text attributes for this position.
3315 bool wxRichTextParagraphLayoutBox::GetStyle(long position, wxRichTextAttr& style)
3316 {
3317 return DoGetStyle(position, style, true);
3318 }
3319
3320 bool wxRichTextParagraphLayoutBox::GetUncombinedStyle(long position, wxRichTextAttr& style)
3321 {
3322 return DoGetStyle(position, style, false);
3323 }
3324
3325 /// Implementation helper for GetStyle. If combineStyles is true, combine base, paragraph and
3326 /// context attributes.
3327 bool wxRichTextParagraphLayoutBox::DoGetStyle(long position, wxRichTextAttr& style, bool combineStyles)
3328 {
3329 wxRichTextObject* obj wxDUMMY_INITIALIZE(NULL);
3330
3331 if (style.IsParagraphStyle())
3332 {
3333 obj = GetParagraphAtPosition(position);
3334 if (obj)
3335 {
3336 if (combineStyles)
3337 {
3338 // Start with the base style
3339 style = GetAttributes();
3340 style.GetTextBoxAttr().Reset();
3341
3342 // Apply the paragraph style
3343 wxRichTextApplyStyle(style, obj->GetAttributes());
3344 }
3345 else
3346 style = obj->GetAttributes();
3347
3348 return true;
3349 }
3350 }
3351 else
3352 {
3353 obj = GetLeafObjectAtPosition(position);
3354 if (obj)
3355 {
3356 if (combineStyles)
3357 {
3358 wxRichTextParagraph* para = wxDynamicCast(obj->GetParent(), wxRichTextParagraph);
3359 style = para ? para->GetCombinedAttributes(obj->GetAttributes()) : obj->GetAttributes();
3360 }
3361 else
3362 style = obj->GetAttributes();
3363
3364 return true;
3365 }
3366 }
3367 return false;
3368 }
3369
3370 static bool wxHasStyle(long flags, long style)
3371 {
3372 return (flags & style) != 0;
3373 }
3374
3375 /// Combines 'style' with 'currentStyle' for the purpose of summarising the attributes of a range of
3376 /// content.
3377 bool wxRichTextParagraphLayoutBox::CollectStyle(wxRichTextAttr& currentStyle, const wxRichTextAttr& style, wxRichTextAttr& clashingAttr, wxRichTextAttr& absentAttr)
3378 {
3379 currentStyle.CollectCommonAttributes(style, clashingAttr, absentAttr);
3380
3381 return true;
3382 }
3383
3384 /// Get the combined style for a range - if any attribute is different within the range,
3385 /// that attribute is not present within the flags.
3386 /// *** Note that this is not recursive, and so assumes that content inside a paragraph is not itself
3387 /// nested.
3388 bool wxRichTextParagraphLayoutBox::GetStyleForRange(const wxRichTextRange& range, wxRichTextAttr& style)
3389 {
3390 style = wxRichTextAttr();
3391
3392 wxRichTextAttr clashingAttr;
3393 wxRichTextAttr absentAttrPara, absentAttrChar;
3394
3395 wxRichTextObjectList::compatibility_iterator node = GetChildren().GetFirst();
3396 while (node)
3397 {
3398 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3399 if (para && !(para->GetRange().GetStart() > range.GetEnd() || para->GetRange().GetEnd() < range.GetStart()))
3400 {
3401 if (para->GetChildren().GetCount() == 0)
3402 {
3403 wxRichTextAttr paraStyle = para->GetCombinedAttributes(true /* use box attributes */);
3404
3405 CollectStyle(style, paraStyle, clashingAttr, absentAttrPara);
3406 }
3407 else
3408 {
3409 wxRichTextRange paraRange(para->GetRange());
3410 paraRange.LimitTo(range);
3411
3412 // First collect paragraph attributes only
3413 wxRichTextAttr paraStyle = para->GetCombinedAttributes();
3414 paraStyle.SetFlags(paraStyle.GetFlags() & wxTEXT_ATTR_PARAGRAPH);
3415 CollectStyle(style, paraStyle, clashingAttr, absentAttrPara);
3416
3417 wxRichTextObjectList::compatibility_iterator childNode = para->GetChildren().GetFirst();
3418
3419 while (childNode)
3420 {
3421 wxRichTextObject* child = childNode->GetData();
3422 if (!(child->GetRange().GetStart() > range.GetEnd() || child->GetRange().GetEnd() < range.GetStart()))
3423 {
3424 wxRichTextAttr childStyle = para->GetCombinedAttributes(child->GetAttributes(), true /* include box attributes */);
3425
3426 // Now collect character attributes only
3427 childStyle.SetFlags(childStyle.GetFlags() & wxTEXT_ATTR_CHARACTER);
3428
3429 CollectStyle(style, childStyle, clashingAttr, absentAttrChar);
3430 }
3431
3432 childNode = childNode->GetNext();
3433 }
3434 }
3435 }
3436 node = node->GetNext();
3437 }
3438 return true;
3439 }
3440
3441 /// Set default style
3442 bool wxRichTextParagraphLayoutBox::SetDefaultStyle(const wxRichTextAttr& style)
3443 {
3444 m_defaultAttributes = style;
3445 return true;
3446 }
3447
3448 /// Test if this whole range has character attributes of the specified kind. If any
3449 /// of the attributes are different within the range, the test fails. You
3450 /// can use this to implement, for example, bold button updating. style must have
3451 /// flags indicating which attributes are of interest.
3452 bool wxRichTextParagraphLayoutBox::HasCharacterAttributes(const wxRichTextRange& range, const wxRichTextAttr& style) const
3453 {
3454 int foundCount = 0;
3455 int matchingCount = 0;
3456
3457 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3458 while (node)
3459 {
3460 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3461 // wxASSERT (para != NULL);
3462
3463 if (para)
3464 {
3465 // Stop searching if we're beyond the range of interest
3466 if (para->GetRange().GetStart() > range.GetEnd())
3467 return foundCount == matchingCount && foundCount != 0;
3468
3469 if (!para->GetRange().IsOutside(range))
3470 {
3471 wxRichTextObjectList::compatibility_iterator node2 = para->GetChildren().GetFirst();
3472
3473 while (node2)
3474 {
3475 wxRichTextObject* child = node2->GetData();
3476 // Allow for empty string if no buffer
3477 wxRichTextRange childRange = child->GetRange();
3478 if (childRange.GetLength() == 0 && GetRange().GetLength() == 1)
3479 childRange.SetEnd(childRange.GetEnd()+1);
3480
3481 if (!childRange.IsOutside(range) && wxDynamicCast(child, wxRichTextPlainText))
3482 {
3483 foundCount ++;
3484 wxRichTextAttr textAttr = para->GetCombinedAttributes(child->GetAttributes());
3485
3486 if (textAttr.EqPartial(style, false /* strong test - attributes must be valid in both objects */))
3487 matchingCount ++;
3488 }
3489
3490 node2 = node2->GetNext();
3491 }
3492 }
3493 }
3494
3495 node = node->GetNext();
3496 }
3497
3498 return foundCount == matchingCount && foundCount != 0;
3499 }
3500
3501 /// Test if this whole range has paragraph attributes of the specified kind. If any
3502 /// of the attributes are different within the range, the test fails. You
3503 /// can use this to implement, for example, centering button updating. style must have
3504 /// flags indicating which attributes are of interest.
3505 bool wxRichTextParagraphLayoutBox::HasParagraphAttributes(const wxRichTextRange& range, const wxRichTextAttr& style) const
3506 {
3507 int foundCount = 0;
3508 int matchingCount = 0;
3509
3510 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3511 while (node)
3512 {
3513 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3514 // wxASSERT (para != NULL);
3515
3516 if (para)
3517 {
3518 // Stop searching if we're beyond the range of interest
3519 if (para->GetRange().GetStart() > range.GetEnd())
3520 return foundCount == matchingCount && foundCount != 0;
3521
3522 if (!para->GetRange().IsOutside(range))
3523 {
3524 wxRichTextAttr textAttr = GetAttributes();
3525 // Apply the paragraph style
3526 wxRichTextApplyStyle(textAttr, para->GetAttributes());
3527
3528 foundCount ++;
3529 if (textAttr.EqPartial(style, false /* strong test */))
3530 matchingCount ++;
3531 }
3532 }
3533
3534 node = node->GetNext();
3535 }
3536 return foundCount == matchingCount && foundCount != 0;
3537 }
3538
3539 void wxRichTextParagraphLayoutBox::PrepareContent(wxRichTextParagraphLayoutBox& container)
3540 {
3541 wxRichTextBuffer* buffer = GetBuffer();
3542 if (buffer && buffer->GetRichTextCtrl())
3543 buffer->GetRichTextCtrl()->PrepareContent(container);
3544 }
3545
3546 /// Set character or paragraph properties
3547 bool wxRichTextParagraphLayoutBox::SetProperties(const wxRichTextRange& range, const wxRichTextProperties& properties, int flags)
3548 {
3549 wxRichTextBuffer* buffer = GetBuffer();
3550
3551 bool withUndo = ((flags & wxRICHTEXT_SETPROPERTIES_WITH_UNDO) != 0);
3552 bool parasOnly = ((flags & wxRICHTEXT_SETPROPERTIES_PARAGRAPHS_ONLY) != 0);
3553 bool charactersOnly = ((flags & wxRICHTEXT_SETPROPERTIES_CHARACTERS_ONLY) != 0);
3554 bool resetExistingProperties = ((flags & wxRICHTEXT_SETPROPERTIES_RESET) != 0);
3555 bool removeProperties = ((flags & wxRICHTEXT_SETPROPERTIES_REMOVE) != 0);
3556
3557 // If we are associated with a control, make undoable; otherwise, apply immediately
3558 // to the data.
3559
3560 bool haveControl = (buffer->GetRichTextCtrl() != NULL);
3561
3562 wxRichTextAction* action = NULL;
3563
3564 if (haveControl && withUndo)
3565 {
3566 action = new wxRichTextAction(NULL, _("Change Properties"), wxRICHTEXT_CHANGE_PROPERTIES, buffer, this, buffer->GetRichTextCtrl());
3567 action->SetRange(range);
3568 action->SetPosition(buffer->GetRichTextCtrl()->GetCaretPosition());
3569 }
3570
3571 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3572 while (node)
3573 {
3574 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3575 // wxASSERT (para != NULL);
3576
3577 if (para && para->GetChildCount() > 0)
3578 {
3579 // Stop searching if we're beyond the range of interest
3580 if (para->GetRange().GetStart() > range.GetEnd())
3581 break;
3582
3583 if (!para->GetRange().IsOutside(range))
3584 {
3585 // We'll be using a copy of the paragraph to make style changes,
3586 // not updating the buffer directly.
3587 wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL);
3588
3589 if (haveControl && withUndo)
3590 {
3591 newPara = new wxRichTextParagraph(*para);
3592 action->GetNewParagraphs().AppendChild(newPara);
3593
3594 // Also store the old ones for Undo
3595 action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para));
3596 }
3597 else
3598 newPara = para;
3599
3600 if (parasOnly)
3601 {
3602 if (removeProperties)
3603 {
3604 // Removes the given style from the paragraph
3605 // TODO
3606 newPara->GetProperties().RemoveProperties(properties);
3607 }
3608 else if (resetExistingProperties)
3609 newPara->GetProperties() = properties;
3610 else
3611 newPara->GetProperties().MergeProperties(properties);
3612 }
3613
3614 // When applying paragraph styles dynamically, don't change the text objects' attributes
3615 // since they will computed as needed. Only apply the character styling if it's _only_
3616 // character styling. This policy is subject to change and might be put under user control.
3617
3618 // Hm. we might well be applying a mix of paragraph and character styles, in which
3619 // case we _do_ want to apply character styles regardless of what para styles are set.
3620 // But if we're applying a paragraph style, which has some character attributes, but
3621 // we only want the paragraphs to hold this character style, then we _don't_ want to
3622 // apply the character style. So we need to be able to choose.
3623
3624 if (!parasOnly && charactersOnly && range.GetStart() != newPara->GetRange().GetEnd())
3625 {
3626 wxRichTextRange childRange(range);
3627 childRange.LimitTo(newPara->GetRange());
3628
3629 // Find the starting position and if necessary split it so
3630 // we can start applying different properties.
3631 // TODO: check that the properties actually change or are different
3632 // from properties outside of range
3633 wxRichTextObject* firstObject wxDUMMY_INITIALIZE(NULL);
3634 wxRichTextObject* lastObject wxDUMMY_INITIALIZE(NULL);
3635
3636 if (childRange.GetStart() == newPara->GetRange().GetStart())
3637 firstObject = newPara->GetChildren().GetFirst()->GetData();
3638 else
3639 firstObject = newPara->SplitAt(range.GetStart());
3640
3641 // Increment by 1 because we're apply the style one _after_ the split point
3642 long splitPoint = childRange.GetEnd();
3643 if (splitPoint != newPara->GetRange().GetEnd())
3644 splitPoint ++;
3645
3646 // Find last object
3647 if (splitPoint == newPara->GetRange().GetEnd())
3648 lastObject = newPara->GetChildren().GetLast()->GetData();
3649 else
3650 // lastObject is set as a side-effect of splitting. It's
3651 // returned as the object before the new object.
3652 (void) newPara->SplitAt(splitPoint, & lastObject);
3653
3654 wxASSERT(firstObject != NULL);
3655 wxASSERT(lastObject != NULL);
3656
3657 if (!firstObject || !lastObject)
3658 continue;
3659
3660 wxRichTextObjectList::compatibility_iterator firstNode = newPara->GetChildren().Find(firstObject);
3661 wxRichTextObjectList::compatibility_iterator lastNode = newPara->GetChildren().Find(lastObject);
3662
3663 wxASSERT(firstNode);
3664 wxASSERT(lastNode);
3665
3666 wxRichTextObjectList::compatibility_iterator node2 = firstNode;
3667
3668 while (node2)
3669 {
3670 wxRichTextObject* child = node2->GetData();
3671
3672 if (removeProperties)
3673 {
3674 // Removes the given properties from the paragraph
3675 child->GetProperties().RemoveProperties(properties);
3676 }
3677 else if (resetExistingProperties)
3678 child->GetProperties() = properties;
3679 else
3680 {
3681 child->GetProperties().MergeProperties(properties);
3682 }
3683
3684 if (node2 == lastNode)
3685 break;
3686
3687 node2 = node2->GetNext();
3688 }
3689 }
3690 }
3691 }
3692
3693 node = node->GetNext();
3694 }
3695
3696 // Do action, or delay it until end of batch.
3697 if (haveControl && withUndo)
3698 buffer->SubmitAction(action);
3699
3700 return true;
3701 }
3702
3703 void wxRichTextParagraphLayoutBox::Reset()
3704 {
3705 Clear();
3706
3707 wxRichTextBuffer* buffer = GetBuffer();
3708 if (buffer && buffer->GetRichTextCtrl())
3709 {
3710 wxRichTextEvent event(wxEVT_COMMAND_RICHTEXT_BUFFER_RESET, buffer->GetRichTextCtrl()->GetId());
3711 event.SetEventObject(buffer->GetRichTextCtrl());
3712 event.SetContainer(this);
3713
3714 buffer->SendEvent(event, true);
3715 }
3716
3717 AddParagraph(wxEmptyString);
3718
3719 PrepareContent(*this);
3720
3721 InvalidateHierarchy(wxRICHTEXT_ALL);
3722 }
3723
3724 /// Invalidate the buffer. With no argument, invalidates whole buffer.
3725 void wxRichTextParagraphLayoutBox::Invalidate(const wxRichTextRange& invalidRange)
3726 {
3727 wxRichTextCompositeObject::Invalidate(invalidRange);
3728
3729 DoInvalidate(invalidRange);
3730 }
3731
3732 // Do the (in)validation for this object only
3733 void wxRichTextParagraphLayoutBox::DoInvalidate(const wxRichTextRange& invalidRange)
3734 {
3735 if (invalidRange == wxRICHTEXT_ALL)
3736 {
3737 m_invalidRange = wxRICHTEXT_ALL;
3738 }
3739 // Already invalidating everything
3740 else if (m_invalidRange == wxRICHTEXT_ALL)
3741 {
3742 }
3743 else
3744 {
3745 if ((invalidRange.GetStart() < m_invalidRange.GetStart()) || m_invalidRange.GetStart() == -1)
3746 m_invalidRange.SetStart(invalidRange.GetStart());
3747 if (invalidRange.GetEnd() > m_invalidRange.GetEnd())
3748 m_invalidRange.SetEnd(invalidRange.GetEnd());
3749 }
3750 }
3751
3752 // Do the (in)validation both up and down the hierarchy
3753 void wxRichTextParagraphLayoutBox::InvalidateHierarchy(const wxRichTextRange& invalidRange)
3754 {
3755 Invalidate(invalidRange);
3756
3757 if (invalidRange != wxRICHTEXT_NONE)
3758 {
3759 // Now go up the hierarchy
3760 wxRichTextObject* thisObj = this;
3761 wxRichTextObject* p = GetParent();
3762 while (p)
3763 {
3764 wxRichTextParagraphLayoutBox* l = wxDynamicCast(p, wxRichTextParagraphLayoutBox);
3765 if (l)
3766 l->DoInvalidate(thisObj->GetRange());
3767
3768 thisObj = p;
3769 p = p->GetParent();
3770 }
3771 }
3772 }
3773
3774 /// Get invalid range, rounding to entire paragraphs if argument is true.
3775 wxRichTextRange wxRichTextParagraphLayoutBox::GetInvalidRange(bool wholeParagraphs) const
3776 {
3777 if (m_invalidRange == wxRICHTEXT_ALL || m_invalidRange == wxRICHTEXT_NONE)
3778 return m_invalidRange;
3779
3780 wxRichTextRange range = m_invalidRange;
3781
3782 if (wholeParagraphs)
3783 {
3784 wxRichTextParagraph* para1 = GetParagraphAtPosition(range.GetStart());
3785 if (para1)
3786 range.SetStart(para1->GetRange().GetStart());
3787 // floating layout make all child should be relayout
3788 range.SetEnd(GetOwnRange().GetEnd());
3789 }
3790 return range;
3791 }
3792
3793 /// Apply the style sheet to the buffer, for example if the styles have changed.
3794 bool wxRichTextParagraphLayoutBox::ApplyStyleSheet(wxRichTextStyleSheet* styleSheet)
3795 {
3796 wxASSERT(styleSheet != NULL);
3797 if (!styleSheet)
3798 return false;
3799
3800 int foundCount = 0;
3801
3802 wxRichTextAttr attr(GetBasicStyle());
3803 if (GetBasicStyle().HasParagraphStyleName())
3804 {
3805 wxRichTextParagraphStyleDefinition* paraDef = styleSheet->FindParagraphStyle(GetBasicStyle().GetParagraphStyleName());
3806 if (paraDef)
3807 {
3808 attr.Apply(paraDef->GetStyleMergedWithBase(styleSheet));
3809 SetBasicStyle(attr);
3810 foundCount ++;
3811 }
3812 }
3813
3814 if (GetBasicStyle().HasCharacterStyleName())
3815 {
3816 wxRichTextCharacterStyleDefinition* charDef = styleSheet->FindCharacterStyle(GetBasicStyle().GetCharacterStyleName());
3817 if (charDef)
3818 {
3819 attr.Apply(charDef->GetStyleMergedWithBase(styleSheet));
3820 SetBasicStyle(attr);
3821 foundCount ++;
3822 }
3823 }
3824
3825 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3826 while (node)
3827 {
3828 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3829 // wxASSERT (para != NULL);
3830
3831 if (para)
3832 {
3833 // Combine paragraph and list styles. If there is a list style in the original attributes,
3834 // the current indentation overrides anything else and is used to find the item indentation.
3835 // Also, for applying paragraph styles, consider having 2 modes: (1) we merge with what we have,
3836 // thereby taking into account all user changes, (2) reset the style completely (except for indentation/list
3837 // exception as above).
3838 // Problem: when changing from one list style to another, there's a danger that the level info will get lost.
3839 // So when changing a list style interactively, could retrieve level based on current style, then
3840 // set appropriate indent and apply new style.
3841
3842 int outline = -1;
3843 int num = -1;
3844 if (para->GetAttributes().HasOutlineLevel())
3845 outline = para->GetAttributes().GetOutlineLevel();
3846 if (para->GetAttributes().HasBulletNumber())
3847 num = para->GetAttributes().GetBulletNumber();
3848
3849 if (!para->GetAttributes().GetParagraphStyleName().IsEmpty() && !para->GetAttributes().GetListStyleName().IsEmpty())
3850 {
3851 int currentIndent = para->GetAttributes().GetLeftIndent();
3852
3853 wxRichTextParagraphStyleDefinition* paraDef = styleSheet->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName());
3854 wxRichTextListStyleDefinition* listDef = styleSheet->FindListStyle(para->GetAttributes().GetListStyleName());
3855 if (paraDef && !listDef)
3856 {
3857 para->GetAttributes() = paraDef->GetStyleMergedWithBase(styleSheet);
3858 foundCount ++;
3859 }
3860 else if (listDef && !paraDef)
3861 {
3862 // Set overall style defined for the list style definition
3863 para->GetAttributes() = listDef->GetStyleMergedWithBase(styleSheet);
3864
3865 // Apply the style for this level
3866 wxRichTextApplyStyle(para->GetAttributes(), * listDef->GetLevelAttributes(listDef->FindLevelForIndent(currentIndent)));
3867 foundCount ++;
3868 }
3869 else if (listDef && paraDef)
3870 {
3871 // Combines overall list style, style for level, and paragraph style
3872 para->GetAttributes() = listDef->CombineWithParagraphStyle(currentIndent, paraDef->GetStyleMergedWithBase(styleSheet));
3873 foundCount ++;
3874 }
3875 }
3876 else if (para->GetAttributes().GetParagraphStyleName().IsEmpty() && !para->GetAttributes().GetListStyleName().IsEmpty())
3877 {
3878 int currentIndent = para->GetAttributes().GetLeftIndent();
3879
3880 wxRichTextListStyleDefinition* listDef = styleSheet->FindListStyle(para->GetAttributes().GetListStyleName());
3881
3882 // Overall list definition style
3883 para->GetAttributes() = listDef->GetStyleMergedWithBase(styleSheet);
3884
3885 // Style for this level
3886 wxRichTextApplyStyle(para->GetAttributes(), * listDef->GetLevelAttributes(listDef->FindLevelForIndent(currentIndent)));
3887
3888 foundCount ++;
3889 }
3890 else if (!para->GetAttributes().GetParagraphStyleName().IsEmpty() && para->GetAttributes().GetListStyleName().IsEmpty())
3891 {
3892 wxRichTextParagraphStyleDefinition* def = styleSheet->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName());
3893 if (def)
3894 {
3895 para->GetAttributes() = def->GetStyleMergedWithBase(styleSheet);
3896 foundCount ++;
3897 }
3898 }
3899
3900 if (outline != -1)
3901 para->GetAttributes().SetOutlineLevel(outline);
3902 if (num != -1)
3903 para->GetAttributes().SetBulletNumber(num);
3904 }
3905
3906 node = node->GetNext();
3907 }
3908 return foundCount != 0;
3909 }
3910
3911 /// Set list style
3912 bool wxRichTextParagraphLayoutBox::SetListStyle(const wxRichTextRange& range, wxRichTextListStyleDefinition* def, int flags, int startFrom, int specifiedLevel)
3913 {
3914 wxRichTextBuffer* buffer = GetBuffer();
3915 wxRichTextStyleSheet* styleSheet = buffer->GetStyleSheet();
3916
3917 bool withUndo = ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0);
3918 // bool applyMinimal = ((flags & wxRICHTEXT_SETSTYLE_OPTIMIZE) != 0);
3919 bool specifyLevel = ((flags & wxRICHTEXT_SETSTYLE_SPECIFY_LEVEL) != 0);
3920 bool renumber = ((flags & wxRICHTEXT_SETSTYLE_RENUMBER) != 0);
3921
3922 // Current number, if numbering
3923 int n = startFrom;
3924
3925 wxASSERT (!specifyLevel || (specifyLevel && (specifiedLevel >= 0)));
3926
3927 // If we are associated with a control, make undoable; otherwise, apply immediately
3928 // to the data.
3929
3930 bool haveControl = (buffer->GetRichTextCtrl() != NULL);
3931
3932 wxRichTextAction* action = NULL;
3933
3934 if (haveControl && withUndo)
3935 {
3936 action = new wxRichTextAction(NULL, _("Change List Style"), wxRICHTEXT_CHANGE_STYLE, buffer, this, buffer->GetRichTextCtrl());
3937 action->SetRange(range);
3938 action->SetPosition(buffer->GetRichTextCtrl()->GetCaretPosition());
3939 }
3940
3941 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3942 while (node)
3943 {
3944 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3945 // wxASSERT (para != NULL);
3946
3947 if (para && para->GetChildCount() > 0)
3948 {
3949 // Stop searching if we're beyond the range of interest
3950 if (para->GetRange().GetStart() > range.GetEnd())
3951 break;
3952
3953 if (!para->GetRange().IsOutside(range))
3954 {
3955 // We'll be using a copy of the paragraph to make style changes,
3956 // not updating the buffer directly.
3957 wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL);
3958
3959 if (haveControl && withUndo)
3960 {
3961 newPara = new wxRichTextParagraph(*para);
3962 action->GetNewParagraphs().AppendChild(newPara);
3963
3964 // Also store the old ones for Undo
3965 action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para));
3966 }
3967 else
3968 newPara = para;
3969
3970 if (def)
3971 {
3972 int thisIndent = newPara->GetAttributes().GetLeftIndent();
3973 int thisLevel = specifyLevel ? specifiedLevel : def->FindLevelForIndent(thisIndent);
3974
3975 // How is numbering going to work?
3976 // If we are renumbering, or numbering for the first time, we need to keep
3977 // track of the number for each level. But we might be simply applying a different
3978 // list style.
3979 // In Word, applying a style to several paragraphs, even if at different levels,
3980 // reverts the level back to the same one. So we could do the same here.
3981 // Renumbering will need to be done when we promote/demote a paragraph.
3982
3983 // Apply the overall list style, and item style for this level
3984 wxRichTextAttr listStyle(def->GetCombinedStyleForLevel(thisLevel, styleSheet));
3985 wxRichTextApplyStyle(newPara->GetAttributes(), listStyle);
3986
3987 // Now we need to do numbering
3988 // Preserve the existing list item continuation bullet style, if any
3989 if (para->GetAttributes().HasBulletStyle() && (para->GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_CONTINUATION))
3990 newPara->GetAttributes().SetBulletStyle(newPara->GetAttributes().GetBulletStyle()|wxTEXT_ATTR_BULLET_STYLE_CONTINUATION);
3991 else
3992 {
3993 // Now we need to do numbering
3994 if (renumber)
3995 {
3996 newPara->GetAttributes().SetBulletNumber(n);
3997 }
3998
3999 n ++;
4000 }
4001 }
4002 else if (!newPara->GetAttributes().GetListStyleName().IsEmpty())
4003 {
4004 // if def is NULL, remove list style, applying any associated paragraph style
4005 // to restore the attributes
4006
4007 newPara->GetAttributes().SetListStyleName(wxEmptyString);
4008 newPara->GetAttributes().SetLeftIndent(0, 0);
4009 newPara->GetAttributes().SetBulletText(wxEmptyString);
4010
4011 // Eliminate the main list-related attributes
4012 newPara->GetAttributes().SetFlags(newPara->GetAttributes().GetFlags() & ~wxTEXT_ATTR_LEFT_INDENT & ~wxTEXT_ATTR_BULLET_STYLE & ~wxTEXT_ATTR_BULLET_NUMBER & ~wxTEXT_ATTR_BULLET_TEXT & wxTEXT_ATTR_LIST_STYLE_NAME);
4013
4014 if (styleSheet && !newPara->GetAttributes().GetParagraphStyleName().IsEmpty())
4015 {
4016 wxRichTextParagraphStyleDefinition* def = styleSheet->FindParagraphStyle(newPara->GetAttributes().GetParagraphStyleName());
4017 if (def)
4018 {
4019 newPara->GetAttributes() = def->GetStyleMergedWithBase(styleSheet);
4020 }
4021 }
4022 }
4023 }
4024 }
4025
4026 node = node->GetNext();
4027 }
4028
4029 // Do action, or delay it until end of batch.
4030 if (haveControl && withUndo)
4031 buffer->SubmitAction(action);
4032
4033 return true;
4034 }
4035
4036 bool wxRichTextParagraphLayoutBox::SetListStyle(const wxRichTextRange& range, const wxString& defName, int flags, int startFrom, int specifiedLevel)
4037 {
4038 wxRichTextBuffer* buffer = GetBuffer();
4039 if (buffer && buffer->GetStyleSheet())
4040 {
4041 wxRichTextListStyleDefinition* def = buffer->GetStyleSheet()->FindListStyle(defName);
4042 if (def)
4043 return SetListStyle(range, def, flags, startFrom, specifiedLevel);
4044 }
4045 return false;
4046 }
4047
4048 /// Clear list for given range
4049 bool wxRichTextParagraphLayoutBox::ClearListStyle(const wxRichTextRange& range, int flags)
4050 {
4051 return SetListStyle(range, NULL, flags);
4052 }
4053
4054 /// Number/renumber any list elements in the given range
4055 bool wxRichTextParagraphLayoutBox::NumberList(const wxRichTextRange& range, wxRichTextListStyleDefinition* def, int flags, int startFrom, int specifiedLevel)
4056 {
4057 return DoNumberList(range, range, 0, def, flags, startFrom, specifiedLevel);
4058 }
4059
4060 /// Number/renumber any list elements in the given range. Also do promotion or demotion of items, if specified
4061 bool wxRichTextParagraphLayoutBox::DoNumberList(const wxRichTextRange& range, const wxRichTextRange& promotionRange, int promoteBy,
4062 wxRichTextListStyleDefinition* def, int flags, int startFrom, int specifiedLevel)
4063 {
4064 wxRichTextBuffer* buffer = GetBuffer();
4065 wxRichTextStyleSheet* styleSheet = buffer->GetStyleSheet();
4066
4067 bool withUndo = ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0);
4068 // bool applyMinimal = ((flags & wxRICHTEXT_SETSTYLE_OPTIMIZE) != 0);
4069 #if wxDEBUG_LEVEL
4070 bool specifyLevel = ((flags & wxRICHTEXT_SETSTYLE_SPECIFY_LEVEL) != 0);
4071 #endif
4072
4073 bool renumber = ((flags & wxRICHTEXT_SETSTYLE_RENUMBER) != 0);
4074
4075 // Max number of levels
4076 const int maxLevels = 10;
4077
4078 // The level we're looking at now
4079 int currentLevel = -1;
4080
4081 // The item number for each level
4082 int levels[maxLevels];
4083 int i;
4084
4085 // Reset all numbering
4086 for (i = 0; i < maxLevels; i++)
4087 {
4088 if (startFrom != -1)
4089 levels[i] = startFrom-1;
4090 else if (renumber) // start again
4091 levels[i] = 0;
4092 else
4093 levels[i] = -1; // start from the number we found, if any
4094 }
4095
4096 #if wxDEBUG_LEVEL
4097 wxASSERT(!specifyLevel || (specifyLevel && (specifiedLevel >= 0)));
4098 #endif
4099
4100 // If we are associated with a control, make undoable; otherwise, apply immediately
4101 // to the data.
4102
4103 bool haveControl = (buffer->GetRichTextCtrl() != NULL);
4104
4105 wxRichTextAction* action = NULL;
4106
4107 if (haveControl && withUndo)
4108 {
4109 action = new wxRichTextAction(NULL, _("Renumber List"), wxRICHTEXT_CHANGE_STYLE, buffer, this, buffer->GetRichTextCtrl());
4110 action->SetRange(range);
4111 action->SetPosition(buffer->GetRichTextCtrl()->GetCaretPosition());
4112 }
4113
4114 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
4115 while (node)
4116 {
4117 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
4118 // wxASSERT (para != NULL);
4119
4120 if (para && para->GetChildCount() > 0)
4121 {
4122 // Stop searching if we're beyond the range of interest
4123 if (para->GetRange().GetStart() > range.GetEnd())
4124 break;
4125
4126 if (!para->GetRange().IsOutside(range))
4127 {
4128 // We'll be using a copy of the paragraph to make style changes,
4129 // not updating the buffer directly.
4130 wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL);
4131
4132 if (haveControl && withUndo)
4133 {
4134 newPara = new wxRichTextParagraph(*para);
4135 action->GetNewParagraphs().AppendChild(newPara);
4136
4137 // Also store the old ones for Undo
4138 action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para));
4139 }
4140 else
4141 newPara = para;
4142
4143 wxRichTextListStyleDefinition* defToUse = def;
4144 if (!defToUse)
4145 {
4146 if (styleSheet && !newPara->GetAttributes().GetListStyleName().IsEmpty())
4147 defToUse = styleSheet->FindListStyle(newPara->GetAttributes().GetListStyleName());
4148 }
4149
4150 if (defToUse)
4151 {
4152 int thisIndent = newPara->GetAttributes().GetLeftIndent();
4153 int thisLevel = defToUse->FindLevelForIndent(thisIndent);
4154
4155 // If we've specified a level to apply to all, change the level.
4156 if (specifiedLevel != -1)
4157 thisLevel = specifiedLevel;
4158
4159 // Do promotion if specified
4160 if ((promoteBy != 0) && !para->GetRange().IsOutside(promotionRange))
4161 {
4162 thisLevel = thisLevel - promoteBy;
4163 if (thisLevel < 0)
4164 thisLevel = 0;
4165 if (thisLevel > 9)
4166 thisLevel = 9;
4167 }
4168
4169 // Apply the overall list style, and item style for this level
4170 wxRichTextAttr listStyle(defToUse->GetCombinedStyleForLevel(thisLevel, styleSheet));
4171 wxRichTextApplyStyle(newPara->GetAttributes(), listStyle);
4172
4173 // Preserve the existing list item continuation bullet style, if any
4174 if (para->GetAttributes().HasBulletStyle() && (para->GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_CONTINUATION))
4175 newPara->GetAttributes().SetBulletStyle(newPara->GetAttributes().GetBulletStyle()|wxTEXT_ATTR_BULLET_STYLE_CONTINUATION);
4176
4177 // OK, we've (re)applied the style, now let's get the numbering right.
4178
4179 if (currentLevel == -1)
4180 currentLevel = thisLevel;
4181
4182 // Same level as before, do nothing except increment level's number afterwards
4183 if (currentLevel == thisLevel)
4184 {
4185 }
4186 // A deeper level: start renumbering all levels after current level
4187 else if (thisLevel > currentLevel)
4188 {
4189 for (i = currentLevel+1; i <= thisLevel; i++)
4190 {
4191 levels[i] = 0;
4192 }
4193 currentLevel = thisLevel;
4194 }
4195 else if (thisLevel < currentLevel)
4196 {
4197 currentLevel = thisLevel;
4198 }
4199
4200 // Use the current numbering if -1 and we have a bullet number already
4201 if (levels[currentLevel] == -1)
4202 {
4203 if (newPara->GetAttributes().HasBulletNumber())
4204 levels[currentLevel] = newPara->GetAttributes().GetBulletNumber();
4205 else
4206 levels[currentLevel] = 1;
4207 }
4208 else
4209 {
4210 if (!(para->GetAttributes().HasBulletStyle() && (para->GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_CONTINUATION)))
4211 levels[currentLevel] ++;
4212 }
4213
4214 newPara->GetAttributes().SetBulletNumber(levels[currentLevel]);
4215
4216 // Create the bullet text if an outline list
4217 if (listStyle.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE)
4218 {
4219 wxString text;
4220 for (i = 0; i <= currentLevel; i++)
4221 {
4222 if (!text.IsEmpty())
4223 text += wxT(".");
4224 text += wxString::Format(wxT("%d"), levels[i]);
4225 }
4226 newPara->GetAttributes().SetBulletText(text);
4227 }
4228 }
4229 }
4230 }
4231
4232 node = node->GetNext();
4233 }
4234
4235 // Do action, or delay it until end of batch.
4236 if (haveControl && withUndo)
4237 buffer->SubmitAction(action);
4238
4239 return true;
4240 }
4241
4242 bool wxRichTextParagraphLayoutBox::NumberList(const wxRichTextRange& range, const wxString& defName, int flags, int startFrom, int specifiedLevel)
4243 {
4244 wxRichTextBuffer* buffer = GetBuffer();
4245 if (buffer->GetStyleSheet())
4246 {
4247 wxRichTextListStyleDefinition* def = NULL;
4248 if (!defName.IsEmpty())
4249 def = buffer->GetStyleSheet()->FindListStyle(defName);
4250 return NumberList(range, def, flags, startFrom, specifiedLevel);
4251 }
4252 return false;
4253 }
4254
4255 /// Promote the list items within the given range. promoteBy can be a positive or negative number, e.g. 1 or -1
4256 bool wxRichTextParagraphLayoutBox::PromoteList(int promoteBy, const wxRichTextRange& range, wxRichTextListStyleDefinition* def, int flags, int specifiedLevel)
4257 {
4258 // TODO
4259 // One strategy is to first work out the range within which renumbering must occur. Then could pass these two ranges
4260 // to NumberList with a flag indicating promotion is required within one of the ranges.
4261 // Find first and last paragraphs in range. Then for first, calculate new indentation and look back until we find
4262 // a paragraph that either has no list style, or has one that is different or whose indentation is less.
4263 // We start renumbering from the para after that different para we found. We specify that the numbering of that
4264 // list position will start from 1.
4265 // Similarly, we look after the last para in the promote range for an indentation that is less (or no list style).
4266 // We can end the renumbering at this point.
4267
4268 // For now, only renumber within the promotion range.
4269
4270 return DoNumberList(range, range, promoteBy, def, flags, 1, specifiedLevel);
4271 }
4272
4273 bool wxRichTextParagraphLayoutBox::PromoteList(int promoteBy, const wxRichTextRange& range, const wxString& defName, int flags, int specifiedLevel)
4274 {
4275 wxRichTextBuffer* buffer = GetBuffer();
4276 if (buffer->GetStyleSheet())
4277 {
4278 wxRichTextListStyleDefinition* def = NULL;
4279 if (!defName.IsEmpty())
4280 def = buffer->GetStyleSheet()->FindListStyle(defName);
4281 return PromoteList(promoteBy, range, def, flags, specifiedLevel);
4282 }
4283 return false;
4284 }
4285
4286 /// Fills in the attributes for numbering a paragraph after previousParagraph. It also finds the
4287 /// position of the paragraph that it had to start looking from.
4288 bool wxRichTextParagraphLayoutBox::FindNextParagraphNumber(wxRichTextParagraph* previousParagraph, wxRichTextAttr& attr) const
4289 {
4290 // Search for a paragraph that isn't a continuation paragraph (no bullet)
4291 while (previousParagraph && previousParagraph->GetAttributes().HasBulletStyle() && previousParagraph->GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_CONTINUATION)
4292 {
4293 wxRichTextObjectList::compatibility_iterator node = ((wxRichTextCompositeObject*) previousParagraph->GetParent())->GetChildren().Find(previousParagraph);
4294 if (node)
4295 {
4296 node = node->GetPrevious();
4297 if (node)
4298 previousParagraph = wxDynamicCast(node->GetData(), wxRichTextParagraph);
4299 else
4300 previousParagraph = NULL;
4301 }
4302 else
4303 previousParagraph = NULL;
4304 }
4305
4306 if (!previousParagraph->GetAttributes().HasFlag(wxTEXT_ATTR_BULLET_STYLE) || previousParagraph->GetAttributes().GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_NONE)
4307 return false;
4308
4309 wxRichTextBuffer* buffer = GetBuffer();
4310 wxRichTextStyleSheet* styleSheet = buffer->GetStyleSheet();
4311 if (styleSheet && !previousParagraph->GetAttributes().GetListStyleName().IsEmpty())
4312 {
4313 wxRichTextListStyleDefinition* def = styleSheet->FindListStyle(previousParagraph->GetAttributes().GetListStyleName());
4314 if (def)
4315 {
4316 // int thisIndent = previousParagraph->GetAttributes().GetLeftIndent();
4317 // int thisLevel = def->FindLevelForIndent(thisIndent);
4318
4319 bool isOutline = (previousParagraph->GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE) != 0;
4320
4321 attr.SetFlags(previousParagraph->GetAttributes().GetFlags() & (wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_BULLET_NUMBER|wxTEXT_ATTR_BULLET_TEXT|wxTEXT_ATTR_BULLET_NAME));
4322 if (previousParagraph->GetAttributes().HasBulletName())
4323 attr.SetBulletName(previousParagraph->GetAttributes().GetBulletName());
4324 attr.SetBulletStyle(previousParagraph->GetAttributes().GetBulletStyle());
4325 attr.SetListStyleName(previousParagraph->GetAttributes().GetListStyleName());
4326
4327 int nextNumber = previousParagraph->GetAttributes().GetBulletNumber() + 1;
4328 attr.SetBulletNumber(nextNumber);
4329
4330 if (isOutline)
4331 {
4332 wxString text = previousParagraph->GetAttributes().GetBulletText();
4333 if (!text.IsEmpty())
4334 {
4335 int pos = text.Find(wxT('.'), true);
4336 if (pos != wxNOT_FOUND)
4337 {
4338 text = text.Mid(0, text.Length() - pos - 1);
4339 }
4340 else
4341 text = wxEmptyString;
4342 if (!text.IsEmpty())
4343 text += wxT(".");
4344 text += wxString::Format(wxT("%d"), nextNumber);
4345 attr.SetBulletText(text);
4346 }
4347 }
4348
4349 return true;
4350 }
4351 else
4352 return false;
4353 }
4354 else
4355 return false;
4356 }
4357
4358 /*!
4359 * wxRichTextParagraph
4360 * This object represents a single paragraph (or in a straight text editor, a line).
4361 */
4362
4363 IMPLEMENT_DYNAMIC_CLASS(wxRichTextParagraph, wxRichTextCompositeObject)
4364
4365 wxArrayInt wxRichTextParagraph::sm_defaultTabs;
4366
4367 wxRichTextParagraph::wxRichTextParagraph(wxRichTextObject* parent, wxRichTextAttr* style):
4368 wxRichTextCompositeObject(parent)
4369 {
4370 if (style)
4371 SetAttributes(*style);
4372 }
4373
4374 wxRichTextParagraph::wxRichTextParagraph(const wxString& text, wxRichTextObject* parent, wxRichTextAttr* paraStyle, wxRichTextAttr* charStyle):
4375 wxRichTextCompositeObject(parent)
4376 {
4377 if (paraStyle)
4378 SetAttributes(*paraStyle);
4379
4380 AppendChild(new wxRichTextPlainText(text, this, charStyle));
4381 }
4382
4383 wxRichTextParagraph::~wxRichTextParagraph()
4384 {
4385 ClearLines();
4386 }
4387
4388 /// Draw the item
4389 bool wxRichTextParagraph::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int WXUNUSED(descent), int style)
4390 {
4391 if (!IsShown())
4392 return true;
4393
4394 // Currently we don't merge these attributes with the parent, but we
4395 // should consider whether we should (e.g. if we set a border colour
4396 // for all paragraphs). But generally box attributes are likely to be
4397 // different for different objects.
4398 wxRect paraRect = GetRect();
4399 wxRichTextAttr attr = GetCombinedAttributes();
4400 context.ApplyVirtualAttributes(attr, this);
4401
4402 DrawBoxAttributes(dc, GetBuffer(), attr, paraRect);
4403
4404 // Draw the bullet, if any
4405 if ((attr.GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_NONE) == 0 && (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_CONTINUATION) == 0)
4406 {
4407 if (attr.GetLeftSubIndent() != 0)
4408 {
4409 int spaceBeforePara = ConvertTenthsMMToPixels(dc, attr.GetParagraphSpacingBefore());
4410 int leftIndent = ConvertTenthsMMToPixels(dc, attr.GetLeftIndent());
4411
4412 wxRichTextAttr bulletAttr(attr);
4413
4414 // Combine with the font of the first piece of content, if one is specified
4415 if (GetChildren().GetCount() > 0)
4416 {
4417 wxRichTextObject* firstObj = (wxRichTextObject*) GetChildren().GetFirst()->GetData();
4418 if (!firstObj->IsFloatable() && firstObj->GetAttributes().HasFont())
4419 {
4420 wxRichTextApplyStyle(bulletAttr, firstObj->GetAttributes());
4421 }
4422 }
4423
4424 // Get line height from first line, if any
4425 wxRichTextLine* line = m_cachedLines.GetFirst() ? (wxRichTextLine* ) m_cachedLines.GetFirst()->GetData() : NULL;
4426
4427 wxPoint linePos;
4428 int lineHeight wxDUMMY_INITIALIZE(0);
4429 if (line)
4430 {
4431 lineHeight = line->GetSize().y;
4432 linePos = line->GetPosition() + GetPosition();
4433 }
4434 else
4435 {
4436 wxFont font;
4437 if (bulletAttr.HasFont() && GetBuffer())
4438 font = GetBuffer()->GetFontTable().FindFont(bulletAttr);
4439 else
4440 font = (*wxNORMAL_FONT);
4441
4442 wxCheckSetFont(dc, font);
4443
4444 lineHeight = dc.GetCharHeight();
4445 linePos = GetPosition();
4446 linePos.y += spaceBeforePara;
4447 }
4448
4449 wxRect bulletRect(GetPosition().x + leftIndent, linePos.y, linePos.x - (GetPosition().x + leftIndent), lineHeight);
4450
4451 if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_BITMAP)
4452 {
4453 if (wxRichTextBuffer::GetRenderer())
4454 wxRichTextBuffer::GetRenderer()->DrawBitmapBullet(this, dc, bulletAttr, bulletRect);
4455 }
4456 else if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_STANDARD)
4457 {
4458 if (wxRichTextBuffer::GetRenderer())
4459 wxRichTextBuffer::GetRenderer()->DrawStandardBullet(this, dc, bulletAttr, bulletRect);
4460 }
4461 else
4462 {
4463 wxString bulletText = GetBulletText();
4464
4465 if (!bulletText.empty() && wxRichTextBuffer::GetRenderer())
4466 wxRichTextBuffer::GetRenderer()->DrawTextBullet(this, dc, bulletAttr, bulletRect, bulletText);
4467 }
4468 }
4469 }
4470
4471 // Draw the range for each line, one object at a time.
4472
4473 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
4474 while (node)
4475 {
4476 wxRichTextLine* line = node->GetData();
4477 wxRichTextRange lineRange = line->GetAbsoluteRange();
4478
4479 // Lines are specified relative to the paragraph
4480
4481 wxPoint linePosition = line->GetPosition() + GetPosition();
4482
4483 // Don't draw if off the screen
4484 if (((style & wxRICHTEXT_DRAW_IGNORE_CACHE) != 0) || ((linePosition.y + line->GetSize().y) >= rect.y && linePosition.y <= rect.y + rect.height))
4485 {
4486 wxPoint objectPosition = linePosition;
4487 int maxDescent = line->GetDescent();
4488
4489 // Loop through objects until we get to the one within range
4490 wxRichTextObjectList::compatibility_iterator node2 = m_children.GetFirst();
4491
4492 int i = 0;
4493 while (node2)
4494 {
4495 wxRichTextObject* child = node2->GetData();
4496
4497 if (!child->IsFloating() && child->GetRange().GetLength() > 0 && !child->GetRange().IsOutside(lineRange) && !lineRange.IsOutside(range))
4498 {
4499 // Draw this part of the line at the correct position
4500 wxRichTextRange objectRange(child->GetRange());
4501 objectRange.LimitTo(lineRange);
4502
4503 wxSize objectSize;
4504 if (child->IsTopLevel())
4505 {
4506 objectSize = child->GetCachedSize();
4507 objectRange = child->GetOwnRange();
4508 }
4509 else
4510 {
4511 #if wxRICHTEXT_USE_OPTIMIZED_LINE_DRAWING && wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
4512 if (i < (int) line->GetObjectSizes().GetCount())
4513 {
4514 objectSize.x = line->GetObjectSizes()[(size_t) i];
4515 }
4516 else
4517 #endif
4518 {
4519 int descent = 0;
4520 child->GetRangeSize(objectRange, objectSize, descent, dc, context, wxRICHTEXT_UNFORMATTED, objectPosition);
4521 }
4522 }
4523
4524 // Use the child object's width, but the whole line's height
4525 wxRect childRect(objectPosition, wxSize(objectSize.x, line->GetSize().y));
4526 child->Draw(dc, context, objectRange, selection, childRect, maxDescent, style);
4527
4528 objectPosition.x += objectSize.x;
4529 i ++;
4530 }
4531 else if (child->GetRange().GetStart() > lineRange.GetEnd())
4532 // Can break out of inner loop now since we've passed this line's range
4533 break;
4534
4535 node2 = node2->GetNext();
4536 }
4537 }
4538
4539 node = node->GetNext();
4540 }
4541
4542 return true;
4543 }
4544
4545 // Get the range width using partial extents calculated for the whole paragraph.
4546 static int wxRichTextGetRangeWidth(const wxRichTextParagraph& para, const wxRichTextRange& range, const wxArrayInt& partialExtents)
4547 {
4548 wxASSERT(partialExtents.GetCount() >= (size_t) range.GetLength());
4549
4550 if (partialExtents.GetCount() < (size_t) range.GetLength())
4551 return 0;
4552
4553 int leftMostPos = 0;
4554 if (range.GetStart() - para.GetRange().GetStart() > 0)
4555 leftMostPos = partialExtents[range.GetStart() - para.GetRange().GetStart() - 1];
4556
4557 int rightMostPos = partialExtents[range.GetEnd() - para.GetRange().GetStart()];
4558
4559 int w = rightMostPos - leftMostPos;
4560
4561 return w;
4562 }
4563
4564 /// Lay the item out
4565 bool wxRichTextParagraph::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& parentRect, int style)
4566 {
4567 // Deal with floating objects firstly before the normal layout
4568 wxRichTextBuffer* buffer = GetBuffer();
4569 wxASSERT(buffer);
4570 wxRichTextFloatCollector* collector = GetContainer()->GetFloatCollector();
4571 wxASSERT(collector);
4572 LayoutFloat(dc, context, rect, style, collector);
4573
4574 wxRichTextAttr attr = GetCombinedAttributes();
4575 context.ApplyVirtualAttributes(attr, this);
4576
4577 // ClearLines();
4578
4579 // Increase the size of the paragraph due to spacing
4580 int spaceBeforePara = ConvertTenthsMMToPixels(dc, attr.GetParagraphSpacingBefore());
4581 int spaceAfterPara = ConvertTenthsMMToPixels(dc, attr.GetParagraphSpacingAfter());
4582 int leftIndent = ConvertTenthsMMToPixels(dc, attr.GetLeftIndent());
4583 int leftSubIndent = ConvertTenthsMMToPixels(dc, attr.GetLeftSubIndent());
4584 int rightIndent = ConvertTenthsMMToPixels(dc, attr.GetRightIndent());
4585
4586 int lineSpacing = 0;
4587
4588 // Let's assume line spacing of 10 is normal, 15 is 1.5, 20 is 2, etc.
4589 if (attr.HasLineSpacing() && attr.GetLineSpacing() > 0 && attr.GetFont().IsOk())
4590 {
4591 wxCheckSetFont(dc, attr.GetFont());
4592 lineSpacing = (int) (double(dc.GetCharHeight()) * (double(attr.GetLineSpacing())/10.0 - 1.0));
4593 }
4594
4595 // Start position for each line relative to the paragraph
4596 int startPositionFirstLine = leftIndent;
4597 int startPositionSubsequentLines = leftIndent + leftSubIndent;
4598
4599 // If we have a bullet in this paragraph, the start position for the first line's text
4600 // is actually leftIndent + leftSubIndent.
4601 if (attr.GetBulletStyle() != wxTEXT_ATTR_BULLET_STYLE_NONE)
4602 startPositionFirstLine = startPositionSubsequentLines;
4603
4604 long lastEndPos = GetRange().GetStart()-1;
4605 long lastCompletedEndPos = lastEndPos;
4606
4607 int currentWidth = 0;
4608 SetPosition(rect.GetPosition());
4609
4610 wxPoint currentPosition(0, spaceBeforePara); // We will calculate lines relative to paragraph
4611 int lineHeight = 0;
4612 int maxWidth = 0;
4613 int maxHeight = currentPosition.y;
4614 int maxAscent = 0;
4615 int maxDescent = 0;
4616 int lineCount = 0;
4617 int lineAscent = 0;
4618 int lineDescent = 0;
4619
4620 wxRichTextObjectList::compatibility_iterator node;
4621
4622 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
4623 wxUnusedVar(style);
4624 wxArrayInt partialExtents;
4625
4626 wxSize paraSize;
4627 int paraDescent = 0;
4628
4629 // This calculates the partial text extents
4630 GetRangeSize(GetRange(), paraSize, paraDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_CACHE_SIZE, rect.GetPosition(), & partialExtents);
4631 #else
4632 node = m_children.GetFirst();
4633 while (node)
4634 {
4635 wxRichTextObject* child = node->GetData();
4636
4637 //child->SetCachedSize(wxDefaultSize);
4638 child->Layout(dc, context, rect, style);
4639
4640 node = node->GetNext();
4641 }
4642 #endif
4643
4644 // Split up lines
4645
4646 // We may need to go back to a previous child, in which case create the new line,
4647 // find the child corresponding to the start position of the string, and
4648 // continue.
4649
4650 wxRect availableRect;
4651
4652 node = m_children.GetFirst();
4653 while (node)
4654 {
4655 wxRichTextObject* child = node->GetData();
4656
4657 // If floating, ignore. We already laid out floats.
4658 // Also ignore if empty object, except if we haven't got any
4659 // size yet.
4660 if (child->IsFloating() || !child->IsShown() ||
4661 (child->GetRange().GetLength() == 0 && maxHeight > spaceBeforePara)
4662 )
4663 {
4664 node = node->GetNext();
4665 continue;
4666 }
4667
4668 // If this is e.g. a composite text box, it will need to be laid out itself.
4669 // But if just a text fragment or image, for example, this will
4670 // do nothing. NB: won't we need to set the position after layout?
4671 // since for example if position is dependent on vertical line size, we
4672 // can't tell the position until the size is determined. So possibly introduce
4673 // another layout phase.
4674
4675 // We may only be looking at part of a child, if we searched back for wrapping
4676 // and found a suitable point some way into the child. So get the size for the fragment
4677 // if necessary.
4678
4679 long nextBreakPos = GetFirstLineBreakPosition(lastEndPos+1);
4680 long lastPosToUse = child->GetRange().GetEnd();
4681 bool lineBreakInThisObject = (nextBreakPos > -1 && nextBreakPos <= child->GetRange().GetEnd());
4682
4683 if (lineBreakInThisObject)
4684 lastPosToUse = nextBreakPos;
4685
4686 wxSize childSize;
4687 int childDescent = 0;
4688
4689 int startOffset = (lineCount == 0 ? startPositionFirstLine : startPositionSubsequentLines);
4690 availableRect = wxRect(rect.x + startOffset, rect.y + currentPosition.y,
4691 rect.width - startOffset - rightIndent, rect.height);
4692
4693 if (child->IsTopLevel())
4694 {
4695 wxSize oldSize = child->GetCachedSize();
4696
4697 child->Invalidate(wxRICHTEXT_ALL);
4698 child->SetPosition(wxPoint(0, 0));
4699
4700 // Lays out the object first with a given amount of space, and then if no width was specified in attr,
4701 // lays out the object again using the minimum size
4702 // The position will be determined by its location in its line,
4703 // and not by the child's actual position.
4704 child->LayoutToBestSize(dc, context, buffer,
4705 attr, child->GetAttributes(), availableRect, parentRect, style);
4706
4707 if (oldSize != child->GetCachedSize())
4708 {
4709 partialExtents.Clear();
4710
4711 // Recalculate the partial text extents since the child object changed size
4712 GetRangeSize(GetRange(), paraSize, paraDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_CACHE_SIZE, wxPoint(0,0), & partialExtents);
4713 }
4714 }
4715
4716 // Problem: we need to layout composites here for which we need the available width,
4717 // but we can't get the available width without using the float collector which
4718 // needs to know the object height.
4719
4720 if ((nextBreakPos == -1) && (lastEndPos == child->GetRange().GetStart() - 1)) // i.e. we want to get the whole thing
4721 {
4722 childSize = child->GetCachedSize();
4723 childDescent = child->GetDescent();
4724 }
4725 else
4726 {
4727 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
4728 // Get height only, then the width using the partial extents
4729 GetRangeSize(wxRichTextRange(lastEndPos+1, lastPosToUse), childSize, childDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_HEIGHT_ONLY);
4730 childSize.x = wxRichTextGetRangeWidth(*this, wxRichTextRange(lastEndPos+1, lastPosToUse), partialExtents);
4731 #else
4732 GetRangeSize(wxRichTextRange(lastEndPos+1, lastPosToUse), childSize, childDescent, dc, context, wxRICHTEXT_UNFORMATTED, rect.GetPosition());
4733 #endif
4734 }
4735
4736 bool doLoop = true;
4737 int loopIterations = 0;
4738
4739 // If there are nested objects that need to lay themselves out, we have to do this in a
4740 // loop because the height of the object may well depend on the available width.
4741 // And because of floating object positioning, the available width depends on the
4742 // height of the object and whether it will clash with the floating objects.
4743 // So, we see whether the available width changes due to the presence of floating images.
4744 // If it does, then we'll use the new restricted width to find the object height again.
4745 // If this causes another restriction in the available width, we'll try again, until
4746 // either we lose patience or the available width settles down.
4747 do
4748 {
4749 loopIterations ++;
4750
4751 wxRect oldAvailableRect = availableRect;
4752
4753 // Available width depends on the floating objects and the line height.
4754 // Note: the floating objects may be placed vertically along the two sides of
4755 // buffer, so we may have different available line widths with different
4756 // [startY, endY]. So, we can't determine how wide the available
4757 // space is until we know the exact line height.
4758 if (childDescent == 0)
4759 {
4760 lineHeight = wxMax(lineHeight, childSize.y);
4761 lineDescent = maxDescent;
4762 lineAscent = maxAscent;
4763 }
4764 else
4765 {
4766 lineDescent = wxMax(childDescent, maxDescent);
4767 lineAscent = wxMax(childSize.y-childDescent, maxAscent);
4768 }
4769 lineHeight = wxMax(lineHeight, (lineDescent + lineAscent));
4770 wxRect floatAvailableRect = collector->GetAvailableRect(rect.y + currentPosition.y, rect.y + currentPosition.y + lineHeight);
4771
4772 // Adjust availableRect to the space that is available when taking floating objects into account.
4773
4774 if (floatAvailableRect.x + startOffset > availableRect.x)
4775 {
4776 int newX = floatAvailableRect.x + startOffset;
4777 int newW = availableRect.width - (newX - availableRect.x);
4778 availableRect.x = newX;
4779 availableRect.width = newW;
4780 }
4781
4782 if (floatAvailableRect.width < availableRect.width)
4783 availableRect.width = floatAvailableRect.width;
4784
4785 currentPosition.x = availableRect.x - rect.x;
4786
4787 if (child->IsTopLevel() && loopIterations <= 20)
4788 {
4789 if (availableRect != oldAvailableRect)
4790 {
4791 wxSize oldSize = child->GetCachedSize();
4792
4793 // Lays out the object first with a given amount of space, and then if no width was specified in attr,
4794 // lays out the object again using the minimum size
4795 child->Invalidate(wxRICHTEXT_ALL);
4796 child->LayoutToBestSize(dc, context, buffer,
4797 attr, child->GetAttributes(), availableRect, parentRect, style);
4798 childSize = child->GetCachedSize();
4799 childDescent = child->GetDescent();
4800
4801 if (oldSize != child->GetCachedSize())
4802 {
4803 partialExtents.Clear();
4804
4805 // Recalculate the partial text extents since the child object changed size
4806 GetRangeSize(GetRange(), paraSize, paraDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_CACHE_SIZE, wxPoint(0,0), & partialExtents);
4807 }
4808
4809 // Go around the loop finding the available rect for the given floating objects
4810 }
4811 else
4812 doLoop = false;
4813 }
4814 else
4815 doLoop = false;
4816 }
4817 while (doLoop);
4818
4819 if (child->IsTopLevel())
4820 {
4821 // We can move it to the correct position at this point
4822 // TODO: probably need to add margin
4823 child->Move(GetPosition() + wxPoint(currentWidth + (wxMax(leftIndent, leftIndent + leftSubIndent)), currentPosition.y));
4824 }
4825
4826 // Cases:
4827 // 1) There was a line break BEFORE the natural break
4828 // 2) There was a line break AFTER the natural break
4829 // 3) It's the last line
4830 // 4) The child still fits (carry on) - 'else' clause
4831
4832 if ((lineBreakInThisObject && (childSize.x + currentWidth <= availableRect.width))
4833 ||
4834 (childSize.x + currentWidth > availableRect.width)
4835 ||
4836 ((childSize.x + currentWidth <= availableRect.width) && !node->GetNext())
4837
4838 )
4839 {
4840 long wrapPosition = 0;
4841 if ((childSize.x + currentWidth <= availableRect.width) && !node->GetNext() && !lineBreakInThisObject)
4842 wrapPosition = child->GetRange().GetEnd();
4843 else
4844
4845 // Find a place to wrap. This may walk back to previous children,
4846 // for example if a word spans several objects.
4847 // Note: one object must contains only one wxTextAtrr, so the line height will not
4848 // change inside one object. Thus, we can pass the remain line width to the
4849 // FindWrapPosition function.
4850 if (!FindWrapPosition(wxRichTextRange(lastCompletedEndPos+1, child->GetRange().GetEnd()), dc, context, availableRect.width, wrapPosition, & partialExtents))
4851 {
4852 // If the function failed, just cut it off at the end of this child.
4853 wrapPosition = child->GetRange().GetEnd();
4854 }
4855
4856 // FindWrapPosition can still return a value that will put us in an endless wrapping loop
4857 if (wrapPosition <= lastCompletedEndPos)
4858 wrapPosition = wxMax(lastCompletedEndPos+1,child->GetRange().GetEnd());
4859
4860 // Line end position shouldn't be the same as the end, or greater.
4861 if (wrapPosition >= GetRange().GetEnd())
4862 wrapPosition = wxMax(0, GetRange().GetEnd()-1);
4863
4864 // wxLogDebug(wxT("Split at %ld"), wrapPosition);
4865
4866 // Let's find the actual size of the current line now
4867 wxSize actualSize;
4868 wxRichTextRange actualRange(lastCompletedEndPos+1, wrapPosition);
4869
4870 childDescent = 0;
4871
4872 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
4873 if (!child->IsEmpty())
4874 {
4875 // Get height only, then the width using the partial extents
4876 GetRangeSize(actualRange, actualSize, childDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_HEIGHT_ONLY);
4877 actualSize.x = wxRichTextGetRangeWidth(*this, actualRange, partialExtents);
4878 }
4879 else
4880 #endif
4881 GetRangeSize(actualRange, actualSize, childDescent, dc, context, wxRICHTEXT_UNFORMATTED);
4882
4883 currentWidth = actualSize.x;
4884
4885 // The descent for the whole line at this point, is the correct max descent
4886 maxDescent = childDescent;
4887 // Maximum ascent
4888 maxAscent = actualSize.y-childDescent;
4889
4890 // lineHeight is given by the height for the whole line, since it will
4891 // take into account ascend/descend.
4892 lineHeight = actualSize.y;
4893
4894 if (lineHeight == 0 && buffer)
4895 {
4896 wxFont font(buffer->GetFontTable().FindFont(attr));
4897 wxCheckSetFont(dc, font);
4898 lineHeight = dc.GetCharHeight();
4899 }
4900
4901 if (maxDescent == 0)
4902 {
4903 int w, h;
4904 dc.GetTextExtent(wxT("X"), & w, &h, & maxDescent);
4905 }
4906
4907 // Add a new line
4908 wxRichTextLine* line = AllocateLine(lineCount);
4909
4910 // Set relative range so we won't have to change line ranges when paragraphs are moved
4911 line->SetRange(wxRichTextRange(actualRange.GetStart() - GetRange().GetStart(), actualRange.GetEnd() - GetRange().GetStart()));
4912 line->SetPosition(currentPosition);
4913 line->SetSize(wxSize(currentWidth, lineHeight));
4914 line->SetDescent(maxDescent);
4915
4916 maxHeight = currentPosition.y + lineHeight;
4917
4918 // Now move down a line. TODO: add margins, spacing
4919 currentPosition.y += lineHeight;
4920 currentPosition.y += lineSpacing;
4921 maxDescent = 0;
4922 maxAscent = 0;
4923 maxWidth = wxMax(maxWidth, currentWidth+startOffset);
4924 currentWidth = 0;
4925
4926 lineCount ++;
4927
4928 // TODO: account for zero-length objects
4929 // wxASSERT(wrapPosition > lastCompletedEndPos);
4930
4931 lastEndPos = wrapPosition;
4932 lastCompletedEndPos = lastEndPos;
4933
4934 lineHeight = 0;
4935
4936 if (wrapPosition < GetRange().GetEnd()-1)
4937 {
4938 // May need to set the node back to a previous one, due to searching back in wrapping
4939 wxRichTextObject* childAfterWrapPosition = FindObjectAtPosition(wrapPosition+1);
4940 if (childAfterWrapPosition)
4941 node = m_children.Find(childAfterWrapPosition);
4942 else
4943 node = node->GetNext();
4944 }
4945 else
4946 node = node->GetNext();
4947
4948 // Apply paragraph styles such as alignment to the wrapped line
4949 ApplyParagraphStyle(line, attr, availableRect, dc);
4950 }
4951 else
4952 {
4953 // We still fit, so don't add a line, and keep going
4954 currentWidth += childSize.x;
4955
4956 if (childDescent == 0)
4957 {
4958 // An object with a zero descend value wants to take up the whole
4959 // height regardless of baseline
4960 lineHeight = wxMax(lineHeight, childSize.y);
4961 }
4962 else
4963 {
4964 maxDescent = wxMax(childDescent, maxDescent);
4965 maxAscent = wxMax(childSize.y-childDescent, maxAscent);
4966 }
4967
4968 lineHeight = wxMax(lineHeight, (maxDescent + maxAscent));
4969
4970 maxWidth = wxMax(maxWidth, currentWidth+startOffset);
4971 lastEndPos = child->GetRange().GetEnd();
4972
4973 node = node->GetNext();
4974 }
4975 }
4976
4977 //wxASSERT(!(lastCompletedEndPos != -1 && lastCompletedEndPos < GetRange().GetEnd()-1));
4978
4979 // Remove remaining unused line objects, if any
4980 ClearUnusedLines(lineCount);
4981
4982 // We need to add back the margins etc.
4983 {
4984 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
4985 contentRect = wxRect(wxPoint(0, 0), wxSize(maxWidth, currentPosition.y + spaceAfterPara));
4986 GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
4987 SetCachedSize(marginRect.GetSize());
4988 }
4989
4990 // The maximum size is the length of the paragraph stretched out into a line.
4991 // So if there were a single word, or an image, or a fixed-size text box, the object could be shrunk around
4992 // this size. TODO: take into account line breaks.
4993 {
4994 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
4995 contentRect = wxRect(wxPoint(0, 0), wxSize(paraSize.x + wxMax(leftIndent, leftIndent + leftSubIndent) + rightIndent, currentPosition.y + spaceAfterPara));
4996 GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
4997 SetMaxSize(marginRect.GetSize());
4998 }
4999
5000 // Find the greatest minimum size. Currently we only look at non-text objects,
5001 // which isn't ideal but it would be slow to find the maximum word width to
5002 // use as the minimum.
5003 {
5004 int minWidth = 0;
5005 node = m_children.GetFirst();
5006 while (node)
5007 {
5008 wxRichTextObject* child = node->GetData();
5009
5010 // If floating, ignore. We already laid out floats.
5011 // Also ignore if empty object, except if we haven't got any
5012 // size yet.
5013 if (!child->IsFloating() && child->GetRange().GetLength() != 0 && !wxDynamicCast(child, wxRichTextPlainText))
5014 {
5015 if (child->GetCachedSize().x > minWidth)
5016 minWidth = child->GetMinSize().x;
5017 }
5018 node = node->GetNext();
5019 }
5020
5021 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
5022 contentRect = wxRect(wxPoint(0, 0), wxSize(minWidth, currentPosition.y + spaceAfterPara));
5023 GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
5024 SetMinSize(marginRect.GetSize());
5025 }
5026
5027 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
5028 #if wxRICHTEXT_USE_OPTIMIZED_LINE_DRAWING
5029 // Use the text extents to calculate the size of each fragment in each line
5030 wxRichTextLineList::compatibility_iterator lineNode = m_cachedLines.GetFirst();
5031 while (lineNode)
5032 {
5033 wxRichTextLine* line = lineNode->GetData();
5034 wxRichTextRange lineRange = line->GetAbsoluteRange();
5035
5036 // Loop through objects until we get to the one within range
5037 wxRichTextObjectList::compatibility_iterator node2 = m_children.GetFirst();
5038
5039 while (node2)
5040 {
5041 wxRichTextObject* child = node2->GetData();
5042
5043 if (child->GetRange().GetLength() > 0 && !child->GetRange().IsOutside(lineRange))
5044 {
5045 wxRichTextRange rangeToUse = lineRange;
5046 rangeToUse.LimitTo(child->GetRange());
5047
5048 // Find the size of the child from the text extents, and store in an array
5049 // for drawing later
5050 int left = 0;
5051 if (rangeToUse.GetStart() > GetRange().GetStart())
5052 left = partialExtents[(rangeToUse.GetStart()-1) - GetRange().GetStart()];
5053 int right = partialExtents[rangeToUse.GetEnd() - GetRange().GetStart()];
5054 int sz = right - left;
5055 line->GetObjectSizes().Add(sz);
5056 }
5057 else if (child->GetRange().GetStart() > lineRange.GetEnd())
5058 // Can break out of inner loop now since we've passed this line's range
5059 break;
5060
5061 node2 = node2->GetNext();
5062 }
5063
5064 lineNode = lineNode->GetNext();
5065 }
5066 #endif
5067 #endif
5068
5069 return true;
5070 }
5071
5072 /// Apply paragraph styles, such as centering, to wrapped lines
5073 /// TODO: take into account box attributes, possibly
5074 void wxRichTextParagraph::ApplyParagraphStyle(wxRichTextLine* line, const wxRichTextAttr& attr, const wxRect& rect, wxDC& dc)
5075 {
5076 if (!attr.HasAlignment())
5077 return;
5078
5079 wxPoint pos = line->GetPosition();
5080 wxPoint originalPos = pos;
5081 wxSize size = line->GetSize();
5082
5083 // centering, right-justification
5084 if (attr.HasAlignment() && attr.GetAlignment() == wxTEXT_ALIGNMENT_CENTRE)
5085 {
5086 int rightIndent = ConvertTenthsMMToPixels(dc, attr.GetRightIndent());
5087 pos.x = (rect.GetWidth() - rightIndent - size.x)/2 + pos.x;
5088 line->SetPosition(pos);
5089 }
5090 else if (attr.HasAlignment() && attr.GetAlignment() == wxTEXT_ALIGNMENT_RIGHT)
5091 {
5092 int rightIndent = ConvertTenthsMMToPixels(dc, attr.GetRightIndent());
5093 pos.x = pos.x + rect.GetWidth() - size.x - rightIndent;
5094 line->SetPosition(pos);
5095 }
5096
5097 if (pos != originalPos)
5098 {
5099 wxPoint inc = pos - originalPos;
5100
5101 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
5102
5103 while (node)
5104 {
5105 wxRichTextObject* child = node->GetData();
5106 if (child->IsTopLevel() && !child->GetRange().IsOutside(line->GetAbsoluteRange()))
5107 child->Move(child->GetPosition() + inc);
5108
5109 node = node->GetNext();
5110 }
5111 }
5112 }
5113
5114 /// Insert text at the given position
5115 bool wxRichTextParagraph::InsertText(long pos, const wxString& text)
5116 {
5117 wxRichTextObject* childToUse = NULL;
5118 wxRichTextObjectList::compatibility_iterator nodeToUse = wxRichTextObjectList::compatibility_iterator();
5119
5120 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
5121 while (node)
5122 {
5123 wxRichTextObject* child = node->GetData();
5124 if (child->GetRange().Contains(pos) && child->GetRange().GetLength() > 0)
5125 {
5126 childToUse = child;
5127 nodeToUse = node;
5128 break;
5129 }
5130
5131 node = node->GetNext();
5132 }
5133
5134 if (childToUse)
5135 {
5136 wxRichTextPlainText* textObject = wxDynamicCast(childToUse, wxRichTextPlainText);
5137 if (textObject)
5138 {
5139 int posInString = pos - textObject->GetRange().GetStart();
5140
5141 wxString newText = textObject->GetText().Mid(0, posInString) +
5142 text + textObject->GetText().Mid(posInString);
5143 textObject->SetText(newText);
5144
5145 int textLength = text.length();
5146
5147 textObject->SetRange(wxRichTextRange(textObject->GetRange().GetStart(),
5148 textObject->GetRange().GetEnd() + textLength));
5149
5150 // Increment the end range of subsequent fragments in this paragraph.
5151 // We'll set the paragraph range itself at a higher level.
5152
5153 wxRichTextObjectList::compatibility_iterator node = nodeToUse->GetNext();
5154 while (node)
5155 {
5156 wxRichTextObject* child = node->GetData();
5157 child->SetRange(wxRichTextRange(textObject->GetRange().GetStart() + textLength,
5158 textObject->GetRange().GetEnd() + textLength));
5159
5160 node = node->GetNext();
5161 }
5162
5163 return true;
5164 }
5165 else
5166 {
5167 // TODO: if not a text object, insert at closest position, e.g. in front of it
5168 }
5169 }
5170 else
5171 {
5172 // Add at end.
5173 // Don't pass parent initially to suppress auto-setting of parent range.
5174 // We'll do that at a higher level.
5175 wxRichTextPlainText* textObject = new wxRichTextPlainText(text, this);
5176
5177 AppendChild(textObject);
5178 return true;
5179 }
5180
5181 return false;
5182 }
5183
5184 void wxRichTextParagraph::Copy(const wxRichTextParagraph& obj)
5185 {
5186 wxRichTextCompositeObject::Copy(obj);
5187 }
5188
5189 /// Clear the cached lines
5190 void wxRichTextParagraph::ClearLines()
5191 {
5192 WX_CLEAR_LIST(wxRichTextLineList, m_cachedLines);
5193 }
5194
5195 /// Get/set the object size for the given range. Returns false if the range
5196 /// is invalid for this object.
5197 bool wxRichTextParagraph::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, wxPoint position, wxArrayInt* partialExtents) const
5198 {
5199 if (!range.IsWithin(GetRange()))
5200 return false;
5201
5202 if (flags & wxRICHTEXT_UNFORMATTED)
5203 {
5204 // Just use unformatted data, assume no line breaks
5205 wxSize sz;
5206
5207 wxArrayInt childExtents;
5208 wxArrayInt* p;
5209 if (partialExtents)
5210 p = & childExtents;
5211 else
5212 p = NULL;
5213
5214 int maxDescent = 0;
5215 int maxAscent = 0;
5216 int maxLineHeight = 0;
5217
5218 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
5219 while (node)
5220 {
5221 wxRichTextObject* child = node->GetData();
5222 if (!child->GetRange().IsOutside(range))
5223 {
5224 // Floating objects have a zero size within the paragraph.
5225 if (child->IsFloating())
5226 {
5227 if (partialExtents)
5228 {
5229 int lastSize;
5230 if (partialExtents->GetCount() > 0)
5231 lastSize = (*partialExtents)[partialExtents->GetCount()-1];
5232 else
5233 lastSize = 0;
5234
5235 partialExtents->Add(0 /* zero size */ + lastSize);
5236 }
5237 }
5238 else
5239 {
5240 wxSize childSize;
5241
5242 wxRichTextRange rangeToUse = range;
5243 rangeToUse.LimitTo(child->GetRange());
5244 int childDescent = 0;
5245
5246 // At present wxRICHTEXT_HEIGHT_ONLY is only fast if we've already cached the size,
5247 // but it's only going to be used after caching has taken place.
5248 if ((flags & wxRICHTEXT_HEIGHT_ONLY) && child->GetCachedSize().y != 0)
5249 {
5250 childDescent = child->GetDescent();
5251 childSize = child->GetCachedSize();
5252
5253 if (childDescent == 0)
5254 {
5255 maxLineHeight = wxMax(maxLineHeight, childSize.y);
5256 }
5257 else
5258 {
5259 maxDescent = wxMax(maxDescent, childDescent);
5260 maxAscent = wxMax(maxAscent, (childSize.y - childDescent));
5261 }
5262
5263 maxLineHeight = wxMax(maxLineHeight, (maxAscent + maxDescent));
5264
5265 sz.y = wxMax(sz.y, maxLineHeight);
5266 sz.x += childSize.x;
5267 descent = maxDescent;
5268 }
5269 else if (child->IsTopLevel())
5270 {
5271 childDescent = child->GetDescent();
5272 childSize = child->GetCachedSize();
5273
5274 if (childDescent == 0)
5275 {
5276 maxLineHeight = wxMax(maxLineHeight, childSize.y);
5277 }
5278 else
5279 {
5280 maxDescent = wxMax(maxDescent, childDescent);
5281 maxAscent = wxMax(maxAscent, (childSize.y - childDescent));
5282 }
5283
5284 maxLineHeight = wxMax(maxLineHeight, (maxAscent + maxDescent));
5285
5286 sz.y = wxMax(sz.y, maxLineHeight);
5287 sz.x += childSize.x;
5288 descent = maxDescent;
5289
5290 // FIXME: this won't change the original values.
5291 // Should we be calling GetRangeSize above instead of using cached values?
5292 #if 0
5293 if ((flags & wxRICHTEXT_CACHE_SIZE) && (rangeToUse == child->GetRange()))
5294 {
5295 child->SetCachedSize(childSize);
5296 child->SetDescent(childDescent);
5297 }
5298 #endif
5299
5300 if (partialExtents)
5301 {
5302 int lastSize;
5303 if (partialExtents->GetCount() > 0)
5304 lastSize = (*partialExtents)[partialExtents->GetCount()-1];
5305 else
5306 lastSize = 0;
5307
5308 partialExtents->Add(childSize.x + lastSize);
5309 }
5310 }
5311 else if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, context, flags, wxPoint(position.x + sz.x, position.y), p))
5312 {
5313 if (childDescent == 0)
5314 {
5315 maxLineHeight = wxMax(maxLineHeight, childSize.y);
5316 }
5317 else
5318 {
5319 maxDescent = wxMax(maxDescent, childDescent);
5320 maxAscent = wxMax(maxAscent, (childSize.y - childDescent));
5321 }
5322
5323 maxLineHeight = wxMax(maxLineHeight, (maxAscent + maxDescent));
5324
5325 sz.y = wxMax(sz.y, maxLineHeight);
5326 sz.x += childSize.x;
5327 descent = maxDescent;
5328
5329 if ((flags & wxRICHTEXT_CACHE_SIZE) && (rangeToUse == child->GetRange()))
5330 {
5331 child->SetCachedSize(childSize);
5332 child->SetDescent(childDescent);
5333 }
5334
5335 if (partialExtents)
5336 {
5337 int lastSize;
5338 if (partialExtents->GetCount() > 0)
5339 lastSize = (*partialExtents)[partialExtents->GetCount()-1];
5340 else
5341 lastSize = 0;
5342
5343 size_t i;
5344 for (i = 0; i < childExtents.GetCount(); i++)
5345 {
5346 partialExtents->Add(childExtents[i] + lastSize);
5347 }
5348 }
5349 }
5350 }
5351
5352 if (p)
5353 p->Clear();
5354 }
5355
5356 node = node->GetNext();
5357 }
5358 size = sz;
5359 }
5360 else
5361 {
5362 // Use formatted data, with line breaks
5363 wxSize sz;
5364
5365 // We're going to loop through each line, and then for each line,
5366 // call GetRangeSize for the fragment that comprises that line.
5367 // Only we have to do that multiple times within the line, because
5368 // the line may be broken into pieces. For now ignore line break commands
5369 // (so we can assume that getting the unformatted size for a fragment
5370 // within a line is the actual size)
5371
5372 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
5373 while (node)
5374 {
5375 wxRichTextLine* line = node->GetData();
5376 wxRichTextRange lineRange = line->GetAbsoluteRange();
5377 if (!lineRange.IsOutside(range))
5378 {
5379 int maxDescent = 0;
5380 int maxAscent = 0;
5381 int maxLineHeight = 0;
5382 int maxLineWidth = 0;
5383
5384 wxRichTextObjectList::compatibility_iterator node2 = m_children.GetFirst();
5385 while (node2)
5386 {
5387 wxRichTextObject* child = node2->GetData();
5388
5389 if (!child->IsFloating() && !child->GetRange().IsOutside(lineRange))
5390 {
5391 wxRichTextRange rangeToUse = lineRange;
5392 rangeToUse.LimitTo(child->GetRange());
5393 if (child->IsTopLevel())
5394 rangeToUse = child->GetOwnRange();
5395
5396 wxSize childSize;
5397 int childDescent = 0;
5398 if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, context, flags, wxPoint(position.x + sz.x, position.y)))
5399 {
5400 if (childDescent == 0)
5401 {
5402 // Assume that if descent is zero, this child can occupy the full line height
5403 // and does not need space for the line's maximum descent. So we influence
5404 // the overall max line height only.
5405 maxLineHeight = wxMax(maxLineHeight, childSize.y);
5406 }
5407 else
5408 {
5409 maxAscent = wxMax(maxAscent, (childSize.y - childDescent));
5410 maxDescent = wxMax(maxAscent, childDescent);
5411 }
5412 maxLineHeight = wxMax(maxLineHeight, (maxAscent + maxDescent));
5413 maxLineWidth += childSize.x;
5414 }
5415 }
5416
5417 node2 = node2->GetNext();
5418 }
5419
5420 descent = wxMax(descent, maxDescent);
5421
5422 // Increase size by a line (TODO: paragraph spacing)
5423 sz.y += maxLineHeight;
5424 sz.x = wxMax(sz.x, maxLineWidth);
5425 }
5426 node = node->GetNext();
5427 }
5428 size = sz;
5429 }
5430 return true;
5431 }
5432
5433 /// Finds the absolute position and row height for the given character position
5434 bool wxRichTextParagraph::FindPosition(wxDC& dc, wxRichTextDrawingContext& context, long index, wxPoint& pt, int* height, bool forceLineStart)
5435 {
5436 if (index == -1)
5437 {
5438 wxRichTextLine* line = ((wxRichTextParagraphLayoutBox*)GetParent())->GetLineAtPosition(0);
5439 if (line)
5440 *height = line->GetSize().y;
5441 else
5442 *height = dc.GetCharHeight();
5443
5444 // -1 means 'the start of the buffer'.
5445 pt = GetPosition();
5446 if (line)
5447 pt = pt + line->GetPosition();
5448
5449 return true;
5450 }
5451
5452 // The final position in a paragraph is taken to mean the position
5453 // at the start of the next paragraph.
5454 if (index == GetRange().GetEnd())
5455 {
5456 wxRichTextParagraphLayoutBox* parent = wxDynamicCast(GetParent(), wxRichTextParagraphLayoutBox);
5457 wxASSERT( parent != NULL );
5458
5459 // Find the height at the next paragraph, if any
5460 wxRichTextLine* line = parent->GetLineAtPosition(index + 1);
5461 if (line)
5462 {
5463 *height = line->GetSize().y;
5464 pt = line->GetAbsolutePosition();
5465 }
5466 else
5467 {
5468 *height = dc.GetCharHeight();
5469 int indent = ConvertTenthsMMToPixels(dc, m_attributes.GetLeftIndent());
5470 pt = wxPoint(indent, GetCachedSize().y);
5471 }
5472
5473 return true;
5474 }
5475
5476 if (index < GetRange().GetStart() || index > GetRange().GetEnd())
5477 return false;
5478
5479 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
5480 while (node)
5481 {
5482 wxRichTextLine* line = node->GetData();
5483 wxRichTextRange lineRange = line->GetAbsoluteRange();
5484 if (index >= lineRange.GetStart() && index <= lineRange.GetEnd())
5485 {
5486 // If this is the last point in the line, and we're forcing the
5487 // returned value to be the start of the next line, do the required
5488 // thing.
5489 if (index == lineRange.GetEnd() && forceLineStart)
5490 {
5491 if (node->GetNext())
5492 {
5493 wxRichTextLine* nextLine = node->GetNext()->GetData();
5494 *height = nextLine->GetSize().y;
5495 pt = nextLine->GetAbsolutePosition();
5496 return true;
5497 }
5498 }
5499
5500 pt.y = line->GetPosition().y + GetPosition().y;
5501
5502 wxRichTextRange r(lineRange.GetStart(), index);
5503 wxSize rangeSize;
5504 int descent = 0;
5505
5506 // We find the size of the line up to this point,
5507 // then we can add this size to the line start position and
5508 // paragraph start position to find the actual position.
5509
5510 if (GetRangeSize(r, rangeSize, descent, dc, context, wxRICHTEXT_UNFORMATTED, line->GetPosition()+ GetPosition()))
5511 {
5512 pt.x = line->GetPosition().x + GetPosition().x + rangeSize.x;
5513 *height = line->GetSize().y;
5514
5515 return true;
5516 }
5517
5518 }
5519
5520 node = node->GetNext();
5521 }
5522
5523 return false;
5524 }
5525
5526 /// Hit-testing: returns a flag indicating hit test details, plus
5527 /// information about position
5528 int wxRichTextParagraph::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int flags)
5529 {
5530 if (!IsShown())
5531 return wxRICHTEXT_HITTEST_NONE;
5532
5533 // If we're in the top-level container, then we can return
5534 // a suitable hit test code even if the point is outside the container area,
5535 // so that we can position the caret sensibly even if we don't
5536 // click on valid content. If we're not at the top-level, and the point
5537 // is not within this paragraph object, then we don't want to stop more
5538 // precise hit-testing from working prematurely, so return immediately.
5539 // NEW STRATEGY: use the parent boundary to test whether we're in the
5540 // right region, not the paragraph, since the paragraph may be positioned
5541 // some way in from where the user clicks.
5542 {
5543 long tmpPos;
5544 wxRichTextObject* tempObj, *tempContextObj;
5545 if (GetParent() && GetParent()->wxRichTextObject::HitTest(dc, context, pt, tmpPos, & tempObj, & tempContextObj, flags) == wxRICHTEXT_HITTEST_NONE)
5546 return wxRICHTEXT_HITTEST_NONE;
5547 }
5548
5549 wxRichTextObjectList::compatibility_iterator objNode = m_children.GetFirst();
5550 while (objNode)
5551 {
5552 wxRichTextObject* child = objNode->GetData();
5553 // Don't recurse if we have wxRICHTEXT_HITTEST_NO_NESTED_OBJECTS,
5554 // and also, if this seems composite but actually is marked as atomic,
5555 // don't recurse.
5556 if (child->IsTopLevel() && ((flags & wxRICHTEXT_HITTEST_NO_NESTED_OBJECTS) == 0) &&
5557 (! (((flags & wxRICHTEXT_HITTEST_HONOUR_ATOMIC) != 0) && child->IsAtomic())))
5558 {
5559 {
5560 int hitTest = child->HitTest(dc, context, pt, textPosition, obj, contextObj);
5561 if (hitTest != wxRICHTEXT_HITTEST_NONE)
5562 return hitTest;
5563 }
5564 }
5565
5566 objNode = objNode->GetNext();
5567 }
5568
5569 wxPoint paraPos = GetPosition();
5570
5571 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
5572 while (node)
5573 {
5574 wxRichTextLine* line = node->GetData();
5575 wxPoint linePos = paraPos + line->GetPosition();
5576 wxSize lineSize = line->GetSize();
5577 wxRichTextRange lineRange = line->GetAbsoluteRange();
5578
5579 if (pt.y <= linePos.y + lineSize.y)
5580 {
5581 if (pt.x < linePos.x)
5582 {
5583 textPosition = lineRange.GetStart();
5584 *obj = FindObjectAtPosition(textPosition);
5585 *contextObj = GetContainer();
5586 return wxRICHTEXT_HITTEST_BEFORE|wxRICHTEXT_HITTEST_OUTSIDE;
5587 }
5588 else if (pt.x >= (linePos.x + lineSize.x))
5589 {
5590 textPosition = lineRange.GetEnd();
5591 *obj = FindObjectAtPosition(textPosition);
5592 *contextObj = GetContainer();
5593 return wxRICHTEXT_HITTEST_AFTER|wxRICHTEXT_HITTEST_OUTSIDE;
5594 }
5595 else
5596 {
5597 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
5598 wxArrayInt partialExtents;
5599
5600 wxSize paraSize;
5601 int paraDescent;
5602
5603 // This calculates the partial text extents
5604 GetRangeSize(lineRange, paraSize, paraDescent, dc, context, wxRICHTEXT_UNFORMATTED, linePos, & partialExtents);
5605
5606 int lastX = linePos.x;
5607 size_t i;
5608 for (i = 0; i < partialExtents.GetCount(); i++)
5609 {
5610 int nextX = partialExtents[i] + linePos.x;
5611
5612 if (pt.x >= lastX && pt.x <= nextX)
5613 {
5614 textPosition = i + lineRange.GetStart(); // minus 1?
5615
5616 *obj = FindObjectAtPosition(textPosition);
5617 *contextObj = GetContainer();
5618
5619 // So now we know it's between i-1 and i.
5620 // Let's see if we can be more precise about
5621 // which side of the position it's on.
5622
5623 int midPoint = (nextX + lastX)/2;
5624 if (pt.x >= midPoint)
5625 return wxRICHTEXT_HITTEST_AFTER;
5626 else
5627 return wxRICHTEXT_HITTEST_BEFORE;
5628 }
5629
5630 lastX = nextX;
5631 }
5632 #else
5633 long i;
5634 int lastX = linePos.x;
5635 for (i = lineRange.GetStart(); i <= lineRange.GetEnd(); i++)
5636 {
5637 wxSize childSize;
5638 int descent = 0;
5639
5640 wxRichTextRange rangeToUse(lineRange.GetStart(), i);
5641
5642 GetRangeSize(rangeToUse, childSize, descent, dc, context, wxRICHTEXT_UNFORMATTED, linePos);
5643
5644 int nextX = childSize.x + linePos.x;
5645
5646 if (pt.x >= lastX && pt.x <= nextX)
5647 {
5648 textPosition = i;
5649
5650 *obj = FindObjectAtPosition(textPosition);
5651 *contextObj = GetContainer();
5652
5653 // So now we know it's between i-1 and i.
5654 // Let's see if we can be more precise about
5655 // which side of the position it's on.
5656
5657 int midPoint = (nextX + lastX)/2;
5658 if (pt.x >= midPoint)
5659 return wxRICHTEXT_HITTEST_AFTER;
5660 else
5661 return wxRICHTEXT_HITTEST_BEFORE;
5662 }
5663 else
5664 {
5665 lastX = nextX;
5666 }
5667 }
5668 #endif
5669 }
5670 }
5671
5672 node = node->GetNext();
5673 }
5674
5675 return wxRICHTEXT_HITTEST_NONE;
5676 }
5677
5678 /// Split an object at this position if necessary, and return
5679 /// the previous object, or NULL if inserting at beginning.
5680 wxRichTextObject* wxRichTextParagraph::SplitAt(long pos, wxRichTextObject** previousObject)
5681 {
5682 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
5683 while (node)
5684 {
5685 wxRichTextObject* child = node->GetData();
5686
5687 if (pos == child->GetRange().GetStart())
5688 {
5689 if (previousObject)
5690 {
5691 if (node->GetPrevious())
5692 *previousObject = node->GetPrevious()->GetData();
5693 else
5694 *previousObject = NULL;
5695 }
5696
5697 return child;
5698 }
5699
5700 if (child->GetRange().Contains(pos))
5701 {
5702 // This should create a new object, transferring part of
5703 // the content to the old object and the rest to the new object.
5704 wxRichTextObject* newObject = child->DoSplit(pos);
5705
5706 // If we couldn't split this object, just insert in front of it.
5707 if (!newObject)
5708 {
5709 // Maybe this is an empty string, try the next one
5710 // return child;
5711 }
5712 else
5713 {
5714 // Insert the new object after 'child'
5715 if (node->GetNext())
5716 m_children.Insert(node->GetNext(), newObject);
5717 else
5718 m_children.Append(newObject);
5719 newObject->SetParent(this);
5720
5721 if (previousObject)
5722 *previousObject = child;
5723
5724 return newObject;
5725 }
5726 }
5727
5728 node = node->GetNext();
5729 }
5730 if (previousObject)
5731 *previousObject = NULL;
5732 return NULL;
5733 }
5734
5735 /// Move content to a list from obj on
5736 void wxRichTextParagraph::MoveToList(wxRichTextObject* obj, wxList& list)
5737 {
5738 wxRichTextObjectList::compatibility_iterator node = m_children.Find(obj);
5739 while (node)
5740 {
5741 wxRichTextObject* child = node->GetData();
5742 list.Append(child);
5743
5744 wxRichTextObjectList::compatibility_iterator oldNode = node;
5745
5746 node = node->GetNext();
5747
5748 m_children.DeleteNode(oldNode);
5749 }
5750 }
5751
5752 /// Add content back from list
5753 void wxRichTextParagraph::MoveFromList(wxList& list)
5754 {
5755 for (wxList::compatibility_iterator node = list.GetFirst(); node; node = node->GetNext())
5756 {
5757 AppendChild((wxRichTextObject*) node->GetData());
5758 }
5759 }
5760
5761 /// Calculate range
5762 void wxRichTextParagraph::CalculateRange(long start, long& end)
5763 {
5764 wxRichTextCompositeObject::CalculateRange(start, end);
5765
5766 // Add one for end of paragraph
5767 end ++;
5768
5769 m_range.SetRange(start, end);
5770 }
5771
5772 /// Find the object at the given position
5773 wxRichTextObject* wxRichTextParagraph::FindObjectAtPosition(long position)
5774 {
5775 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
5776 while (node)
5777 {
5778 wxRichTextObject* obj = node->GetData();
5779 if (obj->GetRange().Contains(position) ||
5780 obj->GetRange().GetStart() == position ||
5781 obj->GetRange().GetEnd() == position)
5782 return obj;
5783
5784 node = node->GetNext();
5785 }
5786 return NULL;
5787 }
5788
5789 /// Get the plain text searching from the start or end of the range.
5790 /// The resulting string may be shorter than the range given.
5791 bool wxRichTextParagraph::GetContiguousPlainText(wxString& text, const wxRichTextRange& range, bool fromStart)
5792 {
5793 text = wxEmptyString;
5794
5795 if (fromStart)
5796 {
5797 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
5798 while (node)
5799 {
5800 wxRichTextObject* obj = node->GetData();
5801 if (!obj->GetRange().IsOutside(range))
5802 {
5803 wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText);
5804 if (textObj)
5805 {
5806 text += textObj->GetTextForRange(range);
5807 }
5808 else
5809 {
5810 text += wxT(" ");
5811 }
5812 }
5813
5814 node = node->GetNext();
5815 }
5816 }
5817 else
5818 {
5819 wxRichTextObjectList::compatibility_iterator node = m_children.GetLast();
5820 while (node)
5821 {
5822 wxRichTextObject* obj = node->GetData();
5823 if (!obj->GetRange().IsOutside(range))
5824 {
5825 wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText);
5826 if (textObj)
5827 {
5828 text = textObj->GetTextForRange(range) + text;
5829 }
5830 else
5831 {
5832 text = wxT(" ") + text;
5833 }
5834 }
5835
5836 node = node->GetPrevious();
5837 }
5838 }
5839
5840 return true;
5841 }
5842
5843 /// Find a suitable wrap position.
5844 bool wxRichTextParagraph::FindWrapPosition(const wxRichTextRange& range, wxDC& dc, wxRichTextDrawingContext& context, int availableSpace, long& wrapPosition, wxArrayInt* partialExtents)
5845 {
5846 if (range.GetLength() <= 0)
5847 return false;
5848
5849 // Find the first position where the line exceeds the available space.
5850 wxSize sz;
5851 long breakPosition = range.GetEnd();
5852
5853 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
5854 if (partialExtents && partialExtents->GetCount() >= (size_t) (GetRange().GetLength()-1)) // the final position in a paragraph is the newline
5855 {
5856 int widthBefore;
5857
5858 if (range.GetStart() > GetRange().GetStart())
5859 widthBefore = (*partialExtents)[range.GetStart() - GetRange().GetStart() - 1];
5860 else
5861 widthBefore = 0;
5862
5863 size_t i;
5864 for (i = (size_t) range.GetStart(); i <= (size_t) range.GetEnd(); i++)
5865 {
5866 int widthFromStartOfThisRange = (*partialExtents)[i - GetRange().GetStart()] - widthBefore;
5867
5868 if (widthFromStartOfThisRange > availableSpace)
5869 {
5870 breakPosition = i-1;
5871 break;
5872 }
5873 }
5874 }
5875 else
5876 #endif
5877 {
5878 // Binary chop for speed
5879 long minPos = range.GetStart();
5880 long maxPos = range.GetEnd();
5881 while (true)
5882 {
5883 if (minPos == maxPos)
5884 {
5885 int descent = 0;
5886 GetRangeSize(wxRichTextRange(range.GetStart(), minPos), sz, descent, dc, context, wxRICHTEXT_UNFORMATTED);
5887
5888 if (sz.x > availableSpace)
5889 breakPosition = minPos - 1;
5890 break;
5891 }
5892 else if ((maxPos - minPos) == 1)
5893 {
5894 int descent = 0;
5895 GetRangeSize(wxRichTextRange(range.GetStart(), minPos), sz, descent, dc, context, wxRICHTEXT_UNFORMATTED);
5896
5897 if (sz.x > availableSpace)
5898 breakPosition = minPos - 1;
5899 else
5900 {
5901 GetRangeSize(wxRichTextRange(range.GetStart(), maxPos), sz, descent, dc, context, wxRICHTEXT_UNFORMATTED);
5902 if (sz.x > availableSpace)
5903 breakPosition = maxPos-1;
5904 }
5905 break;
5906 }
5907 else
5908 {
5909 long nextPos = minPos + ((maxPos - minPos) / 2);
5910
5911 int descent = 0;
5912 GetRangeSize(wxRichTextRange(range.GetStart(), nextPos), sz, descent, dc, context, wxRICHTEXT_UNFORMATTED);
5913
5914 if (sz.x > availableSpace)
5915 {
5916 maxPos = nextPos;
5917 }
5918 else
5919 {
5920 minPos = nextPos;
5921 }
5922 }
5923 }
5924 }
5925
5926 // Now we know the last position on the line.
5927 // Let's try to find a word break.
5928
5929 wxString plainText;
5930 if (GetContiguousPlainText(plainText, wxRichTextRange(range.GetStart(), breakPosition), false))
5931 {
5932 int newLinePos = plainText.Find(wxRichTextLineBreakChar);
5933 if (newLinePos != wxNOT_FOUND)
5934 {
5935 breakPosition = wxMax(0, range.GetStart() + newLinePos);
5936 }
5937 else
5938 {
5939 int spacePos = plainText.Find(wxT(' '), true);
5940 int tabPos = plainText.Find(wxT('\t'), true);
5941 int pos = wxMax(spacePos, tabPos);
5942 if (pos != wxNOT_FOUND)
5943 {
5944 int positionsFromEndOfString = plainText.length() - pos - 1;
5945 breakPosition = breakPosition - positionsFromEndOfString;
5946 }
5947 }
5948 }
5949
5950 wrapPosition = breakPosition;
5951
5952 return true;
5953 }
5954
5955 /// Get the bullet text for this paragraph.
5956 wxString wxRichTextParagraph::GetBulletText()
5957 {
5958 if (GetAttributes().GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_NONE ||
5959 (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_BITMAP))
5960 return wxEmptyString;
5961
5962 int number = GetAttributes().GetBulletNumber();
5963
5964 wxString text;
5965 if ((GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ARABIC) || (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE))
5966 {
5967 text.Printf(wxT("%d"), number);
5968 }
5969 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_LETTERS_UPPER)
5970 {
5971 // TODO: Unicode, and also check if number > 26
5972 text.Printf(wxT("%c"), (wxChar) (number+64));
5973 }
5974 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_LETTERS_LOWER)
5975 {
5976 // TODO: Unicode, and also check if number > 26
5977 text.Printf(wxT("%c"), (wxChar) (number+96));
5978 }
5979 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ROMAN_UPPER)
5980 {
5981 text = wxRichTextDecimalToRoman(number);
5982 }
5983 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ROMAN_LOWER)
5984 {
5985 text = wxRichTextDecimalToRoman(number);
5986 text.MakeLower();
5987 }
5988 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_SYMBOL)
5989 {
5990 text = GetAttributes().GetBulletText();
5991 }
5992
5993 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE)
5994 {
5995 // The outline style relies on the text being computed statically,
5996 // since it depends on other levels points (e.g. 1.2.1.1). So normally the bullet text
5997 // should be stored in the attributes; if not, just use the number for this
5998 // level, as previously computed.
5999 if (!GetAttributes().GetBulletText().IsEmpty())
6000 text = GetAttributes().GetBulletText();
6001 }
6002
6003 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_PARENTHESES)
6004 {
6005 text = wxT("(") + text + wxT(")");
6006 }
6007 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_RIGHT_PARENTHESIS)
6008 {
6009 text = text + wxT(")");
6010 }
6011
6012 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_PERIOD)
6013 {
6014 text += wxT(".");
6015 }
6016
6017 return text;
6018 }
6019
6020 /// Allocate or reuse a line object
6021 wxRichTextLine* wxRichTextParagraph::AllocateLine(int pos)
6022 {
6023 if (pos < (int) m_cachedLines.GetCount())
6024 {
6025 wxRichTextLine* line = m_cachedLines.Item(pos)->GetData();
6026 line->Init(this);
6027 return line;
6028 }
6029 else
6030 {
6031 wxRichTextLine* line = new wxRichTextLine(this);
6032 m_cachedLines.Append(line);
6033 return line;
6034 }
6035 }
6036
6037 /// Clear remaining unused line objects, if any
6038 bool wxRichTextParagraph::ClearUnusedLines(int lineCount)
6039 {
6040 int cachedLineCount = m_cachedLines.GetCount();
6041 if ((int) cachedLineCount > lineCount)
6042 {
6043 for (int i = 0; i < (int) (cachedLineCount - lineCount); i ++)
6044 {
6045 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetLast();
6046 wxRichTextLine* line = node->GetData();
6047 m_cachedLines.Erase(node);
6048 delete line;
6049 }
6050 }
6051 return true;
6052 }
6053
6054 /// Get combined attributes of the base style, paragraph style and character style. We use this to dynamically
6055 /// retrieve the actual style.
6056 wxRichTextAttr wxRichTextParagraph::GetCombinedAttributes(const wxRichTextAttr& contentStyle, bool includingBoxAttr) const
6057 {
6058 wxRichTextAttr attr;
6059 wxRichTextParagraphLayoutBox* buf = wxDynamicCast(GetParent(), wxRichTextParagraphLayoutBox);
6060 if (buf)
6061 {
6062 attr = buf->GetBasicStyle();
6063 if (!includingBoxAttr)
6064 {
6065 attr.GetTextBoxAttr().Reset();
6066 // The background colour will be painted by the container, and we don't
6067 // want to unnecessarily overwrite the background when we're drawing text
6068 // because this may erase the guideline (which appears just under the text
6069 // if there's no padding).
6070 attr.SetFlags(attr.GetFlags() & ~wxTEXT_ATTR_BACKGROUND_COLOUR);
6071 }
6072 wxRichTextApplyStyle(attr, GetAttributes());
6073 }
6074 else
6075 attr = GetAttributes();
6076
6077 wxRichTextApplyStyle(attr, contentStyle);
6078 return attr;
6079 }
6080
6081 /// Get combined attributes of the base style and paragraph style.
6082 wxRichTextAttr wxRichTextParagraph::GetCombinedAttributes(bool includingBoxAttr) const
6083 {
6084 wxRichTextAttr attr;
6085 wxRichTextParagraphLayoutBox* buf = wxDynamicCast(GetParent(), wxRichTextParagraphLayoutBox);
6086 if (buf)
6087 {
6088 attr = buf->GetBasicStyle();
6089 if (!includingBoxAttr)
6090 attr.GetTextBoxAttr().Reset();
6091 wxRichTextApplyStyle(attr, GetAttributes());
6092 }
6093 else
6094 attr = GetAttributes();
6095
6096 return attr;
6097 }
6098
6099 // Create default tabstop array
6100 void wxRichTextParagraph::InitDefaultTabs()
6101 {
6102 // create a default tab list at 10 mm each.
6103 for (int i = 0; i < 20; ++i)
6104 {
6105 sm_defaultTabs.Add(i*100);
6106 }
6107 }
6108
6109 // Clear default tabstop array
6110 void wxRichTextParagraph::ClearDefaultTabs()
6111 {
6112 sm_defaultTabs.Clear();
6113 }
6114
6115 void wxRichTextParagraph::LayoutFloat(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, int style, wxRichTextFloatCollector* floatCollector)
6116 {
6117 wxRichTextObjectList::compatibility_iterator node = GetChildren().GetFirst();
6118 while (node)
6119 {
6120 wxRichTextObject* anchored = node->GetData();
6121 if (anchored && anchored->IsFloating() && !floatCollector->HasFloat(anchored))
6122 {
6123 wxSize size;
6124 int descent, x = 0;
6125 anchored->GetRangeSize(anchored->GetRange(), size, descent, dc, context, style);
6126
6127 int offsetY = 0;
6128 if (anchored->GetAttributes().GetTextBoxAttr().GetTop().IsValid())
6129 {
6130 offsetY = anchored->GetAttributes().GetTextBoxAttr().GetTop().GetValue();
6131 if (anchored->GetAttributes().GetTextBoxAttr().GetTop().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
6132 {
6133 offsetY = ConvertTenthsMMToPixels(dc, offsetY);
6134 }
6135 }
6136
6137 int pos = floatCollector->GetFitPosition(anchored->GetAttributes().GetTextBoxAttr().GetFloatMode(), rect.y + offsetY, size.y);
6138
6139 /* Update the offset */
6140 int newOffsetY = pos - rect.y;
6141 if (newOffsetY != offsetY)
6142 {
6143 if (anchored->GetAttributes().GetTextBoxAttr().GetTop().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
6144 newOffsetY = ConvertPixelsToTenthsMM(dc, newOffsetY);
6145 anchored->GetAttributes().GetTextBoxAttr().GetTop().SetValue(newOffsetY);
6146 }
6147
6148 if (anchored->GetAttributes().GetTextBoxAttr().GetFloatMode() == wxTEXT_BOX_ATTR_FLOAT_LEFT)
6149 x = rect.x;
6150 else if (anchored->GetAttributes().GetTextBoxAttr().GetFloatMode() == wxTEXT_BOX_ATTR_FLOAT_RIGHT)
6151 x = rect.x + rect.width - size.x;
6152
6153 anchored->SetPosition(wxPoint(x, pos));
6154 anchored->SetCachedSize(size);
6155 floatCollector->CollectFloat(this, anchored);
6156 }
6157
6158 node = node->GetNext();
6159 }
6160 }
6161
6162 // Get the first position from pos that has a line break character.
6163 long wxRichTextParagraph::GetFirstLineBreakPosition(long pos)
6164 {
6165 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
6166 while (node)
6167 {
6168 wxRichTextObject* obj = node->GetData();
6169 if (pos >= obj->GetRange().GetStart() && pos <= obj->GetRange().GetEnd())
6170 {
6171 wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText);
6172 if (textObj)
6173 {
6174 long breakPos = textObj->GetFirstLineBreakPosition(pos);
6175 if (breakPos > -1)
6176 return breakPos;
6177 }
6178 }
6179 node = node->GetNext();
6180 }
6181 return -1;
6182 }
6183
6184 /*!
6185 * wxRichTextLine
6186 * This object represents a line in a paragraph, and stores
6187 * offsets from the start of the paragraph representing the
6188 * start and end positions of the line.
6189 */
6190
6191 wxRichTextLine::wxRichTextLine(wxRichTextParagraph* parent)
6192 {
6193 Init(parent);
6194 }
6195
6196 /// Initialisation
6197 void wxRichTextLine::Init(wxRichTextParagraph* parent)
6198 {
6199 m_parent = parent;
6200 m_range.SetRange(-1, -1);
6201 m_pos = wxPoint(0, 0);
6202 m_size = wxSize(0, 0);
6203 m_descent = 0;
6204 #if wxRICHTEXT_USE_OPTIMIZED_LINE_DRAWING
6205 m_objectSizes.Clear();
6206 #endif
6207 }
6208
6209 /// Copy
6210 void wxRichTextLine::Copy(const wxRichTextLine& obj)
6211 {
6212 m_range = obj.m_range;
6213 #if wxRICHTEXT_USE_OPTIMIZED_LINE_DRAWING
6214 m_objectSizes = obj.m_objectSizes;
6215 #endif
6216 }
6217
6218 /// Get the absolute object position
6219 wxPoint wxRichTextLine::GetAbsolutePosition() const
6220 {
6221 return m_parent->GetPosition() + m_pos;
6222 }
6223
6224 /// Get the absolute range
6225 wxRichTextRange wxRichTextLine::GetAbsoluteRange() const
6226 {
6227 wxRichTextRange range(m_range.GetStart() + m_parent->GetRange().GetStart(), 0);
6228 range.SetEnd(range.GetStart() + m_range.GetLength()-1);
6229 return range;
6230 }
6231
6232 /*!
6233 * wxRichTextPlainText
6234 * This object represents a single piece of text.
6235 */
6236
6237 IMPLEMENT_DYNAMIC_CLASS(wxRichTextPlainText, wxRichTextObject)
6238
6239 wxRichTextPlainText::wxRichTextPlainText(const wxString& text, wxRichTextObject* parent, wxRichTextAttr* style):
6240 wxRichTextObject(parent)
6241 {
6242 if (style)
6243 SetAttributes(*style);
6244
6245 m_text = text;
6246 }
6247
6248 #define USE_KERNING_FIX 1
6249
6250 // If insufficient tabs are defined, this is the tab width used
6251 #define WIDTH_FOR_DEFAULT_TABS 50
6252
6253 /// Draw the item
6254 bool wxRichTextPlainText::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int WXUNUSED(style))
6255 {
6256 wxRichTextParagraph* para = wxDynamicCast(GetParent(), wxRichTextParagraph);
6257 wxASSERT (para != NULL);
6258
6259 wxRichTextAttr textAttr(para ? para->GetCombinedAttributes(GetAttributes(), false /* no box attributes */) : GetAttributes());
6260 context.ApplyVirtualAttributes(textAttr, this);
6261
6262 // Let's make the assumption for now that for content in a paragraph, including
6263 // text, we never have a discontinuous selection. So we only deal with a
6264 // single range.
6265 wxRichTextRange selectionRange;
6266 if (selection.IsValid())
6267 {
6268 wxRichTextRangeArray selectionRanges = selection.GetSelectionForObject(this);
6269 if (selectionRanges.GetCount() > 0)
6270 selectionRange = selectionRanges[0];
6271 else
6272 selectionRange = wxRICHTEXT_NO_SELECTION;
6273 }
6274 else
6275 selectionRange = wxRICHTEXT_NO_SELECTION;
6276
6277 int offset = GetRange().GetStart();
6278
6279 // Replace line break characters with spaces
6280 wxString str = m_text;
6281 wxString toRemove = wxRichTextLineBreakChar;
6282 str.Replace(toRemove, wxT(" "));
6283 if (textAttr.HasTextEffects() && (textAttr.GetTextEffects() & (wxTEXT_ATTR_EFFECT_CAPITALS|wxTEXT_ATTR_EFFECT_SMALL_CAPITALS)))
6284 str.MakeUpper();
6285
6286 long len = range.GetLength();
6287 wxString stringChunk = str.Mid(range.GetStart() - offset, (size_t) len);
6288
6289 // Test for the optimized situations where all is selected, or none
6290 // is selected.
6291
6292 wxFont textFont(GetBuffer()->GetFontTable().FindFont(textAttr));
6293 wxCheckSetFont(dc, textFont);
6294 int charHeight = dc.GetCharHeight();
6295
6296 int x, y;
6297 if ( textFont.IsOk() )
6298 {
6299 if (textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SMALL_CAPITALS))
6300 {
6301 textFont.SetPointSize((int) (textFont.GetPointSize()*0.75));
6302 wxCheckSetFont(dc, textFont);
6303 charHeight = dc.GetCharHeight();
6304 }
6305
6306 if ( textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUPERSCRIPT) )
6307 {
6308 if (textFont.IsUsingSizeInPixels())
6309 {
6310 double size = static_cast<double>(textFont.GetPixelSize().y) / wxSCRIPT_MUL_FACTOR;
6311 textFont.SetPixelSize(wxSize(0, static_cast<int>(size)));
6312 x = rect.x;
6313 y = rect.y;
6314 }
6315 else
6316 {
6317 double size = static_cast<double>(textFont.GetPointSize()) / wxSCRIPT_MUL_FACTOR;
6318 textFont.SetPointSize(static_cast<int>(size));
6319 x = rect.x;
6320 y = rect.y;
6321 }
6322 wxCheckSetFont(dc, textFont);
6323 }
6324 else if ( textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUBSCRIPT) )
6325 {
6326 if (textFont.IsUsingSizeInPixels())
6327 {
6328 double size = static_cast<double>(textFont.GetPixelSize().y) / wxSCRIPT_MUL_FACTOR;
6329 textFont.SetPixelSize(wxSize(0, static_cast<int>(size)));
6330 x = rect.x;
6331 int sub_height = static_cast<int>(static_cast<double>(charHeight) / wxSCRIPT_MUL_FACTOR);
6332 y = rect.y + (rect.height - sub_height + (descent - m_descent));
6333 }
6334 else
6335 {
6336 double size = static_cast<double>(textFont.GetPointSize()) / wxSCRIPT_MUL_FACTOR;
6337 textFont.SetPointSize(static_cast<int>(size));
6338 x = rect.x;
6339 int sub_height = static_cast<int>(static_cast<double>(charHeight) / wxSCRIPT_MUL_FACTOR);
6340 y = rect.y + (rect.height - sub_height + (descent - m_descent));
6341 }
6342 wxCheckSetFont(dc, textFont);
6343 }
6344 else
6345 {
6346 x = rect.x;
6347 y = rect.y + (rect.height - charHeight - (descent - m_descent));
6348 }
6349 }
6350 else
6351 {
6352 x = rect.x;
6353 y = rect.y + (rect.height - charHeight - (descent - m_descent));
6354 }
6355
6356 // TODO: new selection code
6357
6358 // (a) All selected.
6359 if (selectionRange.GetStart() <= range.GetStart() && selectionRange.GetEnd() >= range.GetEnd())
6360 {
6361 DrawTabbedString(dc, textAttr, rect, stringChunk, x, y, true);
6362 }
6363 // (b) None selected.
6364 else if (selectionRange.GetEnd() < range.GetStart() || selectionRange.GetStart() > range.GetEnd())
6365 {
6366 // Draw all unselected
6367 DrawTabbedString(dc, textAttr, rect, stringChunk, x, y, false);
6368 }
6369 else
6370 {
6371 // (c) Part selected, part not
6372 // Let's draw unselected chunk, selected chunk, then unselected chunk.
6373
6374 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
6375
6376 // 1. Initial unselected chunk, if any, up until start of selection.
6377 if (selectionRange.GetStart() > range.GetStart() && selectionRange.GetStart() <= range.GetEnd())
6378 {
6379 int r1 = range.GetStart();
6380 int s1 = selectionRange.GetStart()-1;
6381 int fragmentLen = s1 - r1 + 1;
6382 if (fragmentLen < 0)
6383 {
6384 wxLogDebug(wxT("Mid(%d, %d"), (int)(r1 - offset), (int)fragmentLen);
6385 }
6386 wxString stringFragment = str.Mid(r1 - offset, fragmentLen);
6387
6388 DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, false);
6389
6390 #if USE_KERNING_FIX
6391 if (stringChunk.Find(wxT("\t")) == wxNOT_FOUND)
6392 {
6393 // Compensate for kerning difference
6394 wxString stringFragment2(str.Mid(r1 - offset, fragmentLen+1));
6395 wxString stringFragment3(str.Mid(r1 - offset + fragmentLen, 1));
6396
6397 wxCoord w1, h1, w2, h2, w3, h3;
6398 dc.GetTextExtent(stringFragment, & w1, & h1);
6399 dc.GetTextExtent(stringFragment2, & w2, & h2);
6400 dc.GetTextExtent(stringFragment3, & w3, & h3);
6401
6402 int kerningDiff = (w1 + w3) - w2;
6403 x = x - kerningDiff;
6404 }
6405 #endif
6406 }
6407
6408 // 2. Selected chunk, if any.
6409 if (selectionRange.GetEnd() >= range.GetStart())
6410 {
6411 int s1 = wxMax(selectionRange.GetStart(), range.GetStart());
6412 int s2 = wxMin(selectionRange.GetEnd(), range.GetEnd());
6413
6414 int fragmentLen = s2 - s1 + 1;
6415 if (fragmentLen < 0)
6416 {
6417 wxLogDebug(wxT("Mid(%d, %d"), (int)(s1 - offset), (int)fragmentLen);
6418 }
6419 wxString stringFragment = str.Mid(s1 - offset, fragmentLen);
6420
6421 DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, true);
6422
6423 #if USE_KERNING_FIX
6424 if (stringChunk.Find(wxT("\t")) == wxNOT_FOUND)
6425 {
6426 // Compensate for kerning difference
6427 wxString stringFragment2(str.Mid(s1 - offset, fragmentLen+1));
6428 wxString stringFragment3(str.Mid(s1 - offset + fragmentLen, 1));
6429
6430 wxCoord w1, h1, w2, h2, w3, h3;
6431 dc.GetTextExtent(stringFragment, & w1, & h1);
6432 dc.GetTextExtent(stringFragment2, & w2, & h2);
6433 dc.GetTextExtent(stringFragment3, & w3, & h3);
6434
6435 int kerningDiff = (w1 + w3) - w2;
6436 x = x - kerningDiff;
6437 }
6438 #endif
6439 }
6440
6441 // 3. Remaining unselected chunk, if any
6442 if (selectionRange.GetEnd() < range.GetEnd())
6443 {
6444 int s2 = wxMin(selectionRange.GetEnd()+1, range.GetEnd());
6445 int r2 = range.GetEnd();
6446
6447 int fragmentLen = r2 - s2 + 1;
6448 if (fragmentLen < 0)
6449 {
6450 wxLogDebug(wxT("Mid(%d, %d"), (int)(s2 - offset), (int)fragmentLen);
6451 }
6452 wxString stringFragment = str.Mid(s2 - offset, fragmentLen);
6453
6454 DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, false);
6455 }
6456 }
6457
6458 return true;
6459 }
6460
6461 bool wxRichTextPlainText::DrawTabbedString(wxDC& dc, const wxRichTextAttr& attr, const wxRect& rect,wxString& str, wxCoord& x, wxCoord& y, bool selected)
6462 {
6463 bool hasTabs = (str.Find(wxT('\t')) != wxNOT_FOUND);
6464
6465 wxArrayInt tabArray;
6466 int tabCount;
6467 if (hasTabs)
6468 {
6469 if (attr.GetTabs().IsEmpty())
6470 tabArray = wxRichTextParagraph::GetDefaultTabs();
6471 else
6472 tabArray = attr.GetTabs();
6473 tabCount = tabArray.GetCount();
6474
6475 for (int i = 0; i < tabCount; ++i)
6476 {
6477 int pos = tabArray[i];
6478 pos = ConvertTenthsMMToPixels(dc, pos);
6479 tabArray[i] = pos;
6480 }
6481 }
6482 else
6483 tabCount = 0;
6484
6485 int nextTabPos = -1;
6486 int tabPos = -1;
6487 wxCoord w, h;
6488
6489 if (selected)
6490 {
6491 wxColour highlightColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT));
6492 wxColour highlightTextColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT));
6493
6494 wxCheckSetBrush(dc, wxBrush(highlightColour));
6495 wxCheckSetPen(dc, wxPen(highlightColour));
6496 dc.SetTextForeground(highlightTextColour);
6497 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
6498 }
6499 else
6500 {
6501 dc.SetTextForeground(attr.GetTextColour());
6502
6503 if (attr.HasFlag(wxTEXT_ATTR_BACKGROUND_COLOUR) && attr.GetBackgroundColour().IsOk())
6504 {
6505 dc.SetBackgroundMode(wxBRUSHSTYLE_SOLID);
6506 dc.SetTextBackground(attr.GetBackgroundColour());
6507 }
6508 else
6509 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
6510 }
6511
6512 wxCoord x_orig = GetParent()->GetPosition().x;
6513 while (hasTabs)
6514 {
6515 // the string has a tab
6516 // break up the string at the Tab
6517 wxString stringChunk = str.BeforeFirst(wxT('\t'));
6518 str = str.AfterFirst(wxT('\t'));
6519 dc.GetTextExtent(stringChunk, & w, & h);
6520 tabPos = x + w;
6521 bool not_found = true;
6522 for (int i = 0; i < tabCount && not_found; ++i)
6523 {
6524 nextTabPos = tabArray.Item(i) + x_orig;
6525
6526 // Find the next tab position.
6527 // Even if we're at the end of the tab array, we must still draw the chunk.
6528
6529 if (nextTabPos > tabPos || (i == (tabCount - 1)))
6530 {
6531 if (nextTabPos <= tabPos)
6532 {
6533 int defaultTabWidth = ConvertTenthsMMToPixels(dc, WIDTH_FOR_DEFAULT_TABS);
6534 nextTabPos = tabPos + defaultTabWidth;
6535 }
6536
6537 not_found = false;
6538 if (selected)
6539 {
6540 w = nextTabPos - x;
6541 wxRect selRect(x, rect.y, w, rect.GetHeight());
6542 dc.DrawRectangle(selRect);
6543 }
6544 dc.DrawText(stringChunk, x, y);
6545
6546 if (attr.HasTextEffects() && (attr.GetTextEffects() & wxTEXT_ATTR_EFFECT_STRIKETHROUGH))
6547 {
6548 wxPen oldPen = dc.GetPen();
6549 wxCheckSetPen(dc, wxPen(attr.GetTextColour(), 1));
6550 dc.DrawLine(x, (int) (y+(h/2)+0.5), x+w, (int) (y+(h/2)+0.5));
6551 wxCheckSetPen(dc, oldPen);
6552 }
6553
6554 x = nextTabPos;
6555 }
6556 }
6557 hasTabs = (str.Find(wxT('\t')) != wxNOT_FOUND);
6558 }
6559
6560 if (!str.IsEmpty())
6561 {
6562 dc.GetTextExtent(str, & w, & h);
6563 if (selected)
6564 {
6565 wxRect selRect(x, rect.y, w, rect.GetHeight());
6566 dc.DrawRectangle(selRect);
6567 }
6568 dc.DrawText(str, x, y);
6569
6570 if (attr.HasTextEffects() && (attr.GetTextEffects() & wxTEXT_ATTR_EFFECT_STRIKETHROUGH))
6571 {
6572 wxPen oldPen = dc.GetPen();
6573 wxCheckSetPen(dc, wxPen(attr.GetTextColour(), 1));
6574 dc.DrawLine(x, (int) (y+(h/2)+0.5), x+w, (int) (y+(h/2)+0.5));
6575 wxCheckSetPen(dc, oldPen);
6576 }
6577
6578 x += w;
6579 }
6580
6581 return true;
6582 }
6583
6584 /// Lay the item out
6585 bool wxRichTextPlainText::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& WXUNUSED(rect), const wxRect& WXUNUSED(parentRect), int WXUNUSED(style))
6586 {
6587 // Only lay out if we haven't already cached the size
6588 if (m_size.x == -1)
6589 GetRangeSize(GetRange(), m_size, m_descent, dc, context, 0, wxPoint(0, 0));
6590 m_maxSize = m_size;
6591 // Eventually we want to have a reasonable estimate of minimum size.
6592 m_minSize = wxSize(0, 0);
6593 return true;
6594 }
6595
6596 /// Copy
6597 void wxRichTextPlainText::Copy(const wxRichTextPlainText& obj)
6598 {
6599 wxRichTextObject::Copy(obj);
6600
6601 m_text = obj.m_text;
6602 }
6603
6604 /// Get/set the object size for the given range. Returns false if the range
6605 /// is invalid for this object.
6606 bool wxRichTextPlainText::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int WXUNUSED(flags), wxPoint position, wxArrayInt* partialExtents) const
6607 {
6608 if (!range.IsWithin(GetRange()))
6609 return false;
6610
6611 wxRichTextParagraph* para = wxDynamicCast(GetParent(), wxRichTextParagraph);
6612 wxASSERT (para != NULL);
6613
6614 int relativeX = position.x - GetParent()->GetPosition().x;
6615
6616 wxRichTextAttr textAttr(para ? para->GetCombinedAttributes(GetAttributes()) : GetAttributes());
6617 context.ApplyVirtualAttributes(textAttr, (wxRichTextObject*) this);
6618
6619 // Always assume unformatted text, since at this level we have no knowledge
6620 // of line breaks - and we don't need it, since we'll calculate size within
6621 // formatted text by doing it in chunks according to the line ranges
6622
6623 bool bScript(false);
6624 wxFont font(GetBuffer()->GetFontTable().FindFont(textAttr));
6625 if (font.IsOk())
6626 {
6627 if ( textAttr.HasTextEffects() && ( (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUPERSCRIPT)
6628 || (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUBSCRIPT) ) )
6629 {
6630 wxFont textFont = font;
6631 if (textFont.IsUsingSizeInPixels())
6632 {
6633 double size = static_cast<double>(textFont.GetPixelSize().y) / wxSCRIPT_MUL_FACTOR;
6634 textFont.SetPixelSize(wxSize(0, static_cast<int>(size)));
6635 }
6636 else
6637 {
6638 double size = static_cast<double>(textFont.GetPointSize()) / wxSCRIPT_MUL_FACTOR;
6639 textFont.SetPointSize(static_cast<int>(size));
6640 }
6641 wxCheckSetFont(dc, textFont);
6642 bScript = true;
6643 }
6644 else if (textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SMALL_CAPITALS))
6645 {
6646 wxFont textFont = font;
6647 textFont.SetPointSize((int) (textFont.GetPointSize()*0.75));
6648 wxCheckSetFont(dc, textFont);
6649 bScript = true;
6650 }
6651 else
6652 {
6653 wxCheckSetFont(dc, font);
6654 }
6655 }
6656
6657 bool haveDescent = false;
6658 int startPos = range.GetStart() - GetRange().GetStart();
6659 long len = range.GetLength();
6660
6661 wxString str(m_text);
6662 wxString toReplace = wxRichTextLineBreakChar;
6663 str.Replace(toReplace, wxT(" "));
6664
6665 wxString stringChunk = str.Mid(startPos, (size_t) len);
6666
6667 if (textAttr.HasTextEffects() && (textAttr.GetTextEffects() & (wxTEXT_ATTR_EFFECT_CAPITALS|wxTEXT_ATTR_EFFECT_SMALL_CAPITALS)))
6668 stringChunk.MakeUpper();
6669
6670 wxCoord w, h;
6671 int width = 0;
6672 if (stringChunk.Find(wxT('\t')) != wxNOT_FOUND)
6673 {
6674 // the string has a tab
6675 wxArrayInt tabArray;
6676 if (textAttr.GetTabs().IsEmpty())
6677 tabArray = wxRichTextParagraph::GetDefaultTabs();
6678 else
6679 tabArray = textAttr.GetTabs();
6680
6681 int tabCount = tabArray.GetCount();
6682
6683 for (int i = 0; i < tabCount; ++i)
6684 {
6685 int pos = tabArray[i];
6686 pos = ((wxRichTextPlainText*) this)->ConvertTenthsMMToPixels(dc, pos);
6687 tabArray[i] = pos;
6688 }
6689
6690 int nextTabPos = -1;
6691
6692 while (stringChunk.Find(wxT('\t')) >= 0)
6693 {
6694 int absoluteWidth = 0;
6695
6696 // the string has a tab
6697 // break up the string at the Tab
6698 wxString stringFragment = stringChunk.BeforeFirst(wxT('\t'));
6699 stringChunk = stringChunk.AfterFirst(wxT('\t'));
6700
6701 if (partialExtents)
6702 {
6703 int oldWidth;
6704 if (partialExtents->GetCount() > 0)
6705 oldWidth = (*partialExtents)[partialExtents->GetCount()-1];
6706 else
6707 oldWidth = 0;
6708
6709 // Add these partial extents
6710 wxArrayInt p;
6711 dc.GetPartialTextExtents(stringFragment, p);
6712 size_t j;
6713 for (j = 0; j < p.GetCount(); j++)
6714 partialExtents->Add(oldWidth + p[j]);
6715
6716 if (partialExtents->GetCount() > 0)
6717 absoluteWidth = (*partialExtents)[(*partialExtents).GetCount()-1] + relativeX;
6718 else
6719 absoluteWidth = relativeX;
6720 }
6721 else
6722 {
6723 dc.GetTextExtent(stringFragment, & w, & h);
6724 width += w;
6725 absoluteWidth = width + relativeX;
6726 haveDescent = true;
6727 }
6728
6729 bool notFound = true;
6730 for (int i = 0; i < tabCount && notFound; ++i)
6731 {
6732 nextTabPos = tabArray.Item(i);
6733
6734 // Find the next tab position.
6735 // Even if we're at the end of the tab array, we must still process the chunk.
6736
6737 if (nextTabPos > absoluteWidth || (i == (tabCount - 1)))
6738 {
6739 if (nextTabPos <= absoluteWidth)
6740 {
6741 int defaultTabWidth = ((wxRichTextPlainText*) this)->ConvertTenthsMMToPixels(dc, WIDTH_FOR_DEFAULT_TABS);
6742 nextTabPos = absoluteWidth + defaultTabWidth;
6743 }
6744
6745 notFound = false;
6746 width = nextTabPos - relativeX;
6747
6748 if (partialExtents)
6749 partialExtents->Add(width);
6750 }
6751 }
6752 }
6753 }
6754
6755 if (!stringChunk.IsEmpty())
6756 {
6757 if (partialExtents)
6758 {
6759 int oldWidth;
6760 if (partialExtents->GetCount() > 0)
6761 oldWidth = (*partialExtents)[partialExtents->GetCount()-1];
6762 else
6763 oldWidth = 0;
6764
6765 // Add these partial extents
6766 wxArrayInt p;
6767 dc.GetPartialTextExtents(stringChunk, p);
6768 size_t j;
6769 for (j = 0; j < p.GetCount(); j++)
6770 partialExtents->Add(oldWidth + p[j]);
6771 }
6772 else
6773 {
6774 dc.GetTextExtent(stringChunk, & w, & h, & descent);
6775 width += w;
6776 haveDescent = true;
6777 }
6778 }
6779
6780 if (partialExtents)
6781 {
6782 int charHeight = dc.GetCharHeight();
6783 if ((*partialExtents).GetCount() > 0)
6784 w = (*partialExtents)[partialExtents->GetCount()-1];
6785 else
6786 w = 0;
6787 size = wxSize(w, charHeight);
6788 }
6789 else
6790 {
6791 size = wxSize(width, dc.GetCharHeight());
6792 }
6793
6794 if (!haveDescent)
6795 dc.GetTextExtent(wxT("X"), & w, & h, & descent);
6796
6797 if ( bScript )
6798 dc.SetFont(font);
6799
6800 return true;
6801 }
6802
6803 /// Do a split, returning an object containing the second part, and setting
6804 /// the first part in 'this'.
6805 wxRichTextObject* wxRichTextPlainText::DoSplit(long pos)
6806 {
6807 long index = pos - GetRange().GetStart();
6808
6809 if (index < 0 || index >= (int) m_text.length())
6810 return NULL;
6811
6812 wxString firstPart = m_text.Mid(0, index);
6813 wxString secondPart = m_text.Mid(index);
6814
6815 m_text = firstPart;
6816
6817 wxRichTextPlainText* newObject = new wxRichTextPlainText(secondPart);
6818 newObject->SetAttributes(GetAttributes());
6819 newObject->SetProperties(GetProperties());
6820
6821 newObject->SetRange(wxRichTextRange(pos, GetRange().GetEnd()));
6822 GetRange().SetEnd(pos-1);
6823
6824 return newObject;
6825 }
6826
6827 /// Calculate range
6828 void wxRichTextPlainText::CalculateRange(long start, long& end)
6829 {
6830 end = start + m_text.length() - 1;
6831 m_range.SetRange(start, end);
6832 }
6833
6834 /// Delete range
6835 bool wxRichTextPlainText::DeleteRange(const wxRichTextRange& range)
6836 {
6837 wxRichTextRange r = range;
6838
6839 r.LimitTo(GetRange());
6840
6841 if (r.GetStart() == GetRange().GetStart() && r.GetEnd() == GetRange().GetEnd())
6842 {
6843 m_text.Empty();
6844 return true;
6845 }
6846
6847 long startIndex = r.GetStart() - GetRange().GetStart();
6848 long len = r.GetLength();
6849
6850 m_text = m_text.Mid(0, startIndex) + m_text.Mid(startIndex+len);
6851 return true;
6852 }
6853
6854 /// Get text for the given range.
6855 wxString wxRichTextPlainText::GetTextForRange(const wxRichTextRange& range) const
6856 {
6857 wxRichTextRange r = range;
6858
6859 r.LimitTo(GetRange());
6860
6861 long startIndex = r.GetStart() - GetRange().GetStart();
6862 long len = r.GetLength();
6863
6864 return m_text.Mid(startIndex, len);
6865 }
6866
6867 /// Returns true if this object can merge itself with the given one.
6868 bool wxRichTextPlainText::CanMerge(wxRichTextObject* object) const
6869 {
6870 return object->GetClassInfo() == wxCLASSINFO(wxRichTextPlainText) &&
6871 (m_text.empty() || (wxTextAttrEq(GetAttributes(), object->GetAttributes()) && m_properties == object->GetProperties()));
6872 }
6873
6874 /// Returns true if this object merged itself with the given one.
6875 /// The calling code will then delete the given object.
6876 bool wxRichTextPlainText::Merge(wxRichTextObject* object)
6877 {
6878 wxRichTextPlainText* textObject = wxDynamicCast(object, wxRichTextPlainText);
6879 wxASSERT( textObject != NULL );
6880
6881 if (textObject)
6882 {
6883 m_text += textObject->GetText();
6884 wxRichTextApplyStyle(m_attributes, textObject->GetAttributes());
6885 return true;
6886 }
6887 else
6888 return false;
6889 }
6890
6891 /// Dump to output stream for debugging
6892 void wxRichTextPlainText::Dump(wxTextOutputStream& stream)
6893 {
6894 wxRichTextObject::Dump(stream);
6895 stream << m_text << wxT("\n");
6896 }
6897
6898 /// Get the first position from pos that has a line break character.
6899 long wxRichTextPlainText::GetFirstLineBreakPosition(long pos)
6900 {
6901 int i;
6902 int len = m_text.length();
6903 int startPos = pos - m_range.GetStart();
6904 for (i = startPos; i < len; i++)
6905 {
6906 wxChar ch = m_text[i];
6907 if (ch == wxRichTextLineBreakChar)
6908 {
6909 return i + m_range.GetStart();
6910 }
6911 }
6912 return -1;
6913 }
6914
6915 /*!
6916 * wxRichTextBuffer
6917 * This is a kind of box, used to represent the whole buffer
6918 */
6919
6920 IMPLEMENT_DYNAMIC_CLASS(wxRichTextBuffer, wxRichTextParagraphLayoutBox)
6921
6922 wxList wxRichTextBuffer::sm_handlers;
6923 wxList wxRichTextBuffer::sm_drawingHandlers;
6924 wxRichTextFieldTypeHashMap wxRichTextBuffer::sm_fieldTypes;
6925 wxRichTextRenderer* wxRichTextBuffer::sm_renderer = NULL;
6926 int wxRichTextBuffer::sm_bulletRightMargin = 20;
6927 float wxRichTextBuffer::sm_bulletProportion = (float) 0.3;
6928
6929 /// Initialisation
6930 void wxRichTextBuffer::Init()
6931 {
6932 m_commandProcessor = new wxCommandProcessor;
6933 m_styleSheet = NULL;
6934 m_modified = false;
6935 m_batchedCommandDepth = 0;
6936 m_batchedCommand = NULL;
6937 m_suppressUndo = 0;
6938 m_handlerFlags = 0;
6939 m_scale = 1.0;
6940 m_dimensionScale = 1.0;
6941 m_fontScale = 1.0;
6942 SetMargins(4);
6943 }
6944
6945 /// Initialisation
6946 wxRichTextBuffer::~wxRichTextBuffer()
6947 {
6948 delete m_commandProcessor;
6949 delete m_batchedCommand;
6950
6951 ClearStyleStack();
6952 ClearEventHandlers();
6953 }
6954
6955 void wxRichTextBuffer::ResetAndClearCommands()
6956 {
6957 Reset();
6958
6959 GetCommandProcessor()->ClearCommands();
6960
6961 Modify(false);
6962 Invalidate(wxRICHTEXT_ALL);
6963 }
6964
6965 void wxRichTextBuffer::Copy(const wxRichTextBuffer& obj)
6966 {
6967 wxRichTextParagraphLayoutBox::Copy(obj);
6968
6969 m_styleSheet = obj.m_styleSheet;
6970 m_modified = obj.m_modified;
6971 m_batchedCommandDepth = 0;
6972 if (m_batchedCommand)
6973 delete m_batchedCommand;
6974 m_batchedCommand = NULL;
6975 m_suppressUndo = obj.m_suppressUndo;
6976 m_invalidRange = obj.m_invalidRange;
6977 m_dimensionScale = obj.m_dimensionScale;
6978 m_fontScale = obj.m_fontScale;
6979 }
6980
6981 /// Push style sheet to top of stack
6982 bool wxRichTextBuffer::PushStyleSheet(wxRichTextStyleSheet* styleSheet)
6983 {
6984 if (m_styleSheet)
6985 styleSheet->InsertSheet(m_styleSheet);
6986
6987 SetStyleSheet(styleSheet);
6988
6989 return true;
6990 }
6991
6992 /// Pop style sheet from top of stack
6993 wxRichTextStyleSheet* wxRichTextBuffer::PopStyleSheet()
6994 {
6995 if (m_styleSheet)
6996 {
6997 wxRichTextStyleSheet* oldSheet = m_styleSheet;
6998 m_styleSheet = oldSheet->GetNextSheet();
6999 oldSheet->Unlink();
7000
7001 return oldSheet;
7002 }
7003 else
7004 return NULL;
7005 }
7006
7007 /// Submit command to insert paragraphs
7008 bool wxRichTextBuffer::InsertParagraphsWithUndo(long pos, const wxRichTextParagraphLayoutBox& paragraphs, wxRichTextCtrl* ctrl, int flags)
7009 {
7010 return ctrl->GetFocusObject()->InsertParagraphsWithUndo(this, pos, paragraphs, ctrl, flags);
7011 }
7012
7013 /// Submit command to insert paragraphs
7014 bool wxRichTextParagraphLayoutBox::InsertParagraphsWithUndo(wxRichTextBuffer* buffer, long pos, const wxRichTextParagraphLayoutBox& paragraphs, wxRichTextCtrl* ctrl, int WXUNUSED(flags))
7015 {
7016 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
7017
7018 action->GetNewParagraphs() = paragraphs;
7019
7020 action->SetPosition(pos);
7021
7022 wxRichTextRange range = wxRichTextRange(pos, pos + paragraphs.GetOwnRange().GetEnd() - 1);
7023 if (!paragraphs.GetPartialParagraph())
7024 range.SetEnd(range.GetEnd()+1);
7025
7026 // Set the range we'll need to delete in Undo
7027 action->SetRange(range);
7028
7029 buffer->SubmitAction(action);
7030
7031 return true;
7032 }
7033
7034 /// Submit command to insert the given text
7035 bool wxRichTextBuffer::InsertTextWithUndo(long pos, const wxString& text, wxRichTextCtrl* ctrl, int flags)
7036 {
7037 return ctrl->GetFocusObject()->InsertTextWithUndo(this, pos, text, ctrl, flags);
7038 }
7039
7040 /// Submit command to insert the given text
7041 bool wxRichTextParagraphLayoutBox::InsertTextWithUndo(wxRichTextBuffer* buffer, long pos, const wxString& text, wxRichTextCtrl* ctrl, int flags)
7042 {
7043 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
7044
7045 wxRichTextAttr* p = NULL;
7046 wxRichTextAttr paraAttr;
7047 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
7048 {
7049 // Get appropriate paragraph style
7050 paraAttr = GetStyleForNewParagraph(buffer, pos, false, false);
7051 if (!paraAttr.IsDefault())
7052 p = & paraAttr;
7053 }
7054
7055 action->GetNewParagraphs().AddParagraphs(text, p);
7056
7057 int length = action->GetNewParagraphs().GetOwnRange().GetLength();
7058
7059 if (!text.empty() && text.Last() != wxT('\n'))
7060 {
7061 // Don't count the newline when undoing
7062 length --;
7063 action->GetNewParagraphs().SetPartialParagraph(true);
7064 }
7065 else if (!text.empty() && text.Last() == wxT('\n'))
7066 length --;
7067
7068 action->SetPosition(pos);
7069
7070 // Set the range we'll need to delete in Undo
7071 action->SetRange(wxRichTextRange(pos, pos + length - 1));
7072
7073 buffer->SubmitAction(action);
7074
7075 return true;
7076 }
7077
7078 /// Submit command to insert the given text
7079 bool wxRichTextBuffer::InsertNewlineWithUndo(long pos, wxRichTextCtrl* ctrl, int flags)
7080 {
7081 return ctrl->GetFocusObject()->InsertNewlineWithUndo(this, pos, ctrl, flags);
7082 }
7083
7084 /// Submit command to insert the given text
7085 bool wxRichTextParagraphLayoutBox::InsertNewlineWithUndo(wxRichTextBuffer* buffer, long pos, wxRichTextCtrl* ctrl, int flags)
7086 {
7087 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
7088
7089 wxRichTextAttr* p = NULL;
7090 wxRichTextAttr paraAttr;
7091 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
7092 {
7093 paraAttr = GetStyleForNewParagraph(buffer, pos, false, true /* look for next paragraph style */);
7094 if (!paraAttr.IsDefault())
7095 p = & paraAttr;
7096 }
7097
7098 wxRichTextAttr attr(buffer->GetDefaultStyle());
7099 // Don't include box attributes such as margins
7100 attr.GetTextBoxAttr().Reset();
7101
7102 wxRichTextParagraph* newPara = new wxRichTextParagraph(wxEmptyString, this, & attr);
7103 action->GetNewParagraphs().AppendChild(newPara);
7104 action->GetNewParagraphs().UpdateRanges();
7105 action->GetNewParagraphs().SetPartialParagraph(false);
7106 wxRichTextParagraph* para = GetParagraphAtPosition(pos, false);
7107 long pos1 = pos;
7108
7109 if (p)
7110 newPara->SetAttributes(*p);
7111
7112 if (flags & wxRICHTEXT_INSERT_INTERACTIVE)
7113 {
7114 if (para && para->GetRange().GetEnd() == pos)
7115 pos1 ++;
7116
7117 // Now see if we need to number the paragraph.
7118 if (newPara->GetAttributes().HasBulletNumber())
7119 {
7120 wxRichTextAttr numberingAttr;
7121 if (FindNextParagraphNumber(para, numberingAttr))
7122 wxRichTextApplyStyle(newPara->GetAttributes(), (const wxRichTextAttr&) numberingAttr);
7123 }
7124 }
7125
7126 action->SetPosition(pos);
7127
7128 // Use the default character style
7129 if (!buffer->GetDefaultStyle().IsDefault() && newPara->GetChildren().GetFirst())
7130 {
7131 // Check whether the default style merely reflects the paragraph/basic style,
7132 // in which case don't apply it.
7133 wxRichTextAttr defaultStyle(buffer->GetDefaultStyle());
7134 defaultStyle.GetTextBoxAttr().Reset();
7135 wxRichTextAttr toApply;
7136 if (para)
7137 {
7138 wxRichTextAttr combinedAttr = para->GetCombinedAttributes(true /* include box attributes */);
7139 wxRichTextAttr newAttr;
7140 // This filters out attributes that are accounted for by the current
7141 // paragraph/basic style
7142 wxRichTextApplyStyle(toApply, defaultStyle, & combinedAttr);
7143 }
7144 else
7145 toApply = defaultStyle;
7146
7147 if (!toApply.IsDefault())
7148 newPara->GetChildren().GetFirst()->GetData()->SetAttributes(toApply);
7149 }
7150
7151 // Set the range we'll need to delete in Undo
7152 action->SetRange(wxRichTextRange(pos1, pos1));
7153
7154 buffer->SubmitAction(action);
7155
7156 return true;
7157 }
7158
7159 /// Submit command to insert the given image
7160 bool wxRichTextBuffer::InsertImageWithUndo(long pos, const wxRichTextImageBlock& imageBlock, wxRichTextCtrl* ctrl, int flags,
7161 const wxRichTextAttr& textAttr)
7162 {
7163 return ctrl->GetFocusObject()->InsertImageWithUndo(this, pos, imageBlock, ctrl, flags, textAttr);
7164 }
7165
7166 /// Submit command to insert the given image
7167 bool wxRichTextParagraphLayoutBox::InsertImageWithUndo(wxRichTextBuffer* buffer, long pos, const wxRichTextImageBlock& imageBlock,
7168 wxRichTextCtrl* ctrl, int flags,
7169 const wxRichTextAttr& textAttr)
7170 {
7171 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Image"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
7172
7173 wxRichTextAttr* p = NULL;
7174 wxRichTextAttr paraAttr;
7175 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
7176 {
7177 paraAttr = GetStyleForNewParagraph(buffer, pos);
7178 if (!paraAttr.IsDefault())
7179 p = & paraAttr;
7180 }
7181
7182 wxRichTextAttr attr(buffer->GetDefaultStyle());
7183
7184 // Don't include box attributes such as margins
7185 attr.GetTextBoxAttr().Reset();
7186
7187 wxRichTextParagraph* newPara = new wxRichTextParagraph(this, & attr);
7188 if (p)
7189 newPara->SetAttributes(*p);
7190
7191 wxRichTextImage* imageObject = new wxRichTextImage(imageBlock, newPara);
7192 newPara->AppendChild(imageObject);
7193 imageObject->SetAttributes(textAttr);
7194 action->GetNewParagraphs().AppendChild(newPara);
7195 action->GetNewParagraphs().UpdateRanges();
7196
7197 action->GetNewParagraphs().SetPartialParagraph(true);
7198
7199 action->SetPosition(pos);
7200
7201 // Set the range we'll need to delete in Undo
7202 action->SetRange(wxRichTextRange(pos, pos));
7203
7204 buffer->SubmitAction(action);
7205
7206 return true;
7207 }
7208
7209 // Insert an object with no change of it
7210 wxRichTextObject* wxRichTextBuffer::InsertObjectWithUndo(long pos, wxRichTextObject *object, wxRichTextCtrl* ctrl, int flags)
7211 {
7212 return ctrl->GetFocusObject()->InsertObjectWithUndo(this, pos, object, ctrl, flags);
7213 }
7214
7215 // Insert an object with no change of it
7216 wxRichTextObject* wxRichTextParagraphLayoutBox::InsertObjectWithUndo(wxRichTextBuffer* buffer, long pos, wxRichTextObject *object, wxRichTextCtrl* ctrl, int flags)
7217 {
7218 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Object"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
7219
7220 wxRichTextAttr* p = NULL;
7221 wxRichTextAttr paraAttr;
7222 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
7223 {
7224 paraAttr = GetStyleForNewParagraph(buffer, pos);
7225 if (!paraAttr.IsDefault())
7226 p = & paraAttr;
7227 }
7228
7229 wxRichTextAttr attr(buffer->GetDefaultStyle());
7230
7231 // Don't include box attributes such as margins
7232 attr.GetTextBoxAttr().Reset();
7233
7234 wxRichTextParagraph* newPara = new wxRichTextParagraph(this, & attr);
7235 if (p)
7236 newPara->SetAttributes(*p);
7237
7238 newPara->AppendChild(object);
7239 action->GetNewParagraphs().AppendChild(newPara);
7240 action->GetNewParagraphs().UpdateRanges();
7241
7242 action->GetNewParagraphs().SetPartialParagraph(true);
7243
7244 action->SetPosition(pos);
7245
7246 // Set the range we'll need to delete in Undo
7247 action->SetRange(wxRichTextRange(pos, pos));
7248
7249 buffer->SubmitAction(action);
7250
7251 wxRichTextObject* obj = GetLeafObjectAtPosition(pos);
7252 return obj;
7253 }
7254
7255 wxRichTextField* wxRichTextParagraphLayoutBox::InsertFieldWithUndo(wxRichTextBuffer* buffer, long pos, const wxString& fieldType,
7256 const wxRichTextProperties& properties,
7257 wxRichTextCtrl* ctrl, int flags,
7258 const wxRichTextAttr& textAttr)
7259 {
7260 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Field"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
7261
7262 wxRichTextAttr* p = NULL;
7263 wxRichTextAttr paraAttr;
7264 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
7265 {
7266 paraAttr = GetStyleForNewParagraph(buffer, pos);
7267 if (!paraAttr.IsDefault())
7268 p = & paraAttr;
7269 }
7270
7271 wxRichTextAttr attr(buffer->GetDefaultStyle());
7272
7273 // Don't include box attributes such as margins
7274 attr.GetTextBoxAttr().Reset();
7275
7276 wxRichTextParagraph* newPara = new wxRichTextParagraph(this, & attr);
7277 if (p)
7278 newPara->SetAttributes(*p);
7279
7280 wxRichTextField* fieldObject = new wxRichTextField();
7281 fieldObject->wxRichTextObject::SetProperties(properties);
7282 fieldObject->SetFieldType(fieldType);
7283 fieldObject->SetAttributes(textAttr);
7284 newPara->AppendChild(fieldObject);
7285 action->GetNewParagraphs().AppendChild(newPara);
7286 action->GetNewParagraphs().UpdateRanges();
7287 action->GetNewParagraphs().SetPartialParagraph(true);
7288 action->SetPosition(pos);
7289
7290 // Set the range we'll need to delete in Undo
7291 action->SetRange(wxRichTextRange(pos, pos));
7292
7293 buffer->SubmitAction(action);
7294
7295 wxRichTextField* obj = wxDynamicCast(GetLeafObjectAtPosition(pos), wxRichTextField);
7296 return obj;
7297 }
7298
7299 /// Get the style that is appropriate for a new paragraph at this position.
7300 /// If the previous paragraph has a paragraph style name, look up the next-paragraph
7301 /// style.
7302 wxRichTextAttr wxRichTextParagraphLayoutBox::GetStyleForNewParagraph(wxRichTextBuffer* buffer, long pos, bool caretPosition, bool lookUpNewParaStyle) const
7303 {
7304 wxRichTextParagraph* para = GetParagraphAtPosition(pos, caretPosition);
7305 if (para)
7306 {
7307 wxRichTextAttr attr;
7308 bool foundAttributes = false;
7309
7310 // Look for a matching paragraph style
7311 if (lookUpNewParaStyle && !para->GetAttributes().GetParagraphStyleName().IsEmpty() && buffer->GetStyleSheet())
7312 {
7313 wxRichTextParagraphStyleDefinition* paraDef = buffer->GetStyleSheet()->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName());
7314 if (paraDef)
7315 {
7316 // If we're not at the end of the paragraph, then we apply THIS style, and not the designated next style.
7317 if (para->GetRange().GetEnd() == pos && !paraDef->GetNextStyle().IsEmpty())
7318 {
7319 wxRichTextParagraphStyleDefinition* nextParaDef = buffer->GetStyleSheet()->FindParagraphStyle(paraDef->GetNextStyle());
7320 if (nextParaDef)
7321 {
7322 foundAttributes = true;
7323 attr = nextParaDef->GetStyleMergedWithBase(buffer->GetStyleSheet());
7324 }
7325 }
7326
7327 // If we didn't find the 'next style', use this style instead.
7328 if (!foundAttributes)
7329 {
7330 foundAttributes = true;
7331 attr = paraDef->GetStyleMergedWithBase(buffer->GetStyleSheet());
7332 }
7333 }
7334 }
7335
7336 // Also apply list style if present
7337 if (lookUpNewParaStyle && !para->GetAttributes().GetListStyleName().IsEmpty() && buffer->GetStyleSheet())
7338 {
7339 wxRichTextListStyleDefinition* listDef = buffer->GetStyleSheet()->FindListStyle(para->GetAttributes().GetListStyleName());
7340 if (listDef)
7341 {
7342 int thisIndent = para->GetAttributes().GetLeftIndent();
7343 int thisLevel = para->GetAttributes().HasOutlineLevel() ? para->GetAttributes().GetOutlineLevel() : listDef->FindLevelForIndent(thisIndent);
7344
7345 // Apply the overall list style, and item style for this level
7346 wxRichTextAttr listStyle(listDef->GetCombinedStyleForLevel(thisLevel, buffer->GetStyleSheet()));
7347 wxRichTextApplyStyle(attr, listStyle);
7348 attr.SetOutlineLevel(thisLevel);
7349 if (para->GetAttributes().HasBulletNumber())
7350 attr.SetBulletNumber(para->GetAttributes().GetBulletNumber());
7351 }
7352 }
7353
7354 if (!foundAttributes)
7355 {
7356 attr = para->GetAttributes();
7357 int flags = attr.GetFlags();
7358
7359 // Eliminate character styles
7360 flags &= ( (~ wxTEXT_ATTR_FONT) |
7361 (~ wxTEXT_ATTR_TEXT_COLOUR) |
7362 (~ wxTEXT_ATTR_BACKGROUND_COLOUR) );
7363 attr.SetFlags(flags);
7364 }
7365
7366 return attr;
7367 }
7368 else
7369 return wxRichTextAttr();
7370 }
7371
7372 /// Submit command to delete this range
7373 bool wxRichTextBuffer::DeleteRangeWithUndo(const wxRichTextRange& range, wxRichTextCtrl* ctrl)
7374 {
7375 return ctrl->GetFocusObject()->DeleteRangeWithUndo(range, ctrl, this);
7376 }
7377
7378 /// Submit command to delete this range
7379 bool wxRichTextParagraphLayoutBox::DeleteRangeWithUndo(const wxRichTextRange& range, wxRichTextCtrl* ctrl, wxRichTextBuffer* buffer)
7380 {
7381 wxRichTextAction* action = new wxRichTextAction(NULL, _("Delete"), wxRICHTEXT_DELETE, buffer, this, ctrl);
7382
7383 action->SetPosition(ctrl->GetCaretPosition());
7384
7385 // Set the range to delete
7386 action->SetRange(range);
7387
7388 // Copy the fragment that we'll need to restore in Undo
7389 CopyFragment(range, action->GetOldParagraphs());
7390
7391 // See if we're deleting a paragraph marker, in which case we need to
7392 // make a note not to copy the attributes from the 2nd paragraph to the 1st.
7393 if (range.GetStart() == range.GetEnd())
7394 {
7395 wxRichTextParagraph* para = GetParagraphAtPosition(range.GetStart());
7396 if (para && para->GetRange().GetEnd() == range.GetEnd())
7397 {
7398 wxRichTextParagraph* nextPara = GetParagraphAtPosition(range.GetStart()+1);
7399 if (nextPara && nextPara != para)
7400 {
7401 action->GetOldParagraphs().GetChildren().GetFirst()->GetData()->SetAttributes(nextPara->GetAttributes());
7402 action->GetOldParagraphs().GetAttributes().SetFlags(action->GetOldParagraphs().GetAttributes().GetFlags() | wxTEXT_ATTR_KEEP_FIRST_PARA_STYLE);
7403 }
7404 }
7405 }
7406
7407 buffer->SubmitAction(action);
7408
7409 return true;
7410 }
7411
7412 /// Collapse undo/redo commands
7413 bool wxRichTextBuffer::BeginBatchUndo(const wxString& cmdName)
7414 {
7415 if (m_batchedCommandDepth == 0)
7416 {
7417 wxASSERT(m_batchedCommand == NULL);
7418 if (m_batchedCommand)
7419 {
7420 GetCommandProcessor()->Store(m_batchedCommand);
7421 }
7422 m_batchedCommand = new wxRichTextCommand(cmdName);
7423 }
7424
7425 m_batchedCommandDepth ++;
7426
7427 return true;
7428 }
7429
7430 /// Collapse undo/redo commands
7431 bool wxRichTextBuffer::EndBatchUndo()
7432 {
7433 m_batchedCommandDepth --;
7434
7435 wxASSERT(m_batchedCommandDepth >= 0);
7436 wxASSERT(m_batchedCommand != NULL);
7437
7438 if (m_batchedCommandDepth == 0)
7439 {
7440 GetCommandProcessor()->Store(m_batchedCommand);
7441 m_batchedCommand = NULL;
7442 }
7443
7444 return true;
7445 }
7446
7447 /// Submit immediately, or delay according to whether collapsing is on
7448 bool wxRichTextBuffer::SubmitAction(wxRichTextAction* action)
7449 {
7450 if (action && !action->GetNewParagraphs().IsEmpty())
7451 PrepareContent(action->GetNewParagraphs());
7452
7453 if (BatchingUndo() && m_batchedCommand && !SuppressingUndo())
7454 {
7455 wxRichTextCommand* cmd = new wxRichTextCommand(action->GetName());
7456 cmd->AddAction(action);
7457 cmd->Do();
7458 cmd->GetActions().Clear();
7459 delete cmd;
7460
7461 m_batchedCommand->AddAction(action);
7462 }
7463 else
7464 {
7465 wxRichTextCommand* cmd = new wxRichTextCommand(action->GetName());
7466 cmd->AddAction(action);
7467
7468 // Only store it if we're not suppressing undo.
7469 return GetCommandProcessor()->Submit(cmd, !SuppressingUndo());
7470 }
7471
7472 return true;
7473 }
7474
7475 /// Begin suppressing undo/redo commands.
7476 bool wxRichTextBuffer::BeginSuppressUndo()
7477 {
7478 m_suppressUndo ++;
7479
7480 return true;
7481 }
7482
7483 /// End suppressing undo/redo commands.
7484 bool wxRichTextBuffer::EndSuppressUndo()
7485 {
7486 m_suppressUndo --;
7487
7488 return true;
7489 }
7490
7491 /// Begin using a style
7492 bool wxRichTextBuffer::BeginStyle(const wxRichTextAttr& style)
7493 {
7494 wxRichTextAttr newStyle(GetDefaultStyle());
7495 newStyle.GetTextBoxAttr().Reset();
7496
7497 // Save the old default style
7498 m_attributeStack.Append((wxObject*) new wxRichTextAttr(newStyle));
7499
7500 wxRichTextApplyStyle(newStyle, style);
7501 newStyle.SetFlags(style.GetFlags()|newStyle.GetFlags());
7502
7503 SetDefaultStyle(newStyle);
7504
7505 return true;
7506 }
7507
7508 /// End the style
7509 bool wxRichTextBuffer::EndStyle()
7510 {
7511 if (!m_attributeStack.GetFirst())
7512 {
7513 wxLogDebug(_("Too many EndStyle calls!"));
7514 return false;
7515 }
7516
7517 wxList::compatibility_iterator node = m_attributeStack.GetLast();
7518 wxRichTextAttr* attr = (wxRichTextAttr*)node->GetData();
7519 m_attributeStack.Erase(node);
7520
7521 SetDefaultStyle(*attr);
7522
7523 delete attr;
7524 return true;
7525 }
7526
7527 /// End all styles
7528 bool wxRichTextBuffer::EndAllStyles()
7529 {
7530 while (m_attributeStack.GetCount() != 0)
7531 EndStyle();
7532 return true;
7533 }
7534
7535 /// Clear the style stack
7536 void wxRichTextBuffer::ClearStyleStack()
7537 {
7538 for (wxList::compatibility_iterator node = m_attributeStack.GetFirst(); node; node = node->GetNext())
7539 delete (wxRichTextAttr*) node->GetData();
7540 m_attributeStack.Clear();
7541 }
7542
7543 /// Begin using bold
7544 bool wxRichTextBuffer::BeginBold()
7545 {
7546 wxRichTextAttr attr;
7547 attr.SetFontWeight(wxFONTWEIGHT_BOLD);
7548
7549 return BeginStyle(attr);
7550 }
7551
7552 /// Begin using italic
7553 bool wxRichTextBuffer::BeginItalic()
7554 {
7555 wxRichTextAttr attr;
7556 attr.SetFontStyle(wxFONTSTYLE_ITALIC);
7557
7558 return BeginStyle(attr);
7559 }
7560
7561 /// Begin using underline
7562 bool wxRichTextBuffer::BeginUnderline()
7563 {
7564 wxRichTextAttr attr;
7565 attr.SetFontUnderlined(true);
7566
7567 return BeginStyle(attr);
7568 }
7569
7570 /// Begin using point size
7571 bool wxRichTextBuffer::BeginFontSize(int pointSize)
7572 {
7573 wxRichTextAttr attr;
7574 attr.SetFontSize(pointSize);
7575
7576 return BeginStyle(attr);
7577 }
7578
7579 /// Begin using this font
7580 bool wxRichTextBuffer::BeginFont(const wxFont& font)
7581 {
7582 wxRichTextAttr attr;
7583 attr.SetFont(font);
7584
7585 return BeginStyle(attr);
7586 }
7587
7588 /// Begin using this colour
7589 bool wxRichTextBuffer::BeginTextColour(const wxColour& colour)
7590 {
7591 wxRichTextAttr attr;
7592 attr.SetFlags(wxTEXT_ATTR_TEXT_COLOUR);
7593 attr.SetTextColour(colour);
7594
7595 return BeginStyle(attr);
7596 }
7597
7598 /// Begin using alignment
7599 bool wxRichTextBuffer::BeginAlignment(wxTextAttrAlignment alignment)
7600 {
7601 wxRichTextAttr attr;
7602 attr.SetFlags(wxTEXT_ATTR_ALIGNMENT);
7603 attr.SetAlignment(alignment);
7604
7605 return BeginStyle(attr);
7606 }
7607
7608 /// Begin left indent
7609 bool wxRichTextBuffer::BeginLeftIndent(int leftIndent, int leftSubIndent)
7610 {
7611 wxRichTextAttr attr;
7612 attr.SetFlags(wxTEXT_ATTR_LEFT_INDENT);
7613 attr.SetLeftIndent(leftIndent, leftSubIndent);
7614
7615 return BeginStyle(attr);
7616 }
7617
7618 /// Begin right indent
7619 bool wxRichTextBuffer::BeginRightIndent(int rightIndent)
7620 {
7621 wxRichTextAttr attr;
7622 attr.SetFlags(wxTEXT_ATTR_RIGHT_INDENT);
7623 attr.SetRightIndent(rightIndent);
7624
7625 return BeginStyle(attr);
7626 }
7627
7628 /// Begin paragraph spacing
7629 bool wxRichTextBuffer::BeginParagraphSpacing(int before, int after)
7630 {
7631 long flags = 0;
7632 if (before != 0)
7633 flags |= wxTEXT_ATTR_PARA_SPACING_BEFORE;
7634 if (after != 0)
7635 flags |= wxTEXT_ATTR_PARA_SPACING_AFTER;
7636
7637 wxRichTextAttr attr;
7638 attr.SetFlags(flags);
7639 attr.SetParagraphSpacingBefore(before);
7640 attr.SetParagraphSpacingAfter(after);
7641
7642 return BeginStyle(attr);
7643 }
7644
7645 /// Begin line spacing
7646 bool wxRichTextBuffer::BeginLineSpacing(int lineSpacing)
7647 {
7648 wxRichTextAttr attr;
7649 attr.SetFlags(wxTEXT_ATTR_LINE_SPACING);
7650 attr.SetLineSpacing(lineSpacing);
7651
7652 return BeginStyle(attr);
7653 }
7654
7655 /// Begin numbered bullet
7656 bool wxRichTextBuffer::BeginNumberedBullet(int bulletNumber, int leftIndent, int leftSubIndent, int bulletStyle)
7657 {
7658 wxRichTextAttr attr;
7659 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT);
7660 attr.SetBulletStyle(bulletStyle);
7661 attr.SetBulletNumber(bulletNumber);
7662 attr.SetLeftIndent(leftIndent, leftSubIndent);
7663
7664 return BeginStyle(attr);
7665 }
7666
7667 /// Begin symbol bullet
7668 bool wxRichTextBuffer::BeginSymbolBullet(const wxString& symbol, int leftIndent, int leftSubIndent, int bulletStyle)
7669 {
7670 wxRichTextAttr attr;
7671 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT);
7672 attr.SetBulletStyle(bulletStyle);
7673 attr.SetLeftIndent(leftIndent, leftSubIndent);
7674 attr.SetBulletText(symbol);
7675
7676 return BeginStyle(attr);
7677 }
7678
7679 /// Begin standard bullet
7680 bool wxRichTextBuffer::BeginStandardBullet(const wxString& bulletName, int leftIndent, int leftSubIndent, int bulletStyle)
7681 {
7682 wxRichTextAttr attr;
7683 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT);
7684 attr.SetBulletStyle(bulletStyle);
7685 attr.SetLeftIndent(leftIndent, leftSubIndent);
7686 attr.SetBulletName(bulletName);
7687
7688 return BeginStyle(attr);
7689 }
7690
7691 /// Begin named character style
7692 bool wxRichTextBuffer::BeginCharacterStyle(const wxString& characterStyle)
7693 {
7694 if (GetStyleSheet())
7695 {
7696 wxRichTextCharacterStyleDefinition* def = GetStyleSheet()->FindCharacterStyle(characterStyle);
7697 if (def)
7698 {
7699 wxRichTextAttr attr = def->GetStyleMergedWithBase(GetStyleSheet());
7700 return BeginStyle(attr);
7701 }
7702 }
7703 return false;
7704 }
7705
7706 /// Begin named paragraph style
7707 bool wxRichTextBuffer::BeginParagraphStyle(const wxString& paragraphStyle)
7708 {
7709 if (GetStyleSheet())
7710 {
7711 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(paragraphStyle);
7712 if (def)
7713 {
7714 wxRichTextAttr attr = def->GetStyleMergedWithBase(GetStyleSheet());
7715 return BeginStyle(attr);
7716 }
7717 }
7718 return false;
7719 }
7720
7721 /// Begin named list style
7722 bool wxRichTextBuffer::BeginListStyle(const wxString& listStyle, int level, int number)
7723 {
7724 if (GetStyleSheet())
7725 {
7726 wxRichTextListStyleDefinition* def = GetStyleSheet()->FindListStyle(listStyle);
7727 if (def)
7728 {
7729 wxRichTextAttr attr(def->GetCombinedStyleForLevel(level));
7730
7731 attr.SetBulletNumber(number);
7732
7733 return BeginStyle(attr);
7734 }
7735 }
7736 return false;
7737 }
7738
7739 /// Begin URL
7740 bool wxRichTextBuffer::BeginURL(const wxString& url, const wxString& characterStyle)
7741 {
7742 wxRichTextAttr attr;
7743
7744 if (!characterStyle.IsEmpty() && GetStyleSheet())
7745 {
7746 wxRichTextCharacterStyleDefinition* def = GetStyleSheet()->FindCharacterStyle(characterStyle);
7747 if (def)
7748 {
7749 attr = def->GetStyleMergedWithBase(GetStyleSheet());
7750 }
7751 }
7752 attr.SetURL(url);
7753
7754 return BeginStyle(attr);
7755 }
7756
7757 /// Adds a handler to the end
7758 void wxRichTextBuffer::AddHandler(wxRichTextFileHandler *handler)
7759 {
7760 sm_handlers.Append(handler);
7761 }
7762
7763 /// Inserts a handler at the front
7764 void wxRichTextBuffer::InsertHandler(wxRichTextFileHandler *handler)
7765 {
7766 sm_handlers.Insert( handler );
7767 }
7768
7769 /// Removes a handler
7770 bool wxRichTextBuffer::RemoveHandler(const wxString& name)
7771 {
7772 wxRichTextFileHandler *handler = FindHandler(name);
7773 if (handler)
7774 {
7775 sm_handlers.DeleteObject(handler);
7776 delete handler;
7777 return true;
7778 }
7779 else
7780 return false;
7781 }
7782
7783 /// Finds a handler by filename or, if supplied, type
7784 wxRichTextFileHandler *wxRichTextBuffer::FindHandlerFilenameOrType(const wxString& filename,
7785 wxRichTextFileType imageType)
7786 {
7787 if (imageType != wxRICHTEXT_TYPE_ANY)
7788 return FindHandler(imageType);
7789 else if (!filename.IsEmpty())
7790 {
7791 wxString path, file, ext;
7792 wxFileName::SplitPath(filename, & path, & file, & ext);
7793 return FindHandler(ext, imageType);
7794 }
7795 else
7796 return NULL;
7797 }
7798
7799
7800 /// Finds a handler by name
7801 wxRichTextFileHandler* wxRichTextBuffer::FindHandler(const wxString& name)
7802 {
7803 wxList::compatibility_iterator node = sm_handlers.GetFirst();
7804 while (node)
7805 {
7806 wxRichTextFileHandler *handler = (wxRichTextFileHandler*)node->GetData();
7807 if (handler->GetName().Lower() == name.Lower()) return handler;
7808
7809 node = node->GetNext();
7810 }
7811 return NULL;
7812 }
7813
7814 /// Finds a handler by extension and type
7815 wxRichTextFileHandler* wxRichTextBuffer::FindHandler(const wxString& extension, wxRichTextFileType type)
7816 {
7817 wxList::compatibility_iterator node = sm_handlers.GetFirst();
7818 while (node)
7819 {
7820 wxRichTextFileHandler *handler = (wxRichTextFileHandler*)node->GetData();
7821 if ( handler->GetExtension().Lower() == extension.Lower() &&
7822 (type == wxRICHTEXT_TYPE_ANY || handler->GetType() == type) )
7823 return handler;
7824 node = node->GetNext();
7825 }
7826 return 0;
7827 }
7828
7829 /// Finds a handler by type
7830 wxRichTextFileHandler* wxRichTextBuffer::FindHandler(wxRichTextFileType type)
7831 {
7832 wxList::compatibility_iterator node = sm_handlers.GetFirst();
7833 while (node)
7834 {
7835 wxRichTextFileHandler *handler = (wxRichTextFileHandler *)node->GetData();
7836 if (handler->GetType() == type) return handler;
7837 node = node->GetNext();
7838 }
7839 return NULL;
7840 }
7841
7842 void wxRichTextBuffer::InitStandardHandlers()
7843 {
7844 if (!FindHandler(wxRICHTEXT_TYPE_TEXT))
7845 AddHandler(new wxRichTextPlainTextHandler);
7846 }
7847
7848 void wxRichTextBuffer::CleanUpHandlers()
7849 {
7850 wxList::compatibility_iterator node = sm_handlers.GetFirst();
7851 while (node)
7852 {
7853 wxRichTextFileHandler* handler = (wxRichTextFileHandler*)node->GetData();
7854 wxList::compatibility_iterator next = node->GetNext();
7855 delete handler;
7856 node = next;
7857 }
7858
7859 sm_handlers.Clear();
7860 }
7861
7862 wxString wxRichTextBuffer::GetExtWildcard(bool combine, bool save, wxArrayInt* types)
7863 {
7864 if (types)
7865 types->Clear();
7866
7867 wxString wildcard;
7868
7869 wxList::compatibility_iterator node = GetHandlers().GetFirst();
7870 int count = 0;
7871 while (node)
7872 {
7873 wxRichTextFileHandler* handler = (wxRichTextFileHandler*) node->GetData();
7874 if (handler->IsVisible() && ((save && handler->CanSave()) || (!save && handler->CanLoad())))
7875 {
7876 if (combine)
7877 {
7878 if (count > 0)
7879 wildcard += wxT(";");
7880 wildcard += wxT("*.") + handler->GetExtension();
7881 }
7882 else
7883 {
7884 if (count > 0)
7885 wildcard += wxT("|");
7886 wildcard += handler->GetName();
7887 wildcard += wxT(" ");
7888 wildcard += _("files");
7889 wildcard += wxT(" (*.");
7890 wildcard += handler->GetExtension();
7891 wildcard += wxT(")|*.");
7892 wildcard += handler->GetExtension();
7893 if (types)
7894 types->Add(handler->GetType());
7895 }
7896 count ++;
7897 }
7898
7899 node = node->GetNext();
7900 }
7901
7902 if (combine)
7903 wildcard = wxT("(") + wildcard + wxT(")|") + wildcard;
7904 return wildcard;
7905 }
7906
7907 /// Load a file
7908 bool wxRichTextBuffer::LoadFile(const wxString& filename, wxRichTextFileType type)
7909 {
7910 wxRichTextFileHandler* handler = FindHandlerFilenameOrType(filename, type);
7911 if (handler)
7912 {
7913 SetDefaultStyle(wxRichTextAttr());
7914 handler->SetFlags(GetHandlerFlags());
7915 bool success = handler->LoadFile(this, filename);
7916 Invalidate(wxRICHTEXT_ALL);
7917 return success;
7918 }
7919 else
7920 return false;
7921 }
7922
7923 /// Save a file
7924 bool wxRichTextBuffer::SaveFile(const wxString& filename, wxRichTextFileType type)
7925 {
7926 wxRichTextFileHandler* handler = FindHandlerFilenameOrType(filename, type);
7927 if (handler)
7928 {
7929 handler->SetFlags(GetHandlerFlags());
7930 return handler->SaveFile(this, filename);
7931 }
7932 else
7933 return false;
7934 }
7935
7936 /// Load from a stream
7937 bool wxRichTextBuffer::LoadFile(wxInputStream& stream, wxRichTextFileType type)
7938 {
7939 wxRichTextFileHandler* handler = FindHandler(type);
7940 if (handler)
7941 {
7942 SetDefaultStyle(wxRichTextAttr());
7943 handler->SetFlags(GetHandlerFlags());
7944 bool success = handler->LoadFile(this, stream);
7945 Invalidate(wxRICHTEXT_ALL);
7946 return success;
7947 }
7948 else
7949 return false;
7950 }
7951
7952 /// Save to a stream
7953 bool wxRichTextBuffer::SaveFile(wxOutputStream& stream, wxRichTextFileType type)
7954 {
7955 wxRichTextFileHandler* handler = FindHandler(type);
7956 if (handler)
7957 {
7958 handler->SetFlags(GetHandlerFlags());
7959 return handler->SaveFile(this, stream);
7960 }
7961 else
7962 return false;
7963 }
7964
7965 /// Copy the range to the clipboard
7966 bool wxRichTextBuffer::CopyToClipboard(const wxRichTextRange& range)
7967 {
7968 bool success = false;
7969 wxRichTextParagraphLayoutBox* container = this;
7970 if (GetRichTextCtrl())
7971 container = GetRichTextCtrl()->GetFocusObject();
7972
7973 #if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
7974
7975 if (!wxTheClipboard->IsOpened() && wxTheClipboard->Open())
7976 {
7977 wxTheClipboard->Clear();
7978
7979 // Add composite object
7980
7981 wxDataObjectComposite* compositeObject = new wxDataObjectComposite();
7982
7983 {
7984 wxString text = container->GetTextForRange(range);
7985
7986 #ifdef __WXMSW__
7987 text = wxTextFile::Translate(text, wxTextFileType_Dos);
7988 #endif
7989
7990 compositeObject->Add(new wxTextDataObject(text), false /* not preferred */);
7991 }
7992
7993 // Add rich text buffer data object. This needs the XML handler to be present.
7994
7995 if (FindHandler(wxRICHTEXT_TYPE_XML))
7996 {
7997 wxRichTextBuffer* richTextBuf = new wxRichTextBuffer;
7998 container->CopyFragment(range, *richTextBuf);
7999
8000 compositeObject->Add(new wxRichTextBufferDataObject(richTextBuf), true /* preferred */);
8001 }
8002
8003 if (wxTheClipboard->SetData(compositeObject))
8004 success = true;
8005
8006 wxTheClipboard->Close();
8007 }
8008
8009 #else
8010 wxUnusedVar(range);
8011 #endif
8012 return success;
8013 }
8014
8015 /// Paste the clipboard content to the buffer
8016 bool wxRichTextBuffer::PasteFromClipboard(long position)
8017 {
8018 bool success = false;
8019 wxRichTextParagraphLayoutBox* container = this;
8020 if (GetRichTextCtrl())
8021 container = GetRichTextCtrl()->GetFocusObject();
8022
8023 #if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
8024 if (CanPasteFromClipboard())
8025 {
8026 if (wxTheClipboard->Open())
8027 {
8028 if (wxTheClipboard->IsSupported(wxDataFormat(wxRichTextBufferDataObject::GetRichTextBufferFormatId())))
8029 {
8030 wxRichTextBufferDataObject data;
8031 wxTheClipboard->GetData(data);
8032 wxRichTextBuffer* richTextBuffer = data.GetRichTextBuffer();
8033 if (richTextBuffer)
8034 {
8035 container->InsertParagraphsWithUndo(this, position+1, *richTextBuffer, GetRichTextCtrl(), 0);
8036 if (GetRichTextCtrl())
8037 GetRichTextCtrl()->ShowPosition(position + richTextBuffer->GetOwnRange().GetEnd());
8038 delete richTextBuffer;
8039 }
8040 }
8041 else if (wxTheClipboard->IsSupported(wxDF_TEXT)
8042 #if wxUSE_UNICODE
8043 || wxTheClipboard->IsSupported(wxDF_UNICODETEXT)
8044 #endif
8045 )
8046 {
8047 wxTextDataObject data;
8048 wxTheClipboard->GetData(data);
8049 wxString text(data.GetText());
8050 #ifdef __WXMSW__
8051 wxString text2;
8052 text2.Alloc(text.Length()+1);
8053 size_t i;
8054 for (i = 0; i < text.Length(); i++)
8055 {
8056 wxChar ch = text[i];
8057 if (ch != wxT('\r'))
8058 text2 += ch;
8059 }
8060 #else
8061 wxString text2 = text;
8062 #endif
8063 container->InsertTextWithUndo(this, position+1, text2, GetRichTextCtrl(), wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE);
8064
8065 if (GetRichTextCtrl())
8066 GetRichTextCtrl()->ShowPosition(position + text2.Length());
8067
8068 success = true;
8069 }
8070 else if (wxTheClipboard->IsSupported(wxDF_BITMAP))
8071 {
8072 wxBitmapDataObject data;
8073 wxTheClipboard->GetData(data);
8074 wxBitmap bitmap(data.GetBitmap());
8075 wxImage image(bitmap.ConvertToImage());
8076
8077 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Image"), wxRICHTEXT_INSERT, this, container, GetRichTextCtrl(), false);
8078
8079 action->GetNewParagraphs().AddImage(image);
8080
8081 if (action->GetNewParagraphs().GetChildCount() == 1)
8082 action->GetNewParagraphs().SetPartialParagraph(true);
8083
8084 action->SetPosition(position+1);
8085
8086 // Set the range we'll need to delete in Undo
8087 action->SetRange(wxRichTextRange(position+1, position+1));
8088
8089 SubmitAction(action);
8090
8091 success = true;
8092 }
8093 wxTheClipboard->Close();
8094 }
8095 }
8096 #else
8097 wxUnusedVar(position);
8098 #endif
8099 return success;
8100 }
8101
8102 /// Can we paste from the clipboard?
8103 bool wxRichTextBuffer::CanPasteFromClipboard() const
8104 {
8105 bool canPaste = false;
8106 #if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
8107 if (!wxTheClipboard->IsOpened() && wxTheClipboard->Open())
8108 {
8109 if (wxTheClipboard->IsSupported(wxDF_TEXT)
8110 #if wxUSE_UNICODE
8111 || wxTheClipboard->IsSupported(wxDF_UNICODETEXT)
8112 #endif
8113 || wxTheClipboard->IsSupported(wxDataFormat(wxRichTextBufferDataObject::GetRichTextBufferFormatId())) ||
8114 wxTheClipboard->IsSupported(wxDF_BITMAP))
8115 {
8116 canPaste = true;
8117 }
8118 wxTheClipboard->Close();
8119 }
8120 #endif
8121 return canPaste;
8122 }
8123
8124 /// Dumps contents of buffer for debugging purposes
8125 void wxRichTextBuffer::Dump()
8126 {
8127 wxString text;
8128 {
8129 wxStringOutputStream stream(& text);
8130 wxTextOutputStream textStream(stream);
8131 Dump(textStream);
8132 }
8133
8134 wxLogDebug(text);
8135 }
8136
8137 /// Add an event handler
8138 bool wxRichTextBuffer::AddEventHandler(wxEvtHandler* handler)
8139 {
8140 m_eventHandlers.Append(handler);
8141 return true;
8142 }
8143
8144 /// Remove an event handler
8145 bool wxRichTextBuffer::RemoveEventHandler(wxEvtHandler* handler, bool deleteHandler)
8146 {
8147 wxList::compatibility_iterator node = m_eventHandlers.Find(handler);
8148 if (node)
8149 {
8150 m_eventHandlers.Erase(node);
8151 if (deleteHandler)
8152 delete handler;
8153
8154 return true;
8155 }
8156 else
8157 return false;
8158 }
8159
8160 /// Clear event handlers
8161 void wxRichTextBuffer::ClearEventHandlers()
8162 {
8163 m_eventHandlers.Clear();
8164 }
8165
8166 /// Send event to event handlers. If sendToAll is true, will send to all event handlers,
8167 /// otherwise will stop at the first successful one.
8168 bool wxRichTextBuffer::SendEvent(wxEvent& event, bool sendToAll)
8169 {
8170 bool success = false;
8171 for (wxList::compatibility_iterator node = m_eventHandlers.GetFirst(); node; node = node->GetNext())
8172 {
8173 wxEvtHandler* handler = (wxEvtHandler*) node->GetData();
8174 if (handler->ProcessEvent(event))
8175 {
8176 success = true;
8177 if (!sendToAll)
8178 return true;
8179 }
8180 }
8181 return success;
8182 }
8183
8184 /// Set style sheet and notify of the change
8185 bool wxRichTextBuffer::SetStyleSheetAndNotify(wxRichTextStyleSheet* sheet)
8186 {
8187 wxRichTextStyleSheet* oldSheet = GetStyleSheet();
8188
8189 wxWindowID winid = wxID_ANY;
8190 if (GetRichTextCtrl())
8191 winid = GetRichTextCtrl()->GetId();
8192
8193 wxRichTextEvent event(wxEVT_COMMAND_RICHTEXT_STYLESHEET_REPLACING, winid);
8194 event.SetEventObject(GetRichTextCtrl());
8195 event.SetContainer(GetRichTextCtrl()->GetFocusObject());
8196 event.SetOldStyleSheet(oldSheet);
8197 event.SetNewStyleSheet(sheet);
8198 event.Allow();
8199
8200 if (SendEvent(event) && !event.IsAllowed())
8201 {
8202 if (sheet != oldSheet)
8203 delete sheet;
8204
8205 return false;
8206 }
8207
8208 if (oldSheet && oldSheet != sheet)
8209 delete oldSheet;
8210
8211 SetStyleSheet(sheet);
8212
8213 event.SetEventType(wxEVT_COMMAND_RICHTEXT_STYLESHEET_REPLACED);
8214 event.SetOldStyleSheet(NULL);
8215 event.Allow();
8216
8217 return SendEvent(event);
8218 }
8219
8220 /// Set renderer, deleting old one
8221 void wxRichTextBuffer::SetRenderer(wxRichTextRenderer* renderer)
8222 {
8223 if (sm_renderer)
8224 delete sm_renderer;
8225 sm_renderer = renderer;
8226 }
8227
8228 /// Hit-testing: returns a flag indicating hit test details, plus
8229 /// information about position
8230 int wxRichTextBuffer::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int flags)
8231 {
8232 int ret = wxRichTextParagraphLayoutBox::HitTest(dc, context, pt, textPosition, obj, contextObj, flags);
8233 if (ret != wxRICHTEXT_HITTEST_NONE)
8234 {
8235 return ret;
8236 }
8237 else
8238 {
8239 textPosition = m_ownRange.GetEnd()-1;
8240 *obj = this;
8241 *contextObj = this;
8242 return wxRICHTEXT_HITTEST_AFTER|wxRICHTEXT_HITTEST_OUTSIDE;
8243 }
8244 }
8245
8246 void wxRichTextBuffer::SetFontScale(double fontScale)
8247 {
8248 m_fontScale = fontScale;
8249 m_fontTable.SetFontScale(fontScale);
8250 }
8251
8252 void wxRichTextBuffer::SetDimensionScale(double dimScale)
8253 {
8254 m_dimensionScale = dimScale;
8255 }
8256
8257 bool wxRichTextStdRenderer::DrawStandardBullet(wxRichTextParagraph* paragraph, wxDC& dc, const wxRichTextAttr& bulletAttr, const wxRect& rect)
8258 {
8259 if (bulletAttr.GetTextColour().IsOk())
8260 {
8261 wxCheckSetPen(dc, wxPen(bulletAttr.GetTextColour()));
8262 wxCheckSetBrush(dc, wxBrush(bulletAttr.GetTextColour()));
8263 }
8264 else
8265 {
8266 wxCheckSetPen(dc, *wxBLACK_PEN);
8267 wxCheckSetBrush(dc, *wxBLACK_BRUSH);
8268 }
8269
8270 wxFont font;
8271 if (bulletAttr.HasFont())
8272 {
8273 font = paragraph->GetBuffer()->GetFontTable().FindFont(bulletAttr);
8274 }
8275 else
8276 font = (*wxNORMAL_FONT);
8277
8278 wxCheckSetFont(dc, font);
8279
8280 int charHeight = dc.GetCharHeight();
8281
8282 int bulletWidth = (int) (((float) charHeight) * wxRichTextBuffer::GetBulletProportion());
8283 int bulletHeight = bulletWidth;
8284
8285 int x = rect.x;
8286
8287 // Calculate the top position of the character (as opposed to the whole line height)
8288 int y = rect.y + (rect.height - charHeight);
8289
8290 // Calculate where the bullet should be positioned
8291 y = y + (charHeight+1)/2 - (bulletHeight+1)/2;
8292
8293 // The margin between a bullet and text.
8294 int margin = paragraph->ConvertTenthsMMToPixels(dc, wxRichTextBuffer::GetBulletRightMargin());
8295
8296 if (bulletAttr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_RIGHT)
8297 x = rect.x + rect.width - bulletWidth - margin;
8298 else if (bulletAttr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_CENTRE)
8299 x = x + (rect.width)/2 - bulletWidth/2;
8300
8301 if (bulletAttr.GetBulletName() == wxT("standard/square"))
8302 {
8303 dc.DrawRectangle(x, y, bulletWidth, bulletHeight);
8304 }
8305 else if (bulletAttr.GetBulletName() == wxT("standard/diamond"))
8306 {
8307 wxPoint pts[5];
8308 pts[0].x = x; pts[0].y = y + bulletHeight/2;
8309 pts[1].x = x + bulletWidth/2; pts[1].y = y;
8310 pts[2].x = x + bulletWidth; pts[2].y = y + bulletHeight/2;
8311 pts[3].x = x + bulletWidth/2; pts[3].y = y + bulletHeight;
8312
8313 dc.DrawPolygon(4, pts);
8314 }
8315 else if (bulletAttr.GetBulletName() == wxT("standard/triangle"))
8316 {
8317 wxPoint pts[3];
8318 pts[0].x = x; pts[0].y = y;
8319 pts[1].x = x + bulletWidth; pts[1].y = y + bulletHeight/2;
8320 pts[2].x = x; pts[2].y = y + bulletHeight;
8321
8322 dc.DrawPolygon(3, pts);
8323 }
8324 else if (bulletAttr.GetBulletName() == wxT("standard/circle-outline"))
8325 {
8326 wxCheckSetBrush(dc, *wxTRANSPARENT_BRUSH);
8327 dc.DrawEllipse(x, y, bulletWidth, bulletHeight);
8328 }
8329 else // "standard/circle", and catch-all
8330 {
8331 dc.DrawEllipse(x, y, bulletWidth, bulletHeight);
8332 }
8333
8334 return true;
8335 }
8336
8337 bool wxRichTextStdRenderer::DrawTextBullet(wxRichTextParagraph* paragraph, wxDC& dc, const wxRichTextAttr& attr, const wxRect& rect, const wxString& text)
8338 {
8339 if (!text.empty())
8340 {
8341 wxFont font;
8342 if ((attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_SYMBOL) && !attr.GetBulletFont().IsEmpty() && attr.HasFont())
8343 {
8344 wxRichTextAttr fontAttr;
8345 if (attr.HasFontPixelSize())
8346 fontAttr.SetFontPixelSize(attr.GetFontSize());
8347 else
8348 fontAttr.SetFontPointSize(attr.GetFontSize());
8349 fontAttr.SetFontStyle(attr.GetFontStyle());
8350 fontAttr.SetFontWeight(attr.GetFontWeight());
8351 fontAttr.SetFontUnderlined(attr.GetFontUnderlined());
8352 fontAttr.SetFontFaceName(attr.GetBulletFont());
8353 font = paragraph->GetBuffer()->GetFontTable().FindFont(fontAttr);
8354 }
8355 else if (attr.HasFont())
8356 font = paragraph->GetBuffer()->GetFontTable().FindFont(attr);
8357 else
8358 font = (*wxNORMAL_FONT);
8359
8360 wxCheckSetFont(dc, font);
8361
8362 if (attr.GetTextColour().IsOk())
8363 dc.SetTextForeground(attr.GetTextColour());
8364
8365 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
8366
8367 int charHeight = dc.GetCharHeight();
8368 wxCoord tw, th;
8369 dc.GetTextExtent(text, & tw, & th);
8370
8371 int x = rect.x;
8372
8373 // Calculate the top position of the character (as opposed to the whole line height)
8374 int y = rect.y + (rect.height - charHeight);
8375
8376 // The margin between a bullet and text.
8377 int margin = paragraph->ConvertTenthsMMToPixels(dc, wxRichTextBuffer::GetBulletRightMargin());
8378
8379 if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_RIGHT)
8380 x = (rect.x + rect.width) - tw - margin;
8381 else if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_CENTRE)
8382 x = x + (rect.width)/2 - tw/2;
8383
8384 dc.DrawText(text, x, y);
8385
8386 return true;
8387 }
8388 else
8389 return false;
8390 }
8391
8392 bool wxRichTextStdRenderer::DrawBitmapBullet(wxRichTextParagraph* WXUNUSED(paragraph), wxDC& WXUNUSED(dc), const wxRichTextAttr& WXUNUSED(attr), const wxRect& WXUNUSED(rect))
8393 {
8394 // Currently unimplemented. The intention is to store bitmaps by name in a media store associated
8395 // with the buffer. The store will allow retrieval from memory, disk or other means.
8396 return false;
8397 }
8398
8399 /// Enumerate the standard bullet names currently supported
8400 bool wxRichTextStdRenderer::EnumerateStandardBulletNames(wxArrayString& bulletNames)
8401 {
8402 bulletNames.Add(wxTRANSLATE("standard/circle"));
8403 bulletNames.Add(wxTRANSLATE("standard/circle-outline"));
8404 bulletNames.Add(wxTRANSLATE("standard/square"));
8405 bulletNames.Add(wxTRANSLATE("standard/diamond"));
8406 bulletNames.Add(wxTRANSLATE("standard/triangle"));
8407
8408 return true;
8409 }
8410
8411 /*!
8412 * wxRichTextBox
8413 */
8414
8415 IMPLEMENT_DYNAMIC_CLASS(wxRichTextBox, wxRichTextParagraphLayoutBox)
8416
8417 wxRichTextBox::wxRichTextBox(wxRichTextObject* parent):
8418 wxRichTextParagraphLayoutBox(parent)
8419 {
8420 }
8421
8422 /// Draw the item
8423 bool wxRichTextBox::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
8424 {
8425 if (!IsShown())
8426 return true;
8427
8428 // TODO: if the active object in the control, draw an indication.
8429 // We need to add the concept of active object, and not just focus object,
8430 // so we can apply commands (properties, delete, ...) to objects such as text boxes and images.
8431 // Ultimately we would like to be able to interactively resize an active object
8432 // using drag handles.
8433 return wxRichTextParagraphLayoutBox::Draw(dc, context, range, selection, rect, descent, style);
8434 }
8435
8436 /// Copy
8437 void wxRichTextBox::Copy(const wxRichTextBox& obj)
8438 {
8439 wxRichTextParagraphLayoutBox::Copy(obj);
8440 }
8441
8442 // Edit properties via a GUI
8443 bool wxRichTextBox::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer)
8444 {
8445 wxRichTextObjectPropertiesDialog boxDlg(this, wxGetTopLevelParent(parent), wxID_ANY, _("Box Properties"));
8446 boxDlg.SetAttributes(GetAttributes());
8447
8448 if (boxDlg.ShowModal() == wxID_OK)
8449 {
8450 // By passing wxRICHTEXT_SETSTYLE_RESET, indeterminate attributes set by the user will be set as
8451 // indeterminate in the object.
8452 boxDlg.ApplyStyle(buffer->GetRichTextCtrl(), wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_RESET);
8453 return true;
8454 }
8455 else
8456 return false;
8457 }
8458
8459 /*!
8460 * wxRichTextField
8461 */
8462
8463 IMPLEMENT_DYNAMIC_CLASS(wxRichTextField, wxRichTextParagraphLayoutBox)
8464
8465 wxRichTextField::wxRichTextField(const wxString& fieldType, wxRichTextObject* parent):
8466 wxRichTextParagraphLayoutBox(parent)
8467 {
8468 SetFieldType(fieldType);
8469 }
8470
8471 /// Draw the item
8472 bool wxRichTextField::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
8473 {
8474 if (!IsShown())
8475 return true;
8476
8477 wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType());
8478 if (fieldType && fieldType->Draw(this, dc, context, range, selection, rect, descent, style))
8479 return true;
8480
8481 // Fallback; but don't draw guidelines.
8482 style &= ~wxRICHTEXT_DRAW_GUIDELINES;
8483 return wxRichTextParagraphLayoutBox::Draw(dc, context, range, selection, rect, descent, style);
8484 }
8485
8486 bool wxRichTextField::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& parentRect, int style)
8487 {
8488 wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType());
8489 if (fieldType && fieldType->Layout(this, dc, context, rect, parentRect, style))
8490 return true;
8491
8492 // Fallback
8493 return wxRichTextParagraphLayoutBox::Layout(dc, context, rect, parentRect, style);
8494 }
8495
8496 bool wxRichTextField::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, wxPoint position, wxArrayInt* partialExtents) const
8497 {
8498 wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType());
8499 if (fieldType)
8500 return fieldType->GetRangeSize((wxRichTextField*) this, range, size, descent, dc, context, flags, position, partialExtents);
8501
8502 return wxRichTextParagraphLayoutBox::GetRangeSize(range, size, descent, dc, context, flags, position, partialExtents);
8503 }
8504
8505 /// Calculate range
8506 void wxRichTextField::CalculateRange(long start, long& end)
8507 {
8508 if (IsTopLevel())
8509 wxRichTextParagraphLayoutBox::CalculateRange(start, end);
8510 else
8511 wxRichTextObject::CalculateRange(start, end);
8512 }
8513
8514 /// Copy
8515 void wxRichTextField::Copy(const wxRichTextField& obj)
8516 {
8517 wxRichTextParagraphLayoutBox::Copy(obj);
8518
8519 UpdateField(GetBuffer());
8520 }
8521
8522 // Edit properties via a GUI
8523 bool wxRichTextField::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer)
8524 {
8525 wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType());
8526 if (fieldType)
8527 return fieldType->EditProperties(this, parent, buffer);
8528
8529 return false;
8530 }
8531
8532 bool wxRichTextField::CanEditProperties() const
8533 {
8534 wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType());
8535 if (fieldType)
8536 return fieldType->CanEditProperties((wxRichTextField*) this);
8537
8538 return false;
8539 }
8540
8541 wxString wxRichTextField::GetPropertiesMenuLabel() const
8542 {
8543 wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType());
8544 if (fieldType)
8545 return fieldType->GetPropertiesMenuLabel((wxRichTextField*) this);
8546
8547 return wxEmptyString;
8548 }
8549
8550 bool wxRichTextField::UpdateField(wxRichTextBuffer* buffer)
8551 {
8552 wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType());
8553 if (fieldType)
8554 return fieldType->UpdateField(buffer, (wxRichTextField*) this);
8555
8556 return false;
8557 }
8558
8559 bool wxRichTextField::IsTopLevel() const
8560 {
8561 wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType());
8562 if (fieldType)
8563 return fieldType->IsTopLevel((wxRichTextField*) this);
8564
8565 return true;
8566 }
8567
8568 IMPLEMENT_CLASS(wxRichTextFieldType, wxObject)
8569
8570 IMPLEMENT_CLASS(wxRichTextFieldTypeStandard, wxRichTextFieldType)
8571
8572 wxRichTextFieldTypeStandard::wxRichTextFieldTypeStandard(const wxString& name, const wxString& label, int displayStyle)
8573 {
8574 Init();
8575
8576 SetName(name);
8577 SetLabel(label);
8578 SetDisplayStyle(displayStyle);
8579 }
8580
8581 wxRichTextFieldTypeStandard::wxRichTextFieldTypeStandard(const wxString& name, const wxBitmap& bitmap, int displayStyle)
8582 {
8583 Init();
8584
8585 SetName(name);
8586 SetBitmap(bitmap);
8587 SetDisplayStyle(displayStyle);
8588 }
8589
8590 void wxRichTextFieldTypeStandard::Init()
8591 {
8592 m_displayStyle = wxRICHTEXT_FIELD_STYLE_RECTANGLE;
8593 m_font = wxFont(6, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
8594 m_textColour = *wxWHITE;
8595 m_borderColour = *wxBLACK;
8596 m_backgroundColour = *wxBLACK;
8597 m_verticalPadding = 1;
8598 m_horizontalPadding = 3;
8599 m_horizontalMargin = 2;
8600 m_verticalMargin = 0;
8601 }
8602
8603 void wxRichTextFieldTypeStandard::Copy(const wxRichTextFieldTypeStandard& field)
8604 {
8605 wxRichTextFieldType::Copy(field);
8606
8607 m_label = field.m_label;
8608 m_displayStyle = field.m_displayStyle;
8609 m_font = field.m_font;
8610 m_textColour = field.m_textColour;
8611 m_borderColour = field.m_borderColour;
8612 m_backgroundColour = field.m_backgroundColour;
8613 m_verticalPadding = field.m_verticalPadding;
8614 m_horizontalPadding = field.m_horizontalPadding;
8615 m_horizontalMargin = field.m_horizontalMargin;
8616 m_bitmap = field.m_bitmap;
8617 }
8618
8619 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))
8620 {
8621 if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_COMPOSITE)
8622 return false; // USe default composite drawing
8623 else // if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_RECTANGLE || m_displayStyle == wxRICHTEXT_FIELD_STYLE_NOBORDER)
8624 {
8625 int borderSize = 1;
8626
8627 wxPen borderPen(m_borderColour, 1, wxSOLID);
8628 wxBrush backgroundBrush(m_backgroundColour);
8629 wxColour textColour(m_textColour);
8630
8631 if (selection.WithinSelection(obj->GetRange().GetStart(), obj))
8632 {
8633 wxColour highlightColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT));
8634 wxColour highlightTextColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT));
8635
8636 borderPen = wxPen(highlightTextColour, 1, wxSOLID);
8637 backgroundBrush = wxBrush(highlightColour);
8638
8639 wxCheckSetBrush(dc, backgroundBrush);
8640 wxCheckSetPen(dc, wxPen(highlightColour, 1, wxSOLID));
8641 dc.DrawRectangle(rect);
8642 }
8643
8644 if (m_displayStyle != wxRICHTEXT_FIELD_STYLE_NO_BORDER)
8645 borderSize = 0;
8646
8647 // objectRect is the area where the content is drawn, after margins around it have been taken into account
8648 wxRect objectRect = wxRect(wxPoint(rect.x + m_horizontalMargin, rect.y + wxMax(0, rect.height - descent - obj->GetCachedSize().y)),
8649 wxSize(obj->GetCachedSize().x - 2*m_horizontalMargin - borderSize, obj->GetCachedSize().y));
8650
8651 // clientArea is where the text is actually written
8652 wxRect clientArea = objectRect;
8653
8654 if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_RECTANGLE)
8655 {
8656 dc.SetPen(borderPen);
8657 dc.SetBrush(backgroundBrush);
8658 dc.DrawRoundedRectangle(objectRect, 4.0);
8659 }
8660 else if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_START_TAG)
8661 {
8662 int arrowLength = objectRect.height/2;
8663 clientArea.width -= (arrowLength - m_horizontalPadding);
8664
8665 wxPoint pts[5];
8666 pts[0].x = objectRect.x; pts[0].y = objectRect.y;
8667 pts[1].x = objectRect.x + objectRect.width - arrowLength; pts[1].y = objectRect.y;
8668 pts[2].x = objectRect.x + objectRect.width; pts[2].y = objectRect.y + (objectRect.height/2);
8669 pts[3].x = objectRect.x + objectRect.width - arrowLength; pts[3].y = objectRect.y + objectRect.height;
8670 pts[4].x = objectRect.x; pts[4].y = objectRect.y + objectRect.height;
8671 dc.SetPen(borderPen);
8672 dc.SetBrush(backgroundBrush);
8673 dc.DrawPolygon(5, pts);
8674 }
8675 else if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_END_TAG)
8676 {
8677 int arrowLength = objectRect.height/2;
8678 clientArea.width -= (arrowLength - m_horizontalPadding);
8679 clientArea.x += (arrowLength - m_horizontalPadding);
8680
8681 wxPoint pts[5];
8682 pts[0].x = objectRect.x + objectRect.width; pts[0].y = objectRect.y;
8683 pts[1].x = objectRect.x + arrowLength; pts[1].y = objectRect.y;
8684 pts[2].x = objectRect.x; pts[2].y = objectRect.y + (objectRect.height/2);
8685 pts[3].x = objectRect.x + arrowLength; pts[3].y = objectRect.y + objectRect.height;
8686 pts[4].x = objectRect.x + objectRect.width; pts[4].y = objectRect.y + objectRect.height;
8687 dc.SetPen(borderPen);
8688 dc.SetBrush(backgroundBrush);
8689 dc.DrawPolygon(5, pts);
8690 }
8691
8692 if (m_bitmap.IsOk())
8693 {
8694 int x = clientArea.x + (clientArea.width - m_bitmap.GetWidth())/2;
8695 int y = clientArea.y + m_verticalPadding;
8696 dc.DrawBitmap(m_bitmap, x, y, true);
8697
8698 if (selection.WithinSelection(obj->GetRange().GetStart(), obj))
8699 {
8700 wxCheckSetBrush(dc, *wxBLACK_BRUSH);
8701 wxCheckSetPen(dc, *wxBLACK_PEN);
8702 dc.SetLogicalFunction(wxINVERT);
8703 dc.DrawRectangle(wxRect(x, y, m_bitmap.GetWidth(), m_bitmap.GetHeight()));
8704 dc.SetLogicalFunction(wxCOPY);
8705 }
8706 }
8707 else
8708 {
8709 wxString label(m_label);
8710 if (label.IsEmpty())
8711 label = wxT("??");
8712 int w, h, maxDescent;
8713 dc.SetFont(m_font);
8714 dc.GetTextExtent(m_label, & w, &h, & maxDescent);
8715 dc.SetTextForeground(textColour);
8716
8717 int x = clientArea.x + (clientArea.width - w)/2;
8718 int y = clientArea.y + (clientArea.height - (h - maxDescent))/2;
8719 dc.DrawText(m_label, x, y);
8720 }
8721 }
8722
8723 return true;
8724 }
8725
8726 bool wxRichTextFieldTypeStandard::Layout(wxRichTextField* obj, wxDC& dc, wxRichTextDrawingContext& context, const wxRect& WXUNUSED(rect), const wxRect& WXUNUSED(parentRect), int style)
8727 {
8728 if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_COMPOSITE)
8729 return false; // USe default composite layout
8730
8731 wxSize size = GetSize(obj, dc, context, style);
8732 obj->SetCachedSize(size);
8733 obj->SetMinSize(size);
8734 obj->SetMaxSize(size);
8735 return true;
8736 }
8737
8738 bool wxRichTextFieldTypeStandard::GetRangeSize(wxRichTextField* obj, const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, wxPoint position, wxArrayInt* partialExtents) const
8739 {
8740 if (IsTopLevel(obj))
8741 return obj->wxRichTextParagraphLayoutBox::GetRangeSize(range, size, descent, dc, context, flags, position);
8742 else
8743 {
8744 wxSize sz = GetSize(obj, dc, context, 0);
8745 if (partialExtents)
8746 {
8747 int lastSize;
8748 if (partialExtents->GetCount() > 0)
8749 lastSize = (*partialExtents)[partialExtents->GetCount()-1];
8750 else
8751 lastSize = 0;
8752 partialExtents->Add(lastSize + sz.x);
8753 }
8754 size = sz;
8755 return true;
8756 }
8757 }
8758
8759 wxSize wxRichTextFieldTypeStandard::GetSize(wxRichTextField* WXUNUSED(obj), wxDC& dc, wxRichTextDrawingContext& WXUNUSED(context), int WXUNUSED(style)) const
8760 {
8761 int borderSize = 1;
8762 int w = 0, h = 0, maxDescent = 0;
8763
8764 wxSize sz;
8765 if (m_bitmap.IsOk())
8766 {
8767 w = m_bitmap.GetWidth();
8768 h = m_bitmap.GetHeight();
8769
8770 sz = wxSize(w + m_horizontalMargin*2, h + m_verticalMargin*2);
8771 }
8772 else
8773 {
8774 wxString label(m_label);
8775 if (label.IsEmpty())
8776 label = wxT("??");
8777 dc.SetFont(m_font);
8778 dc.GetTextExtent(label, & w, &h, & maxDescent);
8779
8780 sz = wxSize(w + m_horizontalPadding*2 + m_horizontalMargin*2, h + m_verticalPadding *2 + m_verticalMargin*2);
8781 }
8782
8783 if (m_displayStyle != wxRICHTEXT_FIELD_STYLE_NO_BORDER)
8784 {
8785 sz.x += borderSize*2;
8786 sz.y += borderSize*2;
8787 }
8788
8789 if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_START_TAG || m_displayStyle == wxRICHTEXT_FIELD_STYLE_END_TAG)
8790 {
8791 // Add space for the arrow
8792 sz.x += (sz.y/2 - m_horizontalPadding);
8793 }
8794
8795 return sz;
8796 }
8797
8798 IMPLEMENT_DYNAMIC_CLASS(wxRichTextCell, wxRichTextBox)
8799
8800 wxRichTextCell::wxRichTextCell(wxRichTextObject* parent):
8801 wxRichTextBox(parent)
8802 {
8803 }
8804
8805 /// Draw the item
8806 bool wxRichTextCell::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
8807 {
8808 return wxRichTextBox::Draw(dc, context, range, selection, rect, descent, style);
8809 }
8810
8811 /// Copy
8812 void wxRichTextCell::Copy(const wxRichTextCell& obj)
8813 {
8814 wxRichTextBox::Copy(obj);
8815 }
8816
8817 // Edit properties via a GUI
8818 bool wxRichTextCell::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer)
8819 {
8820 // We need to gather common attributes for all selected cells.
8821
8822 wxRichTextTable* table = wxDynamicCast(GetParent(), wxRichTextTable);
8823 bool multipleCells = false;
8824 wxRichTextAttr attr;
8825
8826 if (table && buffer && buffer->GetRichTextCtrl() && buffer->GetRichTextCtrl()->GetSelection().IsValid() &&
8827 buffer->GetRichTextCtrl()->GetSelection().GetContainer() == GetParent())
8828 {
8829 wxRichTextAttr clashingAttr, absentAttr;
8830 const wxRichTextSelection& sel = buffer->GetRichTextCtrl()->GetSelection();
8831 size_t i;
8832 int selectedCellCount = 0;
8833 for (i = 0; i < sel.GetCount(); i++)
8834 {
8835 const wxRichTextRange& range = sel[i];
8836 wxRichTextCell* cell = table->GetCell(range.GetStart());
8837 if (cell)
8838 {
8839 wxRichTextAttr cellStyle = cell->GetAttributes();
8840
8841 CollectStyle(attr, cellStyle, clashingAttr, absentAttr);
8842
8843 selectedCellCount ++;
8844 }
8845 }
8846 multipleCells = selectedCellCount > 1;
8847 }
8848 else
8849 {
8850 attr = GetAttributes();
8851 }
8852
8853 wxString caption;
8854 if (multipleCells)
8855 caption = _("Multiple Cell Properties");
8856 else
8857 caption = _("Cell Properties");
8858
8859 wxRichTextObjectPropertiesDialog cellDlg(this, wxGetTopLevelParent(parent), wxID_ANY, caption);
8860 cellDlg.SetAttributes(attr);
8861
8862 wxRichTextSizePage* sizePage = wxDynamicCast(cellDlg.FindPage(wxCLASSINFO(wxRichTextSizePage)), wxRichTextSizePage);
8863 if (sizePage)
8864 {
8865 // We don't want position and floating controls for a cell.
8866 sizePage->ShowPositionControls(false);
8867 sizePage->ShowFloatingControls(false);
8868 }
8869
8870 if (cellDlg.ShowModal() == wxID_OK)
8871 {
8872 if (multipleCells)
8873 {
8874 const wxRichTextSelection& sel = buffer->GetRichTextCtrl()->GetSelection();
8875 // Apply the style; we interpret indeterminate attributes as 'don't touch this attribute'
8876 // since it may represent clashing attributes across multiple objects.
8877 table->SetCellStyle(sel, attr);
8878 }
8879 else
8880 // For a single object, indeterminate attributes set by the user should be reflected in the
8881 // actual object style, so pass the wxRICHTEXT_SETSTYLE_RESET flag to assign
8882 // the style directly instead of applying (which ignores indeterminate attributes,
8883 // leaving them as they were).
8884 cellDlg.ApplyStyle(buffer->GetRichTextCtrl(), wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_RESET);
8885 return true;
8886 }
8887 else
8888 return false;
8889 }
8890
8891 WX_DEFINE_OBJARRAY(wxRichTextObjectPtrArrayArray)
8892
8893 IMPLEMENT_DYNAMIC_CLASS(wxRichTextTable, wxRichTextBox)
8894
8895 wxRichTextTable::wxRichTextTable(wxRichTextObject* parent): wxRichTextBox(parent)
8896 {
8897 m_rowCount = 0;
8898 m_colCount = 0;
8899 }
8900
8901 // Draws the object.
8902 bool wxRichTextTable::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
8903 {
8904 return wxRichTextBox::Draw(dc, context, range, selection, rect, descent, style);
8905 }
8906
8907 WX_DECLARE_OBJARRAY(wxRect, wxRichTextRectArray);
8908 WX_DEFINE_OBJARRAY(wxRichTextRectArray);
8909
8910 // Lays the object out. rect is the space available for layout. Often it will
8911 // be the specified overall space for this object, if trying to constrain
8912 // layout to a particular size, or it could be the total space available in the
8913 // parent. rect is the overall size, so we must subtract margins and padding.
8914 // to get the actual available space.
8915 bool wxRichTextTable::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& WXUNUSED(parentRect), int style)
8916 {
8917 SetPosition(rect.GetPosition());
8918
8919 // TODO: the meaty bit. Calculate sizes of all cells and rows. Try to use
8920 // minimum size if within alloted size, then divide up remaining size
8921 // between rows/cols.
8922
8923 double scale = 1.0;
8924 wxRichTextBuffer* buffer = GetBuffer();
8925 if (buffer) scale = buffer->GetScale();
8926
8927 wxRect availableSpace = GetAvailableContentArea(dc, context, rect);
8928 wxTextAttrDimensionConverter converter(dc, scale, availableSpace.GetSize());
8929
8930 wxRichTextAttr attr(GetAttributes());
8931 context.ApplyVirtualAttributes(attr, this);
8932
8933 // If we have no fixed table size, and assuming we're not pushed for
8934 // space, then we don't have to try to stretch the table to fit the contents.
8935 bool stretchToFitTableWidth = false;
8936
8937 int tableWidth = rect.width;
8938 if (attr.GetTextBoxAttr().GetWidth().IsValid())
8939 {
8940 tableWidth = converter.GetPixels(attr.GetTextBoxAttr().GetWidth());
8941
8942 // Fixed table width, so we do want to stretch columns out if necessary.
8943 stretchToFitTableWidth = true;
8944
8945 // Shouldn't be able to exceed the size passed to this function
8946 tableWidth = wxMin(rect.width, tableWidth);
8947 }
8948
8949 // Get internal padding
8950 int paddingLeft = 0, paddingTop = 0;
8951 if (attr.GetTextBoxAttr().GetPadding().GetLeft().IsValid())
8952 paddingLeft = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetLeft());
8953 if (attr.GetTextBoxAttr().GetPadding().GetTop().IsValid())
8954 paddingTop = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetTop());
8955
8956 // Assume that left and top padding are also used for inter-cell padding.
8957 int paddingX = paddingLeft;
8958 int paddingY = paddingTop;
8959
8960 int totalLeftMargin = 0, totalRightMargin = 0, totalTopMargin = 0, totalBottomMargin = 0;
8961 GetTotalMargin(dc, buffer, attr, totalLeftMargin, totalRightMargin, totalTopMargin, totalBottomMargin);
8962
8963 // Internal table width - the area for content
8964 int internalTableWidth = tableWidth - totalLeftMargin - totalRightMargin;
8965
8966 int rowCount = m_cells.GetCount();
8967 if (m_colCount == 0 || rowCount == 0)
8968 {
8969 wxRect overallRect(rect.x, rect.y, totalLeftMargin + totalRightMargin, totalTopMargin + totalBottomMargin);
8970 SetCachedSize(overallRect.GetSize());
8971
8972 // Zero content size
8973 SetMinSize(overallRect.GetSize());
8974 SetMaxSize(GetMinSize());
8975 return true;
8976 }
8977
8978 // The final calculated widths
8979 wxArrayInt colWidths;
8980 colWidths.Add(0, m_colCount);
8981
8982 wxArrayInt absoluteColWidths;
8983 absoluteColWidths.Add(0, m_colCount);
8984
8985 wxArrayInt percentageColWidths;
8986 percentageColWidths.Add(0, m_colCount);
8987 // wxArrayInt percentageColWidthsSpanning(m_colCount);
8988 // These are only relevant when the first column contains spanning information.
8989 // wxArrayInt columnSpans(m_colCount); // Each contains 1 for non-spanning cell, > 1 for spanning cell.
8990 wxArrayInt maxColWidths;
8991 maxColWidths.Add(0, m_colCount);
8992 wxArrayInt minColWidths;
8993 minColWidths.Add(0, m_colCount);
8994
8995 wxSize tableSize(tableWidth, 0);
8996
8997 int i, j, k;
8998
8999 for (i = 0; i < m_colCount; i++)
9000 {
9001 absoluteColWidths[i] = 0;
9002 // absoluteColWidthsSpanning[i] = 0;
9003 percentageColWidths[i] = -1;
9004 // percentageColWidthsSpanning[i] = -1;
9005 colWidths[i] = 0;
9006 maxColWidths[i] = 0;
9007 minColWidths[i] = 0;
9008 // columnSpans[i] = 1;
9009 }
9010
9011 // (0) Determine which cells are visible according to spans
9012 // 1 2 3 4 5
9013 // __________________
9014 // | | | | | 1
9015 // |------| |----|
9016 // |------| | | 2
9017 // |------| | | 3
9018 // |------------------|
9019 // |__________________| 4
9020
9021 // To calculate cell visibility:
9022 // First find all spanning cells. Build an array of span records with start x, y and end x, y.
9023 // Then for each cell, test whether we're within one of those cells, and unless we're at the start of
9024 // that cell, hide the cell.
9025
9026 // We can also use this array to match the size of spanning cells to the grid. Or just do
9027 // this when we iterate through all cells.
9028
9029 // 0.1: add spanning cells to an array
9030 wxRichTextRectArray rectArray;
9031 for (j = 0; j < m_rowCount; j++)
9032 {
9033 for (i = 0; i < m_colCount; i++)
9034 {
9035 wxRichTextBox* cell = GetCell(j, i);
9036 int colSpan = 1, rowSpan = 1;
9037 if (cell->GetProperties().HasProperty(wxT("colspan")))
9038 colSpan = cell->GetProperties().GetPropertyLong(wxT("colspan"));
9039 if (cell->GetProperties().HasProperty(wxT("rowspan")))
9040 rowSpan = cell->GetProperties().GetPropertyLong(wxT("rowspan"));
9041 if (colSpan > 1 || rowSpan > 1)
9042 {
9043 rectArray.Add(wxRect(i, j, colSpan, rowSpan));
9044 }
9045 }
9046 }
9047 // 0.2: find which cells are subsumed by a spanning cell
9048 for (j = 0; j < m_rowCount; j++)
9049 {
9050 for (i = 0; i < m_colCount; i++)
9051 {
9052 wxRichTextBox* cell = GetCell(j, i);
9053 if (rectArray.GetCount() == 0)
9054 {
9055 cell->Show(true);
9056 }
9057 else
9058 {
9059 int colSpan = 1, rowSpan = 1;
9060 if (cell->GetProperties().HasProperty(wxT("colspan")))
9061 colSpan = cell->GetProperties().GetPropertyLong(wxT("colspan"));
9062 if (cell->GetProperties().HasProperty(wxT("rowspan")))
9063 rowSpan = cell->GetProperties().GetPropertyLong(wxT("rowspan"));
9064 if (colSpan > 1 || rowSpan > 1)
9065 {
9066 // Assume all spanning cells are shown
9067 cell->Show(true);
9068 }
9069 else
9070 {
9071 bool shown = true;
9072 for (k = 0; k < (int) rectArray.GetCount(); k++)
9073 {
9074 if (rectArray[k].Contains(wxPoint(i, j)))
9075 {
9076 shown = false;
9077 break;
9078 }
9079 }
9080 cell->Show(shown);
9081 }
9082 }
9083 }
9084 }
9085
9086 // TODO: find the first spanned cell in each row that spans the most columns and doesn't
9087 // overlap with a spanned cell starting at a previous column position.
9088 // This means we need to keep an array of rects so we can check. However
9089 // it does also mean that some spans simply may not be taken into account
9090 // where there are different spans happening on different rows. In these cases,
9091 // they will simply be as wide as their constituent columns.
9092
9093 // (1) Do an initial layout for all cells to get minimum and maximum size, and get
9094 // the absolute or percentage width of each column.
9095
9096 for (j = 0; j < m_rowCount; j++)
9097 {
9098 // First get the overall margins so we can calculate percentage widths based on
9099 // the available content space for all cells on the row
9100
9101 int overallRowContentMargin = 0;
9102 int visibleCellCount = 0;
9103
9104 for (i = 0; i < m_colCount; i++)
9105 {
9106 wxRichTextBox* cell = GetCell(j, i);
9107 if (cell->IsShown())
9108 {
9109 int cellTotalLeftMargin = 0, cellTotalRightMargin = 0, cellTotalTopMargin = 0, cellTotalBottomMargin = 0;
9110 GetTotalMargin(dc, buffer, cell->GetAttributes(), cellTotalLeftMargin, cellTotalRightMargin, cellTotalTopMargin, cellTotalBottomMargin);
9111
9112 overallRowContentMargin += (cellTotalLeftMargin + cellTotalRightMargin);
9113 visibleCellCount ++;
9114 }
9115 }
9116
9117 // Add in inter-cell padding
9118 overallRowContentMargin += ((visibleCellCount-1) * paddingX);
9119
9120 int rowContentWidth = internalTableWidth - overallRowContentMargin;
9121 wxSize rowTableSize(rowContentWidth, 0);
9122 wxTextAttrDimensionConverter converter(dc, scale, rowTableSize);
9123
9124 for (i = 0; i < m_colCount; i++)
9125 {
9126 wxRichTextBox* cell = GetCell(j, i);
9127 if (cell->IsShown())
9128 {
9129 int colSpan = 1;
9130 if (cell->GetProperties().HasProperty(wxT("colspan")))
9131 colSpan = cell->GetProperties().GetPropertyLong(wxT("colspan"));
9132
9133 // Lay out cell to find min/max widths
9134 cell->Invalidate(wxRICHTEXT_ALL);
9135 cell->Layout(dc, context, availableSpace, availableSpace, style);
9136
9137 if (colSpan == 1)
9138 {
9139 int absoluteCellWidth = -1;
9140 int percentageCellWidth = -1;
9141
9142 // I think we need to calculate percentages from the internal table size,
9143 // minus the padding between cells which we'll need to calculate from the
9144 // (number of VISIBLE cells - 1)*paddingX. Then percentages that add up to 100%
9145 // will add up to 100%. In CSS, the width specifies the cell's content rect width,
9146 // so if we want to conform to that we'll need to add in the overall cell margins.
9147 // However, this will make it difficult to specify percentages that add up to
9148 // 100% and still fit within the table width.
9149 // Let's say two cells have 50% width. They have 10 pixels of overall margin each.
9150 // The table content rect is 500 pixels and the inter-cell padding is 20 pixels.
9151 // If we're using internal content size for the width, we would calculate the
9152 // the overall cell width for n cells as:
9153 // (500 - 20*(n-1) - overallCellMargin1 - overallCellMargin2 - ...) * percentage / 100
9154 // + thisOverallCellMargin
9155 // = 500 - 20 - 10 - 10) * 0.5 + 10 = 240 pixels overall cell width.
9156 // Adding this back, we get 240 + 240 + 20 = 500 pixels.
9157
9158 if (cell->GetAttributes().GetTextBoxAttr().GetWidth().IsValid())
9159 {
9160 int w = converter.GetPixels(cell->GetAttributes().GetTextBoxAttr().GetWidth());
9161 if (cell->GetAttributes().GetTextBoxAttr().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE)
9162 {
9163 percentageCellWidth = w;
9164 }
9165 else
9166 {
9167 absoluteCellWidth = w;
9168 }
9169 // Override absolute width with minimum width if necessary
9170 if (cell->GetMinSize().x > 0 && absoluteCellWidth !=1 && cell->GetMinSize().x > absoluteCellWidth)
9171 absoluteCellWidth = cell->GetMinSize().x;
9172 }
9173
9174 if (absoluteCellWidth != -1)
9175 {
9176 if (absoluteCellWidth > absoluteColWidths[i])
9177 absoluteColWidths[i] = absoluteCellWidth;
9178 }
9179
9180 if (percentageCellWidth != -1)
9181 {
9182 if (percentageCellWidth > percentageColWidths[i])
9183 percentageColWidths[i] = percentageCellWidth;
9184 }
9185
9186 if (colSpan == 1 && cell->GetMinSize().x && cell->GetMinSize().x > minColWidths[i])
9187 minColWidths[i] = cell->GetMinSize().x;
9188 if (colSpan == 1 && cell->GetMaxSize().x && cell->GetMaxSize().x > maxColWidths[i])
9189 maxColWidths[i] = cell->GetMaxSize().x;
9190 }
9191 }
9192 }
9193 }
9194
9195 // (2) Allocate initial column widths from minimum widths, absolute values and proportions
9196 // TODO: simply merge this into (1).
9197 for (i = 0; i < m_colCount; i++)
9198 {
9199 if (absoluteColWidths[i] > 0)
9200 {
9201 colWidths[i] = absoluteColWidths[i];
9202 }
9203 else if (percentageColWidths[i] > 0)
9204 {
9205 colWidths[i] = percentageColWidths[i];
9206
9207 // This is rubbish - we calculated the absolute widths from percentages, so
9208 // we can't do it again here.
9209 //colWidths[i] = (int) (double(percentageColWidths[i]) * double(tableWidth) / 100.0 + 0.5);
9210 }
9211 }
9212
9213 // (3) Process absolute or proportional widths of spanning columns,
9214 // now that we know what our fixed column widths are going to be.
9215 // Spanned cells will try to adjust columns so the span will fit.
9216 // Even existing fixed column widths can be expanded if necessary.
9217 // Actually, currently fixed columns widths aren't adjusted; instead,
9218 // the algorithm favours earlier rows and adjusts unspecified column widths
9219 // the first time only. After that, we can't know whether the column has been
9220 // specified explicitly or not. (We could make a note if necessary.)
9221 for (j = 0; j < m_rowCount; j++)
9222 {
9223 // First get the overall margins so we can calculate percentage widths based on
9224 // the available content space for all cells on the row
9225
9226 int overallRowContentMargin = 0;
9227 int visibleCellCount = 0;
9228
9229 for (i = 0; i < m_colCount; i++)
9230 {
9231 wxRichTextBox* cell = GetCell(j, i);
9232 if (cell->IsShown())
9233 {
9234 int cellTotalLeftMargin = 0, cellTotalRightMargin = 0, cellTotalTopMargin = 0, cellTotalBottomMargin = 0;
9235 GetTotalMargin(dc, buffer, cell->GetAttributes(), cellTotalLeftMargin, cellTotalRightMargin, cellTotalTopMargin, cellTotalBottomMargin);
9236
9237 overallRowContentMargin += (cellTotalLeftMargin + cellTotalRightMargin);
9238 visibleCellCount ++;
9239 }
9240 }
9241
9242 // Add in inter-cell padding
9243 overallRowContentMargin += ((visibleCellCount-1) * paddingX);
9244
9245 int rowContentWidth = internalTableWidth - overallRowContentMargin;
9246 wxSize rowTableSize(rowContentWidth, 0);
9247 wxTextAttrDimensionConverter converter(dc, scale, rowTableSize);
9248
9249 for (i = 0; i < m_colCount; i++)
9250 {
9251 wxRichTextBox* cell = GetCell(j, i);
9252 if (cell->IsShown())
9253 {
9254 int colSpan = 1;
9255 if (cell->GetProperties().HasProperty(wxT("colspan")))
9256 colSpan = cell->GetProperties().GetPropertyLong(wxT("colspan"));
9257
9258 if (colSpan > 1)
9259 {
9260 int spans = wxMin(colSpan, m_colCount - i);
9261 int cellWidth = 0;
9262 if (spans > 0)
9263 {
9264 if (cell->GetAttributes().GetTextBoxAttr().GetWidth().IsValid())
9265 {
9266 cellWidth = converter.GetPixels(cell->GetAttributes().GetTextBoxAttr().GetWidth());
9267 // Override absolute width with minimum width if necessary
9268 if (cell->GetMinSize().x > 0 && cellWidth !=1 && cell->GetMinSize().x > cellWidth)
9269 cellWidth = cell->GetMinSize().x;
9270 }
9271 else
9272 {
9273 // Do we want to do this? It's the only chance we get to
9274 // use the cell's min/max sizes, so we need to work out
9275 // how we're going to balance the unspecified spanning cell
9276 // width with the possibility more-constrained constituent cell widths.
9277 // Say there's a tiny bitmap giving it a max width of 10 pixels. We
9278 // don't want to constraint all the spanned columns to fit into this cell.
9279 // OK, let's say that if any of the constituent columns don't fit,
9280 // then we simply stop constraining the columns; instead, we'll just fit the spanning
9281 // cells to the columns later.
9282 cellWidth = cell->GetMinSize().x;
9283 if (cell->GetMaxSize().x > cellWidth)
9284 cellWidth = cell->GetMaxSize().x;
9285 }
9286
9287 // Subtract the padding between cells
9288 int spanningWidth = cellWidth;
9289 spanningWidth -= paddingX * (spans-1);
9290
9291 if (spanningWidth > 0)
9292 {
9293 // Now share the spanning width between columns within that span
9294 // TODO: take into account min widths of columns within the span
9295 int spanningWidthLeft = spanningWidth;
9296 int stretchColCount = 0;
9297 for (k = i; k < (i+spans); k++)
9298 {
9299 if (colWidths[k] > 0) // absolute or proportional width has been specified
9300 spanningWidthLeft -= colWidths[k];
9301 else
9302 stretchColCount ++;
9303 }
9304 // Now divide what's left between the remaining columns
9305 int colShare = 0;
9306 if (stretchColCount > 0)
9307 colShare = spanningWidthLeft / stretchColCount;
9308 int colShareRemainder = spanningWidthLeft - (colShare * stretchColCount);
9309
9310 // If fixed-width columns are currently too big, then we'll later
9311 // stretch the spanned cell to fit.
9312
9313 if (spanningWidthLeft > 0)
9314 {
9315 for (k = i; k < (i+spans); k++)
9316 {
9317 if (colWidths[k] <= 0) // absolute or proportional width has not been specified
9318 {
9319 int newWidth = colShare;
9320 if (k == (i+spans-1))
9321 newWidth += colShareRemainder; // ensure all pixels are filled
9322 colWidths[k] = newWidth;
9323 }
9324 }
9325 }
9326 }
9327 }
9328 }
9329 }
9330 }
9331 }
9332
9333 // (4) Next, share any remaining space out between columns that have not yet been calculated.
9334 // TODO: take into account min widths of columns within the span
9335 int tableWidthMinusPadding = internalTableWidth - (m_colCount-1)*paddingX;
9336 int widthLeft = tableWidthMinusPadding;
9337 int stretchColCount = 0;
9338 for (i = 0; i < m_colCount; i++)
9339 {
9340 // TODO: we need to take into account min widths.
9341 // Subtract min width from width left, then
9342 // add the colShare to the min width
9343 if (colWidths[i] > 0) // absolute or proportional width has been specified
9344 widthLeft -= colWidths[i];
9345 else
9346 {
9347 if (minColWidths[i] > 0)
9348 widthLeft -= minColWidths[i];
9349
9350 stretchColCount ++;
9351 }
9352 }
9353
9354 // Now divide what's left between the remaining columns
9355 int colShare = 0;
9356 if (stretchColCount > 0)
9357 colShare = widthLeft / stretchColCount;
9358 int colShareRemainder = widthLeft - (colShare * stretchColCount);
9359
9360 // Check we don't have enough space, in which case shrink all columns, overriding
9361 // any absolute/proportional widths
9362 // TODO: actually we would like to divide up the shrinkage according to size.
9363 // How do we calculate the proportions that will achieve this?
9364 // Could first choose an arbitrary value for stretching cells, and then calculate
9365 // factors to multiply each width by.
9366 // TODO: want to record this fact and pass to an iteration that tries e.g. min widths
9367 if (widthLeft < 0 || (stretchToFitTableWidth && (stretchColCount == 0)))
9368 {
9369 colShare = tableWidthMinusPadding / m_colCount;
9370 colShareRemainder = tableWidthMinusPadding - (colShare * m_colCount);
9371 for (i = 0; i < m_colCount; i++)
9372 {
9373 colWidths[i] = 0;
9374 minColWidths[i] = 0;
9375 }
9376 }
9377
9378 // We have to adjust the columns if either we need to shrink the
9379 // table to fit the parent/table width, or we explicitly set the
9380 // table width and need to stretch out the table.
9381 if (widthLeft < 0 || stretchToFitTableWidth)
9382 {
9383 for (i = 0; i < m_colCount; i++)
9384 {
9385 if (colWidths[i] <= 0) // absolute or proportional width has not been specified
9386 {
9387 if (minColWidths[i] > 0)
9388 colWidths[i] = minColWidths[i] + colShare;
9389 else
9390 colWidths[i] = colShare;
9391 if (i == (m_colCount-1))
9392 colWidths[i] += colShareRemainder; // ensure all pixels are filled
9393 }
9394 }
9395 }
9396
9397 // TODO: if spanned cells have no specified or max width, make them the
9398 // as big as the columns they span. Do this for all spanned cells in all
9399 // rows, of course. Size any spanned cells left over at the end - even if they
9400 // have width > 0, make sure they're limited to the appropriate column edge.
9401
9402
9403 /*
9404 Sort out confusion between content width
9405 and overall width later. For now, assume we specify overall width.
9406
9407 So, now we've laid out the table to fit into the given space
9408 and have used specified widths and minimum widths.
9409
9410 Now we need to consider how we will try to take maximum width into account.
9411
9412 */
9413
9414 // (??) TODO: take max width into account
9415
9416 // (6) Lay out all cells again with the current values
9417
9418 int maxRight = 0;
9419 int y = availableSpace.y;
9420 for (j = 0; j < m_rowCount; j++)
9421 {
9422 int x = availableSpace.x; // TODO: take into account centering etc.
9423 int maxCellHeight = 0;
9424 int maxSpecifiedCellHeight = 0;
9425
9426 wxArrayInt actualWidths;
9427 actualWidths.Add(0, m_colCount);
9428
9429 wxTextAttrDimensionConverter converter(dc, scale);
9430 for (i = 0; i < m_colCount; i++)
9431 {
9432 wxRichTextCell* cell = GetCell(j, i);
9433 if (cell->IsShown())
9434 {
9435 // Get max specified cell height
9436 // Don't handle percentages for height
9437 if (cell->GetAttributes().GetTextBoxAttr().GetHeight().IsValid() && cell->GetAttributes().GetTextBoxAttr().GetHeight().GetUnits() != wxTEXT_ATTR_UNITS_PERCENTAGE)
9438 {
9439 int h = converter.GetPixels(cell->GetAttributes().GetTextBoxAttr().GetHeight());
9440 if (h > maxSpecifiedCellHeight)
9441 maxSpecifiedCellHeight = h;
9442 }
9443
9444 if (colWidths[i] > 0) // absolute or proportional width has been specified
9445 {
9446 int colSpan = 1;
9447 if (cell->GetProperties().HasProperty(wxT("colspan")))
9448 colSpan = cell->GetProperties().GetPropertyLong(wxT("colspan"));
9449
9450 wxRect availableCellSpace;
9451
9452 // TODO: take into acount spans
9453 if (colSpan > 1)
9454 {
9455 // Calculate the size of this spanning cell from its constituent columns
9456 int xx = x;
9457 int spans = wxMin(colSpan, m_colCount - i);
9458 for (k = i; k < spans; k++)
9459 {
9460 if (k != i)
9461 xx += paddingX;
9462 xx += colWidths[k];
9463 }
9464 availableCellSpace = wxRect(x, y, xx, -1);
9465 }
9466 else
9467 availableCellSpace = wxRect(x, y, colWidths[i], -1);
9468
9469 // Store actual width so we can force cell to be the appropriate width on the final loop
9470 actualWidths[i] = availableCellSpace.GetWidth();
9471
9472 // Lay out cell
9473 cell->Invalidate(wxRICHTEXT_ALL);
9474 cell->Layout(dc, context, availableCellSpace, availableSpace, style);
9475
9476 // TODO: use GetCachedSize().x to compute 'natural' size
9477
9478 x += (availableCellSpace.GetWidth() + paddingX);
9479 if (cell->GetCachedSize().y > maxCellHeight)
9480 maxCellHeight = cell->GetCachedSize().y;
9481 }
9482 }
9483 }
9484
9485 maxCellHeight = wxMax(maxCellHeight, maxSpecifiedCellHeight);
9486
9487 for (i = 0; i < m_colCount; i++)
9488 {
9489 wxRichTextCell* cell = GetCell(j, i);
9490 if (cell->IsShown())
9491 {
9492 wxRect availableCellSpace = wxRect(cell->GetPosition(), wxSize(actualWidths[i], maxCellHeight));
9493 // Lay out cell with new height
9494 cell->Invalidate(wxRICHTEXT_ALL);
9495 cell->Layout(dc, context, availableCellSpace, availableSpace, style);
9496
9497 // Make sure the cell size really is the appropriate size,
9498 // not the calculated box size
9499 cell->SetCachedSize(wxSize(actualWidths[i], maxCellHeight));
9500
9501 maxRight = wxMax(maxRight, cell->GetPosition().x + cell->GetCachedSize().x);
9502 }
9503 }
9504
9505 y += maxCellHeight;
9506 if (j < (m_rowCount-1))
9507 y += paddingY;
9508 }
9509
9510 // We need to add back the margins etc.
9511 {
9512 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
9513 contentRect = wxRect(wxPoint(0, 0), wxSize(maxRight - availableSpace.x, y - availableSpace.y));
9514 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
9515 SetCachedSize(marginRect.GetSize());
9516 }
9517
9518 // TODO: calculate max size
9519 {
9520 SetMaxSize(GetCachedSize());
9521 }
9522
9523 // TODO: calculate min size
9524 {
9525 SetMinSize(GetCachedSize());
9526 }
9527
9528 // TODO: currently we use either a fixed table width or the parent's size.
9529 // We also want to be able to calculate the table width from its content,
9530 // whether using fixed column widths or cell content min/max width.
9531 // Probably need a boolean flag to say whether we need to stretch cells
9532 // to fit the table width, or to simply use min/max cell widths. The
9533 // trouble with this is that if cell widths are not specified, they
9534 // will be tiny; we could use arbitrary defaults but this seems unsatisfactory.
9535 // Anyway, ignoring that problem, we probably need to factor layout into a function
9536 // that can can calculate the maximum unconstrained layout in case table size is
9537 // not specified. Then LayoutToBestSize() can choose to use either parent size to
9538 // constrain Layout(), or the previously-calculated max size to constraint layout.
9539
9540 return true;
9541 }
9542
9543 // Finds the absolute position and row height for the given character position
9544 bool wxRichTextTable::FindPosition(wxDC& dc, wxRichTextDrawingContext& context, long index, wxPoint& pt, int* height, bool forceLineStart)
9545 {
9546 wxRichTextCell* child = GetCell(index+1);
9547 if (child)
9548 {
9549 // Find the position at the start of the child cell, since the table doesn't
9550 // have any caret position of its own.
9551 return child->FindPosition(dc, context, -1, pt, height, forceLineStart);
9552 }
9553 else
9554 return false;
9555 }
9556
9557 // Get the cell at the given character position (in the range of the table).
9558 wxRichTextCell* wxRichTextTable::GetCell(long pos) const
9559 {
9560 int row = 0, col = 0;
9561 if (GetCellRowColumnPosition(pos, row, col))
9562 {
9563 return GetCell(row, col);
9564 }
9565 else
9566 return NULL;
9567 }
9568
9569 // Get the row/column for a given character position
9570 bool wxRichTextTable::GetCellRowColumnPosition(long pos, int& row, int& col) const
9571 {
9572 if (m_colCount == 0 || m_rowCount == 0)
9573 return false;
9574
9575 row = (int) (pos / m_colCount);
9576 col = pos - (row * m_colCount);
9577
9578 wxASSERT(row < m_rowCount && col < m_colCount);
9579
9580 if (row < m_rowCount && col < m_colCount)
9581 return true;
9582 else
9583 return false;
9584 }
9585
9586 // Calculate range, taking row/cell ordering into account instead of relying
9587 // on list ordering.
9588 void wxRichTextTable::CalculateRange(long start, long& end)
9589 {
9590 long current = start;
9591 long lastEnd = current;
9592
9593 if (IsTopLevel())
9594 {
9595 current = 0;
9596 lastEnd = 0;
9597 }
9598
9599 int i, j;
9600 for (i = 0; i < m_rowCount; i++)
9601 {
9602 for (j = 0; j < m_colCount; j++)
9603 {
9604 wxRichTextCell* child = GetCell(i, j);
9605 if (child)
9606 {
9607 long childEnd = 0;
9608
9609 child->CalculateRange(current, childEnd);
9610
9611 lastEnd = childEnd;
9612 current = childEnd + 1;
9613 }
9614 }
9615 }
9616
9617 // A top-level object always has a range of size 1,
9618 // because its children don't count at this level.
9619 end = start;
9620 m_range.SetRange(start, start);
9621
9622 // An object with no children has zero length
9623 if (m_children.GetCount() == 0)
9624 lastEnd --;
9625 m_ownRange.SetRange(0, lastEnd);
9626 }
9627
9628 // Gets the range size.
9629 bool wxRichTextTable::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, wxPoint position, wxArrayInt* partialExtents) const
9630 {
9631 return wxRichTextBox::GetRangeSize(range, size, descent, dc, context, flags, position, partialExtents);
9632 }
9633
9634 // Deletes content in the given range.
9635 bool wxRichTextTable::DeleteRange(const wxRichTextRange& WXUNUSED(range))
9636 {
9637 // TODO: implement deletion of cells
9638 return true;
9639 }
9640
9641 // Gets any text in this object for the given range.
9642 wxString wxRichTextTable::GetTextForRange(const wxRichTextRange& range) const
9643 {
9644 return wxRichTextBox::GetTextForRange(range);
9645 }
9646
9647 // Copies this object.
9648 void wxRichTextTable::Copy(const wxRichTextTable& obj)
9649 {
9650 wxRichTextBox::Copy(obj);
9651
9652 ClearTable();
9653
9654 m_rowCount = obj.m_rowCount;
9655 m_colCount = obj.m_colCount;
9656
9657 m_cells.Add(wxRichTextObjectPtrArray(), m_rowCount);
9658
9659 int i, j;
9660 for (i = 0; i < m_rowCount; i++)
9661 {
9662 wxRichTextObjectPtrArray& colArray = m_cells[i];
9663 for (j = 0; j < m_colCount; j++)
9664 {
9665 wxRichTextCell* cell = wxDynamicCast(obj.GetCell(i, j)->Clone(), wxRichTextCell);
9666 AppendChild(cell);
9667
9668 colArray.Add(cell);
9669 }
9670 }
9671 }
9672
9673 void wxRichTextTable::ClearTable()
9674 {
9675 m_cells.Clear();
9676 DeleteChildren();
9677 }
9678
9679 bool wxRichTextTable::CreateTable(int rows, int cols)
9680 {
9681 ClearTable();
9682
9683 m_rowCount = rows;
9684 m_colCount = cols;
9685
9686 m_cells.Add(wxRichTextObjectPtrArray(), rows);
9687
9688 int i, j;
9689 for (i = 0; i < rows; i++)
9690 {
9691 wxRichTextObjectPtrArray& colArray = m_cells[i];
9692 for (j = 0; j < cols; j++)
9693 {
9694 wxRichTextCell* cell = new wxRichTextCell;
9695 AppendChild(cell);
9696 cell->AddParagraph(wxEmptyString);
9697
9698 colArray.Add(cell);
9699 }
9700 }
9701
9702 return true;
9703 }
9704
9705 wxRichTextCell* wxRichTextTable::GetCell(int row, int col) const
9706 {
9707 wxASSERT(row < m_rowCount);
9708 wxASSERT(col < m_colCount);
9709
9710 if (row < m_rowCount && col < m_colCount)
9711 {
9712 wxRichTextObjectPtrArray& colArray = m_cells[row];
9713 wxRichTextObject* obj = colArray[col];
9714 return wxDynamicCast(obj, wxRichTextCell);
9715 }
9716 else
9717 return NULL;
9718 }
9719
9720 // Returns a selection object specifying the selections between start and end character positions.
9721 // For example, a table would deduce what cells (of range length 1) are selected when dragging across the table.
9722 wxRichTextSelection wxRichTextTable::GetSelection(long start, long end) const
9723 {
9724 wxRichTextSelection selection;
9725 selection.SetContainer((wxRichTextTable*) this);
9726
9727 if (start > end)
9728 {
9729 long tmp = end;
9730 end = start;
9731 start = tmp;
9732 }
9733
9734 wxASSERT( start >= 0 && end < (m_colCount * m_rowCount));
9735
9736 if (end >= (m_colCount * m_rowCount))
9737 return selection;
9738
9739 // We need to find the rectangle of cells that is described by the rectangle
9740 // with start, end as the diagonal. Make sure we don't add cells that are
9741 // not currenty visible because they are overlapped by spanning cells.
9742 /*
9743 --------------------------
9744 | 0 | 1 | 2 | 3 | 4 |
9745 --------------------------
9746 | 5 | 6 | 7 | 8 | 9 |
9747 --------------------------
9748 | 10 | 11 | 12 | 13 | 14 |
9749 --------------------------
9750 | 15 | 16 | 17 | 18 | 19 |
9751 --------------------------
9752
9753 Let's say we select 6 -> 18.
9754
9755 Left and right edge cols of rectangle are 1 and 3 inclusive. Find least/greatest to find
9756 which is left and which is right.
9757
9758 Top and bottom edge rows are 1 and 3 inclusive. Again, find least/greatest to find top and bottom.
9759
9760 Now go through rows from 1 to 3 and only add cells that are (a) within above column range
9761 and (b) shown.
9762
9763
9764 */
9765
9766 int leftCol = start - m_colCount * int(start/m_colCount);
9767 int rightCol = end - m_colCount * int(end/m_colCount);
9768
9769 int topRow = int(start/m_colCount);
9770 int bottomRow = int(end/m_colCount);
9771
9772 if (leftCol > rightCol)
9773 {
9774 int tmp = rightCol;
9775 rightCol = leftCol;
9776 leftCol = tmp;
9777 }
9778
9779 if (topRow > bottomRow)
9780 {
9781 int tmp = bottomRow;
9782 bottomRow = topRow;
9783 topRow = tmp;
9784 }
9785
9786 int i, j;
9787 for (i = topRow; i <= bottomRow; i++)
9788 {
9789 for (j = leftCol; j <= rightCol; j++)
9790 {
9791 wxRichTextCell* cell = GetCell(i, j);
9792 if (cell && cell->IsShown())
9793 selection.Add(cell->GetRange());
9794 }
9795 }
9796
9797 return selection;
9798 }
9799
9800 // Sets the attributes for the cells specified by the selection.
9801 bool wxRichTextTable::SetCellStyle(const wxRichTextSelection& selection, const wxRichTextAttr& style, int flags)
9802 {
9803 if (selection.GetContainer() != this)
9804 return false;
9805
9806 wxRichTextBuffer* buffer = GetBuffer();
9807 bool haveControl = (buffer && buffer->GetRichTextCtrl() != NULL);
9808 bool withUndo = haveControl && ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0);
9809
9810 if (withUndo)
9811 buffer->BeginBatchUndo(_("Set Cell Style"));
9812
9813 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
9814 while (node)
9815 {
9816 wxRichTextCell* cell = wxDynamicCast(node->GetData(), wxRichTextCell);
9817 if (cell && selection.WithinSelection(cell->GetRange().GetStart()))
9818 SetStyle(cell, style, flags);
9819 node = node->GetNext();
9820 }
9821
9822 // Do action, or delay it until end of batch.
9823 if (withUndo)
9824 buffer->EndBatchUndo();
9825
9826 return true;
9827 }
9828
9829 bool wxRichTextTable::DeleteRows(int startRow, int noRows)
9830 {
9831 wxASSERT((startRow + noRows) < m_rowCount);
9832 if ((startRow + noRows) >= m_rowCount)
9833 return false;
9834
9835 int i, j;
9836 for (i = startRow; i < (startRow+noRows); i++)
9837 {
9838 wxRichTextObjectPtrArray& colArray = m_cells[startRow];
9839 for (j = 0; j < (int) colArray.GetCount(); j++)
9840 {
9841 wxRichTextObject* cell = colArray[j];
9842 RemoveChild(cell, true);
9843 }
9844
9845 // Keep deleting at the same position, since we move all
9846 // the others up
9847 m_cells.RemoveAt(startRow);
9848 }
9849
9850 m_rowCount = m_rowCount - noRows;
9851
9852 return true;
9853 }
9854
9855 bool wxRichTextTable::DeleteColumns(int startCol, int noCols)
9856 {
9857 wxASSERT((startCol + noCols) < m_colCount);
9858 if ((startCol + noCols) >= m_colCount)
9859 return false;
9860
9861 bool deleteRows = (noCols == m_colCount);
9862
9863 int i, j;
9864 for (i = 0; i < m_rowCount; i++)
9865 {
9866 wxRichTextObjectPtrArray& colArray = m_cells[deleteRows ? 0 : i];
9867 for (j = startCol; j < (startCol+noCols); j++)
9868 {
9869 wxRichTextObject* cell = colArray[j];
9870 RemoveChild(cell, true);
9871 }
9872
9873 if (deleteRows)
9874 m_cells.RemoveAt(0);
9875 }
9876
9877 if (deleteRows)
9878 m_rowCount = 0;
9879 m_colCount = m_colCount - noCols;
9880
9881 return true;
9882 }
9883
9884 bool wxRichTextTable::AddRows(int startRow, int noRows, const wxRichTextAttr& attr)
9885 {
9886 wxASSERT(startRow <= m_rowCount);
9887 if (startRow > m_rowCount)
9888 return false;
9889
9890 int i, j;
9891 for (i = 0; i < noRows; i++)
9892 {
9893 int idx;
9894 if (startRow == m_rowCount)
9895 {
9896 m_cells.Add(wxRichTextObjectPtrArray());
9897 idx = m_cells.GetCount() - 1;
9898 }
9899 else
9900 {
9901 m_cells.Insert(wxRichTextObjectPtrArray(), startRow+i);
9902 idx = startRow+i;
9903 }
9904
9905 wxRichTextObjectPtrArray& colArray = m_cells[idx];
9906 for (j = 0; j < m_colCount; j++)
9907 {
9908 wxRichTextCell* cell = new wxRichTextCell;
9909 cell->GetAttributes() = attr;
9910
9911 AppendChild(cell);
9912 colArray.Add(cell);
9913 }
9914 }
9915
9916 m_rowCount = m_rowCount + noRows;
9917 return true;
9918 }
9919
9920 bool wxRichTextTable::AddColumns(int startCol, int noCols, const wxRichTextAttr& attr)
9921 {
9922 wxASSERT(startCol <= m_colCount);
9923 if (startCol > m_colCount)
9924 return false;
9925
9926 int i, j;
9927 for (i = 0; i < m_rowCount; i++)
9928 {
9929 wxRichTextObjectPtrArray& colArray = m_cells[i];
9930 for (j = 0; j < noCols; j++)
9931 {
9932 wxRichTextCell* cell = new wxRichTextCell;
9933 cell->GetAttributes() = attr;
9934
9935 AppendChild(cell);
9936
9937 if (startCol == m_colCount)
9938 colArray.Add(cell);
9939 else
9940 colArray.Insert(cell, startCol+j);
9941 }
9942 }
9943
9944 m_colCount = m_colCount + noCols;
9945
9946 return true;
9947 }
9948
9949 // Edit properties via a GUI
9950 bool wxRichTextTable::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer)
9951 {
9952 wxRichTextObjectPropertiesDialog boxDlg(this, wxGetTopLevelParent(parent), wxID_ANY, _("Table Properties"));
9953 boxDlg.SetAttributes(GetAttributes());
9954
9955 if (boxDlg.ShowModal() == wxID_OK)
9956 {
9957 boxDlg.ApplyStyle(buffer->GetRichTextCtrl(), wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_RESET);
9958 return true;
9959 }
9960 else
9961 return false;
9962 }
9963
9964 /*
9965 * Module to initialise and clean up handlers
9966 */
9967
9968 class wxRichTextModule: public wxModule
9969 {
9970 DECLARE_DYNAMIC_CLASS(wxRichTextModule)
9971 public:
9972 wxRichTextModule() {}
9973 bool OnInit()
9974 {
9975 wxRichTextBuffer::SetRenderer(new wxRichTextStdRenderer);
9976 wxRichTextBuffer::InitStandardHandlers();
9977 wxRichTextParagraph::InitDefaultTabs();
9978
9979 wxRichTextXMLHandler::RegisterNodeName(wxT("text"), wxT("wxRichTextPlainText"));
9980 wxRichTextXMLHandler::RegisterNodeName(wxT("symbol"), wxT("wxRichTextPlainText"));
9981 wxRichTextXMLHandler::RegisterNodeName(wxT("image"), wxT("wxRichTextImage"));
9982 wxRichTextXMLHandler::RegisterNodeName(wxT("paragraph"), wxT("wxRichTextParagraph"));
9983 wxRichTextXMLHandler::RegisterNodeName(wxT("paragraphlayout"), wxT("wxRichTextParagraphLayoutBox"));
9984 wxRichTextXMLHandler::RegisterNodeName(wxT("textbox"), wxT("wxRichTextBox"));
9985 wxRichTextXMLHandler::RegisterNodeName(wxT("cell"), wxT("wxRichTextCell"));
9986 wxRichTextXMLHandler::RegisterNodeName(wxT("table"), wxT("wxRichTextTable"));
9987 wxRichTextXMLHandler::RegisterNodeName(wxT("field"), wxT("wxRichTextField"));
9988
9989 return true;
9990 }
9991 void OnExit()
9992 {
9993 wxRichTextBuffer::CleanUpHandlers();
9994 wxRichTextBuffer::CleanUpDrawingHandlers();
9995 wxRichTextBuffer::CleanUpFieldTypes();
9996 wxRichTextXMLHandler::ClearNodeToClassMap();
9997 wxRichTextDecimalToRoman(-1);
9998 wxRichTextParagraph::ClearDefaultTabs();
9999 wxRichTextCtrl::ClearAvailableFontNames();
10000 wxRichTextBuffer::SetRenderer(NULL);
10001 }
10002 };
10003
10004 IMPLEMENT_DYNAMIC_CLASS(wxRichTextModule, wxModule)
10005
10006
10007 // If the richtext lib is dynamically loaded after the app has already started
10008 // (such as from wxPython) then the built-in module system will not init this
10009 // module. Provide this function to do it manually.
10010 void wxRichTextModuleInit()
10011 {
10012 wxModule* module = new wxRichTextModule;
10013 module->Init();
10014 wxModule::RegisterModule(module);
10015 }
10016
10017
10018 /*!
10019 * Commands for undo/redo
10020 *
10021 */
10022
10023 wxRichTextCommand::wxRichTextCommand(const wxString& name, wxRichTextCommandId id, wxRichTextBuffer* buffer,
10024 wxRichTextParagraphLayoutBox* container, wxRichTextCtrl* ctrl, bool ignoreFirstTime): wxCommand(true, name)
10025 {
10026 /* wxRichTextAction* action = */ new wxRichTextAction(this, name, id, buffer, container, ctrl, ignoreFirstTime);
10027 }
10028
10029 wxRichTextCommand::wxRichTextCommand(const wxString& name): wxCommand(true, name)
10030 {
10031 }
10032
10033 wxRichTextCommand::~wxRichTextCommand()
10034 {
10035 ClearActions();
10036 }
10037
10038 void wxRichTextCommand::AddAction(wxRichTextAction* action)
10039 {
10040 if (!m_actions.Member(action))
10041 m_actions.Append(action);
10042 }
10043
10044 bool wxRichTextCommand::Do()
10045 {
10046 for (wxList::compatibility_iterator node = m_actions.GetFirst(); node; node = node->GetNext())
10047 {
10048 wxRichTextAction* action = (wxRichTextAction*) node->GetData();
10049 action->Do();
10050 }
10051
10052 return true;
10053 }
10054
10055 bool wxRichTextCommand::Undo()
10056 {
10057 for (wxList::compatibility_iterator node = m_actions.GetLast(); node; node = node->GetPrevious())
10058 {
10059 wxRichTextAction* action = (wxRichTextAction*) node->GetData();
10060 action->Undo();
10061 }
10062
10063 return true;
10064 }
10065
10066 void wxRichTextCommand::ClearActions()
10067 {
10068 WX_CLEAR_LIST(wxList, m_actions);
10069 }
10070
10071 /*!
10072 * Individual action
10073 *
10074 */
10075
10076 wxRichTextAction::wxRichTextAction(wxRichTextCommand* cmd, const wxString& name, wxRichTextCommandId id,
10077 wxRichTextBuffer* buffer, wxRichTextParagraphLayoutBox* container,
10078 wxRichTextCtrl* ctrl, bool ignoreFirstTime)
10079 {
10080 m_buffer = buffer;
10081 m_object = NULL;
10082 m_containerAddress.Create(buffer, container);
10083 m_ignoreThis = ignoreFirstTime;
10084 m_cmdId = id;
10085 m_position = -1;
10086 m_ctrl = ctrl;
10087 m_name = name;
10088 m_newParagraphs.SetDefaultStyle(buffer->GetDefaultStyle());
10089 m_newParagraphs.SetBasicStyle(buffer->GetBasicStyle());
10090 if (cmd)
10091 cmd->AddAction(this);
10092 }
10093
10094 wxRichTextAction::~wxRichTextAction()
10095 {
10096 if (m_object)
10097 delete m_object;
10098 }
10099
10100 // Returns the container that this action refers to, using the container address and top-level buffer.
10101 wxRichTextParagraphLayoutBox* wxRichTextAction::GetContainer() const
10102 {
10103 wxRichTextParagraphLayoutBox* container = wxDynamicCast(GetContainerAddress().GetObject(m_buffer), wxRichTextParagraphLayoutBox);
10104 return container;
10105 }
10106
10107
10108 void wxRichTextAction::CalculateRefreshOptimizations(wxArrayInt& optimizationLineCharPositions, wxArrayInt& optimizationLineYPositions)
10109 {
10110 // Store a list of line start character and y positions so we can figure out which area
10111 // we need to refresh
10112
10113 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
10114 wxRichTextParagraphLayoutBox* container = GetContainer();
10115 wxASSERT(container != NULL);
10116 if (!container)
10117 return;
10118
10119 // NOTE: we're assuming that the buffer is laid out correctly at this point.
10120 // If we had several actions, which only invalidate and leave layout until the
10121 // paint handler is called, then this might not be true. So we may need to switch
10122 // optimisation on only when we're simply adding text and not simultaneously
10123 // deleting a selection, for example. Or, we make sure the buffer is laid out correctly
10124 // first, but of course this means we'll be doing it twice.
10125 if (!m_buffer->IsDirty() && m_ctrl) // can only do optimisation if the buffer is already laid out correctly
10126 {
10127 wxSize clientSize = m_ctrl->GetUnscaledSize(m_ctrl->GetClientSize());
10128 wxPoint firstVisiblePt = m_ctrl->GetUnscaledPoint(m_ctrl->GetFirstVisiblePoint());
10129 int lastY = firstVisiblePt.y + clientSize.y;
10130
10131 wxRichTextParagraph* para = container->GetParagraphAtPosition(GetRange().GetStart());
10132 wxRichTextObjectList::compatibility_iterator node = container->GetChildren().Find(para);
10133 while (node)
10134 {
10135 wxRichTextParagraph* child = (wxRichTextParagraph*) node->GetData();
10136 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
10137 while (node2)
10138 {
10139 wxRichTextLine* line = node2->GetData();
10140 wxPoint pt = line->GetAbsolutePosition();
10141 wxRichTextRange range = line->GetAbsoluteRange();
10142
10143 if (pt.y > lastY)
10144 {
10145 node2 = wxRichTextLineList::compatibility_iterator();
10146 node = wxRichTextObjectList::compatibility_iterator();
10147 }
10148 else if (range.GetStart() > GetPosition() && pt.y >= firstVisiblePt.y)
10149 {
10150 optimizationLineCharPositions.Add(range.GetStart());
10151 optimizationLineYPositions.Add(pt.y);
10152 }
10153
10154 if (node2)
10155 node2 = node2->GetNext();
10156 }
10157
10158 if (node)
10159 node = node->GetNext();
10160 }
10161 }
10162 #endif
10163 }
10164
10165 bool wxRichTextAction::Do()
10166 {
10167 m_buffer->Modify(true);
10168
10169 wxRichTextParagraphLayoutBox* container = GetContainer();
10170 wxASSERT(container != NULL);
10171 if (!container)
10172 return false;
10173
10174 switch (m_cmdId)
10175 {
10176 case wxRICHTEXT_INSERT:
10177 {
10178 // Store a list of line start character and y positions so we can figure out which area
10179 // we need to refresh
10180 wxArrayInt optimizationLineCharPositions;
10181 wxArrayInt optimizationLineYPositions;
10182
10183 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
10184 CalculateRefreshOptimizations(optimizationLineCharPositions, optimizationLineYPositions);
10185 #endif
10186
10187 container->InsertFragment(GetRange().GetStart(), m_newParagraphs);
10188 container->UpdateRanges();
10189
10190 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
10191 // Layout() would stop prematurely at the top level.
10192 container->InvalidateHierarchy(wxRichTextRange(wxMax(0, GetRange().GetStart()-1), GetRange().GetEnd()));
10193
10194 long newCaretPosition = GetPosition() + m_newParagraphs.GetOwnRange().GetLength();
10195
10196 // Character position to caret position
10197 newCaretPosition --;
10198
10199 // Don't take into account the last newline
10200 if (m_newParagraphs.GetPartialParagraph())
10201 newCaretPosition --;
10202 else
10203 if (m_newParagraphs.GetChildren().GetCount() > 1)
10204 {
10205 wxRichTextObject* p = (wxRichTextObject*) m_newParagraphs.GetChildren().GetLast()->GetData();
10206 if (p->GetRange().GetLength() == 1)
10207 newCaretPosition --;
10208 }
10209
10210 newCaretPosition = wxMin(newCaretPosition, (container->GetOwnRange().GetEnd()-1));
10211
10212 UpdateAppearance(newCaretPosition, true /* send update event */, & optimizationLineCharPositions, & optimizationLineYPositions, true /* do */);
10213
10214 wxRichTextEvent cmdEvent(
10215 wxEVT_COMMAND_RICHTEXT_CONTENT_INSERTED,
10216 m_ctrl ? m_ctrl->GetId() : -1);
10217 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
10218 cmdEvent.SetRange(GetRange());
10219 cmdEvent.SetPosition(GetRange().GetStart());
10220 cmdEvent.SetContainer(container);
10221
10222 m_buffer->SendEvent(cmdEvent);
10223
10224 break;
10225 }
10226 case wxRICHTEXT_DELETE:
10227 {
10228 wxArrayInt optimizationLineCharPositions;
10229 wxArrayInt optimizationLineYPositions;
10230
10231 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
10232 CalculateRefreshOptimizations(optimizationLineCharPositions, optimizationLineYPositions);
10233 #endif
10234
10235 container->DeleteRange(GetRange());
10236 container->UpdateRanges();
10237 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
10238 // Layout() would stop prematurely at the top level.
10239 container->InvalidateHierarchy(wxRichTextRange(GetRange().GetStart(), GetRange().GetStart()));
10240
10241 long caretPos = GetRange().GetStart()-1;
10242 if (caretPos >= container->GetOwnRange().GetEnd())
10243 caretPos --;
10244
10245 UpdateAppearance(caretPos, true /* send update event */, & optimizationLineCharPositions, & optimizationLineYPositions, true /* do */);
10246
10247 wxRichTextEvent cmdEvent(
10248 wxEVT_COMMAND_RICHTEXT_CONTENT_DELETED,
10249 m_ctrl ? m_ctrl->GetId() : -1);
10250 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
10251 cmdEvent.SetRange(GetRange());
10252 cmdEvent.SetPosition(GetRange().GetStart());
10253 cmdEvent.SetContainer(container);
10254
10255 m_buffer->SendEvent(cmdEvent);
10256
10257 break;
10258 }
10259 case wxRICHTEXT_CHANGE_STYLE:
10260 case wxRICHTEXT_CHANGE_PROPERTIES:
10261 {
10262 ApplyParagraphs(GetNewParagraphs());
10263
10264 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
10265 // Layout() would stop prematurely at the top level.
10266 container->InvalidateHierarchy(GetRange());
10267
10268 UpdateAppearance(GetPosition());
10269
10270 wxRichTextEvent cmdEvent(
10271 m_cmdId == wxRICHTEXT_CHANGE_STYLE ? wxEVT_COMMAND_RICHTEXT_STYLE_CHANGED : wxEVT_COMMAND_RICHTEXT_PROPERTIES_CHANGED,
10272 m_ctrl ? m_ctrl->GetId() : -1);
10273 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
10274 cmdEvent.SetRange(GetRange());
10275 cmdEvent.SetPosition(GetRange().GetStart());
10276 cmdEvent.SetContainer(container);
10277
10278 m_buffer->SendEvent(cmdEvent);
10279
10280 break;
10281 }
10282 case wxRICHTEXT_CHANGE_ATTRIBUTES:
10283 {
10284 wxRichTextObject* obj = m_objectAddress.GetObject(m_buffer); // container->GetChildAtPosition(GetRange().GetStart());
10285 if (obj)
10286 {
10287 wxRichTextAttr oldAttr = obj->GetAttributes();
10288 obj->GetAttributes() = m_attributes;
10289 m_attributes = oldAttr;
10290 }
10291
10292 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
10293 // Layout() would stop prematurely at the top level.
10294 container->InvalidateHierarchy(GetRange());
10295
10296 UpdateAppearance(GetPosition());
10297
10298 wxRichTextEvent cmdEvent(
10299 wxEVT_COMMAND_RICHTEXT_STYLE_CHANGED,
10300 m_ctrl ? m_ctrl->GetId() : -1);
10301 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
10302 cmdEvent.SetRange(GetRange());
10303 cmdEvent.SetPosition(GetRange().GetStart());
10304 cmdEvent.SetContainer(container);
10305
10306 m_buffer->SendEvent(cmdEvent);
10307
10308 break;
10309 }
10310 case wxRICHTEXT_CHANGE_OBJECT:
10311 {
10312 wxRichTextObject* obj = m_objectAddress.GetObject(m_buffer);
10313 // wxRichTextObject* obj = container->GetChildAtPosition(GetRange().GetStart());
10314 if (obj && m_object)
10315 {
10316 wxRichTextObjectList::compatibility_iterator node = container->GetChildren().Find(obj);
10317 if (node)
10318 {
10319 wxRichTextObject* obj = node->GetData();
10320 node->SetData(m_object);
10321 m_object = obj;
10322 }
10323 }
10324
10325 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
10326 // Layout() would stop prematurely at the top level.
10327 container->InvalidateHierarchy(GetRange());
10328
10329 UpdateAppearance(GetPosition());
10330
10331 // TODO: send new kind of modification event
10332
10333 break;
10334 }
10335 default:
10336 break;
10337 }
10338
10339 return true;
10340 }
10341
10342 bool wxRichTextAction::Undo()
10343 {
10344 m_buffer->Modify(true);
10345
10346 wxRichTextParagraphLayoutBox* container = GetContainer();
10347 wxASSERT(container != NULL);
10348 if (!container)
10349 return false;
10350
10351 switch (m_cmdId)
10352 {
10353 case wxRICHTEXT_INSERT:
10354 {
10355 wxArrayInt optimizationLineCharPositions;
10356 wxArrayInt optimizationLineYPositions;
10357
10358 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
10359 CalculateRefreshOptimizations(optimizationLineCharPositions, optimizationLineYPositions);
10360 #endif
10361
10362 container->DeleteRange(GetRange());
10363 container->UpdateRanges();
10364
10365 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
10366 // Layout() would stop prematurely at the top level.
10367 container->InvalidateHierarchy(wxRichTextRange(GetRange().GetStart(), GetRange().GetStart()));
10368
10369 long newCaretPosition = GetPosition() - 1;
10370
10371 UpdateAppearance(newCaretPosition, true, /* send update event */ & optimizationLineCharPositions, & optimizationLineYPositions, false /* undo */);
10372
10373 wxRichTextEvent cmdEvent(
10374 wxEVT_COMMAND_RICHTEXT_CONTENT_DELETED,
10375 m_ctrl ? m_ctrl->GetId() : -1);
10376 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
10377 cmdEvent.SetRange(GetRange());
10378 cmdEvent.SetPosition(GetRange().GetStart());
10379 cmdEvent.SetContainer(container);
10380
10381 m_buffer->SendEvent(cmdEvent);
10382
10383 break;
10384 }
10385 case wxRICHTEXT_DELETE:
10386 {
10387 wxArrayInt optimizationLineCharPositions;
10388 wxArrayInt optimizationLineYPositions;
10389
10390 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
10391 CalculateRefreshOptimizations(optimizationLineCharPositions, optimizationLineYPositions);
10392 #endif
10393
10394 container->InsertFragment(GetRange().GetStart(), m_oldParagraphs);
10395 container->UpdateRanges();
10396
10397 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
10398 // Layout() would stop prematurely at the top level.
10399 container->InvalidateHierarchy(GetRange());
10400
10401 UpdateAppearance(GetPosition(), true, /* send update event */ & optimizationLineCharPositions, & optimizationLineYPositions, false /* undo */);
10402
10403 wxRichTextEvent cmdEvent(
10404 wxEVT_COMMAND_RICHTEXT_CONTENT_INSERTED,
10405 m_ctrl ? m_ctrl->GetId() : -1);
10406 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
10407 cmdEvent.SetRange(GetRange());
10408 cmdEvent.SetPosition(GetRange().GetStart());
10409 cmdEvent.SetContainer(container);
10410
10411 m_buffer->SendEvent(cmdEvent);
10412
10413 break;
10414 }
10415 case wxRICHTEXT_CHANGE_STYLE:
10416 case wxRICHTEXT_CHANGE_PROPERTIES:
10417 {
10418 ApplyParagraphs(GetOldParagraphs());
10419 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
10420 // Layout() would stop prematurely at the top level.
10421 container->InvalidateHierarchy(GetRange());
10422
10423 UpdateAppearance(GetPosition());
10424
10425 wxRichTextEvent cmdEvent(
10426 m_cmdId == wxRICHTEXT_CHANGE_STYLE ? wxEVT_COMMAND_RICHTEXT_STYLE_CHANGED : wxEVT_COMMAND_RICHTEXT_PROPERTIES_CHANGED,
10427 m_ctrl ? m_ctrl->GetId() : -1);
10428 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
10429 cmdEvent.SetRange(GetRange());
10430 cmdEvent.SetPosition(GetRange().GetStart());
10431 cmdEvent.SetContainer(container);
10432
10433 m_buffer->SendEvent(cmdEvent);
10434
10435 break;
10436 }
10437 case wxRICHTEXT_CHANGE_ATTRIBUTES:
10438 case wxRICHTEXT_CHANGE_OBJECT:
10439 {
10440 return Do();
10441 }
10442 default:
10443 break;
10444 }
10445
10446 return true;
10447 }
10448
10449 /// Update the control appearance
10450 void wxRichTextAction::UpdateAppearance(long caretPosition, bool sendUpdateEvent, wxArrayInt* optimizationLineCharPositions, wxArrayInt* optimizationLineYPositions, bool isDoCmd)
10451 {
10452 wxRichTextParagraphLayoutBox* container = GetContainer();
10453 wxASSERT(container != NULL);
10454 if (!container)
10455 return;
10456
10457 if (m_ctrl)
10458 {
10459 m_ctrl->SetFocusObject(container);
10460 m_ctrl->SetCaretPosition(caretPosition);
10461
10462 if (!m_ctrl->IsFrozen())
10463 {
10464 wxRect containerRect = container->GetRect();
10465
10466 m_ctrl->LayoutContent();
10467
10468 // Refresh everything if there were floating objects or the container changed size
10469 // (we can't yet optimize in these cases, since more complex interaction with other content occurs)
10470 if (container->GetFloatingObjectCount() > 0 || (container->GetParent() && containerRect != container->GetRect()))
10471 {
10472 m_ctrl->Refresh(false);
10473 }
10474 else
10475
10476 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
10477 // Find refresh rectangle if we are in a position to optimise refresh
10478 if ((m_cmdId == wxRICHTEXT_INSERT || m_cmdId == wxRICHTEXT_DELETE) && optimizationLineCharPositions)
10479 {
10480 size_t i;
10481
10482 wxSize clientSize = m_ctrl->GetUnscaledSize(m_ctrl->GetClientSize());
10483 wxPoint firstVisiblePt = m_ctrl->GetUnscaledPoint(m_ctrl->GetFirstVisiblePoint());
10484
10485 // Start/end positions
10486 int firstY = 0;
10487 int lastY = firstVisiblePt.y + clientSize.y;
10488
10489 bool foundEnd = false;
10490
10491 // position offset - how many characters were inserted
10492 int positionOffset = GetRange().GetLength();
10493
10494 // Determine whether this is Do or Undo, and adjust positionOffset accordingly
10495 if ((m_cmdId == wxRICHTEXT_DELETE && isDoCmd) || (m_cmdId == wxRICHTEXT_INSERT && !isDoCmd))
10496 positionOffset = - positionOffset;
10497
10498 // find the first line which is being drawn at the same position as it was
10499 // before. Since we're talking about a simple insertion, we can assume
10500 // that the rest of the window does not need to be redrawn.
10501
10502 wxRichTextParagraph* para = container->GetParagraphAtPosition(GetPosition());
10503 // Since we support floating layout, we should redraw the whole para instead of just
10504 // the first line touching the invalid range.
10505 if (para)
10506 {
10507 firstY = para->GetPosition().y;
10508 }
10509
10510 wxRichTextObjectList::compatibility_iterator node = container->GetChildren().Find(para);
10511 while (node)
10512 {
10513 wxRichTextParagraph* child = (wxRichTextParagraph*) node->GetData();
10514 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
10515 while (node2)
10516 {
10517 wxRichTextLine* line = node2->GetData();
10518 wxPoint pt = line->GetAbsolutePosition();
10519 wxRichTextRange range = line->GetAbsoluteRange();
10520
10521 // we want to find the first line that is in the same position
10522 // as before. This will mean we're at the end of the changed text.
10523
10524 if (pt.y > lastY) // going past the end of the window, no more info
10525 {
10526 node2 = wxRichTextLineList::compatibility_iterator();
10527 node = wxRichTextObjectList::compatibility_iterator();
10528 }
10529 // Detect last line in the buffer
10530 else if (!node2->GetNext() && para->GetRange().Contains(container->GetOwnRange().GetEnd()))
10531 {
10532 // If deleting text, make sure we refresh below as well as above
10533 if (positionOffset >= 0)
10534 {
10535 foundEnd = true;
10536 lastY = pt.y + line->GetSize().y;
10537 }
10538
10539 node2 = wxRichTextLineList::compatibility_iterator();
10540 node = wxRichTextObjectList::compatibility_iterator();
10541
10542 break;
10543 }
10544 else
10545 {
10546 // search for this line being at the same position as before
10547 for (i = 0; i < optimizationLineCharPositions->GetCount(); i++)
10548 {
10549 if (((*optimizationLineCharPositions)[i] + positionOffset == range.GetStart()) &&
10550 ((*optimizationLineYPositions)[i] == pt.y))
10551 {
10552 // Stop, we're now the same as we were
10553 foundEnd = true;
10554
10555 lastY = pt.y;
10556
10557 node2 = wxRichTextLineList::compatibility_iterator();
10558 node = wxRichTextObjectList::compatibility_iterator();
10559
10560 break;
10561 }
10562 }
10563 }
10564
10565 if (node2)
10566 node2 = node2->GetNext();
10567 }
10568
10569 if (node)
10570 node = node->GetNext();
10571 }
10572
10573 firstY = wxMax(firstVisiblePt.y, firstY);
10574 if (!foundEnd)
10575 lastY = firstVisiblePt.y + clientSize.y;
10576
10577 // Convert to device coordinates
10578 wxRect rect(m_ctrl->GetPhysicalPoint(m_ctrl->GetScaledPoint(wxPoint(firstVisiblePt.x, firstY))), m_ctrl->GetScaledSize(wxSize(clientSize.x, lastY - firstY)));
10579 m_ctrl->RefreshRect(rect);
10580 }
10581 else
10582 #endif
10583 m_ctrl->Refresh(false);
10584
10585 m_ctrl->PositionCaret();
10586
10587 // This causes styles to persist when doing programmatic
10588 // content creation except when Freeze/Thaw is used, so
10589 // disable this and check for the consequences.
10590 // m_ctrl->SetDefaultStyleToCursorStyle();
10591
10592 if (sendUpdateEvent)
10593 wxTextCtrl::SendTextUpdatedEvent(m_ctrl);
10594 }
10595 }
10596 }
10597
10598 /// Replace the buffer paragraphs with the new ones.
10599 void wxRichTextAction::ApplyParagraphs(const wxRichTextParagraphLayoutBox& fragment)
10600 {
10601 wxRichTextParagraphLayoutBox* container = GetContainer();
10602 wxASSERT(container != NULL);
10603 if (!container)
10604 return;
10605
10606 wxRichTextObjectList::compatibility_iterator node = fragment.GetChildren().GetFirst();
10607 while (node)
10608 {
10609 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
10610 wxASSERT (para != NULL);
10611
10612 // We'll replace the existing paragraph by finding the paragraph at this position,
10613 // delete its node data, and setting a copy as the new node data.
10614 // TODO: make more efficient by simply swapping old and new paragraph objects.
10615
10616 wxRichTextParagraph* existingPara = container->GetParagraphAtPosition(para->GetRange().GetStart());
10617 if (existingPara)
10618 {
10619 wxRichTextObjectList::compatibility_iterator bufferParaNode = container->GetChildren().Find(existingPara);
10620 if (bufferParaNode)
10621 {
10622 wxRichTextParagraph* newPara = new wxRichTextParagraph(*para);
10623 newPara->SetParent(container);
10624
10625 bufferParaNode->SetData(newPara);
10626
10627 delete existingPara;
10628 }
10629 }
10630
10631 node = node->GetNext();
10632 }
10633 }
10634
10635
10636 /*!
10637 * wxRichTextRange
10638 * This stores beginning and end positions for a range of data.
10639 */
10640
10641 WX_DEFINE_OBJARRAY(wxRichTextRangeArray);
10642
10643 /// Limit this range to be within 'range'
10644 bool wxRichTextRange::LimitTo(const wxRichTextRange& range)
10645 {
10646 if (m_start < range.m_start)
10647 m_start = range.m_start;
10648
10649 if (m_end > range.m_end)
10650 m_end = range.m_end;
10651
10652 return true;
10653 }
10654
10655 /*!
10656 * wxRichTextImage implementation
10657 * This object represents an image.
10658 */
10659
10660 IMPLEMENT_DYNAMIC_CLASS(wxRichTextImage, wxRichTextObject)
10661
10662 wxRichTextImage::wxRichTextImage(const wxImage& image, wxRichTextObject* parent, wxRichTextAttr* charStyle):
10663 wxRichTextObject(parent)
10664 {
10665 Init();
10666 m_imageBlock.MakeImageBlockDefaultQuality(image, wxBITMAP_TYPE_PNG);
10667 if (charStyle)
10668 SetAttributes(*charStyle);
10669 }
10670
10671 wxRichTextImage::wxRichTextImage(const wxRichTextImageBlock& imageBlock, wxRichTextObject* parent, wxRichTextAttr* charStyle):
10672 wxRichTextObject(parent)
10673 {
10674 Init();
10675 m_imageBlock = imageBlock;
10676 if (charStyle)
10677 SetAttributes(*charStyle);
10678 }
10679
10680 void wxRichTextImage::Init()
10681 {
10682 m_originalImageSize = wxSize(-1, -1);
10683 }
10684
10685 /// Create a cached image at the required size
10686 bool wxRichTextImage::LoadImageCache(wxDC& dc, bool resetCache)
10687 {
10688 if (!m_imageBlock.IsOk())
10689 return false;
10690
10691 // If we have an original image size, use that to compute the cached bitmap size
10692 // instead of loading the image each time. This way we can avoid loading
10693 // the image so long as the new cached bitmap size hasn't changed.
10694
10695 wxImage image;
10696 if (resetCache || m_originalImageSize == wxSize(-1, -1))
10697 {
10698 m_imageCache = wxNullBitmap;
10699
10700 m_imageBlock.Load(image);
10701 if (!image.IsOk())
10702 return false;
10703
10704 m_originalImageSize = wxSize(image.GetWidth(), image.GetHeight());
10705 }
10706
10707 int width = m_originalImageSize.GetWidth();
10708 int height = m_originalImageSize.GetHeight();
10709
10710 int parentWidth = 0;
10711 int parentHeight = 0;
10712
10713 int maxWidth = -1;
10714 int maxHeight = -1;
10715
10716 wxRichTextBuffer* buffer = GetBuffer();
10717 if (buffer)
10718 {
10719 wxSize sz;
10720 if (buffer->GetRichTextCtrl())
10721 {
10722 // Subtract borders
10723 sz = buffer->GetRichTextCtrl()->GetClientSize();
10724
10725 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
10726 marginRect = wxRect(0, 0, sz.x, sz.y);
10727 buffer->GetBoxRects(dc, buffer, buffer->GetAttributes(), marginRect, borderRect, contentRect, paddingRect, outlineRect);
10728
10729 sz = contentRect.GetSize();
10730
10731 // Start with a maximum width of the control size, even if not specified by the content,
10732 // to minimize the amount of picture overlapping the right-hand side
10733 maxWidth = sz.x;
10734 }
10735 else
10736 sz = buffer->GetCachedSize();
10737 parentWidth = sz.GetWidth();
10738 parentHeight = sz.GetHeight();
10739 }
10740
10741 if (GetAttributes().GetTextBoxAttr().GetWidth().IsValid() && GetAttributes().GetTextBoxAttr().GetWidth().GetValue() > 0)
10742 {
10743 if (parentWidth > 0 && GetAttributes().GetTextBoxAttr().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE)
10744 width = (int) ((GetAttributes().GetTextBoxAttr().GetWidth().GetValue() * parentWidth)/100.0);
10745 else if (GetAttributes().GetTextBoxAttr().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
10746 width = ConvertTenthsMMToPixels(dc, GetAttributes().GetTextBoxAttr().GetWidth().GetValue());
10747 else if (GetAttributes().GetTextBoxAttr().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_PIXELS)
10748 width = GetAttributes().GetTextBoxAttr().GetWidth().GetValue();
10749 }
10750
10751 // Limit to max width
10752
10753 if (GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().IsValid() && GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetValue() > 0)
10754 {
10755 int mw = -1;
10756
10757 if (parentWidth > 0 && GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE)
10758 mw = (int) ((GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetValue() * parentWidth)/100.0);
10759 else if (GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
10760 mw = ConvertTenthsMMToPixels(dc, GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetValue());
10761 else if (GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_PIXELS)
10762 mw = GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetValue();
10763
10764 // If we already have a smaller max width due to the constraints of the control size,
10765 // don't use the larger max width.
10766 if (mw != -1 && ((maxWidth == -1) || (mw < maxWidth)))
10767 maxWidth = mw;
10768 }
10769
10770 if (maxWidth > 0 && width > maxWidth)
10771 width = maxWidth;
10772
10773 // Preserve the aspect ratio
10774 if (width != m_originalImageSize.GetWidth())
10775 height = (int) (float(m_originalImageSize.GetHeight()) * (float(width)/float(m_originalImageSize.GetWidth())));
10776
10777 if (GetAttributes().GetTextBoxAttr().GetHeight().IsValid() && GetAttributes().GetTextBoxAttr().GetHeight().GetValue() > 0)
10778 {
10779 if (parentHeight > 0 && GetAttributes().GetTextBoxAttr().GetHeight().GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE)
10780 height = (int) ((GetAttributes().GetTextBoxAttr().GetHeight().GetValue() * parentHeight)/100.0);
10781 else if (GetAttributes().GetTextBoxAttr().GetHeight().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
10782 height = ConvertTenthsMMToPixels(dc, GetAttributes().GetTextBoxAttr().GetHeight().GetValue());
10783 else if (GetAttributes().GetTextBoxAttr().GetHeight().GetUnits() == wxTEXT_ATTR_UNITS_PIXELS)
10784 height = GetAttributes().GetTextBoxAttr().GetHeight().GetValue();
10785
10786 // Preserve the aspect ratio
10787 if (height != m_originalImageSize.GetHeight())
10788 width = (int) (float(m_originalImageSize.GetWidth()) * (float(height)/float(m_originalImageSize.GetHeight())));
10789 }
10790
10791 // Limit to max height
10792
10793 if (GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().IsValid() && GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetValue() > 0)
10794 {
10795 if (parentHeight > 0 && GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE)
10796 maxHeight = (int) ((GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetValue() * parentHeight)/100.0);
10797 else if (GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
10798 maxHeight = ConvertTenthsMMToPixels(dc, GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetValue());
10799 else if (GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetUnits() == wxTEXT_ATTR_UNITS_PIXELS)
10800 maxHeight = GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetValue();
10801 }
10802
10803 if (maxHeight > 0 && height > maxHeight)
10804 {
10805 height = maxHeight;
10806
10807 // Preserve the aspect ratio
10808 if (height != m_originalImageSize.GetHeight())
10809 width = (int) (float(m_originalImageSize.GetWidth()) * (float(height)/float(m_originalImageSize.GetHeight())));
10810 }
10811
10812 if (m_imageCache.IsOk() && m_imageCache.GetWidth() == width && m_imageCache.GetHeight() == height)
10813 {
10814 // Do nothing, we didn't need to change the image cache
10815 }
10816 else
10817 {
10818 if (!image.IsOk())
10819 {
10820 m_imageBlock.Load(image);
10821 if (!image.IsOk())
10822 return false;
10823 }
10824
10825 if (image.GetWidth() == width && image.GetHeight() == height)
10826 m_imageCache = wxBitmap(image);
10827 else
10828 {
10829 // If the original width and height is small, e.g. 400 or below,
10830 // scale up and then down to improve image quality. This can make
10831 // a big difference, with not much performance hit.
10832 int upscaleThreshold = 400;
10833 wxImage img;
10834 if (image.GetWidth() <= upscaleThreshold || image.GetHeight() <= upscaleThreshold)
10835 {
10836 img = image.Scale(image.GetWidth()*2, image.GetHeight()*2);
10837 img.Rescale(width, height, wxIMAGE_QUALITY_HIGH);
10838 }
10839 else
10840 img = image.Scale(width, height, wxIMAGE_QUALITY_HIGH);
10841 m_imageCache = wxBitmap(img);
10842 }
10843 }
10844
10845 return m_imageCache.IsOk();
10846 }
10847
10848 /// Draw the item
10849 bool wxRichTextImage::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& WXUNUSED(range), const wxRichTextSelection& selection, const wxRect& rect, int WXUNUSED(descent), int WXUNUSED(style))
10850 {
10851 if (!IsShown())
10852 return true;
10853
10854 // Don't need cached size AFAIK
10855 // wxSize size = GetCachedSize();
10856 if (!LoadImageCache(dc))
10857 return false;
10858
10859 wxRichTextAttr attr(GetAttributes());
10860 context.ApplyVirtualAttributes(attr, this);
10861
10862 DrawBoxAttributes(dc, GetBuffer(), attr, wxRect(rect.GetPosition(), GetCachedSize()));
10863
10864 wxSize imageSize(m_imageCache.GetWidth(), m_imageCache.GetHeight());
10865 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
10866 marginRect = rect; // outer rectangle, will calculate contentRect
10867 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
10868
10869 dc.DrawBitmap(m_imageCache, contentRect.x, contentRect.y, true);
10870
10871 if (selection.WithinSelection(GetRange().GetStart(), this))
10872 {
10873 wxCheckSetBrush(dc, *wxBLACK_BRUSH);
10874 wxCheckSetPen(dc, *wxBLACK_PEN);
10875 dc.SetLogicalFunction(wxINVERT);
10876 dc.DrawRectangle(contentRect);
10877 dc.SetLogicalFunction(wxCOPY);
10878 }
10879
10880 return true;
10881 }
10882
10883 /// Lay the item out
10884 bool wxRichTextImage::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& WXUNUSED(parentRect), int WXUNUSED(style))
10885 {
10886 if (!LoadImageCache(dc))
10887 return false;
10888
10889 wxSize imageSize(m_imageCache.GetWidth(), m_imageCache.GetHeight());
10890 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
10891 contentRect = wxRect(wxPoint(0,0), imageSize);
10892
10893 wxRichTextAttr attr(GetAttributes());
10894 context.ApplyVirtualAttributes(attr, this);
10895
10896 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
10897
10898 wxSize overallSize = marginRect.GetSize();
10899
10900 SetCachedSize(overallSize);
10901 SetMaxSize(overallSize);
10902 SetMinSize(overallSize);
10903 SetPosition(rect.GetPosition());
10904
10905 return true;
10906 }
10907
10908 /// Get/set the object size for the given range. Returns false if the range
10909 /// is invalid for this object.
10910 bool wxRichTextImage::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& WXUNUSED(descent), wxDC& dc, wxRichTextDrawingContext& context, int WXUNUSED(flags), wxPoint WXUNUSED(position), wxArrayInt* partialExtents) const
10911 {
10912 if (!range.IsWithin(GetRange()))
10913 return false;
10914
10915 if (!((wxRichTextImage*)this)->LoadImageCache(dc))
10916 {
10917 size.x = 0; size.y = 0;
10918 if (partialExtents)
10919 partialExtents->Add(0);
10920 return false;
10921 }
10922
10923 wxRichTextAttr attr(GetAttributes());
10924 context.ApplyVirtualAttributes(attr, (wxRichTextObject*) this);
10925
10926 wxSize imageSize(m_imageCache.GetWidth(), m_imageCache.GetHeight());
10927 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
10928 contentRect = wxRect(wxPoint(0,0), imageSize);
10929 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
10930
10931 wxSize overallSize = marginRect.GetSize();
10932
10933 if (partialExtents)
10934 partialExtents->Add(overallSize.x);
10935
10936 size = overallSize;
10937
10938 return true;
10939 }
10940
10941 // Get the 'natural' size for an object. For an image, it would be the
10942 // image size.
10943 wxTextAttrSize wxRichTextImage::GetNaturalSize() const
10944 {
10945 wxTextAttrSize size;
10946 if (GetImageCache().IsOk())
10947 {
10948 size.SetWidth(GetImageCache().GetWidth(), wxTEXT_ATTR_UNITS_PIXELS);
10949 size.SetHeight(GetImageCache().GetHeight(), wxTEXT_ATTR_UNITS_PIXELS);
10950 }
10951 return size;
10952 }
10953
10954
10955 /// Copy
10956 void wxRichTextImage::Copy(const wxRichTextImage& obj)
10957 {
10958 wxRichTextObject::Copy(obj);
10959
10960 m_imageBlock = obj.m_imageBlock;
10961 m_originalImageSize = obj.m_originalImageSize;
10962 }
10963
10964 /// Edit properties via a GUI
10965 bool wxRichTextImage::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer)
10966 {
10967 wxRichTextObjectPropertiesDialog imageDlg(this, wxGetTopLevelParent(parent), wxID_ANY, _("Picture Properties"));
10968 imageDlg.SetAttributes(GetAttributes());
10969
10970 if (imageDlg.ShowModal() == wxID_OK)
10971 {
10972 // By passing wxRICHTEXT_SETSTYLE_RESET, indeterminate attributes set by the user will be set as
10973 // indeterminate in the object.
10974 imageDlg.ApplyStyle(buffer->GetRichTextCtrl(), wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_RESET);
10975 return true;
10976 }
10977 else
10978 return false;
10979 }
10980
10981 /*!
10982 * Utilities
10983 *
10984 */
10985
10986 /// Compare two attribute objects
10987 bool wxTextAttrEq(const wxRichTextAttr& attr1, const wxRichTextAttr& attr2)
10988 {
10989 return (attr1 == attr2);
10990 }
10991
10992 /// Compare tabs
10993 bool wxRichTextTabsEq(const wxArrayInt& tabs1, const wxArrayInt& tabs2)
10994 {
10995 if (tabs1.GetCount() != tabs2.GetCount())
10996 return false;
10997
10998 size_t i;
10999 for (i = 0; i < tabs1.GetCount(); i++)
11000 {
11001 if (tabs1[i] != tabs2[i])
11002 return false;
11003 }
11004 return true;
11005 }
11006
11007 bool wxRichTextApplyStyle(wxRichTextAttr& destStyle, const wxRichTextAttr& style, wxRichTextAttr* compareWith)
11008 {
11009 return destStyle.Apply(style, compareWith);
11010 }
11011
11012 // Remove attributes
11013 bool wxRichTextRemoveStyle(wxRichTextAttr& destStyle, const wxRichTextAttr& style)
11014 {
11015 return destStyle.RemoveStyle(style);
11016 }
11017
11018 /// Combine two bitlists, specifying the bits of interest with separate flags.
11019 bool wxRichTextCombineBitlists(int& valueA, int valueB, int& flagsA, int flagsB)
11020 {
11021 return wxRichTextAttr::CombineBitlists(valueA, valueB, flagsA, flagsB);
11022 }
11023
11024 /// Compare two bitlists
11025 bool wxRichTextBitlistsEqPartial(int valueA, int valueB, int flags)
11026 {
11027 return wxRichTextAttr::BitlistsEqPartial(valueA, valueB, flags);
11028 }
11029
11030 /// Split into paragraph and character styles
11031 bool wxRichTextSplitParaCharStyles(const wxRichTextAttr& style, wxRichTextAttr& parStyle, wxRichTextAttr& charStyle)
11032 {
11033 return wxRichTextAttr::SplitParaCharStyles(style, parStyle, charStyle);
11034 }
11035
11036 /// Convert a decimal to Roman numerals
11037 wxString wxRichTextDecimalToRoman(long n)
11038 {
11039 static wxArrayInt decimalNumbers;
11040 static wxArrayString romanNumbers;
11041
11042 // Clean up arrays
11043 if (n == -1)
11044 {
11045 decimalNumbers.Clear();
11046 romanNumbers.Clear();
11047 return wxEmptyString;
11048 }
11049
11050 if (decimalNumbers.GetCount() == 0)
11051 {
11052 #define wxRichTextAddDecRom(n, r) decimalNumbers.Add(n); romanNumbers.Add(r);
11053
11054 wxRichTextAddDecRom(1000, wxT("M"));
11055 wxRichTextAddDecRom(900, wxT("CM"));
11056 wxRichTextAddDecRom(500, wxT("D"));
11057 wxRichTextAddDecRom(400, wxT("CD"));
11058 wxRichTextAddDecRom(100, wxT("C"));
11059 wxRichTextAddDecRom(90, wxT("XC"));
11060 wxRichTextAddDecRom(50, wxT("L"));
11061 wxRichTextAddDecRom(40, wxT("XL"));
11062 wxRichTextAddDecRom(10, wxT("X"));
11063 wxRichTextAddDecRom(9, wxT("IX"));
11064 wxRichTextAddDecRom(5, wxT("V"));
11065 wxRichTextAddDecRom(4, wxT("IV"));
11066 wxRichTextAddDecRom(1, wxT("I"));
11067 }
11068
11069 int i = 0;
11070 wxString roman;
11071
11072 while (n > 0 && i < 13)
11073 {
11074 if (n >= decimalNumbers[i])
11075 {
11076 n -= decimalNumbers[i];
11077 roman += romanNumbers[i];
11078 }
11079 else
11080 {
11081 i ++;
11082 }
11083 }
11084 if (roman.IsEmpty())
11085 roman = wxT("0");
11086 return roman;
11087 }
11088
11089 /*!
11090 * wxRichTextFileHandler
11091 * Base class for file handlers
11092 */
11093
11094 IMPLEMENT_CLASS(wxRichTextFileHandler, wxObject)
11095
11096 #if wxUSE_FFILE && wxUSE_STREAMS
11097 bool wxRichTextFileHandler::LoadFile(wxRichTextBuffer *buffer, const wxString& filename)
11098 {
11099 wxFFileInputStream stream(filename);
11100 if (stream.IsOk())
11101 return LoadFile(buffer, stream);
11102
11103 return false;
11104 }
11105
11106 bool wxRichTextFileHandler::SaveFile(wxRichTextBuffer *buffer, const wxString& filename)
11107 {
11108 wxFFileOutputStream stream(filename);
11109 if (stream.IsOk())
11110 return SaveFile(buffer, stream);
11111
11112 return false;
11113 }
11114 #endif // wxUSE_FFILE && wxUSE_STREAMS
11115
11116 /// Can we handle this filename (if using files)? By default, checks the extension.
11117 bool wxRichTextFileHandler::CanHandle(const wxString& filename) const
11118 {
11119 wxString path, file, ext;
11120 wxFileName::SplitPath(filename, & path, & file, & ext);
11121
11122 return (ext.Lower() == GetExtension());
11123 }
11124
11125 /*!
11126 * wxRichTextTextHandler
11127 * Plain text handler
11128 */
11129
11130 IMPLEMENT_CLASS(wxRichTextPlainTextHandler, wxRichTextFileHandler)
11131
11132 #if wxUSE_STREAMS
11133 bool wxRichTextPlainTextHandler::DoLoadFile(wxRichTextBuffer *buffer, wxInputStream& stream)
11134 {
11135 if (!stream.IsOk())
11136 return false;
11137
11138 wxString str;
11139 int lastCh = 0;
11140
11141 while (!stream.Eof())
11142 {
11143 int ch = stream.GetC();
11144
11145 if (!stream.Eof())
11146 {
11147 if (ch == 10 && lastCh != 13)
11148 str += wxT('\n');
11149
11150 if (ch > 0 && ch != 10)
11151 str += wxChar(ch);
11152
11153 lastCh = ch;
11154 }
11155 }
11156
11157 buffer->ResetAndClearCommands();
11158 buffer->Clear();
11159 buffer->AddParagraphs(str);
11160 buffer->UpdateRanges();
11161
11162 return true;
11163 }
11164
11165 bool wxRichTextPlainTextHandler::DoSaveFile(wxRichTextBuffer *buffer, wxOutputStream& stream)
11166 {
11167 if (!stream.IsOk())
11168 return false;
11169
11170 wxString text = buffer->GetText();
11171
11172 wxString newLine = wxRichTextLineBreakChar;
11173 text.Replace(newLine, wxT("\n"));
11174
11175 wxCharBuffer buf = text.ToAscii();
11176
11177 stream.Write((const char*) buf, text.length());
11178 return true;
11179 }
11180 #endif // wxUSE_STREAMS
11181
11182 /*
11183 * Stores information about an image, in binary in-memory form
11184 */
11185
11186 wxRichTextImageBlock::wxRichTextImageBlock()
11187 {
11188 Init();
11189 }
11190
11191 wxRichTextImageBlock::wxRichTextImageBlock(const wxRichTextImageBlock& block):wxObject()
11192 {
11193 Init();
11194 Copy(block);
11195 }
11196
11197 wxRichTextImageBlock::~wxRichTextImageBlock()
11198 {
11199 wxDELETEA(m_data);
11200 }
11201
11202 void wxRichTextImageBlock::Init()
11203 {
11204 m_data = NULL;
11205 m_dataSize = 0;
11206 m_imageType = wxBITMAP_TYPE_INVALID;
11207 }
11208
11209 void wxRichTextImageBlock::Clear()
11210 {
11211 wxDELETEA(m_data);
11212 m_dataSize = 0;
11213 m_imageType = wxBITMAP_TYPE_INVALID;
11214 }
11215
11216
11217 // Load the original image into a memory block.
11218 // If the image is not a JPEG, we must convert it into a JPEG
11219 // to conserve space.
11220 // If it's not a JPEG we can make use of 'image', already scaled, so we don't have to
11221 // load the image a 2nd time.
11222
11223 bool wxRichTextImageBlock::MakeImageBlock(const wxString& filename, wxBitmapType imageType,
11224 wxImage& image, bool convertToJPEG)
11225 {
11226 m_imageType = imageType;
11227
11228 wxString filenameToRead(filename);
11229 bool removeFile = false;
11230
11231 if (imageType == wxBITMAP_TYPE_INVALID)
11232 return false; // Could not determine image type
11233
11234 if ((imageType != wxBITMAP_TYPE_JPEG) && convertToJPEG)
11235 {
11236 wxString tempFile =
11237 wxFileName::CreateTempFileName(_("image"));
11238
11239 wxASSERT(!tempFile.IsEmpty());
11240
11241 image.SaveFile(tempFile, wxBITMAP_TYPE_JPEG);
11242 filenameToRead = tempFile;
11243 removeFile = true;
11244
11245 m_imageType = wxBITMAP_TYPE_JPEG;
11246 }
11247 wxFile file;
11248 if (!file.Open(filenameToRead))
11249 return false;
11250
11251 m_dataSize = (size_t) file.Length();
11252 file.Close();
11253
11254 if (m_data)
11255 delete[] m_data;
11256 m_data = ReadBlock(filenameToRead, m_dataSize);
11257
11258 if (removeFile)
11259 wxRemoveFile(filenameToRead);
11260
11261 return (m_data != NULL);
11262 }
11263
11264 // Make an image block from the wxImage in the given
11265 // format.
11266 bool wxRichTextImageBlock::MakeImageBlock(wxImage& image, wxBitmapType imageType, int quality)
11267 {
11268 image.SetOption(wxT("quality"), quality);
11269
11270 if (imageType == wxBITMAP_TYPE_INVALID)
11271 return false; // Could not determine image type
11272
11273 return DoMakeImageBlock(image, imageType);
11274 }
11275
11276 // Uses a const wxImage for efficiency, but can't set quality (only relevant for JPEG)
11277 bool wxRichTextImageBlock::MakeImageBlockDefaultQuality(const wxImage& image, wxBitmapType imageType)
11278 {
11279 if (imageType == wxBITMAP_TYPE_INVALID)
11280 return false; // Could not determine image type
11281
11282 return DoMakeImageBlock(image, imageType);
11283 }
11284
11285 // Makes the image block
11286 bool wxRichTextImageBlock::DoMakeImageBlock(const wxImage& image, wxBitmapType imageType)
11287 {
11288 wxMemoryOutputStream memStream;
11289 if (!image.SaveFile(memStream, imageType))
11290 {
11291 return false;
11292 }
11293
11294 unsigned char* block = new unsigned char[memStream.GetSize()];
11295 if (!block)
11296 return false;
11297
11298 if (m_data)
11299 delete[] m_data;
11300 m_data = block;
11301
11302 m_imageType = imageType;
11303 m_dataSize = memStream.GetSize();
11304
11305 memStream.CopyTo(m_data, m_dataSize);
11306
11307 return (m_data != NULL);
11308 }
11309
11310 // Write to a file
11311 bool wxRichTextImageBlock::Write(const wxString& filename)
11312 {
11313 return WriteBlock(filename, m_data, m_dataSize);
11314 }
11315
11316 void wxRichTextImageBlock::Copy(const wxRichTextImageBlock& block)
11317 {
11318 m_imageType = block.m_imageType;
11319 wxDELETEA(m_data);
11320 m_dataSize = block.m_dataSize;
11321 if (m_dataSize == 0)
11322 return;
11323
11324 m_data = new unsigned char[m_dataSize];
11325 unsigned int i;
11326 for (i = 0; i < m_dataSize; i++)
11327 m_data[i] = block.m_data[i];
11328 }
11329
11330 //// Operators
11331 void wxRichTextImageBlock::operator=(const wxRichTextImageBlock& block)
11332 {
11333 Copy(block);
11334 }
11335
11336 // Load a wxImage from the block
11337 bool wxRichTextImageBlock::Load(wxImage& image)
11338 {
11339 if (!m_data)
11340 return false;
11341
11342 // Read in the image.
11343 #if wxUSE_STREAMS
11344 wxMemoryInputStream mstream(m_data, m_dataSize);
11345 bool success = image.LoadFile(mstream, GetImageType());
11346 #else
11347 wxString tempFile = wxFileName::CreateTempFileName(_("image"));
11348 wxASSERT(!tempFile.IsEmpty());
11349
11350 if (!WriteBlock(tempFile, m_data, m_dataSize))
11351 {
11352 return false;
11353 }
11354 success = image.LoadFile(tempFile, GetImageType());
11355 wxRemoveFile(tempFile);
11356 #endif
11357
11358 return success;
11359 }
11360
11361 // Write data in hex to a stream
11362 bool wxRichTextImageBlock::WriteHex(wxOutputStream& stream)
11363 {
11364 if (m_dataSize == 0)
11365 return true;
11366
11367 int bufSize = 100000;
11368 if (int(2*m_dataSize) < bufSize)
11369 bufSize = 2*m_dataSize;
11370 char* buf = new char[bufSize+1];
11371
11372 int left = m_dataSize;
11373 int n, i, j;
11374 j = 0;
11375 while (left > 0)
11376 {
11377 if (left*2 > bufSize)
11378 {
11379 n = bufSize; left -= (bufSize/2);
11380 }
11381 else
11382 {
11383 n = left*2; left = 0;
11384 }
11385
11386 char* b = buf;
11387 for (i = 0; i < (n/2); i++)
11388 {
11389 wxDecToHex(m_data[j], b, b+1);
11390 b += 2; j ++;
11391 }
11392
11393 buf[n] = 0;
11394 stream.Write((const char*) buf, n);
11395 }
11396 delete[] buf;
11397 return true;
11398 }
11399
11400 // Read data in hex from a stream
11401 bool wxRichTextImageBlock::ReadHex(wxInputStream& stream, int length, wxBitmapType imageType)
11402 {
11403 int dataSize = length/2;
11404
11405 if (m_data)
11406 delete[] m_data;
11407
11408 // create a null terminated temporary string:
11409 char str[3];
11410 str[2] = '\0';
11411
11412 m_data = new unsigned char[dataSize];
11413 int i;
11414 for (i = 0; i < dataSize; i ++)
11415 {
11416 str[0] = (char)stream.GetC();
11417 str[1] = (char)stream.GetC();
11418
11419 m_data[i] = (unsigned char)wxHexToDec(str);
11420 }
11421
11422 m_dataSize = dataSize;
11423 m_imageType = imageType;
11424
11425 return true;
11426 }
11427
11428 // Allocate and read from stream as a block of memory
11429 unsigned char* wxRichTextImageBlock::ReadBlock(wxInputStream& stream, size_t size)
11430 {
11431 unsigned char* block = new unsigned char[size];
11432 if (!block)
11433 return NULL;
11434
11435 stream.Read(block, size);
11436
11437 return block;
11438 }
11439
11440 unsigned char* wxRichTextImageBlock::ReadBlock(const wxString& filename, size_t size)
11441 {
11442 wxFileInputStream stream(filename);
11443 if (!stream.IsOk())
11444 return NULL;
11445
11446 return ReadBlock(stream, size);
11447 }
11448
11449 // Write memory block to stream
11450 bool wxRichTextImageBlock::WriteBlock(wxOutputStream& stream, unsigned char* block, size_t size)
11451 {
11452 stream.Write((void*) block, size);
11453 return stream.IsOk();
11454
11455 }
11456
11457 // Write memory block to file
11458 bool wxRichTextImageBlock::WriteBlock(const wxString& filename, unsigned char* block, size_t size)
11459 {
11460 wxFileOutputStream outStream(filename);
11461 if (!outStream.IsOk())
11462 return false;
11463
11464 return WriteBlock(outStream, block, size);
11465 }
11466
11467 // Gets the extension for the block's type
11468 wxString wxRichTextImageBlock::GetExtension() const
11469 {
11470 wxImageHandler* handler = wxImage::FindHandler(GetImageType());
11471 if (handler)
11472 return handler->GetExtension();
11473 else
11474 return wxEmptyString;
11475 }
11476
11477 #if wxUSE_DATAOBJ
11478
11479 /*!
11480 * The data object for a wxRichTextBuffer
11481 */
11482
11483 const wxChar *wxRichTextBufferDataObject::ms_richTextBufferFormatId = wxT("wxShape");
11484
11485 wxRichTextBufferDataObject::wxRichTextBufferDataObject(wxRichTextBuffer* richTextBuffer)
11486 {
11487 m_richTextBuffer = richTextBuffer;
11488
11489 // this string should uniquely identify our format, but is otherwise
11490 // arbitrary
11491 m_formatRichTextBuffer.SetId(GetRichTextBufferFormatId());
11492
11493 SetFormat(m_formatRichTextBuffer);
11494 }
11495
11496 wxRichTextBufferDataObject::~wxRichTextBufferDataObject()
11497 {
11498 delete m_richTextBuffer;
11499 }
11500
11501 // after a call to this function, the richTextBuffer is owned by the caller and it
11502 // is responsible for deleting it!
11503 wxRichTextBuffer* wxRichTextBufferDataObject::GetRichTextBuffer()
11504 {
11505 wxRichTextBuffer* richTextBuffer = m_richTextBuffer;
11506 m_richTextBuffer = NULL;
11507
11508 return richTextBuffer;
11509 }
11510
11511 wxDataFormat wxRichTextBufferDataObject::GetPreferredFormat(Direction WXUNUSED(dir)) const
11512 {
11513 return m_formatRichTextBuffer;
11514 }
11515
11516 size_t wxRichTextBufferDataObject::GetDataSize() const
11517 {
11518 if (!m_richTextBuffer)
11519 return 0;
11520
11521 wxString bufXML;
11522
11523 {
11524 wxStringOutputStream stream(& bufXML);
11525 if (!m_richTextBuffer->SaveFile(stream, wxRICHTEXT_TYPE_XML))
11526 {
11527 wxLogError(wxT("Could not write the buffer to an XML stream.\nYou may have forgotten to add the XML file handler."));
11528 return 0;
11529 }
11530 }
11531
11532 #if wxUSE_UNICODE
11533 wxCharBuffer buffer = bufXML.mb_str(wxConvUTF8);
11534 return strlen(buffer) + 1;
11535 #else
11536 return bufXML.Length()+1;
11537 #endif
11538 }
11539
11540 bool wxRichTextBufferDataObject::GetDataHere(void *pBuf) const
11541 {
11542 if (!pBuf || !m_richTextBuffer)
11543 return false;
11544
11545 wxString bufXML;
11546
11547 {
11548 wxStringOutputStream stream(& bufXML);
11549 if (!m_richTextBuffer->SaveFile(stream, wxRICHTEXT_TYPE_XML))
11550 {
11551 wxLogError(wxT("Could not write the buffer to an XML stream.\nYou may have forgotten to add the XML file handler."));
11552 return 0;
11553 }
11554 }
11555
11556 #if wxUSE_UNICODE
11557 wxCharBuffer buffer = bufXML.mb_str(wxConvUTF8);
11558 size_t len = strlen(buffer);
11559 memcpy((char*) pBuf, (const char*) buffer, len);
11560 ((char*) pBuf)[len] = 0;
11561 #else
11562 size_t len = bufXML.Length();
11563 memcpy((char*) pBuf, (const char*) bufXML.c_str(), len);
11564 ((char*) pBuf)[len] = 0;
11565 #endif
11566
11567 return true;
11568 }
11569
11570 bool wxRichTextBufferDataObject::SetData(size_t WXUNUSED(len), const void *buf)
11571 {
11572 wxDELETE(m_richTextBuffer);
11573
11574 wxString bufXML((const char*) buf, wxConvUTF8);
11575
11576 m_richTextBuffer = new wxRichTextBuffer;
11577
11578 wxStringInputStream stream(bufXML);
11579 if (!m_richTextBuffer->LoadFile(stream, wxRICHTEXT_TYPE_XML))
11580 {
11581 wxLogError(wxT("Could not read the buffer from an XML stream.\nYou may have forgotten to add the XML file handler."));
11582
11583 wxDELETE(m_richTextBuffer);
11584
11585 return false;
11586 }
11587 return true;
11588 }
11589
11590 #endif
11591 // wxUSE_DATAOBJ
11592
11593
11594 /*
11595 * wxRichTextFontTable
11596 * Manages quick access to a pool of fonts for rendering rich text
11597 */
11598
11599 WX_DECLARE_STRING_HASH_MAP_WITH_DECL(wxFont, wxRichTextFontTableHashMap, class WXDLLIMPEXP_RICHTEXT);
11600
11601 class wxRichTextFontTableData: public wxObjectRefData
11602 {
11603 public:
11604 wxRichTextFontTableData() {}
11605
11606 wxFont FindFont(const wxRichTextAttr& fontSpec, double fontScale);
11607
11608 wxRichTextFontTableHashMap m_hashMap;
11609 };
11610
11611 wxFont wxRichTextFontTableData::FindFont(const wxRichTextAttr& fontSpec, double fontScale)
11612 {
11613 wxString facename(fontSpec.GetFontFaceName());
11614
11615 int fontSize = fontSpec.GetFontSize();
11616 if (fontScale != 1.0)
11617 fontSize = (int) ((double(fontSize) * fontScale) + 0.5);
11618
11619 wxString units;
11620 if (fontSpec.HasFontPixelSize() && !fontSpec.HasFontPointSize())
11621 units = wxT("px");
11622 else
11623 units = wxT("pt");
11624 wxString spec = wxString::Format(wxT("%d-%s-%d-%d-%d-%d-%s-%d"),
11625 fontSize, units.c_str(), fontSpec.GetFontStyle(), fontSpec.GetFontWeight(), (int) fontSpec.GetFontUnderlined(), (int) fontSpec.GetFontStrikethrough(),
11626 facename.c_str(), (int) fontSpec.GetFontEncoding());
11627
11628 wxRichTextFontTableHashMap::iterator entry = m_hashMap.find(spec);
11629 if ( entry == m_hashMap.end() )
11630 {
11631 if (fontSpec.HasFontPixelSize() && !fontSpec.HasFontPointSize())
11632 {
11633 wxFont font(wxSize(0, fontSize), wxFONTFAMILY_DEFAULT, fontSpec.GetFontStyle(), fontSpec.GetFontWeight(), fontSpec.GetFontUnderlined(), facename);
11634 if (fontSpec.HasFontStrikethrough() && fontSpec.GetFontStrikethrough())
11635 font.SetStrikethrough(true);
11636 m_hashMap[spec] = font;
11637 return font;
11638 }
11639 else
11640 {
11641 wxFont font(fontSize, wxFONTFAMILY_DEFAULT, fontSpec.GetFontStyle(), fontSpec.GetFontWeight(), fontSpec.GetFontUnderlined(), facename.c_str());
11642 if (fontSpec.HasFontStrikethrough() && fontSpec.GetFontStrikethrough())
11643 font.SetStrikethrough(true);
11644
11645 m_hashMap[spec] = font;
11646 return font;
11647 }
11648 }
11649 else
11650 {
11651 return entry->second;
11652 }
11653 }
11654
11655 IMPLEMENT_DYNAMIC_CLASS(wxRichTextFontTable, wxObject)
11656
11657 wxRichTextFontTable::wxRichTextFontTable()
11658 {
11659 m_refData = new wxRichTextFontTableData;
11660 m_fontScale = 1.0;
11661 }
11662
11663 wxRichTextFontTable::wxRichTextFontTable(const wxRichTextFontTable& table)
11664 : wxObject()
11665 {
11666 (*this) = table;
11667 }
11668
11669 wxRichTextFontTable::~wxRichTextFontTable()
11670 {
11671 UnRef();
11672 }
11673
11674 bool wxRichTextFontTable::operator == (const wxRichTextFontTable& table) const
11675 {
11676 return (m_refData == table.m_refData);
11677 }
11678
11679 void wxRichTextFontTable::operator= (const wxRichTextFontTable& table)
11680 {
11681 Ref(table);
11682 m_fontScale = table.m_fontScale;
11683 }
11684
11685 wxFont wxRichTextFontTable::FindFont(const wxRichTextAttr& fontSpec)
11686 {
11687 wxRichTextFontTableData* data = (wxRichTextFontTableData*) m_refData;
11688 if (data)
11689 return data->FindFont(fontSpec, m_fontScale);
11690 else
11691 return wxFont();
11692 }
11693
11694 void wxRichTextFontTable::Clear()
11695 {
11696 wxRichTextFontTableData* data = (wxRichTextFontTableData*) m_refData;
11697 if (data)
11698 data->m_hashMap.clear();
11699 }
11700
11701 void wxRichTextFontTable::SetFontScale(double fontScale)
11702 {
11703 if (fontScale != m_fontScale)
11704 Clear();
11705 m_fontScale = fontScale;
11706 }
11707
11708 // wxTextBoxAttr
11709
11710 void wxTextBoxAttr::Reset()
11711 {
11712 m_flags = 0;
11713 m_floatMode = wxTEXT_BOX_ATTR_FLOAT_NONE;
11714 m_clearMode = wxTEXT_BOX_ATTR_CLEAR_NONE;
11715 m_collapseMode = wxTEXT_BOX_ATTR_COLLAPSE_NONE;
11716 m_verticalAlignment = wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT_NONE;
11717 m_boxStyleName = wxEmptyString;
11718
11719 m_margins.Reset();
11720 m_padding.Reset();
11721 m_position.Reset();
11722
11723 m_size.Reset();
11724 m_minSize.Reset();
11725 m_maxSize.Reset();
11726
11727 m_border.Reset();
11728 m_outline.Reset();
11729 }
11730
11731 // Equality test
11732 bool wxTextBoxAttr::operator== (const wxTextBoxAttr& attr) const
11733 {
11734 return (
11735 m_flags == attr.m_flags &&
11736 m_floatMode == attr.m_floatMode &&
11737 m_clearMode == attr.m_clearMode &&
11738 m_collapseMode == attr.m_collapseMode &&
11739 m_verticalAlignment == attr.m_verticalAlignment &&
11740
11741 m_margins == attr.m_margins &&
11742 m_padding == attr.m_padding &&
11743 m_position == attr.m_position &&
11744
11745 m_size == attr.m_size &&
11746 m_minSize == attr.m_minSize &&
11747 m_maxSize == attr.m_maxSize &&
11748
11749 m_border == attr.m_border &&
11750 m_outline == attr.m_outline &&
11751
11752 m_boxStyleName == attr.m_boxStyleName
11753 );
11754 }
11755
11756 // Partial equality test
11757 bool wxTextBoxAttr::EqPartial(const wxTextBoxAttr& attr, bool weakTest) const
11758 {
11759 if (!weakTest &&
11760 ((!HasFloatMode() && attr.HasFloatMode()) ||
11761 (!HasClearMode() && attr.HasClearMode()) ||
11762 (!HasCollapseBorders() && attr.HasCollapseBorders()) ||
11763 (!HasVerticalAlignment() && attr.HasVerticalAlignment()) ||
11764 (!HasBoxStyleName() && attr.HasBoxStyleName())))
11765 {
11766 return false;
11767 }
11768 if (attr.HasFloatMode() && HasFloatMode() && (GetFloatMode() != attr.GetFloatMode()))
11769 return false;
11770
11771 if (attr.HasClearMode() && HasClearMode() && (GetClearMode() != attr.GetClearMode()))
11772 return false;
11773
11774 if (attr.HasCollapseBorders() && HasCollapseBorders() && (attr.GetCollapseBorders() != GetCollapseBorders()))
11775 return false;
11776
11777 if (attr.HasVerticalAlignment() && HasVerticalAlignment() && (attr.GetVerticalAlignment() != GetVerticalAlignment()))
11778 return false;
11779
11780 if (attr.HasBoxStyleName() && HasBoxStyleName() && (attr.GetBoxStyleName() != GetBoxStyleName()))
11781 return false;
11782
11783 // Position
11784
11785 if (!m_position.EqPartial(attr.m_position, weakTest))
11786 return false;
11787
11788 // Size
11789
11790 if (!m_size.EqPartial(attr.m_size, weakTest))
11791 return false;
11792 if (!m_minSize.EqPartial(attr.m_minSize, weakTest))
11793 return false;
11794 if (!m_maxSize.EqPartial(attr.m_maxSize, weakTest))
11795 return false;
11796
11797 // Margins
11798
11799 if (!m_margins.EqPartial(attr.m_margins, weakTest))
11800 return false;
11801
11802 // Padding
11803
11804 if (!m_padding.EqPartial(attr.m_padding, weakTest))
11805 return false;
11806
11807 // Border
11808
11809 if (!GetBorder().EqPartial(attr.GetBorder(), weakTest))
11810 return false;
11811
11812 // Outline
11813
11814 if (!GetOutline().EqPartial(attr.GetOutline(), weakTest))
11815 return false;
11816
11817 return true;
11818 }
11819
11820 // Merges the given attributes. If compareWith
11821 // is non-NULL, then it will be used to mask out those attributes that are the same in style
11822 // and compareWith, for situations where we don't want to explicitly set inherited attributes.
11823 bool wxTextBoxAttr::Apply(const wxTextBoxAttr& attr, const wxTextBoxAttr* compareWith)
11824 {
11825 if (attr.HasFloatMode())
11826 {
11827 if (!(compareWith && compareWith->HasFloatMode() && compareWith->GetFloatMode() == attr.GetFloatMode()))
11828 SetFloatMode(attr.GetFloatMode());
11829 }
11830
11831 if (attr.HasClearMode())
11832 {
11833 if (!(compareWith && compareWith->HasClearMode() && compareWith->GetClearMode() == attr.GetClearMode()))
11834 SetClearMode(attr.GetClearMode());
11835 }
11836
11837 if (attr.HasCollapseBorders())
11838 {
11839 if (!(compareWith && compareWith->HasCollapseBorders() && compareWith->GetCollapseBorders() == attr.GetCollapseBorders()))
11840 SetCollapseBorders(attr.GetCollapseBorders());
11841 }
11842
11843 if (attr.HasVerticalAlignment())
11844 {
11845 if (!(compareWith && compareWith->HasVerticalAlignment() && compareWith->GetVerticalAlignment() == attr.GetVerticalAlignment()))
11846 SetVerticalAlignment(attr.GetVerticalAlignment());
11847 }
11848
11849 if (attr.HasBoxStyleName())
11850 {
11851 if (!(compareWith && compareWith->HasBoxStyleName() && compareWith->GetBoxStyleName() == attr.GetBoxStyleName()))
11852 SetBoxStyleName(attr.GetBoxStyleName());
11853 }
11854
11855 m_margins.Apply(attr.m_margins, compareWith ? (& attr.m_margins) : (const wxTextAttrDimensions*) NULL);
11856 m_padding.Apply(attr.m_padding, compareWith ? (& attr.m_padding) : (const wxTextAttrDimensions*) NULL);
11857 m_position.Apply(attr.m_position, compareWith ? (& attr.m_position) : (const wxTextAttrDimensions*) NULL);
11858
11859 m_size.Apply(attr.m_size, compareWith ? (& attr.m_size) : (const wxTextAttrSize*) NULL);
11860 m_minSize.Apply(attr.m_minSize, compareWith ? (& attr.m_minSize) : (const wxTextAttrSize*) NULL);
11861 m_maxSize.Apply(attr.m_maxSize, compareWith ? (& attr.m_maxSize) : (const wxTextAttrSize*) NULL);
11862
11863 m_border.Apply(attr.m_border, compareWith ? (& attr.m_border) : (const wxTextAttrBorders*) NULL);
11864 m_outline.Apply(attr.m_outline, compareWith ? (& attr.m_outline) : (const wxTextAttrBorders*) NULL);
11865
11866 return true;
11867 }
11868
11869 // Remove specified attributes from this object
11870 bool wxTextBoxAttr::RemoveStyle(const wxTextBoxAttr& attr)
11871 {
11872 if (attr.HasFloatMode())
11873 RemoveFlag(wxTEXT_BOX_ATTR_FLOAT);
11874
11875 if (attr.HasClearMode())
11876 RemoveFlag(wxTEXT_BOX_ATTR_CLEAR);
11877
11878 if (attr.HasCollapseBorders())
11879 RemoveFlag(wxTEXT_BOX_ATTR_COLLAPSE_BORDERS);
11880
11881 if (attr.HasVerticalAlignment())
11882 RemoveFlag(wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT);
11883
11884 if (attr.HasBoxStyleName())
11885 {
11886 SetBoxStyleName(wxEmptyString);
11887 RemoveFlag(wxTEXT_BOX_ATTR_BOX_STYLE_NAME);
11888 }
11889
11890 m_margins.RemoveStyle(attr.m_margins);
11891 m_padding.RemoveStyle(attr.m_padding);
11892 m_position.RemoveStyle(attr.m_position);
11893
11894 m_size.RemoveStyle(attr.m_size);
11895 m_minSize.RemoveStyle(attr.m_minSize);
11896 m_maxSize.RemoveStyle(attr.m_maxSize);
11897
11898 m_border.RemoveStyle(attr.m_border);
11899 m_outline.RemoveStyle(attr.m_outline);
11900
11901 return true;
11902 }
11903
11904 // Collects the attributes that are common to a range of content, building up a note of
11905 // which attributes are absent in some objects and which clash in some objects.
11906 void wxTextBoxAttr::CollectCommonAttributes(const wxTextBoxAttr& attr, wxTextBoxAttr& clashingAttr, wxTextBoxAttr& absentAttr)
11907 {
11908 if (attr.HasFloatMode())
11909 {
11910 if (!clashingAttr.HasFloatMode() && !absentAttr.HasFloatMode())
11911 {
11912 if (HasFloatMode())
11913 {
11914 if (GetFloatMode() != attr.GetFloatMode())
11915 {
11916 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_FLOAT);
11917 RemoveFlag(wxTEXT_BOX_ATTR_FLOAT);
11918 }
11919 }
11920 else
11921 SetFloatMode(attr.GetFloatMode());
11922 }
11923 }
11924 else
11925 absentAttr.AddFlag(wxTEXT_BOX_ATTR_FLOAT);
11926
11927 if (attr.HasClearMode())
11928 {
11929 if (!clashingAttr.HasClearMode() && !absentAttr.HasClearMode())
11930 {
11931 if (HasClearMode())
11932 {
11933 if (GetClearMode() != attr.GetClearMode())
11934 {
11935 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_CLEAR);
11936 RemoveFlag(wxTEXT_BOX_ATTR_CLEAR);
11937 }
11938 }
11939 else
11940 SetClearMode(attr.GetClearMode());
11941 }
11942 }
11943 else
11944 absentAttr.AddFlag(wxTEXT_BOX_ATTR_CLEAR);
11945
11946 if (attr.HasCollapseBorders())
11947 {
11948 if (!clashingAttr.HasCollapseBorders() && !absentAttr.HasCollapseBorders())
11949 {
11950 if (HasCollapseBorders())
11951 {
11952 if (GetCollapseBorders() != attr.GetCollapseBorders())
11953 {
11954 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_COLLAPSE_BORDERS);
11955 RemoveFlag(wxTEXT_BOX_ATTR_COLLAPSE_BORDERS);
11956 }
11957 }
11958 else
11959 SetCollapseBorders(attr.GetCollapseBorders());
11960 }
11961 }
11962 else
11963 absentAttr.AddFlag(wxTEXT_BOX_ATTR_COLLAPSE_BORDERS);
11964
11965 if (attr.HasVerticalAlignment())
11966 {
11967 if (!clashingAttr.HasVerticalAlignment() && !absentAttr.HasVerticalAlignment())
11968 {
11969 if (HasVerticalAlignment())
11970 {
11971 if (GetVerticalAlignment() != attr.GetVerticalAlignment())
11972 {
11973 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT);
11974 RemoveFlag(wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT);
11975 }
11976 }
11977 else
11978 SetVerticalAlignment(attr.GetVerticalAlignment());
11979 }
11980 }
11981 else
11982 absentAttr.AddFlag(wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT);
11983
11984 if (attr.HasBoxStyleName())
11985 {
11986 if (!clashingAttr.HasBoxStyleName() && !absentAttr.HasBoxStyleName())
11987 {
11988 if (HasBoxStyleName())
11989 {
11990 if (GetBoxStyleName() != attr.GetBoxStyleName())
11991 {
11992 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_BOX_STYLE_NAME);
11993 RemoveFlag(wxTEXT_BOX_ATTR_BOX_STYLE_NAME);
11994 }
11995 }
11996 else
11997 SetBoxStyleName(attr.GetBoxStyleName());
11998 }
11999 }
12000 else
12001 absentAttr.AddFlag(wxTEXT_BOX_ATTR_BOX_STYLE_NAME);
12002
12003 m_margins.CollectCommonAttributes(attr.m_margins, clashingAttr.m_margins, absentAttr.m_margins);
12004 m_padding.CollectCommonAttributes(attr.m_padding, clashingAttr.m_padding, absentAttr.m_padding);
12005 m_position.CollectCommonAttributes(attr.m_position, clashingAttr.m_position, absentAttr.m_position);
12006
12007 m_size.CollectCommonAttributes(attr.m_size, clashingAttr.m_size, absentAttr.m_size);
12008 m_minSize.CollectCommonAttributes(attr.m_minSize, clashingAttr.m_minSize, absentAttr.m_minSize);
12009 m_maxSize.CollectCommonAttributes(attr.m_maxSize, clashingAttr.m_maxSize, absentAttr.m_maxSize);
12010
12011 m_border.CollectCommonAttributes(attr.m_border, clashingAttr.m_border, absentAttr.m_border);
12012 m_outline.CollectCommonAttributes(attr.m_outline, clashingAttr.m_outline, absentAttr.m_outline);
12013 }
12014
12015 bool wxTextBoxAttr::IsDefault() const
12016 {
12017 return GetFlags() == 0 && !m_border.IsValid() && !m_outline.IsValid() &&
12018 !m_size.IsValid() && !m_minSize.IsValid() && !m_maxSize.IsValid() &&
12019 !m_position.IsValid() && !m_padding.IsValid() && !m_margins.IsValid();
12020 }
12021
12022 // wxRichTextAttr
12023
12024 void wxRichTextAttr::Copy(const wxRichTextAttr& attr)
12025 {
12026 wxTextAttr::Copy(attr);
12027
12028 m_textBoxAttr = attr.m_textBoxAttr;
12029 }
12030
12031 bool wxRichTextAttr::operator==(const wxRichTextAttr& attr) const
12032 {
12033 if (!(wxTextAttr::operator==(attr)))
12034 return false;
12035
12036 return (m_textBoxAttr == attr.m_textBoxAttr);
12037 }
12038
12039 // Partial equality test
12040 bool wxRichTextAttr::EqPartial(const wxRichTextAttr& attr, bool weakTest) const
12041 {
12042 if (!(wxTextAttr::EqPartial(attr, weakTest)))
12043 return false;
12044
12045 return m_textBoxAttr.EqPartial(attr.m_textBoxAttr, weakTest);
12046 }
12047
12048 // Merges the given attributes. If compareWith
12049 // is non-NULL, then it will be used to mask out those attributes that are the same in style
12050 // and compareWith, for situations where we don't want to explicitly set inherited attributes.
12051 bool wxRichTextAttr::Apply(const wxRichTextAttr& style, const wxRichTextAttr* compareWith)
12052 {
12053 wxTextAttr::Apply(style, compareWith);
12054
12055 return m_textBoxAttr.Apply(style.m_textBoxAttr, compareWith ? (& compareWith->m_textBoxAttr) : (const wxTextBoxAttr*) NULL);
12056 }
12057
12058 // Remove specified attributes from this object
12059 bool wxRichTextAttr::RemoveStyle(const wxRichTextAttr& attr)
12060 {
12061 wxTextAttr::RemoveStyle(*this, attr);
12062
12063 return m_textBoxAttr.RemoveStyle(attr.m_textBoxAttr);
12064 }
12065
12066 // Collects the attributes that are common to a range of content, building up a note of
12067 // which attributes are absent in some objects and which clash in some objects.
12068 void wxRichTextAttr::CollectCommonAttributes(const wxRichTextAttr& attr, wxRichTextAttr& clashingAttr, wxRichTextAttr& absentAttr)
12069 {
12070 wxTextAttrCollectCommonAttributes(*this, attr, clashingAttr, absentAttr);
12071
12072 m_textBoxAttr.CollectCommonAttributes(attr.m_textBoxAttr, clashingAttr.m_textBoxAttr, absentAttr.m_textBoxAttr);
12073 }
12074
12075 // Partial equality test
12076 bool wxTextAttrBorder::EqPartial(const wxTextAttrBorder& border, bool weakTest) const
12077 {
12078 if (!weakTest &&
12079 ((!HasStyle() && border.HasStyle()) ||
12080 (!HasColour() && border.HasColour()) ||
12081 (!HasWidth() && border.HasWidth())))
12082 {
12083 return false;
12084 }
12085
12086 if (border.HasStyle() && HasStyle() && (border.GetStyle() != GetStyle()))
12087 return false;
12088
12089 if (border.HasColour() && HasColour() && (border.GetColourLong() != GetColourLong()))
12090 return false;
12091
12092 if (border.HasWidth() && HasWidth() && !(border.GetWidth() == GetWidth()))
12093 return false;
12094
12095 return true;
12096 }
12097
12098 // Apply border to 'this', but not if the same as compareWith
12099 bool wxTextAttrBorder::Apply(const wxTextAttrBorder& border, const wxTextAttrBorder* compareWith)
12100 {
12101 if (border.HasStyle())
12102 {
12103 if (!(compareWith && (border.GetStyle() == compareWith->GetStyle())))
12104 SetStyle(border.GetStyle());
12105 }
12106 if (border.HasColour())
12107 {
12108 if (!(compareWith && (border.GetColourLong() == compareWith->GetColourLong())))
12109 SetColour(border.GetColourLong());
12110 }
12111 if (border.HasWidth())
12112 {
12113 if (!(compareWith && (border.GetWidth() == compareWith->GetWidth())))
12114 SetWidth(border.GetWidth());
12115 }
12116
12117 return true;
12118 }
12119
12120 // Remove specified attributes from this object
12121 bool wxTextAttrBorder::RemoveStyle(const wxTextAttrBorder& attr)
12122 {
12123 if (attr.HasStyle() && HasStyle())
12124 SetFlags(GetFlags() & ~wxTEXT_BOX_ATTR_BORDER_STYLE);
12125 if (attr.HasColour() && HasColour())
12126 SetFlags(GetFlags() & ~wxTEXT_BOX_ATTR_BORDER_COLOUR);
12127 if (attr.HasWidth() && HasWidth())
12128 m_borderWidth.Reset();
12129
12130 return true;
12131 }
12132
12133 // Collects the attributes that are common to a range of content, building up a note of
12134 // which attributes are absent in some objects and which clash in some objects.
12135 void wxTextAttrBorder::CollectCommonAttributes(const wxTextAttrBorder& attr, wxTextAttrBorder& clashingAttr, wxTextAttrBorder& absentAttr)
12136 {
12137 if (attr.HasStyle())
12138 {
12139 if (!clashingAttr.HasStyle() && !absentAttr.HasStyle())
12140 {
12141 if (HasStyle())
12142 {
12143 if (GetStyle() != attr.GetStyle())
12144 {
12145 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_BORDER_STYLE);
12146 RemoveFlag(wxTEXT_BOX_ATTR_BORDER_STYLE);
12147 }
12148 }
12149 else
12150 SetStyle(attr.GetStyle());
12151 }
12152 }
12153 else
12154 absentAttr.AddFlag(wxTEXT_BOX_ATTR_BORDER_STYLE);
12155
12156 if (attr.HasColour())
12157 {
12158 if (!clashingAttr.HasColour() && !absentAttr.HasColour())
12159 {
12160 if (HasColour())
12161 {
12162 if (GetColour() != attr.GetColour())
12163 {
12164 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_BORDER_COLOUR);
12165 RemoveFlag(wxTEXT_BOX_ATTR_BORDER_COLOUR);
12166 }
12167 }
12168 else
12169 SetColour(attr.GetColourLong());
12170 }
12171 }
12172 else
12173 absentAttr.AddFlag(wxTEXT_BOX_ATTR_BORDER_COLOUR);
12174
12175 m_borderWidth.CollectCommonAttributes(attr.m_borderWidth, clashingAttr.m_borderWidth, absentAttr.m_borderWidth);
12176 }
12177
12178 // Partial equality test
12179 bool wxTextAttrBorders::EqPartial(const wxTextAttrBorders& borders, bool weakTest) const
12180 {
12181 return m_left.EqPartial(borders.m_left, weakTest) && m_right.EqPartial(borders.m_right, weakTest) &&
12182 m_top.EqPartial(borders.m_top, weakTest) && m_bottom.EqPartial(borders.m_bottom, weakTest);
12183 }
12184
12185 // Apply border to 'this', but not if the same as compareWith
12186 bool wxTextAttrBorders::Apply(const wxTextAttrBorders& borders, const wxTextAttrBorders* compareWith)
12187 {
12188 m_left.Apply(borders.m_left, compareWith ? (& compareWith->m_left) : (const wxTextAttrBorder*) NULL);
12189 m_right.Apply(borders.m_right, compareWith ? (& compareWith->m_right) : (const wxTextAttrBorder*) NULL);
12190 m_top.Apply(borders.m_top, compareWith ? (& compareWith->m_top) : (const wxTextAttrBorder*) NULL);
12191 m_bottom.Apply(borders.m_bottom, compareWith ? (& compareWith->m_bottom) : (const wxTextAttrBorder*) NULL);
12192 return true;
12193 }
12194
12195 // Remove specified attributes from this object
12196 bool wxTextAttrBorders::RemoveStyle(const wxTextAttrBorders& attr)
12197 {
12198 m_left.RemoveStyle(attr.m_left);
12199 m_right.RemoveStyle(attr.m_right);
12200 m_top.RemoveStyle(attr.m_top);
12201 m_bottom.RemoveStyle(attr.m_bottom);
12202 return true;
12203 }
12204
12205 // Collects the attributes that are common to a range of content, building up a note of
12206 // which attributes are absent in some objects and which clash in some objects.
12207 void wxTextAttrBorders::CollectCommonAttributes(const wxTextAttrBorders& attr, wxTextAttrBorders& clashingAttr, wxTextAttrBorders& absentAttr)
12208 {
12209 m_left.CollectCommonAttributes(attr.m_left, clashingAttr.m_left, absentAttr.m_left);
12210 m_right.CollectCommonAttributes(attr.m_right, clashingAttr.m_right, absentAttr.m_right);
12211 m_top.CollectCommonAttributes(attr.m_top, clashingAttr.m_top, absentAttr.m_top);
12212 m_bottom.CollectCommonAttributes(attr.m_bottom, clashingAttr.m_bottom, absentAttr.m_bottom);
12213 }
12214
12215 // Set style of all borders
12216 void wxTextAttrBorders::SetStyle(int style)
12217 {
12218 m_left.SetStyle(style);
12219 m_right.SetStyle(style);
12220 m_top.SetStyle(style);
12221 m_bottom.SetStyle(style);
12222 }
12223
12224 // Set colour of all borders
12225 void wxTextAttrBorders::SetColour(unsigned long colour)
12226 {
12227 m_left.SetColour(colour);
12228 m_right.SetColour(colour);
12229 m_top.SetColour(colour);
12230 m_bottom.SetColour(colour);
12231 }
12232
12233 void wxTextAttrBorders::SetColour(const wxColour& colour)
12234 {
12235 m_left.SetColour(colour);
12236 m_right.SetColour(colour);
12237 m_top.SetColour(colour);
12238 m_bottom.SetColour(colour);
12239 }
12240
12241 // Set width of all borders
12242 void wxTextAttrBorders::SetWidth(const wxTextAttrDimension& width)
12243 {
12244 m_left.SetWidth(width);
12245 m_right.SetWidth(width);
12246 m_top.SetWidth(width);
12247 m_bottom.SetWidth(width);
12248 }
12249
12250 // Partial equality test
12251 bool wxTextAttrDimension::EqPartial(const wxTextAttrDimension& dim, bool weakTest) const
12252 {
12253 if (!weakTest && !IsValid() && dim.IsValid())
12254 return false;
12255
12256 if (dim.IsValid() && IsValid() && !((*this) == dim))
12257 return false;
12258 else
12259 return true;
12260 }
12261
12262 bool wxTextAttrDimension::Apply(const wxTextAttrDimension& dim, const wxTextAttrDimension* compareWith)
12263 {
12264 if (dim.IsValid())
12265 {
12266 if (!(compareWith && dim == (*compareWith)))
12267 (*this) = dim;
12268 }
12269
12270 return true;
12271 }
12272
12273 // Collects the attributes that are common to a range of content, building up a note of
12274 // which attributes are absent in some objects and which clash in some objects.
12275 void wxTextAttrDimension::CollectCommonAttributes(const wxTextAttrDimension& attr, wxTextAttrDimension& clashingAttr, wxTextAttrDimension& absentAttr)
12276 {
12277 if (attr.IsValid())
12278 {
12279 if (!clashingAttr.IsValid() && !absentAttr.IsValid())
12280 {
12281 if (IsValid())
12282 {
12283 if (!((*this) == attr))
12284 {
12285 clashingAttr.SetValid(true);
12286 SetValid(false);
12287 }
12288 }
12289 else
12290 (*this) = attr;
12291 }
12292 }
12293 else
12294 absentAttr.SetValid(true);
12295 }
12296
12297 wxTextAttrDimensionConverter::wxTextAttrDimensionConverter(wxDC& dc, double scale, const wxSize& parentSize)
12298 {
12299 m_ppi = dc.GetPPI().x; m_scale = scale; m_parentSize = parentSize;
12300 }
12301
12302 wxTextAttrDimensionConverter::wxTextAttrDimensionConverter(int ppi, double scale, const wxSize& parentSize)
12303 {
12304 m_ppi = ppi; m_scale = scale; m_parentSize = parentSize;
12305 }
12306
12307 int wxTextAttrDimensionConverter::ConvertTenthsMMToPixels(int units) const
12308 {
12309 return wxRichTextObject::ConvertTenthsMMToPixels(m_ppi, units, m_scale);
12310 }
12311
12312 int wxTextAttrDimensionConverter::ConvertPixelsToTenthsMM(int pixels) const
12313 {
12314 return wxRichTextObject::ConvertPixelsToTenthsMM(m_ppi, pixels, m_scale);
12315 }
12316
12317 int wxTextAttrDimensionConverter::GetPixels(const wxTextAttrDimension& dim, int direction) const
12318 {
12319 if (dim.GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
12320 return ConvertTenthsMMToPixels(dim.GetValue());
12321 else if (dim.GetUnits() == wxTEXT_ATTR_UNITS_PIXELS)
12322 return dim.GetValue();
12323 else if (dim.GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE)
12324 {
12325 wxASSERT(m_parentSize != wxDefaultSize);
12326 if (direction == wxHORIZONTAL)
12327 return (int) (double(m_parentSize.x) * double(dim.GetValue()) / 100.0);
12328 else
12329 return (int) (double(m_parentSize.y) * double(dim.GetValue()) / 100.0);
12330 }
12331 else
12332 {
12333 wxASSERT(false);
12334 return 0;
12335 }
12336 }
12337
12338 int wxTextAttrDimensionConverter::GetTenthsMM(const wxTextAttrDimension& dim) const
12339 {
12340 if (dim.GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
12341 return dim.GetValue();
12342 else if (dim.GetUnits() == wxTEXT_ATTR_UNITS_PIXELS)
12343 return ConvertPixelsToTenthsMM(dim.GetValue());
12344 else
12345 {
12346 wxASSERT(false);
12347 return 0;
12348 }
12349 }
12350
12351 // Partial equality test
12352 bool wxTextAttrDimensions::EqPartial(const wxTextAttrDimensions& dims, bool weakTest) const
12353 {
12354 if (!m_left.EqPartial(dims.m_left, weakTest))
12355 return false;
12356
12357 if (!m_right.EqPartial(dims.m_right, weakTest))
12358 return false;
12359
12360 if (!m_top.EqPartial(dims.m_top, weakTest))
12361 return false;
12362
12363 if (!m_bottom.EqPartial(dims.m_bottom, weakTest))
12364 return false;
12365
12366 return true;
12367 }
12368
12369 // Apply border to 'this', but not if the same as compareWith
12370 bool wxTextAttrDimensions::Apply(const wxTextAttrDimensions& dims, const wxTextAttrDimensions* compareWith)
12371 {
12372 m_left.Apply(dims.m_left, compareWith ? (& compareWith->m_left) : (const wxTextAttrDimension*) NULL);
12373 m_right.Apply(dims.m_right, compareWith ? (& compareWith->m_right): (const wxTextAttrDimension*) NULL);
12374 m_top.Apply(dims.m_top, compareWith ? (& compareWith->m_top): (const wxTextAttrDimension*) NULL);
12375 m_bottom.Apply(dims.m_bottom, compareWith ? (& compareWith->m_bottom): (const wxTextAttrDimension*) NULL);
12376
12377 return true;
12378 }
12379
12380 // Remove specified attributes from this object
12381 bool wxTextAttrDimensions::RemoveStyle(const wxTextAttrDimensions& attr)
12382 {
12383 if (attr.m_left.IsValid())
12384 m_left.Reset();
12385 if (attr.m_right.IsValid())
12386 m_right.Reset();
12387 if (attr.m_top.IsValid())
12388 m_top.Reset();
12389 if (attr.m_bottom.IsValid())
12390 m_bottom.Reset();
12391
12392 return true;
12393 }
12394
12395 // Collects the attributes that are common to a range of content, building up a note of
12396 // which attributes are absent in some objects and which clash in some objects.
12397 void wxTextAttrDimensions::CollectCommonAttributes(const wxTextAttrDimensions& attr, wxTextAttrDimensions& clashingAttr, wxTextAttrDimensions& absentAttr)
12398 {
12399 m_left.CollectCommonAttributes(attr.m_left, clashingAttr.m_left, absentAttr.m_left);
12400 m_right.CollectCommonAttributes(attr.m_right, clashingAttr.m_right, absentAttr.m_right);
12401 m_top.CollectCommonAttributes(attr.m_top, clashingAttr.m_top, absentAttr.m_top);
12402 m_bottom.CollectCommonAttributes(attr.m_bottom, clashingAttr.m_bottom, absentAttr.m_bottom);
12403 }
12404
12405 // Partial equality test
12406 bool wxTextAttrSize::EqPartial(const wxTextAttrSize& size, bool weakTest) const
12407 {
12408 if (!m_width.EqPartial(size.m_width, weakTest))
12409 return false;
12410
12411 if (!m_height.EqPartial(size.m_height, weakTest))
12412 return false;
12413
12414 return true;
12415 }
12416
12417 // Apply border to 'this', but not if the same as compareWith
12418 bool wxTextAttrSize::Apply(const wxTextAttrSize& size, const wxTextAttrSize* compareWith)
12419 {
12420 m_width.Apply(size.m_width, compareWith ? (& compareWith->m_width) : (const wxTextAttrDimension*) NULL);
12421 m_height.Apply(size.m_height, compareWith ? (& compareWith->m_height): (const wxTextAttrDimension*) NULL);
12422
12423 return true;
12424 }
12425
12426 // Remove specified attributes from this object
12427 bool wxTextAttrSize::RemoveStyle(const wxTextAttrSize& attr)
12428 {
12429 if (attr.m_width.IsValid())
12430 m_width.Reset();
12431 if (attr.m_height.IsValid())
12432 m_height.Reset();
12433
12434 return true;
12435 }
12436
12437 // Collects the attributes that are common to a range of content, building up a note of
12438 // which attributes are absent in some objects and which clash in some objects.
12439 void wxTextAttrSize::CollectCommonAttributes(const wxTextAttrSize& attr, wxTextAttrSize& clashingAttr, wxTextAttrSize& absentAttr)
12440 {
12441 m_width.CollectCommonAttributes(attr.m_width, clashingAttr.m_width, absentAttr.m_width);
12442 m_height.CollectCommonAttributes(attr.m_height, clashingAttr.m_height, absentAttr.m_height);
12443 }
12444
12445 // Collects the attributes that are common to a range of content, building up a note of
12446 // which attributes are absent in some objects and which clash in some objects.
12447 void wxTextAttrCollectCommonAttributes(wxTextAttr& currentStyle, const wxTextAttr& attr, wxTextAttr& clashingAttr, wxTextAttr& absentAttr)
12448 {
12449 absentAttr.SetFlags(absentAttr.GetFlags() | (~attr.GetFlags() & wxTEXT_ATTR_ALL));
12450 absentAttr.SetTextEffectFlags(absentAttr.GetTextEffectFlags() | (~attr.GetTextEffectFlags() & 0xFFFF));
12451
12452 long forbiddenFlags = clashingAttr.GetFlags()|absentAttr.GetFlags();
12453
12454 if (attr.HasFont())
12455 {
12456 // If different font size units are being used, this is a clash.
12457 if (((attr.GetFlags() & wxTEXT_ATTR_FONT_SIZE) | (currentStyle.GetFlags() & wxTEXT_ATTR_FONT_SIZE)) == wxTEXT_ATTR_FONT_SIZE)
12458 {
12459 currentStyle.SetFontSize(0);
12460 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_SIZE);
12461 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_SIZE);
12462 }
12463 else
12464 {
12465 if (attr.HasFontPointSize() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_POINT_SIZE))
12466 {
12467 if (currentStyle.HasFontPointSize())
12468 {
12469 if (currentStyle.GetFontSize() != attr.GetFontSize())
12470 {
12471 // Clash of attr - mark as such
12472 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_POINT_SIZE);
12473 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_POINT_SIZE);
12474 }
12475 }
12476 else
12477 currentStyle.SetFontSize(attr.GetFontSize());
12478 }
12479
12480 if (attr.HasFontPixelSize() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_PIXEL_SIZE))
12481 {
12482 if (currentStyle.HasFontPixelSize())
12483 {
12484 if (currentStyle.GetFontSize() != attr.GetFontSize())
12485 {
12486 // Clash of attr - mark as such
12487 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_PIXEL_SIZE);
12488 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_PIXEL_SIZE);
12489 }
12490 }
12491 else
12492 currentStyle.SetFontPixelSize(attr.GetFontSize());
12493 }
12494 }
12495
12496 if (attr.HasFontItalic() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_ITALIC))
12497 {
12498 if (currentStyle.HasFontItalic())
12499 {
12500 if (currentStyle.GetFontStyle() != attr.GetFontStyle())
12501 {
12502 // Clash of attr - mark as such
12503 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_ITALIC);
12504 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_ITALIC);
12505 }
12506 }
12507 else
12508 currentStyle.SetFontStyle(attr.GetFontStyle());
12509 }
12510
12511 if (attr.HasFontFamily() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_FAMILY))
12512 {
12513 if (currentStyle.HasFontFamily())
12514 {
12515 if (currentStyle.GetFontFamily() != attr.GetFontFamily())
12516 {
12517 // Clash of attr - mark as such
12518 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_FAMILY);
12519 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_FAMILY);
12520 }
12521 }
12522 else
12523 currentStyle.SetFontFamily(attr.GetFontFamily());
12524 }
12525
12526 if (attr.HasFontWeight() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_WEIGHT))
12527 {
12528 if (currentStyle.HasFontWeight())
12529 {
12530 if (currentStyle.GetFontWeight() != attr.GetFontWeight())
12531 {
12532 // Clash of attr - mark as such
12533 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_WEIGHT);
12534 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_WEIGHT);
12535 }
12536 }
12537 else
12538 currentStyle.SetFontWeight(attr.GetFontWeight());
12539 }
12540
12541 if (attr.HasFontFaceName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_FACE))
12542 {
12543 if (currentStyle.HasFontFaceName())
12544 {
12545 wxString faceName1(currentStyle.GetFontFaceName());
12546 wxString faceName2(attr.GetFontFaceName());
12547
12548 if (faceName1 != faceName2)
12549 {
12550 // Clash of attr - mark as such
12551 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_FACE);
12552 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_FACE);
12553 }
12554 }
12555 else
12556 currentStyle.SetFontFaceName(attr.GetFontFaceName());
12557 }
12558
12559 if (attr.HasFontUnderlined() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_UNDERLINE))
12560 {
12561 if (currentStyle.HasFontUnderlined())
12562 {
12563 if (currentStyle.GetFontUnderlined() != attr.GetFontUnderlined())
12564 {
12565 // Clash of attr - mark as such
12566 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_UNDERLINE);
12567 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_UNDERLINE);
12568 }
12569 }
12570 else
12571 currentStyle.SetFontUnderlined(attr.GetFontUnderlined());
12572 }
12573
12574 if (attr.HasFontStrikethrough() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_STRIKETHROUGH))
12575 {
12576 if (currentStyle.HasFontStrikethrough())
12577 {
12578 if (currentStyle.GetFontStrikethrough() != attr.GetFontStrikethrough())
12579 {
12580 // Clash of attr - mark as such
12581 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_STRIKETHROUGH);
12582 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_STRIKETHROUGH);
12583 }
12584 }
12585 else
12586 currentStyle.SetFontStrikethrough(attr.GetFontStrikethrough());
12587 }
12588 }
12589
12590 if (attr.HasTextColour() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_TEXT_COLOUR))
12591 {
12592 if (currentStyle.HasTextColour())
12593 {
12594 if (currentStyle.GetTextColour() != attr.GetTextColour())
12595 {
12596 // Clash of attr - mark as such
12597 clashingAttr.AddFlag(wxTEXT_ATTR_TEXT_COLOUR);
12598 currentStyle.RemoveFlag(wxTEXT_ATTR_TEXT_COLOUR);
12599 }
12600 }
12601 else
12602 currentStyle.SetTextColour(attr.GetTextColour());
12603 }
12604
12605 if (attr.HasBackgroundColour() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BACKGROUND_COLOUR))
12606 {
12607 if (currentStyle.HasBackgroundColour())
12608 {
12609 if (currentStyle.GetBackgroundColour() != attr.GetBackgroundColour())
12610 {
12611 // Clash of attr - mark as such
12612 clashingAttr.AddFlag(wxTEXT_ATTR_BACKGROUND_COLOUR);
12613 currentStyle.RemoveFlag(wxTEXT_ATTR_BACKGROUND_COLOUR);
12614 }
12615 }
12616 else
12617 currentStyle.SetBackgroundColour(attr.GetBackgroundColour());
12618 }
12619
12620 if (attr.HasAlignment() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_ALIGNMENT))
12621 {
12622 if (currentStyle.HasAlignment())
12623 {
12624 if (currentStyle.GetAlignment() != attr.GetAlignment())
12625 {
12626 // Clash of attr - mark as such
12627 clashingAttr.AddFlag(wxTEXT_ATTR_ALIGNMENT);
12628 currentStyle.RemoveFlag(wxTEXT_ATTR_ALIGNMENT);
12629 }
12630 }
12631 else
12632 currentStyle.SetAlignment(attr.GetAlignment());
12633 }
12634
12635 if (attr.HasTabs() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_TABS))
12636 {
12637 if (currentStyle.HasTabs())
12638 {
12639 if (!wxRichTextTabsEq(currentStyle.GetTabs(), attr.GetTabs()))
12640 {
12641 // Clash of attr - mark as such
12642 clashingAttr.AddFlag(wxTEXT_ATTR_TABS);
12643 currentStyle.RemoveFlag(wxTEXT_ATTR_TABS);
12644 }
12645 }
12646 else
12647 currentStyle.SetTabs(attr.GetTabs());
12648 }
12649
12650 if (attr.HasLeftIndent() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_LEFT_INDENT))
12651 {
12652 if (currentStyle.HasLeftIndent())
12653 {
12654 if (currentStyle.GetLeftIndent() != attr.GetLeftIndent() || currentStyle.GetLeftSubIndent() != attr.GetLeftSubIndent())
12655 {
12656 // Clash of attr - mark as such
12657 clashingAttr.AddFlag(wxTEXT_ATTR_LEFT_INDENT);
12658 currentStyle.RemoveFlag(wxTEXT_ATTR_LEFT_INDENT);
12659 }
12660 }
12661 else
12662 currentStyle.SetLeftIndent(attr.GetLeftIndent(), attr.GetLeftSubIndent());
12663 }
12664
12665 if (attr.HasRightIndent() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_RIGHT_INDENT))
12666 {
12667 if (currentStyle.HasRightIndent())
12668 {
12669 if (currentStyle.GetRightIndent() != attr.GetRightIndent())
12670 {
12671 // Clash of attr - mark as such
12672 clashingAttr.AddFlag(wxTEXT_ATTR_RIGHT_INDENT);
12673 currentStyle.RemoveFlag(wxTEXT_ATTR_RIGHT_INDENT);
12674 }
12675 }
12676 else
12677 currentStyle.SetRightIndent(attr.GetRightIndent());
12678 }
12679
12680 if (attr.HasParagraphSpacingAfter() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_PARA_SPACING_AFTER))
12681 {
12682 if (currentStyle.HasParagraphSpacingAfter())
12683 {
12684 if (currentStyle.GetParagraphSpacingAfter() != attr.GetParagraphSpacingAfter())
12685 {
12686 // Clash of attr - mark as such
12687 clashingAttr.AddFlag(wxTEXT_ATTR_PARA_SPACING_AFTER);
12688 currentStyle.RemoveFlag(wxTEXT_ATTR_PARA_SPACING_AFTER);
12689 }
12690 }
12691 else
12692 currentStyle.SetParagraphSpacingAfter(attr.GetParagraphSpacingAfter());
12693 }
12694
12695 if (attr.HasParagraphSpacingBefore() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_PARA_SPACING_BEFORE))
12696 {
12697 if (currentStyle.HasParagraphSpacingBefore())
12698 {
12699 if (currentStyle.GetParagraphSpacingBefore() != attr.GetParagraphSpacingBefore())
12700 {
12701 // Clash of attr - mark as such
12702 clashingAttr.AddFlag(wxTEXT_ATTR_PARA_SPACING_BEFORE);
12703 currentStyle.RemoveFlag(wxTEXT_ATTR_PARA_SPACING_BEFORE);
12704 }
12705 }
12706 else
12707 currentStyle.SetParagraphSpacingBefore(attr.GetParagraphSpacingBefore());
12708 }
12709
12710 if (attr.HasLineSpacing() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_LINE_SPACING))
12711 {
12712 if (currentStyle.HasLineSpacing())
12713 {
12714 if (currentStyle.GetLineSpacing() != attr.GetLineSpacing())
12715 {
12716 // Clash of attr - mark as such
12717 clashingAttr.AddFlag(wxTEXT_ATTR_LINE_SPACING);
12718 currentStyle.RemoveFlag(wxTEXT_ATTR_LINE_SPACING);
12719 }
12720 }
12721 else
12722 currentStyle.SetLineSpacing(attr.GetLineSpacing());
12723 }
12724
12725 if (attr.HasCharacterStyleName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_CHARACTER_STYLE_NAME))
12726 {
12727 if (currentStyle.HasCharacterStyleName())
12728 {
12729 if (currentStyle.GetCharacterStyleName() != attr.GetCharacterStyleName())
12730 {
12731 // Clash of attr - mark as such
12732 clashingAttr.AddFlag(wxTEXT_ATTR_CHARACTER_STYLE_NAME);
12733 currentStyle.RemoveFlag(wxTEXT_ATTR_CHARACTER_STYLE_NAME);
12734 }
12735 }
12736 else
12737 currentStyle.SetCharacterStyleName(attr.GetCharacterStyleName());
12738 }
12739
12740 if (attr.HasParagraphStyleName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_PARAGRAPH_STYLE_NAME))
12741 {
12742 if (currentStyle.HasParagraphStyleName())
12743 {
12744 if (currentStyle.GetParagraphStyleName() != attr.GetParagraphStyleName())
12745 {
12746 // Clash of attr - mark as such
12747 clashingAttr.AddFlag(wxTEXT_ATTR_PARAGRAPH_STYLE_NAME);
12748 currentStyle.RemoveFlag(wxTEXT_ATTR_PARAGRAPH_STYLE_NAME);
12749 }
12750 }
12751 else
12752 currentStyle.SetParagraphStyleName(attr.GetParagraphStyleName());
12753 }
12754
12755 if (attr.HasListStyleName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_LIST_STYLE_NAME))
12756 {
12757 if (currentStyle.HasListStyleName())
12758 {
12759 if (currentStyle.GetListStyleName() != attr.GetListStyleName())
12760 {
12761 // Clash of attr - mark as such
12762 clashingAttr.AddFlag(wxTEXT_ATTR_LIST_STYLE_NAME);
12763 currentStyle.RemoveFlag(wxTEXT_ATTR_LIST_STYLE_NAME);
12764 }
12765 }
12766 else
12767 currentStyle.SetListStyleName(attr.GetListStyleName());
12768 }
12769
12770 if (attr.HasBulletStyle() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BULLET_STYLE))
12771 {
12772 if (currentStyle.HasBulletStyle())
12773 {
12774 if (currentStyle.GetBulletStyle() != attr.GetBulletStyle())
12775 {
12776 // Clash of attr - mark as such
12777 clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_STYLE);
12778 currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_STYLE);
12779 }
12780 }
12781 else
12782 currentStyle.SetBulletStyle(attr.GetBulletStyle());
12783 }
12784
12785 if (attr.HasBulletNumber() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BULLET_NUMBER))
12786 {
12787 if (currentStyle.HasBulletNumber())
12788 {
12789 if (currentStyle.GetBulletNumber() != attr.GetBulletNumber())
12790 {
12791 // Clash of attr - mark as such
12792 clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_NUMBER);
12793 currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_NUMBER);
12794 }
12795 }
12796 else
12797 currentStyle.SetBulletNumber(attr.GetBulletNumber());
12798 }
12799
12800 if (attr.HasBulletText() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BULLET_TEXT))
12801 {
12802 if (currentStyle.HasBulletText())
12803 {
12804 if (currentStyle.GetBulletText() != attr.GetBulletText())
12805 {
12806 // Clash of attr - mark as such
12807 clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_TEXT);
12808 currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_TEXT);
12809 }
12810 }
12811 else
12812 {
12813 currentStyle.SetBulletText(attr.GetBulletText());
12814 currentStyle.SetBulletFont(attr.GetBulletFont());
12815 }
12816 }
12817
12818 if (attr.HasBulletName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BULLET_NAME))
12819 {
12820 if (currentStyle.HasBulletName())
12821 {
12822 if (currentStyle.GetBulletName() != attr.GetBulletName())
12823 {
12824 // Clash of attr - mark as such
12825 clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_NAME);
12826 currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_NAME);
12827 }
12828 }
12829 else
12830 {
12831 currentStyle.SetBulletName(attr.GetBulletName());
12832 }
12833 }
12834
12835 if (attr.HasURL() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_URL))
12836 {
12837 if (currentStyle.HasURL())
12838 {
12839 if (currentStyle.GetURL() != attr.GetURL())
12840 {
12841 // Clash of attr - mark as such
12842 clashingAttr.AddFlag(wxTEXT_ATTR_URL);
12843 currentStyle.RemoveFlag(wxTEXT_ATTR_URL);
12844 }
12845 }
12846 else
12847 {
12848 currentStyle.SetURL(attr.GetURL());
12849 }
12850 }
12851
12852 if (attr.HasTextEffects() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_EFFECTS))
12853 {
12854 if (currentStyle.HasTextEffects())
12855 {
12856 // We need to find the bits in the new attr that are different:
12857 // just look at those bits that are specified by the new attr.
12858
12859 // We need to remove the bits and flags that are not common between current attr
12860 // and new attr. In so doing we need to take account of the styles absent from one or more of the
12861 // previous styles.
12862
12863 int currentRelevantTextEffects = currentStyle.GetTextEffects() & attr.GetTextEffectFlags();
12864 int newRelevantTextEffects = attr.GetTextEffects() & attr.GetTextEffectFlags();
12865
12866 if (currentRelevantTextEffects != newRelevantTextEffects)
12867 {
12868 // Find the text effects that were different, using XOR
12869 int differentEffects = currentRelevantTextEffects ^ newRelevantTextEffects;
12870
12871 // Clash of attr - mark as such
12872 clashingAttr.SetTextEffectFlags(clashingAttr.GetTextEffectFlags() | differentEffects);
12873 currentStyle.SetTextEffectFlags(currentStyle.GetTextEffectFlags() & ~differentEffects);
12874 }
12875 }
12876 else
12877 {
12878 currentStyle.SetTextEffects(attr.GetTextEffects());
12879 currentStyle.SetTextEffectFlags(attr.GetTextEffectFlags());
12880 }
12881
12882 // Mask out the flags and values that cannot be common because they were absent in one or more objecrs
12883 // that we've looked at so far
12884 currentStyle.SetTextEffects(currentStyle.GetTextEffects() & ~absentAttr.GetTextEffectFlags());
12885 currentStyle.SetTextEffectFlags(currentStyle.GetTextEffectFlags() & ~absentAttr.GetTextEffectFlags());
12886
12887 if (currentStyle.GetTextEffectFlags() == 0)
12888 currentStyle.RemoveFlag(wxTEXT_ATTR_EFFECTS);
12889 }
12890
12891 if (attr.HasOutlineLevel() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_OUTLINE_LEVEL))
12892 {
12893 if (currentStyle.HasOutlineLevel())
12894 {
12895 if (currentStyle.GetOutlineLevel() != attr.GetOutlineLevel())
12896 {
12897 // Clash of attr - mark as such
12898 clashingAttr.AddFlag(wxTEXT_ATTR_OUTLINE_LEVEL);
12899 currentStyle.RemoveFlag(wxTEXT_ATTR_OUTLINE_LEVEL);
12900 }
12901 }
12902 else
12903 currentStyle.SetOutlineLevel(attr.GetOutlineLevel());
12904 }
12905 }
12906
12907 WX_DEFINE_OBJARRAY(wxRichTextVariantArray);
12908
12909 IMPLEMENT_DYNAMIC_CLASS(wxRichTextProperties, wxObject)
12910
12911 bool wxRichTextProperties::operator==(const wxRichTextProperties& props) const
12912 {
12913 if (m_properties.GetCount() != props.GetCount())
12914 return false;
12915
12916 size_t i;
12917 for (i = 0; i < m_properties.GetCount(); i++)
12918 {
12919 const wxVariant& var1 = m_properties[i];
12920 int idx = props.Find(var1.GetName());
12921 if (idx == -1)
12922 return false;
12923 const wxVariant& var2 = props.m_properties[idx];
12924 if (!(var1 == var2))
12925 return false;
12926 }
12927
12928 return true;
12929 }
12930
12931 wxArrayString wxRichTextProperties::GetPropertyNames() const
12932 {
12933 wxArrayString arr;
12934 size_t i;
12935 for (i = 0; i < m_properties.GetCount(); i++)
12936 {
12937 arr.Add(m_properties[i].GetName());
12938 }
12939 return arr;
12940 }
12941
12942 int wxRichTextProperties::Find(const wxString& name) const
12943 {
12944 size_t i;
12945 for (i = 0; i < m_properties.GetCount(); i++)
12946 {
12947 if (m_properties[i].GetName() == name)
12948 return (int) i;
12949 }
12950 return -1;
12951 }
12952
12953 bool wxRichTextProperties::Remove(const wxString& name)
12954 {
12955 int idx = Find(name);
12956 if (idx != -1)
12957 {
12958 m_properties.RemoveAt(idx);
12959 return true;
12960 }
12961 else
12962 return false;
12963 }
12964
12965 wxVariant* wxRichTextProperties::FindOrCreateProperty(const wxString& name)
12966 {
12967 int idx = Find(name);
12968 if (idx == wxNOT_FOUND)
12969 SetProperty(name, wxString());
12970 idx = Find(name);
12971 if (idx != wxNOT_FOUND)
12972 {
12973 return & (*this)[idx];
12974 }
12975 else
12976 return NULL;
12977 }
12978
12979 const wxVariant& wxRichTextProperties::GetProperty(const wxString& name) const
12980 {
12981 static const wxVariant nullVariant;
12982 int idx = Find(name);
12983 if (idx != -1)
12984 return m_properties[idx];
12985 else
12986 return nullVariant;
12987 }
12988
12989 wxString wxRichTextProperties::GetPropertyString(const wxString& name) const
12990 {
12991 return GetProperty(name).GetString();
12992 }
12993
12994 long wxRichTextProperties::GetPropertyLong(const wxString& name) const
12995 {
12996 return GetProperty(name).GetLong();
12997 }
12998
12999 bool wxRichTextProperties::GetPropertyBool(const wxString& name) const
13000 {
13001 return GetProperty(name).GetBool();
13002 }
13003
13004 double wxRichTextProperties::GetPropertyDouble(const wxString& name) const
13005 {
13006 return GetProperty(name).GetDouble();
13007 }
13008
13009 void wxRichTextProperties::SetProperty(const wxVariant& variant)
13010 {
13011 wxASSERT(!variant.GetName().IsEmpty());
13012
13013 int idx = Find(variant.GetName());
13014
13015 if (idx == -1)
13016 m_properties.Add(variant);
13017 else
13018 m_properties[idx] = variant;
13019 }
13020
13021 void wxRichTextProperties::SetProperty(const wxString& name, const wxVariant& variant)
13022 {
13023 int idx = Find(name);
13024 wxVariant var(variant);
13025 var.SetName(name);
13026
13027 if (idx == -1)
13028 m_properties.Add(var);
13029 else
13030 m_properties[idx] = var;
13031 }
13032
13033 void wxRichTextProperties::SetProperty(const wxString& name, const wxString& value)
13034 {
13035 SetProperty(name, wxVariant(value, name));
13036 }
13037
13038 void wxRichTextProperties::SetProperty(const wxString& name, long value)
13039 {
13040 SetProperty(name, wxVariant(value, name));
13041 }
13042
13043 void wxRichTextProperties::SetProperty(const wxString& name, double value)
13044 {
13045 SetProperty(name, wxVariant(value, name));
13046 }
13047
13048 void wxRichTextProperties::SetProperty(const wxString& name, bool value)
13049 {
13050 SetProperty(name, wxVariant(value, name));
13051 }
13052
13053 void wxRichTextProperties::RemoveProperties(const wxRichTextProperties& properties)
13054 {
13055 size_t i;
13056 for (i = 0; i < properties.GetCount(); i++)
13057 {
13058 wxString name = properties.GetProperties()[i].GetName();
13059 if (HasProperty(name))
13060 Remove(name);
13061 }
13062 }
13063
13064 void wxRichTextProperties::MergeProperties(const wxRichTextProperties& properties)
13065 {
13066 size_t i;
13067 for (i = 0; i < properties.GetCount(); i++)
13068 {
13069 SetProperty(properties.GetProperties()[i]);
13070 }
13071 }
13072
13073 wxRichTextObject* wxRichTextObjectAddress::GetObject(wxRichTextParagraphLayoutBox* topLevelContainer) const
13074 {
13075 if (m_address.GetCount() == 0)
13076 return topLevelContainer;
13077
13078 wxRichTextCompositeObject* p = topLevelContainer;
13079 size_t i = 0;
13080 while (p && i < m_address.GetCount())
13081 {
13082 int pos = m_address[i];
13083 wxASSERT(pos >= 0 && pos < (int) p->GetChildren().GetCount());
13084 if (pos < 0 || pos >= (int) p->GetChildren().GetCount())
13085 return NULL;
13086
13087 wxRichTextObject* p1 = p->GetChild(pos);
13088 if (i == (m_address.GetCount()-1))
13089 return p1;
13090
13091 p = wxDynamicCast(p1, wxRichTextCompositeObject);
13092 i ++;
13093 }
13094 return NULL;
13095 }
13096
13097 bool wxRichTextObjectAddress::Create(wxRichTextParagraphLayoutBox* topLevelContainer, wxRichTextObject* obj)
13098 {
13099 m_address.Clear();
13100
13101 if (topLevelContainer == obj)
13102 return true;
13103
13104 wxRichTextObject* o = obj;
13105 while (o)
13106 {
13107 wxRichTextCompositeObject* p = wxDynamicCast(o->GetParent(), wxRichTextCompositeObject);
13108 if (!p)
13109 return false;
13110
13111 int pos = p->GetChildren().IndexOf(o);
13112 if (pos == -1)
13113 return false;
13114
13115 m_address.Insert(pos, 0);
13116
13117 if (p == topLevelContainer)
13118 return true;
13119
13120 o = p;
13121 }
13122 return false;
13123 }
13124
13125 // Equality test
13126 bool wxRichTextSelection::operator==(const wxRichTextSelection& sel) const
13127 {
13128 if (m_container != sel.m_container)
13129 return false;
13130 if (m_ranges.GetCount() != sel.m_ranges.GetCount())
13131 return false;
13132 size_t i;
13133 for (i = 0; i < m_ranges.GetCount(); i++)
13134 if (!(m_ranges[i] == sel.m_ranges[i]))
13135 return false;
13136 return true;
13137 }
13138
13139 // Get the selections appropriate to the specified object, if any; returns wxRICHTEXT_NO_SELECTION if none
13140 // or none at the level of the object's container.
13141 wxRichTextRangeArray wxRichTextSelection::GetSelectionForObject(wxRichTextObject* obj) const
13142 {
13143 if (IsValid())
13144 {
13145 wxRichTextParagraphLayoutBox* container = obj->GetParentContainer();
13146
13147 if (container == m_container)
13148 return m_ranges;
13149
13150 container = obj->GetContainer();
13151 while (container)
13152 {
13153 if (container->GetParent())
13154 {
13155 // If we found that our object's container is within the range of
13156 // a selection higher up, then assume the whole original object
13157 // is also selected.
13158 wxRichTextParagraphLayoutBox* parentContainer = container->GetParentContainer();
13159 if (parentContainer == m_container)
13160 {
13161 if (WithinSelection(container->GetRange().GetStart(), m_ranges))
13162 {
13163 wxRichTextRangeArray ranges;
13164 ranges.Add(obj->GetRange());
13165 return ranges;
13166 }
13167 }
13168
13169 container = parentContainer;
13170 }
13171 else
13172 {
13173 container = NULL;
13174 break;
13175 }
13176 }
13177 }
13178 return wxRichTextRangeArray();
13179 }
13180
13181 // Is the given position within the selection?
13182 bool wxRichTextSelection::WithinSelection(long pos, wxRichTextObject* obj) const
13183 {
13184 if (!IsValid())
13185 return false;
13186 else
13187 {
13188 wxRichTextRangeArray selectionRanges = GetSelectionForObject(obj);
13189 return WithinSelection(pos, selectionRanges);
13190 }
13191 }
13192
13193 // Is the given position within the selection range?
13194 bool wxRichTextSelection::WithinSelection(long pos, const wxRichTextRangeArray& ranges)
13195 {
13196 size_t i;
13197 for (i = 0; i < ranges.GetCount(); i++)
13198 {
13199 const wxRichTextRange& range = ranges[i];
13200 if (pos >= range.GetStart() && pos <= range.GetEnd())
13201 return true;
13202 }
13203 return false;
13204 }
13205
13206 // Is the given range completely within the selection range?
13207 bool wxRichTextSelection::WithinSelection(const wxRichTextRange& range, const wxRichTextRangeArray& ranges)
13208 {
13209 size_t i;
13210 for (i = 0; i < ranges.GetCount(); i++)
13211 {
13212 const wxRichTextRange& eachRange = ranges[i];
13213 if (range.IsWithin(eachRange))
13214 return true;
13215 }
13216 return false;
13217 }
13218
13219 IMPLEMENT_CLASS(wxRichTextDrawingHandler, wxObject)
13220 IMPLEMENT_CLASS(wxRichTextDrawingContext, wxObject)
13221
13222 bool wxRichTextDrawingContext::HasVirtualAttributes(wxRichTextObject* obj) const
13223 {
13224 wxList::compatibility_iterator node = m_buffer->GetDrawingHandlers().GetFirst();
13225 while (node)
13226 {
13227 wxRichTextDrawingHandler *handler = (wxRichTextDrawingHandler*)node->GetData();
13228 if (handler->HasVirtualAttributes(obj))
13229 return true;
13230
13231 node = node->GetNext();
13232 }
13233 return false;
13234 }
13235
13236 wxRichTextAttr wxRichTextDrawingContext::GetVirtualAttributes(wxRichTextObject* obj) const
13237 {
13238 wxRichTextAttr attr;
13239 // We apply all handlers, so we can may combine several different attributes
13240 wxList::compatibility_iterator node = m_buffer->GetDrawingHandlers().GetFirst();
13241 while (node)
13242 {
13243 wxRichTextDrawingHandler *handler = (wxRichTextDrawingHandler*)node->GetData();
13244 if (handler->HasVirtualAttributes(obj))
13245 {
13246 bool success = handler->GetVirtualAttributes(attr, obj);
13247 wxASSERT(success);
13248 wxUnusedVar(success);
13249 }
13250
13251 node = node->GetNext();
13252 }
13253 return attr;
13254 }
13255
13256 bool wxRichTextDrawingContext::ApplyVirtualAttributes(wxRichTextAttr& attr, wxRichTextObject* obj) const
13257 {
13258 if (HasVirtualAttributes(obj))
13259 {
13260 wxRichTextAttr a(GetVirtualAttributes(obj));
13261 attr.Apply(a);
13262 return true;
13263 }
13264 else
13265 return false;
13266 }
13267
13268 /// Adds a handler to the end
13269 void wxRichTextBuffer::AddDrawingHandler(wxRichTextDrawingHandler *handler)
13270 {
13271 sm_drawingHandlers.Append(handler);
13272 }
13273
13274 /// Inserts a handler at the front
13275 void wxRichTextBuffer::InsertDrawingHandler(wxRichTextDrawingHandler *handler)
13276 {
13277 sm_drawingHandlers.Insert( handler );
13278 }
13279
13280 /// Removes a handler
13281 bool wxRichTextBuffer::RemoveDrawingHandler(const wxString& name)
13282 {
13283 wxRichTextDrawingHandler *handler = FindDrawingHandler(name);
13284 if (handler)
13285 {
13286 sm_drawingHandlers.DeleteObject(handler);
13287 delete handler;
13288 return true;
13289 }
13290 else
13291 return false;
13292 }
13293
13294 wxRichTextDrawingHandler* wxRichTextBuffer::FindDrawingHandler(const wxString& name)
13295 {
13296 wxList::compatibility_iterator node = sm_drawingHandlers.GetFirst();
13297 while (node)
13298 {
13299 wxRichTextDrawingHandler *handler = (wxRichTextDrawingHandler*)node->GetData();
13300 if (handler->GetName().Lower() == name.Lower()) return handler;
13301
13302 node = node->GetNext();
13303 }
13304 return NULL;
13305 }
13306
13307 void wxRichTextBuffer::CleanUpDrawingHandlers()
13308 {
13309 wxList::compatibility_iterator node = sm_drawingHandlers.GetFirst();
13310 while (node)
13311 {
13312 wxRichTextDrawingHandler* handler = (wxRichTextDrawingHandler*)node->GetData();
13313 wxList::compatibility_iterator next = node->GetNext();
13314 delete handler;
13315 node = next;
13316 }
13317
13318 sm_drawingHandlers.Clear();
13319 }
13320
13321 void wxRichTextBuffer::AddFieldType(wxRichTextFieldType *fieldType)
13322 {
13323 sm_fieldTypes[fieldType->GetName()] = fieldType;
13324 }
13325
13326 bool wxRichTextBuffer::RemoveFieldType(const wxString& name)
13327 {
13328 wxRichTextFieldTypeHashMap::iterator it = sm_fieldTypes.find(name);
13329 if (it == sm_fieldTypes.end())
13330 return false;
13331 else
13332 {
13333 wxRichTextFieldType* fieldType = it->second;
13334 sm_fieldTypes.erase(it);
13335 delete fieldType;
13336 return true;
13337 }
13338 }
13339
13340 wxRichTextFieldType *wxRichTextBuffer::FindFieldType(const wxString& name)
13341 {
13342 wxRichTextFieldTypeHashMap::iterator it = sm_fieldTypes.find(name);
13343 if (it == sm_fieldTypes.end())
13344 return NULL;
13345 else
13346 return it->second;
13347 }
13348
13349 void wxRichTextBuffer::CleanUpFieldTypes()
13350 {
13351 wxRichTextFieldTypeHashMap::iterator it;
13352 for( it = sm_fieldTypes.begin(); it != sm_fieldTypes.end(); ++it )
13353 {
13354 wxRichTextFieldType* fieldType = it->second;
13355 delete fieldType;
13356 }
13357
13358 sm_fieldTypes.clear();
13359 }
13360
13361 #endif
13362 // wxUSE_RICHTEXT